@prose-reader/streamer 28.0.0 → 28.1.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=>{if(t.spineItems.length!==0&&t.spineItems.every(t=>((0,e.parseContentType)(t.mediaType??``)||(0,e.detectMimeTypeFromName)(t.containerUri))?.startsWith(`audio/`)))return t.spineItems.map(t=>({title:o((0,e.getUriBasename)(t.containerUri)),href:t.href,path:t.containerUri,contents:[]}))},c=e=>encodeURI(e).replace(/#/g,`%23`).replace(/\?/g,`%3F`),l=({baseUrl:e=``,resourcePath:t})=>{if(!e&&/^https?:\/\//.test(t))return encodeURI(t);let n=e?`${e}${e.endsWith(`/`)?``:`/`}`:`file://`;return`${encodeURI(n)}${c(t)}`},u=t=>n=>({title:n.title,path:n.path,href:n.containerHref?(0,e.urlJoin)(t,n.containerHref):``,contents:n.contents.map(u(t))}),d=e=>{let t=e?.trim();if(t===`cover`||t===`title-page`||t===`copyright-page`||t===`text`)return t},f=e=>/^https?:\/\//.test(e),p=e=>e.records.find(({dir:e})=>e)?.basename.replace(/\/$/,``),m=(n,r,i,a)=>{let{metadata:o,readingOrder:s}=i,c=a.basePath,l=[];for(let e of a.opf.guide){let t=d(e.type);t!==void 0&&l.push({href:e.href,title:e.title,type:t})}let m=(0,e.createXmlSafeIdFactory)();return{filename:n.filename??``,mediaType:i.mediaType,title:(0,t.mainTitle)(o)||p(n)||``,renditionLayout:o.renditionLayout,renditionFlow:o.renditionFlow??`auto`,renditionSpread:o.renditionSpread,readingDirection:o.readingDirection,spineItems:s.map((e,t)=>({id:e.id??m(e.uri),index:t,href:f(e.uri)?e.uri:`${r||`file://`}${e.containerHref??e.uri}`,containerUri:e.uri,renditionLayout:e.renditionLayout,...e.renditionFlow===void 0?{}:{renditionFlow:e.renditionFlow},progressionWeight:e.progressionWeight,pageSpreadLeft:e.pageSpreadLeft,pageSpreadRight:e.pageSpreadRight,mediaType:e.mediaType})),items:a.opf.manifestItems.map(e=>({id:e.id,href:f(e.href)?e.href:`${r||`file://`}${c?`${c}/`:``}${e.href}`,mediaType:e.mediaType})),guide:l.length>0?l:void 0,nav:{toc:(i.toc??[]).map(u(r))}}},h=(n,r,i)=>{let{metadata:a,readingOrder:o}=i,c=n.records.filter(e=>!e.dir),d=(0,e.createXmlSafeIdFactory)(),f=new Map(c.map(e=>[e.uri,d(e.uri)])),m={filename:n.filename??``,mediaType:i.mediaType,title:(0,t.mainTitle)(a)||p(n)||n.filename||``,renditionLayout:a.renditionLayout,renditionSpread:`auto`,readingDirection:a.readingDirection,spineItems:o.map((e,t)=>({id:f.get(e.uri)??d(e.uri),index:t,href:l({baseUrl:r,resourcePath:e.uri}),containerUri:e.uri,renditionLayout:e.renditionLayout,progressionWeight:e.progressionWeight,pageSpreadLeft:void 0,pageSpreadRight:void 0,mediaType:e.mediaType})),items:c.map(e=>({id:f.get(e.uri)??d(e.uri),href:encodeURI(`${r}${e.uri}`)}))},h=s(m)??i.toc?.map(u(r));return h===void 0?m:{...m,nav:{toc:h}}},g=({archive:e,baseUrl:t,resolved:n})=>{let r=n.sources.opf;return r?m(e,t,n,r):h(e,t,n)},_=e=>e?e.endsWith(`/`)?e:`${e}/`:``,v=async(e,{baseUrl:n=``,hooks:r=[]}={})=>{i.log(`Generating manifest from archive`,e);let a=_(n);try{let n=g({archive:e,baseUrl:a,resolved:await(0,t.resolveArchive)(e,{include:[`metadata`,`readingOrder`,`toc`,`sources`],layoutScan:!0})});for(let t of r)n=await t({archive:e,baseUrl:a})(n);if(n={...n,readingDirection:n.readingDirection??`ltr`},i.log(`Generated manifest`,n),process.env.NODE_ENV===`development`&&i.isEnabled()){let e=JSON.stringify(n,null,2);i.groupCollapsed(...i.getGroupArgs(`Generated manifest`)),i.log(`\n${e}`),i.groupEnd()}return n}catch(e){throw i.error(e),e}},y=(e,n)=>({archive:r,resourcePath:i})=>async a=>{let o=(0,t.getArchiveFileRecordByUri)(r,i);if(o?.basename.endsWith(e)){let e=await n(typeof a.body==`string`?a.body:await(0,t.readRecordAsText)(o));if(e!==void 0)return{...a,body:e}}return a},b=e=>{let t=e.descendantWithPath(`head`)?.childrenNamed(`meta`).find(e=>e.attr.name===`calibre:cover`);return!!(t&&t.attr.name===`calibre:cover`)},x=e=>e.descendantWithPath(`body`)?.descendantWithPath(`div`)?.childrenNamed(`svg`)?.find(e=>e.attr.width===`100%`&&e.attr.preserveAspectRatio===`none`),S=y(`.xhtml`,e=>{let t=new n.XmlDocument(e);if(!b(t))return;let r=x(t);return r&&delete r.attr.preserveAspectRatio,t.toString()}),C=y(`.css`,e=>e.replaceAll(`-webkit-writing-mode`,`writing-mode`)),w=async(e,n)=>{let r=await(0,t.readArchiveOpf)(e);if(r){let{opf:e,basePath:t}=r,i=e.manifestItems.find(e=>{let r=t?`${t}/${e.href}`:e.href;return n.endsWith(r)})?.mediaType;if(i)return{mediaType:i}}return{mediaType:T(n)}},T=e=>{if(e.endsWith(`.css`))return`text/css; charset=UTF-8`;if(e.endsWith(`.jpg`))return`image/jpg`;if(e.endsWith(`.xhtml`))return`application/xhtml+xml`;if(e.endsWith(`.mp4`))return`video/mp4`;if(e.endsWith(`.svg`))return`image/svg+xml`},E=({archive:e,resourcePath:n})=>async r=>{let i=(0,t.getArchiveFileRecordByUri)(e,n);if(!i)return r;let a=await w(e,n);return{...r,params:{...r.params,...i?.encodingFormat&&{contentType:i.encodingFormat},...a.mediaType&&{contentType:a.mediaType}}}},D=`div.span.p.a.li.ul.ol.h1.h2.h3.h4.h5.h6.table.tr.td.th.thead.tbody.tfoot.section.article.header.footer.nav.aside.main.figure.figcaption.blockquote.pre.code.form.textarea.select.option.button.label.fieldset.legend.caption.dl.dt.dd.iframe.video.audio.canvas.script.style`.split(`.`),O=y(`.xhtml`,e=>{if(!RegExp(`<(${D.join(`|`)})[\\s/>]`,`i`).test(e))return;let t=RegExp(`<(${D.join(`|`)})(\\s[^>]*)?\\s*/>`,`gi`);return e.replace(t,(e,t,n=``)=>`<${t} ${n.trim()}></${t}>`)}),k=async(e,n,{hooks:r=[]}={})=>{let a={params:{}},o=[...r.map(t=>t({archive:e,resourcePath:n})),E({archive:e,resourcePath:n}),O({archive:e,resourcePath:n}),C({archive:e,resourcePath:n}),S({archive:e,resourcePath:n})];try{let r=await o.reduce(async(e,t)=>await t(await e),Promise.resolve(a));if(i.log(`Generated resource`,n,r),r.body!==void 0)return r;let s=(0,t.getArchiveFileRecordByUri)(e,n);if(!s)throw Error(`no file found for resourcePath:${n}`);return{...r,body:await s.blob()}}catch(e){throw i.error(e),e}},A=class{constructor(e){this.cleanArchiveAfter=e,this.state$=new r.BehaviorSubject({status:`idle`,locks:0})}update(e){this.state$.next({...this.state$.getValue(),...e})}get locks$(){return this.state$.pipe((0,r.map)(({locks:e})=>e))}get state(){return this.state$.getValue()}get isUnlocked$(){return this.locks$.pipe((0,r.map)(e=>e<=0),(0,r.distinctUntilChanged)(),(0,r.shareReplay)())}get overTTL$(){return this.isUnlocked$.pipe((0,r.switchMap)(e=>e?this.cleanArchiveAfter===1/0?r.NEVER:(0,r.timer)(this.cleanArchiveAfter):r.NEVER))}},j=({getArchive:e,cleanArchiveAfter:t=3e5})=>{let n=new r.Subject,a=new r.Subject,o=new r.Subject,s={};return n.pipe((0,r.mergeMap)(t=>{let n=s[t];if(n?.state.status!==`idle`)return r.EMPTY;let a=!1,c=e=>{i.debug(`Cleaning up archive with key: ${e}`);let t=s[e];delete s[e],a||=(t?.state.archive?.close(),!0)};n.update({status:`loading`});let l=n.locks$,u=n.isUnlocked$,d=l.pipe((0,r.pairwise)(),(0,r.filter)(([e,t])=>t>e),(0,r.startWith)(!0));return(0,r.from)(e(t)).pipe((0,r.tap)(e=>{n.update({archive:e,status:`success`})}),(0,r.catchError)(e=>(c(t),n.update({status:`error`,error:e}),r.EMPTY)),(0,r.switchMap)(()=>{let e=d.pipe((0,r.switchMap)(()=>o),(0,r.switchMap)(()=>u),(0,r.filter)(e=>e));return(0,r.merge)(e,n.overTTL$).pipe((0,r.first)(),(0,r.tap)(()=>{c(t)}))}))}),(0,r.takeUntil)(a)).subscribe(),{access:e=>{let i=!1,a=s[e]??new A(t);s[e]=a,a.update({locks:a.state.locks+1});let o=()=>{i||(i=!0,a.update({locks:a.state.locks-1}))};n.next(e);let c=a.state$.pipe((0,r.map)(({archive:e})=>e),(0,r.filter)(e=>!!e)),l=a.state$.pipe((0,r.tap)(({error:e})=>{if(e)throw e}),(0,r.ignoreElements)());return(0,r.merge)(c,l).pipe((0,r.first)(),(0,r.map)(e=>({archive:e,release:o})),(0,r.catchError)(e=>{throw o(),e}))},purge:()=>{o.next()},_archives:s}},M=e=>e?/^\d+$/.test(e)?{valid:!0,value:Number.parseInt(e,10)}:{valid:!1,value:void 0}:{valid:!0,value:void 0},N=e=>{if(!e.toLowerCase().startsWith(`bytes=`))return{kind:`missing`};let t=e.slice(6).trim();if(!t)return{kind:`invalid`};if(t.includes(`,`))return{kind:`multi`};let n=/^(\d*)-(\d*)$/.exec(t);if(!n)return{kind:`invalid`};let[,r=``,i=``]=n,a=M(r.trim()),o=M(i.trim());return!a.valid||!o.valid?{kind:`invalid`}:{kind:`single`,start:a.value,end:o.value}},P=e=>{if(e instanceof Blob)return{size:e.size,slice:(t,n)=>{let r=e.slice(t,n);return{content:r,length:r.size}}};let t=new TextEncoder().encode(e);return{size:t.byteLength,slice:(e,n)=>{let r=t.slice(e,n);return{content:r,length:r.byteLength}}}},F=({body:e,contentType:t,rangeHeader:n})=>{let r=new Headers;if(t&&r.set(`Content-Type`,t),r.set(`Accept-Ranges`,`bytes`),!n)return e instanceof Blob&&r.set(`Content-Length`,String(e.size)),new Response(e,{status:200,headers:r});let i=N(n);if(i.kind===`missing`||i.kind===`multi`)return e instanceof Blob&&r.set(`Content-Length`,String(e.size)),new Response(e,{status:200,headers:r});let a=P(e),o=a.size;if(i.kind===`invalid`)return new Response(null,{status:416,headers:{"Content-Range":`bytes */${o}`}});let s=i.start,c=i.end;if(s===void 0&&c===void 0||(s===void 0?(s=Math.max(0,o-Math.min(c??0,o)),c=o-1):(c===void 0||c>=o)&&(c=o-1),s<0||c<0||s>=o||c>=o||s>c))return new Response(null,{status:416,headers:{"Content-Range":`bytes */${o}`}});let l=a.slice(s,c+1);return r.set(`Content-Length`,String(l.length)),r.set(`Content-Range`,`bytes ${s}-${c}/${o}`),new Response(l.content,{status:206,headers:r})},I=`file://`,L=/^https?:\/\//,R=e=>{try{return decodeURIComponent(e)}catch{return e}},z=e=>e.startsWith(I)?e.slice(I.length):e,B=e=>{let t=z(e);return L.test(t)?t:z(R(t))},V=class{constructor({hooks:e,onError:t,onManifestSuccess:n,...r}){this.onError=e=>(console.error(e),new Response(String(e),{status:500})),this.archiveLoader=j(r),this.hooks=e??{},this.onManifestSuccess=n??(({manifest:e})=>Promise.resolve(e)),this.onError=t??this.onError}prune(){this.archiveLoader.purge()}accessArchive(e){return this.lastAccessedKey!==void 0&&this.lastAccessedKey!==e&&this.archiveLoader.purge(),this.lastAccessedKey=e,this.archiveLoader.access(e)}accessArchiveWithoutLock(e){return this.accessArchive(e).pipe((0,r.map)(({archive:e,release:t})=>(t(),e)))}withArchiveResponse({key:e,getResponse:t}){let n=this.accessArchive(e).pipe((0,r.mergeMap)(({archive:e,release:n})=>(0,r.from)(t(e)).pipe((0,r.finalize)(()=>{n()}))),(0,r.catchError)(e=>(0,r.of)(this.onError(e))));return(0,r.lastValueFrom)(n)}fetchManifest({key:e,baseUrl:t}){return this.withArchiveResponse({key:e,getResponse:e=>(0,r.from)(v(e,{baseUrl:t,hooks:this.hooks.manifest})).pipe((0,r.switchMap)(t=>(0,r.from)(this.onManifestSuccess({manifest:t,archive:e}))),(0,r.map)(e=>new Response(JSON.stringify(e),{status:200})))})}fetchResource({key:e,resourcePath:t,request:n}){return this.withArchiveResponse({key:e,getResponse:e=>{let i=B(t);return(0,r.from)(k(e,i,{hooks:this.hooks.resource})).pipe((0,r.map)(e=>F({body:e.body??``,contentType:e.params.contentType,rangeHeader:n?.headers.get(`range`)})))}})}},H=class extends V{constructor({getUriInfo:e,...t}){super(t),this.getUriInfo=e,this.fetchEventListener=this.fetchEventListener.bind(this)}fetchEventListener(t){try{let n=this.getUriInfo(t);if(!n)return;let r=(0,e.removeTrailingSlash)(n.baseUrl),i=t.request.url.substring(r.length+1),[a=``]=i.split(`/`),o=(0,e.removeTrailingSlash)(i.substring(a.length+1));i.endsWith(`/manifest`)?t.respondWith(this.fetchManifest({key:a,baseUrl:`${r}/${a}/`})):t.respondWith(this.fetchResource({key:a,resourcePath:o,request:t.request}))}catch(e){t.respondWith(new Response(String(e),{status:500}))}}};exports.ServiceWorkerStreamer=H,exports.Streamer=V,exports.configure=a,exports.createManifestResourceHref=l,exports.generateManifestFromArchive=v,exports.generateResourceFromArchive=k;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@prose-reader/shared"),t=require("@prose-reader/archive-reader"),n=require("xmldoc"),r=require("rxjs");var i=e.Report.namespace(`@prose-reader/streamer`,void 0,{color:`#ffae42`}),a=({enableReport:e}={})=>{i.enable(!!e)},o=e=>e.replace(/\.[^.]+$/,``).replace(/[_-]/g,` `).replace(/\s+/g,` `).trim(),s=t=>{if(t.spineItems.length!==0&&t.spineItems.every(t=>((0,e.parseContentType)(t.mediaType??``)||(0,e.detectMimeTypeFromName)(t.containerUri))?.startsWith(`audio/`)))return t.spineItems.map(t=>({title:o((0,e.getUriBasename)(t.containerUri)),href:t.href,path:t.containerUri,contents:[]}))},c=e=>encodeURI(e).replace(/#/g,`%23`).replace(/\?/g,`%3F`),l=({baseUrl:e=``,resourcePath:t})=>{if(!e&&/^https?:\/\//.test(t))return encodeURI(t);let n=e?`${e}${e.endsWith(`/`)?``:`/`}`:`file://`;return`${encodeURI(n)}${c(t)}`},u=t=>n=>({title:n.title,path:n.path,href:n.containerHref?(0,e.urlJoin)(t,n.containerHref):``,contents:n.contents.map(u(t))}),d=e=>{let t=e?.trim();if(t===`cover`||t===`title-page`||t===`copyright-page`||t===`text`)return t},f=e=>/^https?:\/\//.test(e),p=e=>e.records.find(({dir:e})=>e)?.basename.replace(/\/$/,``),m=(n,r,i,a)=>{let{metadata:o,readingOrder:s}=i,c=a.basePath,l=[];for(let e of a.opf.guide){let t=d(e.type);t!==void 0&&l.push({href:e.href,title:e.title,type:t})}let m=(0,e.createXmlSafeIdFactory)();return{filename:n.filename??``,mediaType:i.mediaType,title:(0,t.mainTitle)(o)||p(n)||``,renditionLayout:o.renditionLayout,renditionFlow:o.renditionFlow??`auto`,renditionSpread:o.renditionSpread,readingDirection:o.readingDirection,spineItems:s.map((e,t)=>({id:e.id??m(e.uri),index:t,href:f(e.uri)?e.uri:`${r||`file://`}${e.containerHref??e.uri}`,containerUri:e.uri,renditionLayout:e.renditionLayout,...e.renditionFlow===void 0?{}:{renditionFlow:e.renditionFlow},progressionWeight:e.progressionWeight,pageSpreadLeft:e.pageSpreadLeft,pageSpreadRight:e.pageSpreadRight,mediaType:e.mediaType})),items:a.opf.manifestItems.map(e=>({id:e.id,href:f(e.href)?e.href:`${r||`file://`}${(0,t.toContainerHref)(e.href,c)}`,mediaType:e.mediaType})),guide:l.length>0?l:void 0,nav:{toc:(i.toc??[]).map(u(r))}}},h=(n,r,i)=>{let{metadata:a,readingOrder:o}=i,c=n.records.filter(e=>!e.dir),d=(0,e.createXmlSafeIdFactory)(),f=new Map(c.map(e=>[e.uri,d(e.uri)])),m={filename:n.filename??``,mediaType:i.mediaType,title:(0,t.mainTitle)(a)||p(n)||n.filename||``,renditionLayout:a.renditionLayout,renditionSpread:`auto`,readingDirection:a.readingDirection,spineItems:o.map((e,t)=>({id:f.get(e.uri)??d(e.uri),index:t,href:l({baseUrl:r,resourcePath:e.uri}),containerUri:e.uri,renditionLayout:e.renditionLayout,progressionWeight:e.progressionWeight,pageSpreadLeft:void 0,pageSpreadRight:void 0,mediaType:e.mediaType})),items:c.map(e=>({id:f.get(e.uri)??d(e.uri),href:encodeURI(`${r}${e.uri}`)}))},h=s(m)??i.toc?.map(u(r));return h===void 0?m:{...m,nav:{toc:h}}},g=({archive:e,baseUrl:t,resolved:n})=>{let r=n.sources.opf;return r?m(e,t,n,r):h(e,t,n)},_=e=>e?e.endsWith(`/`)?e:`${e}/`:``,v=async(e,{baseUrl:n=``,hooks:r=[]}={})=>{i.log(`Generating manifest from archive`,e);let a=_(n);try{let n=g({archive:e,baseUrl:a,resolved:await(0,t.resolveArchive)(e,{include:[`metadata`,`readingOrder`,`toc`,`sources`],layoutScan:!0})});for(let t of r)n=await t({archive:e,baseUrl:a})(n);if(n={...n,readingDirection:n.readingDirection??`ltr`},i.log(`Generated manifest`,n),process.env.NODE_ENV===`development`&&i.isEnabled()){let e=JSON.stringify(n,null,2);i.groupCollapsed(...i.getGroupArgs(`Generated manifest`)),i.log(`\n${e}`),i.groupEnd()}return n}catch(e){throw i.error(e),e}},y=(e,n)=>({archive:r,resourcePath:i})=>async a=>{let o=(0,t.getArchiveFileRecordByUri)(r,i);if(o?.basename.endsWith(e)){let e=await n(typeof a.body==`string`?a.body:await(0,t.readRecordAsText)(o));if(e!==void 0)return{...a,body:e}}return a},b=e=>{let t=e.descendantWithPath(`head`)?.childrenNamed(`meta`).find(e=>e.attr.name===`calibre:cover`);return!!(t&&t.attr.name===`calibre:cover`)},x=e=>e.descendantWithPath(`body`)?.descendantWithPath(`div`)?.childrenNamed(`svg`)?.find(e=>e.attr.width===`100%`&&e.attr.preserveAspectRatio===`none`),S=y(`.xhtml`,e=>{let t=new n.XmlDocument(e);if(!b(t))return;let r=x(t);return r&&delete r.attr.preserveAspectRatio,t.toString()}),C=y(`.css`,e=>e.replaceAll(`-webkit-writing-mode`,`writing-mode`)),w=async(e,n)=>{let r=await(0,t.readArchiveOpf)(e);if(r){let{opf:e,basePath:i}=r,a=e.manifestItems.find(e=>(0,t.toContainerUri)(e.href,i)===n)?.mediaType;if(a)return{mediaType:a}}return{mediaType:T(n)}},T=e=>{if(e.endsWith(`.css`))return`text/css; charset=UTF-8`;if(e.endsWith(`.jpg`))return`image/jpg`;if(e.endsWith(`.xhtml`))return`application/xhtml+xml`;if(e.endsWith(`.mp4`))return`video/mp4`;if(e.endsWith(`.svg`))return`image/svg+xml`},E=({archive:e,resourcePath:n})=>async r=>{let i=(0,t.getArchiveFileRecordByUri)(e,n);if(!i)return r;let a=await w(e,n);return{...r,params:{...r.params,...i?.encodingFormat&&{contentType:i.encodingFormat},...a.mediaType&&{contentType:a.mediaType}}}},D=`div.span.p.a.li.ul.ol.h1.h2.h3.h4.h5.h6.table.tr.td.th.thead.tbody.tfoot.section.article.header.footer.nav.aside.main.figure.figcaption.blockquote.pre.code.form.textarea.select.option.button.label.fieldset.legend.caption.dl.dt.dd.iframe.video.audio.canvas.script.style`.split(`.`),O=y(`.xhtml`,e=>{if(!RegExp(`<(${D.join(`|`)})[\\s/>]`,`i`).test(e))return;let t=RegExp(`<(${D.join(`|`)})(\\s[^>]*)?\\s*/>`,`gi`);return e.replace(t,(e,t,n=``)=>`<${t} ${n.trim()}></${t}>`)}),k=async(e,n,{hooks:r=[]}={})=>{let a={params:{}},o=[...r.map(t=>t({archive:e,resourcePath:n})),E({archive:e,resourcePath:n}),O({archive:e,resourcePath:n}),C({archive:e,resourcePath:n}),S({archive:e,resourcePath:n})];try{let r=await o.reduce(async(e,t)=>await t(await e),Promise.resolve(a));if(i.log(`Generated resource`,n,r),r.body!==void 0)return r;let s=(0,t.getArchiveFileRecordByUri)(e,n);if(!s)throw Error(`no file found for resourcePath:${n}`);return{...r,body:await s.blob()}}catch(e){throw i.error(e),e}},A=class{constructor(e){this.cleanArchiveAfter=e,this.state$=new r.BehaviorSubject({status:`idle`,locks:0})}update(e){this.state$.next({...this.state$.getValue(),...e})}get locks$(){return this.state$.pipe((0,r.map)(({locks:e})=>e))}get state(){return this.state$.getValue()}get isUnlocked$(){return this.locks$.pipe((0,r.map)(e=>e<=0),(0,r.distinctUntilChanged)(),(0,r.shareReplay)())}get overTTL$(){return this.isUnlocked$.pipe((0,r.switchMap)(e=>e?this.cleanArchiveAfter===1/0?r.NEVER:(0,r.timer)(this.cleanArchiveAfter):r.NEVER))}},j=({getArchive:e,cleanArchiveAfter:t=3e5})=>{let n=new r.Subject,a=new r.Subject,o=new r.Subject,s={};return n.pipe((0,r.mergeMap)(t=>{let n=s[t];if(n?.state.status!==`idle`)return r.EMPTY;let a=!1,c=e=>{i.debug(`Cleaning up archive with key: ${e}`);let t=s[e];delete s[e],a||=(t?.state.archive?.close(),!0)};n.update({status:`loading`});let l=n.locks$,u=n.isUnlocked$,d=l.pipe((0,r.pairwise)(),(0,r.filter)(([e,t])=>t>e),(0,r.startWith)(!0));return(0,r.from)(e(t)).pipe((0,r.tap)(e=>{n.update({archive:e,status:`success`})}),(0,r.catchError)(e=>(c(t),n.update({status:`error`,error:e}),r.EMPTY)),(0,r.switchMap)(()=>{let e=d.pipe((0,r.switchMap)(()=>o),(0,r.switchMap)(()=>u),(0,r.filter)(e=>e));return(0,r.merge)(e,n.overTTL$).pipe((0,r.first)(),(0,r.tap)(()=>{c(t)}))}))}),(0,r.takeUntil)(a)).subscribe(),{access:e=>{let i=!1,a=s[e]??new A(t);s[e]=a,a.update({locks:a.state.locks+1});let o=()=>{i||(i=!0,a.update({locks:a.state.locks-1}))};n.next(e);let c=a.state$.pipe((0,r.map)(({archive:e})=>e),(0,r.filter)(e=>!!e)),l=a.state$.pipe((0,r.tap)(({error:e})=>{if(e)throw e}),(0,r.ignoreElements)());return(0,r.merge)(c,l).pipe((0,r.first)(),(0,r.map)(e=>({archive:e,release:o})),(0,r.catchError)(e=>{throw o(),e}))},purge:()=>{o.next()},_archives:s}},M=e=>e?/^\d+$/.test(e)?{valid:!0,value:Number.parseInt(e,10)}:{valid:!1,value:void 0}:{valid:!0,value:void 0},N=e=>{if(!e.toLowerCase().startsWith(`bytes=`))return{kind:`missing`};let t=e.slice(6).trim();if(!t)return{kind:`invalid`};if(t.includes(`,`))return{kind:`multi`};let n=/^(\d*)-(\d*)$/.exec(t);if(!n)return{kind:`invalid`};let[,r=``,i=``]=n,a=M(r.trim()),o=M(i.trim());return!a.valid||!o.valid?{kind:`invalid`}:{kind:`single`,start:a.value,end:o.value}},P=e=>{if(e instanceof Blob)return{size:e.size,slice:(t,n)=>{let r=e.slice(t,n);return{content:r,length:r.size}}};let t=new TextEncoder().encode(e);return{size:t.byteLength,slice:(e,n)=>{let r=t.slice(e,n);return{content:r,length:r.byteLength}}}},F=({body:e,contentType:t,rangeHeader:n})=>{let r=new Headers;if(t&&r.set(`Content-Type`,t),r.set(`Accept-Ranges`,`bytes`),!n)return e instanceof Blob&&r.set(`Content-Length`,String(e.size)),new Response(e,{status:200,headers:r});let i=N(n);if(i.kind===`missing`||i.kind===`multi`)return e instanceof Blob&&r.set(`Content-Length`,String(e.size)),new Response(e,{status:200,headers:r});let a=P(e),o=a.size;if(i.kind===`invalid`)return new Response(null,{status:416,headers:{"Content-Range":`bytes */${o}`}});let s=i.start,c=i.end;if(s===void 0&&c===void 0||(s===void 0?(s=Math.max(0,o-Math.min(c??0,o)),c=o-1):(c===void 0||c>=o)&&(c=o-1),s<0||c<0||s>=o||c>=o||s>c))return new Response(null,{status:416,headers:{"Content-Range":`bytes */${o}`}});let l=a.slice(s,c+1);return r.set(`Content-Length`,String(l.length)),r.set(`Content-Range`,`bytes ${s}-${c}/${o}`),new Response(l.content,{status:206,headers:r})},I=`file://`,L=/^https?:\/\//,R=e=>{try{return decodeURIComponent(e)}catch{return e}},z=e=>e.startsWith(I)?e.slice(I.length):e,B=e=>{let t=z(e);return L.test(t)?t:z(R(t))},V=class{constructor({hooks:e,onError:t,onManifestSuccess:n,...r}){this.onError=e=>(console.error(e),new Response(String(e),{status:500})),this.archiveLoader=j(r),this.hooks=e??{},this.onManifestSuccess=n??(({manifest:e})=>Promise.resolve(e)),this.onError=t??this.onError}prune(){this.archiveLoader.purge()}accessArchive(e){return this.lastAccessedKey!==void 0&&this.lastAccessedKey!==e&&this.archiveLoader.purge(),this.lastAccessedKey=e,this.archiveLoader.access(e)}accessArchiveWithoutLock(e){return this.accessArchive(e).pipe((0,r.map)(({archive:e,release:t})=>(t(),e)))}withArchiveResponse({key:e,getResponse:t}){let n=this.accessArchive(e).pipe((0,r.mergeMap)(({archive:e,release:n})=>(0,r.from)(t(e)).pipe((0,r.finalize)(()=>{n()}))),(0,r.catchError)(e=>(0,r.of)(this.onError(e))));return(0,r.lastValueFrom)(n)}fetchManifest({key:e,baseUrl:t}){return this.withArchiveResponse({key:e,getResponse:e=>(0,r.from)(v(e,{baseUrl:t,hooks:this.hooks.manifest})).pipe((0,r.switchMap)(t=>(0,r.from)(this.onManifestSuccess({manifest:t,archive:e}))),(0,r.map)(e=>new Response(JSON.stringify(e),{status:200})))})}fetchResource({key:e,resourcePath:t,request:n}){return this.withArchiveResponse({key:e,getResponse:e=>{let i=B(t);return(0,r.from)(k(e,i,{hooks:this.hooks.resource})).pipe((0,r.map)(e=>F({body:e.body??``,contentType:e.params.contentType,rangeHeader:n?.headers.get(`range`)})))}})}},H=class extends V{constructor({getUriInfo:e,...t}){super(t),this.getUriInfo=e,this.fetchEventListener=this.fetchEventListener.bind(this)}fetchEventListener(t){try{let n=this.getUriInfo(t);if(!n)return;let r=(0,e.removeTrailingSlash)(n.baseUrl),i=t.request.url.substring(r.length+1),[a=``]=i.split(`/`),o=(0,e.removeTrailingSlash)(i.substring(a.length+1));i.endsWith(`/manifest`)?t.respondWith(this.fetchManifest({key:a,baseUrl:`${r}/${a}/`})):t.respondWith(this.fetchResource({key:a,resourcePath:o,request:t.request}))}catch(e){t.respondWith(new Response(String(e),{status:500}))}}};exports.ServiceWorkerStreamer=H,exports.Streamer=V,exports.configure=a,exports.createManifestResourceHref=l,exports.generateManifestFromArchive=v,exports.generateResourceFromArchive=k;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["SharedReport","parseContentType","detectMimeTypeFromName","getUriBasename","urlJoin","createXmlSafeIdFactory","mainTitle","resolveArchive","getArchiveFileRecordByUri","readRecordAsText","XmlDocument","readArchiveOpf","getArchiveFileRecordByUri","getArchiveFileRecordByUri","BehaviorSubject","map","distinctUntilChanged","shareReplay","switchMap","NEVER","timer","Subject","mergeMap","EMPTY","pairwise","filter","startWith","from","tap","catchError","merge","first","takeUntil","ignoreElements","map","mergeMap","from","finalize","catchError","of","lastValueFrom","switchMap","removeTrailingSlash","_prose_reader_shared","_prose_reader_archive_reader","xmldoc","rxjs","Report","configure","enableReport","normalizeFilenameAsTitle","filename","buildAudiobookToc","manifest","item","encodeResourcePath","resourcePath","createManifestResourceHref","baseUrl","hrefBaseUrl","toManifestTocItem","manifestGuideType","v","raw","isHttpUrl","value","firstDirectoryTitle","archive","dir","epubManifest","resolved","opfBasePath","opf","guide","reference","type","createSafeId","metadata","readingOrder","recordsManifest","files","file","idByUri","toc","manifestFromResolvedArchive","normalizeBaseUrl","generateManifestFromArchive","normalizedBaseUrl","hookFactory","hooks","manifestStr","createTextResourceHook","extension","newBody","transform","resource","hasCalibreCoverMeta","metaElm","doc","node","getBuggyCoverSvg","calibreFixHook","opfXmlDoc","bodyToParse","buggySvg","cssFixHook","body","getMetadata","parsed","foundMediaType","containerHref","basePath","getContentTypeFromExtension","uri","defaultHook","invalidSelfClosingTags","selfClosingTagsFixHook","tagPattern","tagName","attributes","generateResourceFromArchive","defaultResource","resourceHooks","hook","gen","ArchiveEntry","cleanArchiveAfter","update","locks","isUnlocked","createArchiveLoader","loadSubject","destroySubject","purgeSubject","archives","archiveEntry","key","isClosed","cleanupArchive","entry","locks$","isUnlocked$","newAccess$","prev","curent","getArchive","readyForPurge$","releaseCalled","release","archive$","error$","error","parseByteRangePart","parseSingleByteRange","rangeHeader","rangeValue","matches","rawStart","rawEnd","parsedStart","parsedEnd","materializeForSlicing","part","start","endExclusive","bytes","createRangeResponse","headers","contentType","parsedRange","rangeBody","size","end","partial","fileProtocol","httpProtocolPattern","decodeResourcePath","stripFileProtocol","normalizeResourcePath","resourcePathWithoutFileProtocol","Streamer","rest","onManifestSuccess","onError","response$","getResponse","cleanedResourcePath","request","ServiceWorkerStreamer","getUriInfo","uriInfo","event","streamerPath"],"sources":["../../package.json","../../src/report.ts","../../src/configure.ts","../../src/generators/manifest/audiobookToc.ts","../../src/generators/manifest/createManifestResourceHref.ts","../../src/generators/manifest/manifestFromResolvedArchive.ts","../../src/generators/manifest/index.ts","../../src/generators/resources/hooks/createTextResourceHook.ts","../../src/generators/resources/hooks/calibreFixHook.ts","../../src/generators/resources/hooks/cssFixHook.ts","../../src/generators/resources/hooks/defaultHook.ts","../../src/generators/resources/hooks/selfClosingTagsFixHook.ts","../../src/generators/resources/index.ts","../../src/archiveLoader.ts","../../src/utils/createRangeResponse.ts","../../src/Streamer.ts","../../src/ServiceWorkerStreamer.ts"],"sourcesContent":["{\n \"name\": \"@prose-reader/streamer\",\n \"version\": \"28.0.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index/index.js\",\n \"require\": \"./dist/index/index.cjs\"\n }\n },\n \"types\": \"./dist/index.d.ts\",\n \"license\": \"MIT\",\n \"files\": [\n \"/dist\"\n ],\n \"scripts\": {\n \"start\": \"vite build --watch --mode development\",\n \"build\": \"tsc && vite build\",\n \"test\": \"vitest run --coverage\",\n \"tsc\": \"tsc\",\n \"test:watch\": \"vitest watch\"\n },\n \"dependencies\": {\n \"@prose-reader/archive-reader\": \"workspace:^\",\n \"@prose-reader/shared\": \"workspace:^\",\n \"xmldoc\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"^7.5.5\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\",\n \"engines\": {\n \"node\": \">=22.12\"\n }\n}\n","import { Report as SharedReport } from \"@prose-reader/shared\"\nimport { name } from \"../package.json\"\n\n// No explicit `enabled`: inherit the shared root, which auto-configures from\n// the `globalThis.__PROSE_READER_DEBUG` flag (works in window, workers, node).\n// `configure()` remains as an explicit override for setups where the flag\n// cannot be set before the module evaluates.\nexport const Report = SharedReport.namespace(name, undefined, {\n color: \"#ffae42\",\n})\n","import { Report } from \"./report\"\n\nexport const configure = ({\n enableReport,\n}: {\n enableReport?: boolean\n} = {}) => {\n Report.enable(!!enableReport)\n}\n","import {\n detectMimeTypeFromName,\n getUriBasename,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\n\ntype Toc = NonNullable<Manifest[\"nav\"]>[\"toc\"]\n\nexport const normalizeFilenameAsTitle = (filename: string) =>\n filename\n .replace(/\\.[^.]+$/, \"\")\n .replace(/[_-]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n\n/**\n * One entry per track for a book made only of audio, each titled after the\n * archive record it plays.\n */\nexport const buildAudiobookToc = (manifest: Manifest): Toc | undefined => {\n if (manifest.spineItems.length === 0) return undefined\n\n const allAudio = manifest.spineItems.every((item) => {\n const mimeType =\n parseContentType(item.mediaType ?? \"\") ||\n detectMimeTypeFromName(item.containerUri)\n\n return mimeType?.startsWith(\"audio/\")\n })\n\n if (!allAudio) return undefined\n\n return manifest.spineItems.map((item) => ({\n title: normalizeFilenameAsTitle(getUriBasename(item.containerUri)),\n href: item.href,\n path: item.containerUri,\n contents: [],\n }))\n}\n","// encodeURI leaves `#` and `?` untouched (reserved URI delimiters), but in\n// a raw archive filename they are data and would otherwise be parsed as\n// fragment/query by whatever fetches the href (a fragment never even reaches\n// the streamer). Same escaping as the archive-reader toc containerHref.\nconst encodeResourcePath = (resourcePath: string) =>\n encodeURI(resourcePath).replace(/#/g, `%23`).replace(/\\?/g, `%3F`)\n\nexport const createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return `${encodeURI(hrefBaseUrl)}${encodeResourcePath(resourcePath)}`\n}\n","import {\n type Archive,\n type ArchiveTocItem,\n mainTitle,\n type ResolvedArchive,\n} from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeIdFactory, urlJoin } from \"@prose-reader/shared\"\nimport { buildAudiobookToc } from \"./audiobookToc\"\nimport { createManifestResourceHref } from \"./createManifestResourceHref\"\n\ntype TocItem = NonNullable<Manifest[\"nav\"]>[\"toc\"][number]\ntype ManifestGuideEntry = NonNullable<Manifest[\"guide\"]>[number]\n\n/**\n * The archive reader resolves references within the container, the streamer\n * owns where they are served from.\n */\nconst toManifestTocItem =\n (baseUrl: string) =>\n (item: ArchiveTocItem): TocItem => ({\n title: item.title,\n path: item.path,\n // rebase the container-relative ref into serving space; entries without a\n // target (eg nav heading without link) keep an empty href\n href: item.containerHref ? urlJoin(baseUrl, item.containerHref) : ``,\n contents: item.contents.map(toManifestTocItem(baseUrl)),\n })\n\nconst manifestGuideType = (\n raw: string | undefined,\n): ManifestGuideEntry[\"type\"] | undefined => {\n const v = raw?.trim()\n if (\n v === `cover` ||\n v === `title-page` ||\n v === `copyright-page` ||\n v === `text`\n ) {\n return v\n }\n return undefined\n}\n\nconst isHttpUrl = (value: string) => /^https?:\\/\\//.test(value)\n\nconst firstDirectoryTitle = (archive: Archive): string | undefined =>\n archive.records.find(({ dir }) => dir)?.basename.replace(/\\/$/, ``)\n\n/**\n * The subset of {@link ResolvedArchive} the manifest mapping consumes: the\n * normalized publication plus `sources` — the OPF manifest (all package\n * resources, not just the spine) and the legacy guide only exist there.\n */\ntype ResolvedArchiveForManifest = Pick<\n ResolvedArchive,\n \"mediaType\" | \"metadata\" | \"readingOrder\" | \"toc\" | \"sources\"\n>\n\nconst epubManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n opf: NonNullable<ResolvedArchiveForManifest[\"sources\"][\"opf\"]>,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n const opfBasePath = opf.basePath\n\n const guide: ManifestGuideEntry[] = []\n for (const reference of opf.opf.guide) {\n const type = manifestGuideType(reference.type)\n if (type === undefined) continue\n // guide hrefs stay as authored (opf-relative), matching the legacy\n // EPUB 2 contract consumers already handle\n guide.push({ href: reference.href, title: reference.title, type })\n }\n\n const createSafeId = createXmlSafeIdFactory()\n\n return {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title: mainTitle(metadata) || firstDirectoryTitle(archive) || ``,\n renditionLayout: metadata.renditionLayout,\n renditionFlow: metadata.renditionFlow ?? `auto`,\n renditionSpread: metadata.renditionSpread,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: item.id ?? createSafeId(item.uri),\n index,\n // remote resources pass through; local ones get the serving base, or\n // the file:// scheme when the manifest is not served from anywhere, and\n // the reference the book makes to them, which its navigation and links\n // match as written\n href: isHttpUrl(item.uri)\n ? item.uri\n : `${baseUrl || `file://`}${item.containerHref ?? item.uri}`,\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n ...(item.renditionFlow !== undefined\n ? { renditionFlow: item.renditionFlow }\n : {}),\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: item.pageSpreadLeft,\n pageSpreadRight: item.pageSpreadRight,\n mediaType: item.mediaType,\n })),\n items: opf.opf.manifestItems.map((item) => ({\n id: item.id,\n href: isHttpUrl(item.href)\n ? item.href\n : `${baseUrl || `file://`}${opfBasePath ? `${opfBasePath}/` : ``}${item.href}`,\n mediaType: item.mediaType,\n })),\n guide: guide.length > 0 ? guide : undefined,\n nav: {\n // EPUB-like containers keep an explicit (possibly empty) toc\n toc: (resolved.toc ?? []).map(toManifestTocItem(baseUrl)),\n },\n }\n}\n\nconst recordsManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n\n const files = archive.records.filter((file) => !file.dir)\n const createSafeId = createXmlSafeIdFactory()\n const idByUri = new Map(\n files.map((file) => [file.uri, createSafeId(file.uri)]),\n )\n\n const manifest: Manifest = {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title:\n mainTitle(metadata) ||\n firstDirectoryTitle(archive) ||\n archive.filename ||\n ``,\n renditionLayout: metadata.renditionLayout,\n renditionSpread: `auto`,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: idByUri.get(item.uri) ?? createSafeId(item.uri),\n index,\n href: createManifestResourceHref({ baseUrl, resourcePath: item.uri }),\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: undefined,\n pageSpreadRight: undefined,\n mediaType: item.mediaType,\n })),\n // every container file stays addressable, sidecars included — items is\n // the resource map, the reading order is the curation\n items: files.map((file) => ({\n id: idByUri.get(file.uri) ?? createSafeId(file.uri),\n href: encodeURI(`${baseUrl}${file.uri}`),\n })),\n }\n\n const audiobookToc = buildAudiobookToc(manifest)\n const toc = audiobookToc ?? resolved.toc?.map(toManifestTocItem(baseUrl))\n\n return toc !== undefined ? { ...manifest, nav: { toc } } : manifest\n}\n\n/**\n * Maps a resolved archive into the streamer's serving space: hrefs get the\n * base url (or `file://`) baked in, ids become XML-safe, defaults are the\n * manifest's own. The container understanding itself (format detection,\n * metadata precedence, spine rules) all happened in\n * `@prose-reader/archive-reader`.\n */\nexport const manifestFromResolvedArchive = ({\n archive,\n baseUrl,\n resolved,\n}: {\n archive: Archive\n baseUrl: string\n resolved: ResolvedArchiveForManifest\n}): Manifest => {\n const opf = resolved.sources.opf\n\n if (opf) return epubManifest(archive, baseUrl, resolved, opf)\n\n return recordsManifest(archive, baseUrl, resolved)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { resolveArchive } from \"@prose-reader/archive-reader\"\nimport type { StreamerManifestHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { manifestFromResolvedArchive } from \"./manifestFromResolvedArchive\"\n\nconst normalizeBaseUrl = (baseUrl: string | undefined) => {\n if (!baseUrl) return ``\n\n return baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n}\n\n/**\n * Generates the prose `Manifest` for an archive: `resolveArchive` does the\n * book understanding (metadata precedence, reading order, toc, the viewport\n * layout scan), then the result is mapped into serving space and handed to\n * the consumer `hooks` (ordered `Manifest` transforms). The final defaults\n * (`readingDirection` falling back to `ltr`) apply after the hooks, so a\n * hook can still observe \"no source decided it\".\n */\nexport const generateManifestFromArchive = async (\n archive: Archive,\n {\n baseUrl = ``,\n hooks = [],\n }: { baseUrl?: string; hooks?: StreamerManifestHookFactory[] } = {},\n) => {\n Report.log(\"Generating manifest from archive\", archive)\n\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n try {\n const resolved = await resolveArchive(archive, {\n include: [`metadata`, `readingOrder`, `toc`, `sources`],\n layoutScan: true,\n })\n\n let manifest = manifestFromResolvedArchive({\n archive,\n baseUrl: normalizedBaseUrl,\n resolved,\n })\n\n for (const hookFactory of hooks) {\n manifest = await hookFactory({\n archive,\n baseUrl: normalizedBaseUrl,\n })(manifest)\n }\n\n manifest = {\n ...manifest,\n readingDirection: manifest.readingDirection ?? \"ltr\",\n }\n\n Report.log(\"Generated manifest\", manifest)\n\n if (process.env.NODE_ENV === \"development\") {\n if (Report.isEnabled()) {\n const manifestStr = JSON.stringify(manifest, null, 2)\n Report.groupCollapsed(...Report.getGroupArgs(\"Generated manifest\"))\n Report.log(`\\n${manifestStr}`)\n Report.groupEnd()\n }\n }\n\n return manifest\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import {\n getArchiveFileRecordByUri,\n readRecordAsText,\n} from \"@prose-reader/archive-reader\"\nimport type {\n StreamerResourceHook,\n StreamerResourceHookContext,\n StreamerResourceHookFactory,\n} from \"../../../hooks\"\n\n/**\n * Shared scaffolding for resource hooks that rewrite the text body of a single\n * archive file matching a given extension.\n *\n * Resolves the archive record for `resourcePath` and, when its basename ends\n * with `extension`, reads the resource body as text (reusing an already-string\n * body, otherwise reading the record) and passes it to `transform`. When\n * `transform` returns a string the resource body is replaced; when it returns\n * `undefined` the resource is returned untouched (so a non-string body is\n * preserved as-is).\n */\nexport const createTextResourceHook =\n (\n extension: string,\n transform: (\n body: string,\n ) => string | undefined | Promise<string | undefined>,\n ): StreamerResourceHookFactory =>\n ({\n archive,\n resourcePath,\n }: StreamerResourceHookContext): StreamerResourceHook =>\n async (resource) => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (file?.basename.endsWith(extension)) {\n const bodyToParse =\n typeof resource.body === `string`\n ? resource.body\n : await readRecordAsText(file)\n\n const newBody = await transform(bodyToParse)\n\n if (newBody !== undefined) {\n return {\n ...resource,\n body: newBody,\n }\n }\n }\n\n return resource\n }\n","import { XmlDocument } from \"xmldoc\"\nimport { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst hasCalibreCoverMeta = (doc: XmlDocument) => {\n const metaElm = doc\n .descendantWithPath(\"head\")\n ?.childrenNamed(\"meta\")\n .find((node) => node.attr.name === \"calibre:cover\")\n\n return !!(metaElm && metaElm.attr.name === \"calibre:cover\")\n}\n\nconst getBuggyCoverSvg = (doc: XmlDocument) => {\n return doc\n .descendantWithPath(\"body\")\n ?.descendantWithPath(\"div\")\n ?.childrenNamed(\"svg\")\n ?.find(\n (node) =>\n node.attr.width === \"100%\" && node.attr.preserveAspectRatio === \"none\",\n )\n}\n\nexport const calibreFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const opfXmlDoc = new XmlDocument(bodyToParse)\n\n if (!hasCalibreCoverMeta(opfXmlDoc)) {\n return undefined\n }\n\n const buggySvg = getBuggyCoverSvg(opfXmlDoc)\n\n if (buggySvg) {\n delete buggySvg.attr.preserveAspectRatio\n }\n\n return opfXmlDoc.toString()\n },\n)\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nexport const cssFixHook = createTextResourceHook(\".css\", (body) =>\n /**\n * Fix the potentially invalid writing mode present on some vertical book.\n * This has the benefit of making it compatible with firefox as well.\n */\n body.replaceAll(`-webkit-writing-mode`, `writing-mode`),\n)\n","import {\n type Archive,\n getArchiveFileRecordByUri,\n readArchiveOpf,\n} from \"@prose-reader/archive-reader\"\nimport type { HookResource } from \"./types\"\n\n/**\n * Resolves `contentType` from the package OPF when possible, else from the URI.\n * Uses `readArchiveOpf` each time — see `generateResourceFromArchive` JSDoc for\n * why that can mean repeated OPF work on the resource path.\n */\nconst getMetadata = async (archive: Archive, resourcePath: string) => {\n const parsed = await readArchiveOpf(archive)\n\n if (parsed) {\n const { opf, basePath } = parsed\n\n // we are comparing opf items container-relative path in epub archive\n // against resourcePath (which is an absolute path in archive).\n // They should in theory match.\n const foundMediaType = opf.manifestItems.find((item) => {\n const containerHref = basePath ? `${basePath}/${item.href}` : item.href\n\n return resourcePath.endsWith(containerHref)\n })?.mediaType\n\n if (foundMediaType) {\n return {\n mediaType: foundMediaType,\n }\n }\n }\n\n return {\n mediaType: getContentTypeFromExtension(resourcePath),\n }\n}\n\nconst getContentTypeFromExtension = (uri: string) => {\n if (uri.endsWith(`.css`)) {\n return `text/css; charset=UTF-8`\n }\n if (uri.endsWith(`.jpg`)) {\n return `image/jpg`\n }\n if (uri.endsWith(`.xhtml`)) {\n return `application/xhtml+xml`\n }\n if (uri.endsWith(`.mp4`)) {\n return `video/mp4`\n }\n if (uri.endsWith(`.svg`)) {\n return `image/svg+xml`\n }\n}\n\nexport const defaultHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) return resource\n\n const metadata = await getMetadata(archive, resourcePath)\n\n return {\n ...resource,\n params: {\n ...resource.params,\n ...(file?.encodingFormat && {\n contentType: file.encodingFormat,\n }),\n ...(metadata.mediaType && {\n contentType: metadata.mediaType,\n }),\n },\n }\n }\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst invalidSelfClosingTags = [\n \"div\",\n \"span\",\n \"p\",\n \"a\",\n \"li\",\n \"ul\",\n \"ol\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"table\",\n \"tr\",\n \"td\",\n \"th\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"section\",\n \"article\",\n \"header\",\n \"footer\",\n \"nav\",\n \"aside\",\n \"main\",\n \"figure\",\n \"figcaption\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"form\",\n \"textarea\",\n \"select\",\n \"option\",\n \"button\",\n \"label\",\n \"fieldset\",\n \"legend\",\n \"caption\",\n \"dl\",\n \"dt\",\n \"dd\",\n \"iframe\",\n \"video\",\n \"audio\",\n \"canvas\",\n \"script\",\n \"style\",\n]\n\n/**\n * Some books uses xhtml files but also includes wrong self closin tags. This happens\n * a lot with kobo epub which have a lot of self closing <script ... />. This breaks on some\n * browser such as webkit. We use a regex to replace and fix them. There\n * is a first lighter regex which check if any such tag exist in the first place before running\n * the full replace regex empty.\n */\nexport const selfClosingTagsFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const tagCheckPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})[\\\\s/>]`,\n \"i\",\n )\n if (!tagCheckPattern.test(bodyToParse)) {\n return undefined\n }\n\n const tagPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})(\\\\s[^>]*)?\\\\s*/>`,\n \"gi\",\n )\n\n return bodyToParse.replace(tagPattern, (_, tagName, attributes = \"\") => {\n // Convert to an opening and closing tag\n return `<${tagName} ${attributes.trim()}></${tagName}>`\n })\n },\n)\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { getArchiveFileRecordByUri } from \"@prose-reader/archive-reader\"\nimport type { StreamerResourceHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { calibreFixHook } from \"./hooks/calibreFixHook\"\nimport { cssFixHook } from \"./hooks/cssFixHook\"\nimport { defaultHook } from \"./hooks/defaultHook\"\nimport { selfClosingTagsFixHook } from \"./hooks/selfClosingTagsFixHook\"\nimport type { HookResource } from \"./hooks/types\"\n\n/**\n * Builds one streamed asset from `archive` (hot path: typically once per\n * fetched file in the reader).\n *\n * **OPF limitation:** Unlike manifest generation, this API does not take\n * `ArchiveOpfParsed`. `defaultHook` calls `readArchiveOpf(archive)` to resolve\n * `media-type` from the package document, so EPUBs repeat OPF I/O + parse per\n * resource. Usually fine (small OPF); if profiling shows regressions, address\n * explicitly—e.g. optional `archiveOpf` (or equivalent) threaded from\n * `Streamer.fetchResource` / manifest, or an opt-in cache on `Archive` wired at\n * construction time—rather than hidden process-wide memoization.\n */\nexport const generateResourceFromArchive = async (\n archive: Archive,\n resourcePath: string,\n { hooks = [] }: { hooks?: StreamerResourceHookFactory[] } = {},\n) => {\n const defaultResource: HookResource = {\n params: {},\n }\n const externalHooks = hooks.map((hook) => hook({ archive, resourcePath }))\n\n /**\n * EPUB: `defaultHook` calls `readArchiveOpf` → repeated OPF I/O + parse per\n * resource until we thread `ArchiveOpfParsed` here (see export JSDoc).\n */\n const resourceHooks = [\n ...externalHooks,\n defaultHook({ archive, resourcePath }),\n selfClosingTagsFixHook({ archive, resourcePath }),\n cssFixHook({ archive, resourcePath }),\n calibreFixHook({ archive, resourcePath }),\n ]\n\n try {\n const resource = await resourceHooks.reduce(async (manifest, gen) => {\n return await gen(await manifest)\n }, Promise.resolve(defaultResource))\n\n Report.log(\"Generated resource\", resourcePath, resource)\n\n if (resource.body !== undefined) {\n return resource\n }\n\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) {\n throw new Error(`no file found for resourcePath:${resourcePath}`)\n }\n\n return {\n ...resource,\n body: await file.blob(),\n }\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport {\n BehaviorSubject,\n catchError,\n distinctUntilChanged,\n EMPTY,\n filter,\n first,\n from,\n ignoreElements,\n map,\n merge,\n mergeMap,\n NEVER,\n type ObservedValueOf,\n pairwise,\n Subject,\n shareReplay,\n startWith,\n switchMap,\n takeUntil,\n tap,\n timer,\n} from \"rxjs\"\nimport { Report } from \"./report\"\n\nclass ArchiveEntry {\n state$ = new BehaviorSubject<{\n status: \"idle\" | \"loading\" | \"success\" | \"error\"\n error?: unknown | undefined\n archive?: undefined | Archive\n locks: number\n }>({\n status: `idle`,\n locks: 0,\n })\n\n constructor(private cleanArchiveAfter: number) {}\n\n update(update: Partial<ObservedValueOf<typeof this.state$>>) {\n this.state$.next({ ...this.state$.getValue(), ...update })\n }\n\n get locks$() {\n return this.state$.pipe(map(({ locks }) => locks))\n }\n\n get state() {\n return this.state$.getValue()\n }\n\n get isUnlocked$() {\n return this.locks$.pipe(\n map((locks) => locks <= 0),\n distinctUntilChanged(),\n shareReplay(),\n )\n }\n\n get overTTL$() {\n return this.isUnlocked$.pipe(\n switchMap((isUnlocked) =>\n !isUnlocked\n ? NEVER\n : this.cleanArchiveAfter === Infinity\n ? NEVER\n : timer(this.cleanArchiveAfter),\n ),\n )\n }\n}\n\nexport const createArchiveLoader = ({\n getArchive,\n cleanArchiveAfter = 5 * 60 * 1000, // 5mn\n}: {\n getArchive: (key: string) => Promise<Archive>\n cleanArchiveAfter?: number\n}) => {\n const loadSubject = new Subject<string>()\n const destroySubject = new Subject<void>()\n const purgeSubject = new Subject<void>()\n const archives: Record<string, ArchiveEntry> = {}\n\n const loadArchive$ = loadSubject.pipe(\n mergeMap((key) => {\n const archiveEntry = archives[key]\n\n if (archiveEntry?.state.status !== \"idle\") return EMPTY\n\n let isClosed = false\n\n const cleanupArchive = (key: string) => {\n Report.debug(`Cleaning up archive with key: ${key}`)\n\n const entry = archives[key]\n\n delete archives[key]\n\n if (!isClosed) {\n entry?.state.archive?.close()\n isClosed = true\n }\n }\n\n archiveEntry.update({\n status: \"loading\",\n })\n\n const locks$ = archiveEntry.locks$\n const isUnlocked$ = archiveEntry.isUnlocked$\n\n const newAccess$ = locks$.pipe(\n pairwise(),\n filter(([prev, curent]) => curent > prev),\n startWith(true),\n )\n\n const archive$ = from(getArchive(key))\n\n return archive$.pipe(\n tap((archive) => {\n archiveEntry.update({\n archive,\n status: \"success\",\n })\n }),\n catchError((error) => {\n cleanupArchive(key)\n\n archiveEntry.update({\n status: \"error\",\n error,\n })\n\n return EMPTY\n }),\n switchMap(() => {\n const readyForPurge$ = newAccess$.pipe(\n switchMap(() => purgeSubject),\n switchMap(() => isUnlocked$),\n filter((isUnlocked) => isUnlocked),\n )\n\n const toCleanup$ = merge(readyForPurge$, archiveEntry.overTTL$)\n\n return toCleanup$.pipe(\n first(),\n tap(() => {\n cleanupArchive(key)\n }),\n )\n }),\n )\n }),\n takeUntil(destroySubject),\n )\n\n const access = (key: string) => {\n let releaseCalled = false\n\n const archiveEntry = archives[key] ?? new ArchiveEntry(cleanArchiveAfter)\n\n archives[key] = archiveEntry\n\n archiveEntry.update({\n locks: archiveEntry.state.locks + 1,\n })\n\n const release = () => {\n if (releaseCalled) return\n\n releaseCalled = true\n\n archiveEntry.update({\n locks: archiveEntry.state.locks - 1,\n })\n }\n\n loadSubject.next(key)\n\n const archive$ = archiveEntry.state$.pipe(\n map(({ archive }) => archive),\n filter((archive) => !!archive),\n )\n\n const error$ = archiveEntry.state$.pipe(\n tap(({ error }) => {\n if (error) {\n throw error\n }\n }),\n ignoreElements(),\n )\n\n return merge(archive$, error$).pipe(\n first(),\n map((archive) => ({ archive, release })),\n catchError((error) => {\n release()\n\n throw error\n }),\n )\n }\n\n /**\n * Will purge immediatly archives as soon as they are released\n */\n const purge = () => {\n purgeSubject.next()\n }\n\n loadArchive$.subscribe()\n\n return {\n access,\n purge,\n _archives: archives,\n }\n}\n","const parseByteRangePart = (value: string) => {\n if (!value) {\n return {\n valid: true,\n value: undefined,\n }\n }\n\n if (!/^\\d+$/.test(value)) {\n return {\n valid: false,\n value: undefined,\n }\n }\n\n return {\n valid: true,\n value: Number.parseInt(value, 10),\n }\n}\n\nconst parseSingleByteRange = (rangeHeader: string) => {\n if (!rangeHeader.toLowerCase().startsWith(\"bytes=\")) {\n return {\n kind: \"missing\" as const,\n }\n }\n\n const rangeValue = rangeHeader.slice(\"bytes=\".length).trim()\n\n if (!rangeValue) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n if (rangeValue.includes(\",\")) {\n return {\n kind: \"multi\" as const,\n }\n }\n\n const matches = /^(\\d*)-(\\d*)$/.exec(rangeValue)\n\n if (!matches) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n const [, rawStart = \"\", rawEnd = \"\"] = matches\n const parsedStart = parseByteRangePart(rawStart.trim())\n const parsedEnd = parseByteRangePart(rawEnd.trim())\n\n if (!parsedStart.valid || !parsedEnd.valid) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n return {\n kind: \"single\" as const,\n start: parsedStart.value,\n end: parsedEnd.value,\n }\n}\n\n/**\n * Normalize the body into a sliceable form that avoids passing a\n * `Blob.slice()` result directly to `new Response()`. Some Node.js\n * versions do not recognise sliced Blobs as valid `BodyInit`,\n * producing `\"[object Blob]\"` instead of the actual content.\n */\nconst materializeForSlicing = (body: Blob | string) => {\n if (body instanceof Blob) {\n return {\n size: body.size,\n slice: (start: number, endExclusive: number) => {\n const part = body.slice(start, endExclusive)\n return { content: part, length: part.size }\n },\n }\n }\n\n const bytes = new TextEncoder().encode(body)\n\n return {\n size: bytes.byteLength,\n slice: (start: number, endExclusive: number) => {\n const part = bytes.slice(start, endExclusive)\n return { content: part, length: part.byteLength }\n },\n }\n}\n\nexport const createRangeResponse = ({\n body,\n contentType,\n rangeHeader,\n}: {\n body: Blob | string\n contentType?: string\n rangeHeader?: string | null\n}) => {\n const headers = new Headers()\n\n if (contentType) {\n headers.set(\"Content-Type\", contentType)\n }\n\n headers.set(\"Accept-Ranges\", \"bytes\")\n\n if (!rangeHeader) {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const parsedRange = parseSingleByteRange(rangeHeader)\n\n if (parsedRange.kind === \"missing\" || parsedRange.kind === \"multi\") {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const rangeBody = materializeForSlicing(body)\n const size = rangeBody.size\n\n if (parsedRange.kind === \"invalid\") {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n let start = parsedRange.start\n let end = parsedRange.end\n\n if (start === undefined && end === undefined) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n if (start === undefined) {\n const suffixLength = Math.min(end ?? 0, size)\n start = Math.max(0, size - suffixLength)\n end = size - 1\n } else if (end === undefined || end >= size) {\n end = size - 1\n }\n\n if (start < 0 || end < 0 || start >= size || end >= size || start > end) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n const partial = rangeBody.slice(start, end + 1)\n\n headers.set(\"Content-Length\", String(partial.length))\n headers.set(\"Content-Range\", `bytes ${start}-${end}/${size}`)\n\n return new Response(partial.content, {\n status: 206,\n headers,\n })\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport {\n catchError,\n finalize,\n from,\n lastValueFrom,\n map,\n mergeMap,\n type Observable,\n of,\n switchMap,\n} from \"rxjs\"\nimport { createArchiveLoader } from \"./archiveLoader\"\nimport { generateManifestFromArchive } from \"./generators/manifest\"\nimport { generateResourceFromArchive } from \"./generators/resources\"\nimport type { StreamerHooks } from \"./hooks\"\nimport { createRangeResponse } from \"./utils/createRangeResponse\"\n\ntype OnError = (error: unknown) => Response\ntype OnManifestSuccess = (params: {\n manifest: Manifest\n archive: Archive\n}) => Observable<Manifest> | Promise<Manifest>\ntype WithArchiveResponse = (\n archive: Archive,\n) => Observable<Response> | Promise<Response>\n\nconst fileProtocol = `file://`\nconst httpProtocolPattern = /^https?:\\/\\//\n\nconst decodeResourcePath = (resourcePath: string) => {\n try {\n return decodeURIComponent(resourcePath)\n } catch {\n return resourcePath\n }\n}\n\nconst stripFileProtocol = (resourcePath: string) =>\n resourcePath.startsWith(fileProtocol)\n ? resourcePath.slice(fileProtocol.length)\n : resourcePath\n\nconst normalizeResourcePath = (resourcePath: string) => {\n const resourcePathWithoutFileProtocol = stripFileProtocol(resourcePath)\n\n if (httpProtocolPattern.test(resourcePathWithoutFileProtocol)) {\n return resourcePathWithoutFileProtocol\n }\n\n return stripFileProtocol(decodeResourcePath(resourcePathWithoutFileProtocol))\n}\n\nexport class Streamer {\n protected archiveLoader: ReturnType<typeof createArchiveLoader>\n protected onError: OnError = (error) => {\n console.error(error)\n\n return new Response(String(error), { status: 500 })\n }\n protected hooks: StreamerHooks\n protected onManifestSuccess: OnManifestSuccess\n protected lastAccessedKey: string | undefined\n\n constructor({\n hooks,\n onError,\n onManifestSuccess,\n ...rest\n }: Parameters<typeof createArchiveLoader>[0] & {\n hooks?: StreamerHooks\n onError?: OnError\n onManifestSuccess?: OnManifestSuccess\n }) {\n this.archiveLoader = createArchiveLoader(rest)\n this.hooks = hooks ?? {}\n\n this.onManifestSuccess =\n onManifestSuccess ?? (({ manifest }) => Promise.resolve(manifest))\n this.onError = onError ?? this.onError\n }\n\n public prune() {\n this.archiveLoader.purge()\n }\n\n public accessArchive(key: string) {\n if (this.lastAccessedKey !== undefined && this.lastAccessedKey !== key) {\n this.archiveLoader.purge()\n }\n\n this.lastAccessedKey = key\n\n return this.archiveLoader.access(key)\n }\n\n public accessArchiveWithoutLock(key: string) {\n return this.accessArchive(key).pipe(\n map(({ archive, release }) => {\n release()\n\n return archive\n }),\n )\n }\n\n protected withArchiveResponse({\n key,\n getResponse,\n }: {\n key: string\n getResponse: WithArchiveResponse\n }) {\n const response$ = this.accessArchive(key).pipe(\n mergeMap(({ archive, release }) =>\n from(getResponse(archive)).pipe(\n finalize(() => {\n release()\n }),\n ),\n ),\n catchError((error) => {\n return of(this.onError(error))\n }),\n )\n\n return lastValueFrom(response$)\n }\n\n public fetchManifest({ key, baseUrl }: { key: string; baseUrl?: string }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n const manifest$ = from(\n generateManifestFromArchive(archive, {\n baseUrl,\n hooks: this.hooks.manifest,\n }),\n )\n\n return manifest$.pipe(\n switchMap((manifest) =>\n from(this.onManifestSuccess({ manifest, archive })),\n ),\n map(\n (manifest) =>\n new Response(JSON.stringify(manifest satisfies Manifest), {\n status: 200,\n }),\n ),\n )\n },\n })\n }\n\n public fetchResource({\n key,\n resourcePath,\n request,\n }: {\n key: string\n resourcePath: string\n request?: Request\n }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n /**\n * We commonly use file:// for manifest without baseUrl. This ensure we\n * have valid URL for the reader while flagging them as local.\n *\n * However, we obviously don't have the file:// prefix on the archive.\n */\n const cleanedResourcePath = normalizeResourcePath(resourcePath)\n\n /**\n * Reader hot path: one `generateResourceFromArchive` per request. For\n * EPUBs, `defaultHook` re-reads and re-parses the package OPF each time\n * (no `archiveOpf` threading yet). See `generateResourceFromArchive` JSDoc.\n */\n const resource$ = from(\n generateResourceFromArchive(archive, cleanedResourcePath, {\n hooks: this.hooks.resource,\n }),\n )\n\n return resource$.pipe(\n map((resource) =>\n createRangeResponse({\n body: resource.body ?? \"\",\n contentType: resource.params.contentType,\n rangeHeader: request?.headers.get(\"range\"),\n }),\n ),\n )\n },\n })\n }\n}\n","import { removeTrailingSlash } from \"@prose-reader/shared\"\nimport { Streamer } from \"./Streamer\"\n\ntype ConflictFreeWebWorkerFetchEvent = {\n readonly request: Request\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */\n respondWith(r: Response | PromiseLike<Response>): void\n}\n\nexport class ServiceWorkerStreamer extends Streamer {\n protected getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n\n constructor({\n getUriInfo,\n ...rest\n }: ConstructorParameters<typeof Streamer>[0] & {\n getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n }) {\n super(rest)\n\n this.getUriInfo = getUriInfo\n this.fetchEventListener = this.fetchEventListener.bind(this)\n }\n\n fetchEventListener(event: ConflictFreeWebWorkerFetchEvent) {\n try {\n const uriInfo = this.getUriInfo(event)\n\n if (!uriInfo) return\n\n const baseUrl = removeTrailingSlash(uriInfo.baseUrl)\n const streamerPath = event.request.url.substring(\n baseUrl.length + `/`.length,\n )\n const [key = ``] = streamerPath.split(\"/\")\n const resourcePath = removeTrailingSlash(\n streamerPath.substring(key.length + `/`.length),\n )\n\n if (streamerPath.endsWith(`/manifest`)) {\n event.respondWith(\n this.fetchManifest({ key, baseUrl: `${baseUrl}/${key}/` }),\n )\n } else {\n event.respondWith(\n this.fetchResource({\n key,\n resourcePath,\n request: event.request,\n }),\n )\n }\n } catch (e) {\n event.respondWith(new Response(String(e), { status: 500 }))\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["SharedReport","parseContentType","detectMimeTypeFromName","getUriBasename","urlJoin","createXmlSafeIdFactory","mainTitle","toContainerHref","resolveArchive","getArchiveFileRecordByUri","readRecordAsText","XmlDocument","readArchiveOpf","toContainerUri","getArchiveFileRecordByUri","getArchiveFileRecordByUri","BehaviorSubject","map","distinctUntilChanged","shareReplay","switchMap","NEVER","timer","Subject","mergeMap","EMPTY","pairwise","filter","startWith","from","tap","catchError","merge","first","takeUntil","ignoreElements","map","mergeMap","from","finalize","catchError","of","lastValueFrom","switchMap","removeTrailingSlash","_prose_reader_shared","_prose_reader_archive_reader","xmldoc","rxjs","Report","configure","enableReport","normalizeFilenameAsTitle","filename","buildAudiobookToc","manifest","item","encodeResourcePath","resourcePath","createManifestResourceHref","baseUrl","hrefBaseUrl","toManifestTocItem","manifestGuideType","v","raw","isHttpUrl","value","firstDirectoryTitle","archive","dir","epubManifest","resolved","opfBasePath","opf","guide","reference","type","createSafeId","metadata","readingOrder","recordsManifest","files","file","idByUri","toc","manifestFromResolvedArchive","normalizeBaseUrl","generateManifestFromArchive","normalizedBaseUrl","hookFactory","hooks","manifestStr","createTextResourceHook","extension","newBody","transform","resource","hasCalibreCoverMeta","metaElm","doc","node","getBuggyCoverSvg","calibreFixHook","opfXmlDoc","bodyToParse","buggySvg","cssFixHook","body","getMetadata","parsed","foundMediaType","basePath","getContentTypeFromExtension","uri","defaultHook","invalidSelfClosingTags","selfClosingTagsFixHook","tagPattern","tagName","attributes","generateResourceFromArchive","defaultResource","resourceHooks","hook","gen","ArchiveEntry","cleanArchiveAfter","update","locks","isUnlocked","createArchiveLoader","loadSubject","destroySubject","purgeSubject","archives","archiveEntry","key","isClosed","cleanupArchive","entry","locks$","isUnlocked$","newAccess$","prev","curent","getArchive","readyForPurge$","releaseCalled","release","archive$","error$","error","parseByteRangePart","parseSingleByteRange","rangeHeader","rangeValue","matches","rawStart","rawEnd","parsedStart","parsedEnd","materializeForSlicing","part","start","endExclusive","bytes","createRangeResponse","headers","contentType","parsedRange","rangeBody","size","end","partial","fileProtocol","httpProtocolPattern","decodeResourcePath","stripFileProtocol","normalizeResourcePath","resourcePathWithoutFileProtocol","Streamer","rest","onManifestSuccess","onError","response$","getResponse","cleanedResourcePath","request","ServiceWorkerStreamer","getUriInfo","uriInfo","event","streamerPath"],"sources":["../../package.json","../../src/report.ts","../../src/configure.ts","../../src/generators/manifest/audiobookToc.ts","../../src/generators/manifest/createManifestResourceHref.ts","../../src/generators/manifest/manifestFromResolvedArchive.ts","../../src/generators/manifest/index.ts","../../src/generators/resources/hooks/createTextResourceHook.ts","../../src/generators/resources/hooks/calibreFixHook.ts","../../src/generators/resources/hooks/cssFixHook.ts","../../src/generators/resources/hooks/defaultHook.ts","../../src/generators/resources/hooks/selfClosingTagsFixHook.ts","../../src/generators/resources/index.ts","../../src/archiveLoader.ts","../../src/utils/createRangeResponse.ts","../../src/Streamer.ts","../../src/ServiceWorkerStreamer.ts"],"sourcesContent":["{\n \"name\": \"@prose-reader/streamer\",\n \"version\": \"28.1.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index/index.js\",\n \"require\": \"./dist/index/index.cjs\"\n }\n },\n \"types\": \"./dist/index.d.ts\",\n \"license\": \"MIT\",\n \"files\": [\n \"/dist\"\n ],\n \"scripts\": {\n \"start\": \"vite build --watch --mode development\",\n \"build\": \"tsc && vite build\",\n \"test\": \"vitest run --coverage\",\n \"tsc\": \"tsc\",\n \"test:watch\": \"vitest watch\"\n },\n \"dependencies\": {\n \"@prose-reader/archive-reader\": \"workspace:^\",\n \"@prose-reader/shared\": \"workspace:^\",\n \"xmldoc\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"^7.5.5\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\",\n \"engines\": {\n \"node\": \">=22.12\"\n }\n}\n","import { Report as SharedReport } from \"@prose-reader/shared\"\nimport { name } from \"../package.json\"\n\n// No explicit `enabled`: inherit the shared root, which auto-configures from\n// the `globalThis.__PROSE_READER_DEBUG` flag (works in window, workers, node).\n// `configure()` remains as an explicit override for setups where the flag\n// cannot be set before the module evaluates.\nexport const Report = SharedReport.namespace(name, undefined, {\n color: \"#ffae42\",\n})\n","import { Report } from \"./report\"\n\nexport const configure = ({\n enableReport,\n}: {\n enableReport?: boolean\n} = {}) => {\n Report.enable(!!enableReport)\n}\n","import {\n detectMimeTypeFromName,\n getUriBasename,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\n\ntype Toc = NonNullable<Manifest[\"nav\"]>[\"toc\"]\n\nexport const normalizeFilenameAsTitle = (filename: string) =>\n filename\n .replace(/\\.[^.]+$/, \"\")\n .replace(/[_-]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n\n/**\n * One entry per track for a book made only of audio, each titled after the\n * archive record it plays.\n */\nexport const buildAudiobookToc = (manifest: Manifest): Toc | undefined => {\n if (manifest.spineItems.length === 0) return undefined\n\n const allAudio = manifest.spineItems.every((item) => {\n const mimeType =\n parseContentType(item.mediaType ?? \"\") ||\n detectMimeTypeFromName(item.containerUri)\n\n return mimeType?.startsWith(\"audio/\")\n })\n\n if (!allAudio) return undefined\n\n return manifest.spineItems.map((item) => ({\n title: normalizeFilenameAsTitle(getUriBasename(item.containerUri)),\n href: item.href,\n path: item.containerUri,\n contents: [],\n }))\n}\n","// encodeURI leaves `#` and `?` untouched (reserved URI delimiters), but in\n// a raw archive filename they are data and would otherwise be parsed as\n// fragment/query by whatever fetches the href (a fragment never even reaches\n// the streamer). Same escaping as the archive-reader toc containerHref.\nconst encodeResourcePath = (resourcePath: string) =>\n encodeURI(resourcePath).replace(/#/g, `%23`).replace(/\\?/g, `%3F`)\n\nexport const createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return `${encodeURI(hrefBaseUrl)}${encodeResourcePath(resourcePath)}`\n}\n","import {\n type Archive,\n type ArchiveTocItem,\n mainTitle,\n type ResolvedArchive,\n toContainerHref,\n} from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeIdFactory, urlJoin } from \"@prose-reader/shared\"\nimport { buildAudiobookToc } from \"./audiobookToc\"\nimport { createManifestResourceHref } from \"./createManifestResourceHref\"\n\ntype TocItem = NonNullable<Manifest[\"nav\"]>[\"toc\"][number]\ntype ManifestGuideEntry = NonNullable<Manifest[\"guide\"]>[number]\n\n/**\n * The archive reader resolves references within the container, the streamer\n * owns where they are served from.\n */\nconst toManifestTocItem =\n (baseUrl: string) =>\n (item: ArchiveTocItem): TocItem => ({\n title: item.title,\n path: item.path,\n // rebase the container-relative ref into serving space; entries without a\n // target (eg nav heading without link) keep an empty href\n href: item.containerHref ? urlJoin(baseUrl, item.containerHref) : ``,\n contents: item.contents.map(toManifestTocItem(baseUrl)),\n })\n\nconst manifestGuideType = (\n raw: string | undefined,\n): ManifestGuideEntry[\"type\"] | undefined => {\n const v = raw?.trim()\n if (\n v === `cover` ||\n v === `title-page` ||\n v === `copyright-page` ||\n v === `text`\n ) {\n return v\n }\n return undefined\n}\n\nconst isHttpUrl = (value: string) => /^https?:\\/\\//.test(value)\n\nconst firstDirectoryTitle = (archive: Archive): string | undefined =>\n archive.records.find(({ dir }) => dir)?.basename.replace(/\\/$/, ``)\n\n/**\n * The subset of {@link ResolvedArchive} the manifest mapping consumes: the\n * normalized publication plus `sources` — the OPF manifest (all package\n * resources, not just the spine) and the legacy guide only exist there.\n */\ntype ResolvedArchiveForManifest = Pick<\n ResolvedArchive,\n \"mediaType\" | \"metadata\" | \"readingOrder\" | \"toc\" | \"sources\"\n>\n\nconst epubManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n opf: NonNullable<ResolvedArchiveForManifest[\"sources\"][\"opf\"]>,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n const opfBasePath = opf.basePath\n\n const guide: ManifestGuideEntry[] = []\n for (const reference of opf.opf.guide) {\n const type = manifestGuideType(reference.type)\n if (type === undefined) continue\n // guide hrefs stay as authored (opf-relative), matching the legacy\n // EPUB 2 contract consumers already handle\n guide.push({ href: reference.href, title: reference.title, type })\n }\n\n const createSafeId = createXmlSafeIdFactory()\n\n return {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title: mainTitle(metadata) || firstDirectoryTitle(archive) || ``,\n renditionLayout: metadata.renditionLayout,\n renditionFlow: metadata.renditionFlow ?? `auto`,\n renditionSpread: metadata.renditionSpread,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: item.id ?? createSafeId(item.uri),\n index,\n // remote resources pass through; local ones get the serving base, or\n // the file:// scheme when the manifest is not served from anywhere, and\n // the reference the book makes to them, which its navigation and links\n // match as written\n href: isHttpUrl(item.uri)\n ? item.uri\n : `${baseUrl || `file://`}${item.containerHref ?? item.uri}`,\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n ...(item.renditionFlow !== undefined\n ? { renditionFlow: item.renditionFlow }\n : {}),\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: item.pageSpreadLeft,\n pageSpreadRight: item.pageSpreadRight,\n mediaType: item.mediaType,\n })),\n items: opf.opf.manifestItems.map((item) => ({\n id: item.id,\n // rebased as the spine item of the same resource is\n href: isHttpUrl(item.href)\n ? item.href\n : `${baseUrl || `file://`}${toContainerHref(item.href, opfBasePath)}`,\n mediaType: item.mediaType,\n })),\n guide: guide.length > 0 ? guide : undefined,\n nav: {\n // EPUB-like containers keep an explicit (possibly empty) toc\n toc: (resolved.toc ?? []).map(toManifestTocItem(baseUrl)),\n },\n }\n}\n\nconst recordsManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n\n const files = archive.records.filter((file) => !file.dir)\n const createSafeId = createXmlSafeIdFactory()\n const idByUri = new Map(\n files.map((file) => [file.uri, createSafeId(file.uri)]),\n )\n\n const manifest: Manifest = {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title:\n mainTitle(metadata) ||\n firstDirectoryTitle(archive) ||\n archive.filename ||\n ``,\n renditionLayout: metadata.renditionLayout,\n renditionSpread: `auto`,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: idByUri.get(item.uri) ?? createSafeId(item.uri),\n index,\n href: createManifestResourceHref({ baseUrl, resourcePath: item.uri }),\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: undefined,\n pageSpreadRight: undefined,\n mediaType: item.mediaType,\n })),\n // every container file stays addressable, sidecars included — items is\n // the resource map, the reading order is the curation\n items: files.map((file) => ({\n id: idByUri.get(file.uri) ?? createSafeId(file.uri),\n href: encodeURI(`${baseUrl}${file.uri}`),\n })),\n }\n\n const audiobookToc = buildAudiobookToc(manifest)\n const toc = audiobookToc ?? resolved.toc?.map(toManifestTocItem(baseUrl))\n\n return toc !== undefined ? { ...manifest, nav: { toc } } : manifest\n}\n\n/**\n * Maps a resolved archive into the streamer's serving space: hrefs get the\n * base url (or `file://`) baked in, ids become XML-safe, defaults are the\n * manifest's own. The container understanding itself (format detection,\n * metadata precedence, spine rules) all happened in\n * `@prose-reader/archive-reader`.\n */\nexport const manifestFromResolvedArchive = ({\n archive,\n baseUrl,\n resolved,\n}: {\n archive: Archive\n baseUrl: string\n resolved: ResolvedArchiveForManifest\n}): Manifest => {\n const opf = resolved.sources.opf\n\n if (opf) return epubManifest(archive, baseUrl, resolved, opf)\n\n return recordsManifest(archive, baseUrl, resolved)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { resolveArchive } from \"@prose-reader/archive-reader\"\nimport type { StreamerManifestHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { manifestFromResolvedArchive } from \"./manifestFromResolvedArchive\"\n\nconst normalizeBaseUrl = (baseUrl: string | undefined) => {\n if (!baseUrl) return ``\n\n return baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n}\n\n/**\n * Generates the prose `Manifest` for an archive: `resolveArchive` does the\n * book understanding (metadata precedence, reading order, toc, the viewport\n * layout scan), then the result is mapped into serving space and handed to\n * the consumer `hooks` (ordered `Manifest` transforms). The final defaults\n * (`readingDirection` falling back to `ltr`) apply after the hooks, so a\n * hook can still observe \"no source decided it\".\n */\nexport const generateManifestFromArchive = async (\n archive: Archive,\n {\n baseUrl = ``,\n hooks = [],\n }: { baseUrl?: string; hooks?: StreamerManifestHookFactory[] } = {},\n) => {\n Report.log(\"Generating manifest from archive\", archive)\n\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n try {\n const resolved = await resolveArchive(archive, {\n include: [`metadata`, `readingOrder`, `toc`, `sources`],\n layoutScan: true,\n })\n\n let manifest = manifestFromResolvedArchive({\n archive,\n baseUrl: normalizedBaseUrl,\n resolved,\n })\n\n for (const hookFactory of hooks) {\n manifest = await hookFactory({\n archive,\n baseUrl: normalizedBaseUrl,\n })(manifest)\n }\n\n manifest = {\n ...manifest,\n readingDirection: manifest.readingDirection ?? \"ltr\",\n }\n\n Report.log(\"Generated manifest\", manifest)\n\n if (process.env.NODE_ENV === \"development\") {\n if (Report.isEnabled()) {\n const manifestStr = JSON.stringify(manifest, null, 2)\n Report.groupCollapsed(...Report.getGroupArgs(\"Generated manifest\"))\n Report.log(`\\n${manifestStr}`)\n Report.groupEnd()\n }\n }\n\n return manifest\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import {\n getArchiveFileRecordByUri,\n readRecordAsText,\n} from \"@prose-reader/archive-reader\"\nimport type {\n StreamerResourceHook,\n StreamerResourceHookContext,\n StreamerResourceHookFactory,\n} from \"../../../hooks\"\n\n/**\n * Shared scaffolding for resource hooks that rewrite the text body of a single\n * archive file matching a given extension.\n *\n * Resolves the archive record for `resourcePath` and, when its basename ends\n * with `extension`, reads the resource body as text (reusing an already-string\n * body, otherwise reading the record) and passes it to `transform`. When\n * `transform` returns a string the resource body is replaced; when it returns\n * `undefined` the resource is returned untouched (so a non-string body is\n * preserved as-is).\n */\nexport const createTextResourceHook =\n (\n extension: string,\n transform: (\n body: string,\n ) => string | undefined | Promise<string | undefined>,\n ): StreamerResourceHookFactory =>\n ({\n archive,\n resourcePath,\n }: StreamerResourceHookContext): StreamerResourceHook =>\n async (resource) => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (file?.basename.endsWith(extension)) {\n const bodyToParse =\n typeof resource.body === `string`\n ? resource.body\n : await readRecordAsText(file)\n\n const newBody = await transform(bodyToParse)\n\n if (newBody !== undefined) {\n return {\n ...resource,\n body: newBody,\n }\n }\n }\n\n return resource\n }\n","import { XmlDocument } from \"xmldoc\"\nimport { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst hasCalibreCoverMeta = (doc: XmlDocument) => {\n const metaElm = doc\n .descendantWithPath(\"head\")\n ?.childrenNamed(\"meta\")\n .find((node) => node.attr.name === \"calibre:cover\")\n\n return !!(metaElm && metaElm.attr.name === \"calibre:cover\")\n}\n\nconst getBuggyCoverSvg = (doc: XmlDocument) => {\n return doc\n .descendantWithPath(\"body\")\n ?.descendantWithPath(\"div\")\n ?.childrenNamed(\"svg\")\n ?.find(\n (node) =>\n node.attr.width === \"100%\" && node.attr.preserveAspectRatio === \"none\",\n )\n}\n\nexport const calibreFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const opfXmlDoc = new XmlDocument(bodyToParse)\n\n if (!hasCalibreCoverMeta(opfXmlDoc)) {\n return undefined\n }\n\n const buggySvg = getBuggyCoverSvg(opfXmlDoc)\n\n if (buggySvg) {\n delete buggySvg.attr.preserveAspectRatio\n }\n\n return opfXmlDoc.toString()\n },\n)\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nexport const cssFixHook = createTextResourceHook(\".css\", (body) =>\n /**\n * Fix the potentially invalid writing mode present on some vertical book.\n * This has the benefit of making it compatible with firefox as well.\n */\n body.replaceAll(`-webkit-writing-mode`, `writing-mode`),\n)\n","import {\n type Archive,\n getArchiveFileRecordByUri,\n readArchiveOpf,\n toContainerUri,\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 // the manifest item whose href names the requested record\n const foundMediaType = opf.manifestItems.find(\n (item) => toContainerUri(item.href, basePath) === resourcePath,\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,34 +1,34 @@
1
1
  import { Report as e, createXmlSafeIdFactory as t, detectMimeTypeFromName as n, getUriBasename as r, parseContentType as i, removeTrailingSlash as a, urlJoin as o } from "@prose-reader/shared";
2
- import { getArchiveFileRecordByUri as s, mainTitle as c, readArchiveOpf as l, readRecordAsText as u, resolveArchive as d } from "@prose-reader/archive-reader";
3
- import { XmlDocument as f } from "xmldoc";
4
- import { BehaviorSubject as p, EMPTY as m, NEVER as h, Subject as g, catchError as _, distinctUntilChanged as ee, filter as v, finalize as y, first as b, from as x, ignoreElements as te, lastValueFrom as ne, map as S, merge as C, mergeMap as w, of as T, pairwise as E, shareReplay as D, startWith as re, switchMap as O, takeUntil as ie, tap as k, timer as ae } from "rxjs";
2
+ import { getArchiveFileRecordByUri as s, mainTitle as c, readArchiveOpf as l, readRecordAsText as u, resolveArchive as d, toContainerHref as ee, toContainerUri as f } from "@prose-reader/archive-reader";
3
+ import { XmlDocument as p } from "xmldoc";
4
+ import { BehaviorSubject as te, EMPTY as m, NEVER as h, Subject as g, catchError as _, distinctUntilChanged as v, filter as y, finalize as ne, first as b, from as x, ignoreElements as S, lastValueFrom as re, map as C, merge as w, mergeMap as T, of as E, pairwise as D, shareReplay as O, startWith as ie, switchMap as k, takeUntil as ae, tap as A, timer as oe } from "rxjs";
5
5
  //#endregion
6
6
  //#region src/report.ts
7
- var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe = ({ enableReport: e } = {}) => {
8
- A.enable(!!e);
9
- }, j = (e) => e.replace(/\.[^.]+$/, "").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim(), M = (e) => {
7
+ var j = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), M = ({ enableReport: e } = {}) => {
8
+ j.enable(!!e);
9
+ }, N = (e) => e.replace(/\.[^.]+$/, "").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim(), P = (e) => {
10
10
  if (e.spineItems.length !== 0 && e.spineItems.every((e) => (i(e.mediaType ?? "") || n(e.containerUri))?.startsWith("audio/"))) return e.spineItems.map((e) => ({
11
- title: j(r(e.containerUri)),
11
+ title: N(r(e.containerUri)),
12
12
  href: e.href,
13
13
  path: e.containerUri,
14
14
  contents: []
15
15
  }));
16
- }, N = (e) => encodeURI(e).replace(/#/g, "%23").replace(/\?/g, "%3F"), P = ({ baseUrl: e = "", resourcePath: t }) => {
16
+ }, F = (e) => encodeURI(e).replace(/#/g, "%23").replace(/\?/g, "%3F"), I = ({ baseUrl: e = "", resourcePath: t }) => {
17
17
  if (!e && /^https?:\/\//.test(t)) return encodeURI(t);
18
18
  let n = e ? `${e}${e.endsWith("/") ? "" : "/"}` : "file://";
19
- return `${encodeURI(n)}${N(t)}`;
20
- }, F = (e) => (t) => ({
19
+ return `${encodeURI(n)}${F(t)}`;
20
+ }, L = (e) => (t) => ({
21
21
  title: t.title,
22
22
  path: t.path,
23
23
  href: t.containerHref ? o(e, t.containerHref) : "",
24
- contents: t.contents.map(F(e))
25
- }), I = (e) => {
24
+ contents: t.contents.map(L(e))
25
+ }), R = (e) => {
26
26
  let t = e?.trim();
27
27
  if (t === "cover" || t === "title-page" || t === "copyright-page" || t === "text") return t;
28
- }, L = (e) => /^https?:\/\//.test(e), R = (e) => e.records.find(({ dir: e }) => e)?.basename.replace(/\/$/, ""), z = (e, n, r, i) => {
28
+ }, z = (e) => /^https?:\/\//.test(e), B = (e) => e.records.find(({ dir: e }) => e)?.basename.replace(/\/$/, ""), V = (e, n, r, i) => {
29
29
  let { metadata: a, readingOrder: o } = r, s = i.basePath, l = [];
30
30
  for (let e of i.opf.guide) {
31
- let t = I(e.type);
31
+ let t = R(e.type);
32
32
  t !== void 0 && l.push({
33
33
  href: e.href,
34
34
  title: e.title,
@@ -39,7 +39,7 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
39
39
  return {
40
40
  filename: e.filename ?? "",
41
41
  mediaType: r.mediaType,
42
- title: c(a) || R(e) || "",
42
+ title: c(a) || B(e) || "",
43
43
  renditionLayout: a.renditionLayout,
44
44
  renditionFlow: a.renditionFlow ?? "auto",
45
45
  renditionSpread: a.renditionSpread,
@@ -47,7 +47,7 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
47
47
  spineItems: o.map((e, t) => ({
48
48
  id: e.id ?? u(e.uri),
49
49
  index: t,
50
- href: L(e.uri) ? e.uri : `${n || "file://"}${e.containerHref ?? e.uri}`,
50
+ href: z(e.uri) ? e.uri : `${n || "file://"}${e.containerHref ?? e.uri}`,
51
51
  containerUri: e.uri,
52
52
  renditionLayout: e.renditionLayout,
53
53
  ...e.renditionFlow === void 0 ? {} : { renditionFlow: e.renditionFlow },
@@ -58,24 +58,24 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
58
58
  })),
59
59
  items: i.opf.manifestItems.map((e) => ({
60
60
  id: e.id,
61
- href: L(e.href) ? e.href : `${n || "file://"}${s ? `${s}/` : ""}${e.href}`,
61
+ href: z(e.href) ? e.href : `${n || "file://"}${ee(e.href, s)}`,
62
62
  mediaType: e.mediaType
63
63
  })),
64
64
  guide: l.length > 0 ? l : void 0,
65
- nav: { toc: (r.toc ?? []).map(F(n)) }
65
+ nav: { toc: (r.toc ?? []).map(L(n)) }
66
66
  };
67
- }, B = (e, n, r) => {
67
+ }, H = (e, n, r) => {
68
68
  let { metadata: i, readingOrder: a } = r, o = e.records.filter((e) => !e.dir), s = t(), l = new Map(o.map((e) => [e.uri, s(e.uri)])), u = {
69
69
  filename: e.filename ?? "",
70
70
  mediaType: r.mediaType,
71
- title: c(i) || R(e) || e.filename || "",
71
+ title: c(i) || B(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: l.get(e.uri) ?? s(e.uri),
77
77
  index: t,
78
- href: P({
78
+ href: I({
79
79
  baseUrl: n,
80
80
  resourcePath: e.uri
81
81
  }),
@@ -90,19 +90,19 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
90
90
  id: l.get(e.uri) ?? s(e.uri),
91
91
  href: encodeURI(`${n}${e.uri}`)
92
92
  }))
93
- }, d = M(u) ?? r.toc?.map(F(n));
93
+ }, d = P(u) ?? r.toc?.map(L(n));
94
94
  return d === void 0 ? u : {
95
95
  ...u,
96
96
  nav: { toc: d }
97
97
  };
98
- }, V = ({ archive: e, baseUrl: t, resolved: n }) => {
98
+ }, U = ({ archive: e, baseUrl: t, resolved: n }) => {
99
99
  let r = n.sources.opf;
100
- return r ? z(e, t, n, r) : B(e, t, n);
101
- }, H = (e) => e ? e.endsWith("/") ? e : `${e}/` : "", U = async (e, { baseUrl: t = "", hooks: n = [] } = {}) => {
102
- A.log("Generating manifest from archive", e);
103
- let r = H(t);
100
+ return r ? V(e, t, n, r) : H(e, t, n);
101
+ }, W = (e) => e ? e.endsWith("/") ? e : `${e}/` : "", G = async (e, { baseUrl: t = "", hooks: n = [] } = {}) => {
102
+ j.log("Generating manifest from archive", e);
103
+ let r = W(t);
104
104
  try {
105
- let t = V({
105
+ let t = U({
106
106
  archive: e,
107
107
  baseUrl: r,
108
108
  resolved: await d(e, {
@@ -122,15 +122,15 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
122
122
  if (t = {
123
123
  ...t,
124
124
  readingDirection: t.readingDirection ?? "ltr"
125
- }, A.log("Generated manifest", t), process.env.NODE_ENV === "development" && A.isEnabled()) {
125
+ }, j.log("Generated manifest", t), process.env.NODE_ENV === "development" && j.isEnabled()) {
126
126
  let e = JSON.stringify(t, null, 2);
127
- A.groupCollapsed(...A.getGroupArgs("Generated manifest")), A.log(`\n${e}`), A.groupEnd();
127
+ j.groupCollapsed(...j.getGroupArgs("Generated manifest")), j.log(`\n${e}`), j.groupEnd();
128
128
  }
129
129
  return t;
130
130
  } catch (e) {
131
- throw A.error(e), e;
131
+ throw j.error(e), e;
132
132
  }
133
- }, W = (e, t) => ({ archive: n, resourcePath: r }) => async (i) => {
133
+ }, K = (e, t) => ({ archive: n, resourcePath: r }) => async (i) => {
134
134
  let a = s(n, r);
135
135
  if (a?.basename.endsWith(e)) {
136
136
  let e = await t(typeof i.body == "string" ? i.body : await u(a));
@@ -140,34 +140,31 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
140
140
  };
141
141
  }
142
142
  return i;
143
- }, G = (e) => {
143
+ }, se = (e) => {
144
144
  let t = e.descendantWithPath("head")?.childrenNamed("meta").find((e) => e.attr.name === "calibre:cover");
145
145
  return !!(t && t.attr.name === "calibre:cover");
146
- }, K = (e) => e.descendantWithPath("body")?.descendantWithPath("div")?.childrenNamed("svg")?.find((e) => e.attr.width === "100%" && e.attr.preserveAspectRatio === "none"), se = W(".xhtml", (e) => {
147
- let t = new f(e);
148
- if (!G(t)) return;
149
- let n = K(t);
146
+ }, ce = (e) => e.descendantWithPath("body")?.descendantWithPath("div")?.childrenNamed("svg")?.find((e) => e.attr.width === "100%" && e.attr.preserveAspectRatio === "none"), le = K(".xhtml", (e) => {
147
+ let t = new p(e);
148
+ if (!se(t)) return;
149
+ let n = ce(t);
150
150
  return n && delete n.attr.preserveAspectRatio, t.toString();
151
- }), ce = W(".css", (e) => e.replaceAll("-webkit-writing-mode", "writing-mode")), le = async (e, t) => {
151
+ }), q = K(".css", (e) => e.replaceAll("-webkit-writing-mode", "writing-mode")), ue = async (e, t) => {
152
152
  let n = await l(e);
153
153
  if (n) {
154
- let { opf: e, basePath: r } = n, i = e.manifestItems.find((e) => {
155
- let n = r ? `${r}/${e.href}` : e.href;
156
- return t.endsWith(n);
157
- })?.mediaType;
154
+ let { opf: e, basePath: r } = n, i = e.manifestItems.find((e) => f(e.href, r) === t)?.mediaType;
158
155
  if (i) return { mediaType: i };
159
156
  }
160
- return { mediaType: ue(t) };
161
- }, ue = (e) => {
157
+ return { mediaType: de(t) };
158
+ }, de = (e) => {
162
159
  if (e.endsWith(".css")) return "text/css; charset=UTF-8";
163
160
  if (e.endsWith(".jpg")) return "image/jpg";
164
161
  if (e.endsWith(".xhtml")) return "application/xhtml+xml";
165
162
  if (e.endsWith(".mp4")) return "video/mp4";
166
163
  if (e.endsWith(".svg")) return "image/svg+xml";
167
- }, de = ({ archive: e, resourcePath: t }) => async (n) => {
164
+ }, fe = ({ archive: e, resourcePath: t }) => async (n) => {
168
165
  let r = s(e, t);
169
166
  if (!r) return n;
170
- let i = await le(e, t);
167
+ let i = await ue(e, t);
171
168
  return {
172
169
  ...n,
173
170
  params: {
@@ -176,36 +173,36 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
176
173
  ...i.mediaType && { contentType: i.mediaType }
177
174
  }
178
175
  };
179
- }, q = /* @__PURE__ */ "div.span.p.a.li.ul.ol.h1.h2.h3.h4.h5.h6.table.tr.td.th.thead.tbody.tfoot.section.article.header.footer.nav.aside.main.figure.figcaption.blockquote.pre.code.form.textarea.select.option.button.label.fieldset.legend.caption.dl.dt.dd.iframe.video.audio.canvas.script.style".split("."), fe = W(".xhtml", (e) => {
180
- if (!RegExp(`<(${q.join("|")})[\\s/>]`, "i").test(e)) return;
181
- let t = RegExp(`<(${q.join("|")})(\\s[^>]*)?\\s*/>`, "gi");
176
+ }, 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("."), pe = K(".xhtml", (e) => {
177
+ if (!RegExp(`<(${J.join("|")})[\\s/>]`, "i").test(e)) return;
178
+ let t = RegExp(`<(${J.join("|")})(\\s[^>]*)?\\s*/>`, "gi");
182
179
  return e.replace(t, (e, t, n = "") => `<${t} ${n.trim()}></${t}>`);
183
- }), J = async (e, t, { hooks: n = [] } = {}) => {
180
+ }), Y = async (e, t, { hooks: n = [] } = {}) => {
184
181
  let r = { params: {} }, i = [
185
182
  ...n.map((n) => n({
186
183
  archive: e,
187
184
  resourcePath: t
188
185
  })),
189
- de({
186
+ fe({
190
187
  archive: e,
191
188
  resourcePath: t
192
189
  }),
193
- fe({
190
+ pe({
194
191
  archive: e,
195
192
  resourcePath: t
196
193
  }),
197
- ce({
194
+ q({
198
195
  archive: e,
199
196
  resourcePath: t
200
197
  }),
201
- se({
198
+ le({
202
199
  archive: e,
203
200
  resourcePath: t
204
201
  })
205
202
  ];
206
203
  try {
207
204
  let n = await i.reduce(async (e, t) => await t(await e), Promise.resolve(r));
208
- if (A.log("Generated resource", t, n), n.body !== void 0) return n;
205
+ if (j.log("Generated resource", t, n), n.body !== void 0) return n;
209
206
  let a = s(e, t);
210
207
  if (!a) throw Error(`no file found for resourcePath:${t}`);
211
208
  return {
@@ -213,11 +210,11 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
213
210
  body: await a.blob()
214
211
  };
215
212
  } catch (e) {
216
- throw A.error(e), e;
213
+ throw j.error(e), e;
217
214
  }
218
- }, pe = class {
215
+ }, me = class {
219
216
  constructor(e) {
220
- this.cleanArchiveAfter = e, this.state$ = new p({
217
+ this.cleanArchiveAfter = e, this.state$ = new te({
221
218
  status: "idle",
222
219
  locks: 0
223
220
  });
@@ -229,30 +226,30 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
229
226
  });
230
227
  }
231
228
  get locks$() {
232
- return this.state$.pipe(S(({ locks: e }) => e));
229
+ return this.state$.pipe(C(({ locks: e }) => e));
233
230
  }
234
231
  get state() {
235
232
  return this.state$.getValue();
236
233
  }
237
234
  get isUnlocked$() {
238
- return this.locks$.pipe(S((e) => e <= 0), ee(), D());
235
+ return this.locks$.pipe(C((e) => e <= 0), v(), O());
239
236
  }
240
237
  get overTTL$() {
241
- return this.isUnlocked$.pipe(O((e) => e ? this.cleanArchiveAfter === Infinity ? h : ae(this.cleanArchiveAfter) : h));
238
+ return this.isUnlocked$.pipe(k((e) => e ? this.cleanArchiveAfter === Infinity ? h : oe(this.cleanArchiveAfter) : h));
242
239
  }
243
- }, me = ({ getArchive: e, cleanArchiveAfter: t = 3e5 }) => {
240
+ }, he = ({ getArchive: e, cleanArchiveAfter: t = 3e5 }) => {
244
241
  let n = new g(), r = new g(), i = new g(), a = {};
245
- return n.pipe(w((t) => {
242
+ return n.pipe(T((t) => {
246
243
  let n = a[t];
247
244
  if (n?.state.status !== "idle") return m;
248
245
  let r = !1, o = (e) => {
249
- A.debug(`Cleaning up archive with key: ${e}`);
246
+ j.debug(`Cleaning up archive with key: ${e}`);
250
247
  let t = a[e];
251
248
  delete a[e], r ||= (t?.state.archive?.close(), !0);
252
249
  };
253
250
  n.update({ status: "loading" });
254
- let s = n.locks$, c = n.isUnlocked$, l = s.pipe(E(), v(([e, t]) => t > e), re(!0));
255
- return x(e(t)).pipe(k((e) => {
251
+ let s = n.locks$, c = n.isUnlocked$, l = s.pipe(D(), y(([e, t]) => t > e), ie(!0));
252
+ return x(e(t)).pipe(A((e) => {
256
253
  n.update({
257
254
  archive: e,
258
255
  status: "success"
@@ -260,24 +257,24 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
260
257
  }), _((e) => (o(t), n.update({
261
258
  status: "error",
262
259
  error: e
263
- }), m)), O(() => {
264
- let e = l.pipe(O(() => i), O(() => c), v((e) => e));
265
- return C(e, n.overTTL$).pipe(b(), k(() => {
260
+ }), m)), k(() => {
261
+ let e = l.pipe(k(() => i), k(() => c), y((e) => e));
262
+ return w(e, n.overTTL$).pipe(b(), A(() => {
266
263
  o(t);
267
264
  }));
268
265
  }));
269
- }), ie(r)).subscribe(), {
266
+ }), ae(r)).subscribe(), {
270
267
  access: (e) => {
271
- let r = !1, i = a[e] ?? new pe(t);
268
+ let r = !1, i = a[e] ?? new me(t);
272
269
  a[e] = i, i.update({ locks: i.state.locks + 1 });
273
270
  let o = () => {
274
271
  r || (r = !0, i.update({ locks: i.state.locks - 1 }));
275
272
  };
276
273
  n.next(e);
277
- let s = i.state$.pipe(S(({ archive: e }) => e), v((e) => !!e)), c = i.state$.pipe(k(({ error: e }) => {
274
+ let s = i.state$.pipe(C(({ archive: e }) => e), y((e) => !!e)), c = i.state$.pipe(A(({ error: e }) => {
278
275
  if (e) throw e;
279
- }), te());
280
- return C(s, c).pipe(b(), S((e) => ({
276
+ }), S());
277
+ return w(s, c).pipe(b(), C((e) => ({
281
278
  archive: e,
282
279
  release: o
283
280
  })), _((e) => {
@@ -289,7 +286,7 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
289
286
  },
290
287
  _archives: a
291
288
  };
292
- }, Y = (e) => e ? /^\d+$/.test(e) ? {
289
+ }, X = (e) => e ? /^\d+$/.test(e) ? {
293
290
  valid: !0,
294
291
  value: Number.parseInt(e, 10)
295
292
  } : {
@@ -298,20 +295,20 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
298
295
  } : {
299
296
  valid: !0,
300
297
  value: void 0
301
- }, he = (e) => {
298
+ }, ge = (e) => {
302
299
  if (!e.toLowerCase().startsWith("bytes=")) return { kind: "missing" };
303
300
  let t = e.slice(6).trim();
304
301
  if (!t) return { kind: "invalid" };
305
302
  if (t.includes(",")) return { kind: "multi" };
306
303
  let n = /^(\d*)-(\d*)$/.exec(t);
307
304
  if (!n) return { kind: "invalid" };
308
- let [, r = "", i = ""] = n, a = Y(r.trim()), o = Y(i.trim());
305
+ let [, r = "", i = ""] = n, a = X(r.trim()), o = X(i.trim());
309
306
  return !a.valid || !o.valid ? { kind: "invalid" } : {
310
307
  kind: "single",
311
308
  start: a.value,
312
309
  end: o.value
313
310
  };
314
- }, ge = (e) => {
311
+ }, _e = (e) => {
315
312
  if (e instanceof Blob) return {
316
313
  size: e.size,
317
314
  slice: (t, n) => {
@@ -333,18 +330,18 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
333
330
  };
334
331
  }
335
332
  };
336
- }, X = ({ body: e, contentType: t, rangeHeader: n }) => {
333
+ }, ve = ({ body: e, contentType: t, rangeHeader: n }) => {
337
334
  let r = new Headers();
338
335
  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, {
339
336
  status: 200,
340
337
  headers: r
341
338
  });
342
- let i = he(n);
339
+ let i = ge(n);
343
340
  if (i.kind === "missing" || i.kind === "multi") return e instanceof Blob && r.set("Content-Length", String(e.size)), new Response(e, {
344
341
  status: 200,
345
342
  headers: r
346
343
  });
347
- let a = ge(e), o = a.size;
344
+ let a = _e(e), o = a.size;
348
345
  if (i.kind === "invalid") return new Response(null, {
349
346
  status: 416,
350
347
  headers: { "Content-Range": `bytes */${o}` }
@@ -359,18 +356,18 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
359
356
  status: 206,
360
357
  headers: r
361
358
  });
362
- }, Z = "file://", _e = /^https?:\/\//, ve = (e) => {
359
+ }, Z = "file://", ye = /^https?:\/\//, be = (e) => {
363
360
  try {
364
361
  return decodeURIComponent(e);
365
362
  } catch {
366
363
  return e;
367
364
  }
368
- }, Q = (e) => e.startsWith(Z) ? e.slice(Z.length) : e, ye = (e) => {
365
+ }, Q = (e) => e.startsWith(Z) ? e.slice(Z.length) : e, xe = (e) => {
369
366
  let t = Q(e);
370
- return _e.test(t) ? t : Q(ve(t));
367
+ return ye.test(t) ? t : Q(be(t));
371
368
  }, $ = class {
372
369
  constructor({ hooks: e, onError: t, onManifestSuccess: n, ...r }) {
373
- this.onError = (e) => (console.error(e), new Response(String(e), { status: 500 })), this.archiveLoader = me(r), this.hooks = e ?? {}, this.onManifestSuccess = n ?? (({ manifest: e }) => Promise.resolve(e)), this.onError = t ?? this.onError;
370
+ this.onError = (e) => (console.error(e), new Response(String(e), { status: 500 })), this.archiveLoader = he(r), this.hooks = e ?? {}, this.onManifestSuccess = n ?? (({ manifest: e }) => Promise.resolve(e)), this.onError = t ?? this.onError;
374
371
  }
375
372
  prune() {
376
373
  this.archiveLoader.purge();
@@ -379,32 +376,32 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
379
376
  return this.lastAccessedKey !== void 0 && this.lastAccessedKey !== e && this.archiveLoader.purge(), this.lastAccessedKey = e, this.archiveLoader.access(e);
380
377
  }
381
378
  accessArchiveWithoutLock(e) {
382
- return this.accessArchive(e).pipe(S(({ archive: e, release: t }) => (t(), e)));
379
+ return this.accessArchive(e).pipe(C(({ archive: e, release: t }) => (t(), e)));
383
380
  }
384
381
  withArchiveResponse({ key: e, getResponse: t }) {
385
- let n = this.accessArchive(e).pipe(w(({ archive: e, release: n }) => x(t(e)).pipe(y(() => {
382
+ let n = this.accessArchive(e).pipe(T(({ archive: e, release: n }) => x(t(e)).pipe(ne(() => {
386
383
  n();
387
- }))), _((e) => T(this.onError(e))));
388
- return ne(n);
384
+ }))), _((e) => E(this.onError(e))));
385
+ return re(n);
389
386
  }
390
387
  fetchManifest({ key: e, baseUrl: t }) {
391
388
  return this.withArchiveResponse({
392
389
  key: e,
393
- getResponse: (e) => x(U(e, {
390
+ getResponse: (e) => x(G(e, {
394
391
  baseUrl: t,
395
392
  hooks: this.hooks.manifest
396
- })).pipe(O((t) => x(this.onManifestSuccess({
393
+ })).pipe(k((t) => x(this.onManifestSuccess({
397
394
  manifest: t,
398
395
  archive: e
399
- }))), S((e) => new Response(JSON.stringify(e), { status: 200 })))
396
+ }))), C((e) => new Response(JSON.stringify(e), { status: 200 })))
400
397
  });
401
398
  }
402
399
  fetchResource({ key: e, resourcePath: t, request: n }) {
403
400
  return this.withArchiveResponse({
404
401
  key: e,
405
402
  getResponse: (e) => {
406
- let r = ye(t);
407
- return x(J(e, r, { hooks: this.hooks.resource })).pipe(S((e) => X({
403
+ let r = xe(t);
404
+ return x(Y(e, r, { hooks: this.hooks.resource })).pipe(C((e) => ve({
408
405
  body: e.body ?? "",
409
406
  contentType: e.params.contentType,
410
407
  rangeHeader: n?.headers.get("range")
@@ -412,7 +409,7 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
412
409
  }
413
410
  });
414
411
  }
415
- }, be = class extends $ {
412
+ }, Se = class extends $ {
416
413
  constructor({ getUriInfo: e, ...t }) {
417
414
  super(t), this.getUriInfo = e, this.fetchEventListener = this.fetchEventListener.bind(this);
418
415
  }
@@ -435,6 +432,6 @@ var A = e.namespace("@prose-reader/streamer", void 0, { color: "#ffae42" }), oe
435
432
  }
436
433
  };
437
434
  //#endregion
438
- export { be as ServiceWorkerStreamer, $ as Streamer, oe as configure, P as createManifestResourceHref, U as generateManifestFromArchive, J as generateResourceFromArchive };
435
+ export { Se as ServiceWorkerStreamer, $ as Streamer, M as configure, I as createManifestResourceHref, G as generateManifestFromArchive, Y as generateResourceFromArchive };
439
436
 
440
437
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Report","SharedReport","Report","Report$1","Report","configure","enableReport","normalizeFilenameAsTitle","filename","buildAudiobookToc","manifest","parseContentType","item","detectMimeTypeFromName","getUriBasename","encodeResourcePath","resourcePath","createManifestResourceHref","baseUrl","hrefBaseUrl","toManifestTocItem","urlJoin","manifestGuideType","v","raw","isHttpUrl","value","firstDirectoryTitle","archive","dir","epubManifest","resolved","opfBasePath","opf","guide","reference","type","createSafeId","createXmlSafeIdFactory","mainTitle","metadata","readingOrder","recordsManifest","files","file","idByUri","toc","manifestFromResolvedArchive","normalizeBaseUrl","generateManifestFromArchive","normalizedBaseUrl","resolveArchive","hookFactory","hooks","manifestStr","createTextResourceHook","getArchiveFileRecordByUri","extension","newBody","transform","resource","readRecordAsText","hasCalibreCoverMeta","metaElm","doc","node","getBuggyCoverSvg","calibreFixHook","opfXmlDoc","XmlDocument","bodyToParse","buggySvg","cssFixHook","body","getMetadata","parsed","readArchiveOpf","foundMediaType","containerHref","basePath","getContentTypeFromExtension","uri","defaultHook","invalidSelfClosingTags","selfClosingTagsFixHook","tagPattern","tagName","attributes","generateResourceFromArchive","defaultResource","resourceHooks","hook","gen","ArchiveEntry","cleanArchiveAfter","BehaviorSubject","update","map","locks","distinctUntilChanged","shareReplay","switchMap","isUnlocked","NEVER","timer","createArchiveLoader","loadSubject","Subject","destroySubject","purgeSubject","archives","mergeMap","archiveEntry","key","EMPTY","isClosed","cleanupArchive","entry","locks$","isUnlocked$","newAccess$","pairwise","filter","prev","curent","startWith","from","getArchive","tap","catchError","readyForPurge$","merge","first","takeUntil","releaseCalled","release","archive$","error$","error","ignoreElements","parseByteRangePart","parseSingleByteRange","rangeHeader","rangeValue","matches","rawStart","rawEnd","parsedStart","parsedEnd","materializeForSlicing","part","start","endExclusive","bytes","createRangeResponse","headers","contentType","parsedRange","rangeBody","size","end","partial","fileProtocol","httpProtocolPattern","decodeResourcePath","stripFileProtocol","normalizeResourcePath","resourcePathWithoutFileProtocol","Streamer","rest","onManifestSuccess","onError","response$","getResponse","finalize","of","lastValueFrom","cleanedResourcePath","request","ServiceWorkerStreamer","getUriInfo","uriInfo","event","removeTrailingSlash","streamerPath","e"],"sources":["../../package.json","../../src/report.ts","../../src/configure.ts","../../src/generators/manifest/audiobookToc.ts","../../src/generators/manifest/createManifestResourceHref.ts","../../src/generators/manifest/manifestFromResolvedArchive.ts","../../src/generators/manifest/index.ts","../../src/generators/resources/hooks/createTextResourceHook.ts","../../src/generators/resources/hooks/calibreFixHook.ts","../../src/generators/resources/hooks/cssFixHook.ts","../../src/generators/resources/hooks/defaultHook.ts","../../src/generators/resources/hooks/selfClosingTagsFixHook.ts","../../src/generators/resources/index.ts","../../src/archiveLoader.ts","../../src/utils/createRangeResponse.ts","../../src/Streamer.ts","../../src/ServiceWorkerStreamer.ts"],"sourcesContent":["{\n \"name\": \"@prose-reader/streamer\",\n \"version\": \"28.0.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index/index.js\",\n \"require\": \"./dist/index/index.cjs\"\n }\n },\n \"types\": \"./dist/index.d.ts\",\n \"license\": \"MIT\",\n \"files\": [\n \"/dist\"\n ],\n \"scripts\": {\n \"start\": \"vite build --watch --mode development\",\n \"build\": \"tsc && vite build\",\n \"test\": \"vitest run --coverage\",\n \"tsc\": \"tsc\",\n \"test:watch\": \"vitest watch\"\n },\n \"dependencies\": {\n \"@prose-reader/archive-reader\": \"workspace:^\",\n \"@prose-reader/shared\": \"workspace:^\",\n \"xmldoc\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"^7.5.5\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\",\n \"engines\": {\n \"node\": \">=22.12\"\n }\n}\n","import { Report as SharedReport } from \"@prose-reader/shared\"\nimport { name } from \"../package.json\"\n\n// No explicit `enabled`: inherit the shared root, which auto-configures from\n// the `globalThis.__PROSE_READER_DEBUG` flag (works in window, workers, node).\n// `configure()` remains as an explicit override for setups where the flag\n// cannot be set before the module evaluates.\nexport const Report = SharedReport.namespace(name, undefined, {\n color: \"#ffae42\",\n})\n","import { Report } from \"./report\"\n\nexport const configure = ({\n enableReport,\n}: {\n enableReport?: boolean\n} = {}) => {\n Report.enable(!!enableReport)\n}\n","import {\n detectMimeTypeFromName,\n getUriBasename,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\n\ntype Toc = NonNullable<Manifest[\"nav\"]>[\"toc\"]\n\nexport const normalizeFilenameAsTitle = (filename: string) =>\n filename\n .replace(/\\.[^.]+$/, \"\")\n .replace(/[_-]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n\n/**\n * One entry per track for a book made only of audio, each titled after the\n * archive record it plays.\n */\nexport const buildAudiobookToc = (manifest: Manifest): Toc | undefined => {\n if (manifest.spineItems.length === 0) return undefined\n\n const allAudio = manifest.spineItems.every((item) => {\n const mimeType =\n parseContentType(item.mediaType ?? \"\") ||\n detectMimeTypeFromName(item.containerUri)\n\n return mimeType?.startsWith(\"audio/\")\n })\n\n if (!allAudio) return undefined\n\n return manifest.spineItems.map((item) => ({\n title: normalizeFilenameAsTitle(getUriBasename(item.containerUri)),\n href: item.href,\n path: item.containerUri,\n contents: [],\n }))\n}\n","// encodeURI leaves `#` and `?` untouched (reserved URI delimiters), but in\n// a raw archive filename they are data and would otherwise be parsed as\n// fragment/query by whatever fetches the href (a fragment never even reaches\n// the streamer). Same escaping as the archive-reader toc containerHref.\nconst encodeResourcePath = (resourcePath: string) =>\n encodeURI(resourcePath).replace(/#/g, `%23`).replace(/\\?/g, `%3F`)\n\nexport const createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return `${encodeURI(hrefBaseUrl)}${encodeResourcePath(resourcePath)}`\n}\n","import {\n type Archive,\n type ArchiveTocItem,\n mainTitle,\n type ResolvedArchive,\n} from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeIdFactory, urlJoin } from \"@prose-reader/shared\"\nimport { buildAudiobookToc } from \"./audiobookToc\"\nimport { createManifestResourceHref } from \"./createManifestResourceHref\"\n\ntype TocItem = NonNullable<Manifest[\"nav\"]>[\"toc\"][number]\ntype ManifestGuideEntry = NonNullable<Manifest[\"guide\"]>[number]\n\n/**\n * The archive reader resolves references within the container, the streamer\n * owns where they are served from.\n */\nconst toManifestTocItem =\n (baseUrl: string) =>\n (item: ArchiveTocItem): TocItem => ({\n title: item.title,\n path: item.path,\n // rebase the container-relative ref into serving space; entries without a\n // target (eg nav heading without link) keep an empty href\n href: item.containerHref ? urlJoin(baseUrl, item.containerHref) : ``,\n contents: item.contents.map(toManifestTocItem(baseUrl)),\n })\n\nconst manifestGuideType = (\n raw: string | undefined,\n): ManifestGuideEntry[\"type\"] | undefined => {\n const v = raw?.trim()\n if (\n v === `cover` ||\n v === `title-page` ||\n v === `copyright-page` ||\n v === `text`\n ) {\n return v\n }\n return undefined\n}\n\nconst isHttpUrl = (value: string) => /^https?:\\/\\//.test(value)\n\nconst firstDirectoryTitle = (archive: Archive): string | undefined =>\n archive.records.find(({ dir }) => dir)?.basename.replace(/\\/$/, ``)\n\n/**\n * The subset of {@link ResolvedArchive} the manifest mapping consumes: the\n * normalized publication plus `sources` — the OPF manifest (all package\n * resources, not just the spine) and the legacy guide only exist there.\n */\ntype ResolvedArchiveForManifest = Pick<\n ResolvedArchive,\n \"mediaType\" | \"metadata\" | \"readingOrder\" | \"toc\" | \"sources\"\n>\n\nconst epubManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n opf: NonNullable<ResolvedArchiveForManifest[\"sources\"][\"opf\"]>,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n const opfBasePath = opf.basePath\n\n const guide: ManifestGuideEntry[] = []\n for (const reference of opf.opf.guide) {\n const type = manifestGuideType(reference.type)\n if (type === undefined) continue\n // guide hrefs stay as authored (opf-relative), matching the legacy\n // EPUB 2 contract consumers already handle\n guide.push({ href: reference.href, title: reference.title, type })\n }\n\n const createSafeId = createXmlSafeIdFactory()\n\n return {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title: mainTitle(metadata) || firstDirectoryTitle(archive) || ``,\n renditionLayout: metadata.renditionLayout,\n renditionFlow: metadata.renditionFlow ?? `auto`,\n renditionSpread: metadata.renditionSpread,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: item.id ?? createSafeId(item.uri),\n index,\n // remote resources pass through; local ones get the serving base, or\n // the file:// scheme when the manifest is not served from anywhere, and\n // the reference the book makes to them, which its navigation and links\n // match as written\n href: isHttpUrl(item.uri)\n ? item.uri\n : `${baseUrl || `file://`}${item.containerHref ?? item.uri}`,\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n ...(item.renditionFlow !== undefined\n ? { renditionFlow: item.renditionFlow }\n : {}),\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: item.pageSpreadLeft,\n pageSpreadRight: item.pageSpreadRight,\n mediaType: item.mediaType,\n })),\n items: opf.opf.manifestItems.map((item) => ({\n id: item.id,\n href: isHttpUrl(item.href)\n ? item.href\n : `${baseUrl || `file://`}${opfBasePath ? `${opfBasePath}/` : ``}${item.href}`,\n mediaType: item.mediaType,\n })),\n guide: guide.length > 0 ? guide : undefined,\n nav: {\n // EPUB-like containers keep an explicit (possibly empty) toc\n toc: (resolved.toc ?? []).map(toManifestTocItem(baseUrl)),\n },\n }\n}\n\nconst recordsManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n\n const files = archive.records.filter((file) => !file.dir)\n const createSafeId = createXmlSafeIdFactory()\n const idByUri = new Map(\n files.map((file) => [file.uri, createSafeId(file.uri)]),\n )\n\n const manifest: Manifest = {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title:\n mainTitle(metadata) ||\n firstDirectoryTitle(archive) ||\n archive.filename ||\n ``,\n renditionLayout: metadata.renditionLayout,\n renditionSpread: `auto`,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: idByUri.get(item.uri) ?? createSafeId(item.uri),\n index,\n href: createManifestResourceHref({ baseUrl, resourcePath: item.uri }),\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: undefined,\n pageSpreadRight: undefined,\n mediaType: item.mediaType,\n })),\n // every container file stays addressable, sidecars included — items is\n // the resource map, the reading order is the curation\n items: files.map((file) => ({\n id: idByUri.get(file.uri) ?? createSafeId(file.uri),\n href: encodeURI(`${baseUrl}${file.uri}`),\n })),\n }\n\n const audiobookToc = buildAudiobookToc(manifest)\n const toc = audiobookToc ?? resolved.toc?.map(toManifestTocItem(baseUrl))\n\n return toc !== undefined ? { ...manifest, nav: { toc } } : manifest\n}\n\n/**\n * Maps a resolved archive into the streamer's serving space: hrefs get the\n * base url (or `file://`) baked in, ids become XML-safe, defaults are the\n * manifest's own. The container understanding itself (format detection,\n * metadata precedence, spine rules) all happened in\n * `@prose-reader/archive-reader`.\n */\nexport const manifestFromResolvedArchive = ({\n archive,\n baseUrl,\n resolved,\n}: {\n archive: Archive\n baseUrl: string\n resolved: ResolvedArchiveForManifest\n}): Manifest => {\n const opf = resolved.sources.opf\n\n if (opf) return epubManifest(archive, baseUrl, resolved, opf)\n\n return recordsManifest(archive, baseUrl, resolved)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { resolveArchive } from \"@prose-reader/archive-reader\"\nimport type { StreamerManifestHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { manifestFromResolvedArchive } from \"./manifestFromResolvedArchive\"\n\nconst normalizeBaseUrl = (baseUrl: string | undefined) => {\n if (!baseUrl) return ``\n\n return baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n}\n\n/**\n * Generates the prose `Manifest` for an archive: `resolveArchive` does the\n * book understanding (metadata precedence, reading order, toc, the viewport\n * layout scan), then the result is mapped into serving space and handed to\n * the consumer `hooks` (ordered `Manifest` transforms). The final defaults\n * (`readingDirection` falling back to `ltr`) apply after the hooks, so a\n * hook can still observe \"no source decided it\".\n */\nexport const generateManifestFromArchive = async (\n archive: Archive,\n {\n baseUrl = ``,\n hooks = [],\n }: { baseUrl?: string; hooks?: StreamerManifestHookFactory[] } = {},\n) => {\n Report.log(\"Generating manifest from archive\", archive)\n\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n try {\n const resolved = await resolveArchive(archive, {\n include: [`metadata`, `readingOrder`, `toc`, `sources`],\n layoutScan: true,\n })\n\n let manifest = manifestFromResolvedArchive({\n archive,\n baseUrl: normalizedBaseUrl,\n resolved,\n })\n\n for (const hookFactory of hooks) {\n manifest = await hookFactory({\n archive,\n baseUrl: normalizedBaseUrl,\n })(manifest)\n }\n\n manifest = {\n ...manifest,\n readingDirection: manifest.readingDirection ?? \"ltr\",\n }\n\n Report.log(\"Generated manifest\", manifest)\n\n if (process.env.NODE_ENV === \"development\") {\n if (Report.isEnabled()) {\n const manifestStr = JSON.stringify(manifest, null, 2)\n Report.groupCollapsed(...Report.getGroupArgs(\"Generated manifest\"))\n Report.log(`\\n${manifestStr}`)\n Report.groupEnd()\n }\n }\n\n return manifest\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import {\n getArchiveFileRecordByUri,\n readRecordAsText,\n} from \"@prose-reader/archive-reader\"\nimport type {\n StreamerResourceHook,\n StreamerResourceHookContext,\n StreamerResourceHookFactory,\n} from \"../../../hooks\"\n\n/**\n * Shared scaffolding for resource hooks that rewrite the text body of a single\n * archive file matching a given extension.\n *\n * Resolves the archive record for `resourcePath` and, when its basename ends\n * with `extension`, reads the resource body as text (reusing an already-string\n * body, otherwise reading the record) and passes it to `transform`. When\n * `transform` returns a string the resource body is replaced; when it returns\n * `undefined` the resource is returned untouched (so a non-string body is\n * preserved as-is).\n */\nexport const createTextResourceHook =\n (\n extension: string,\n transform: (\n body: string,\n ) => string | undefined | Promise<string | undefined>,\n ): StreamerResourceHookFactory =>\n ({\n archive,\n resourcePath,\n }: StreamerResourceHookContext): StreamerResourceHook =>\n async (resource) => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (file?.basename.endsWith(extension)) {\n const bodyToParse =\n typeof resource.body === `string`\n ? resource.body\n : await readRecordAsText(file)\n\n const newBody = await transform(bodyToParse)\n\n if (newBody !== undefined) {\n return {\n ...resource,\n body: newBody,\n }\n }\n }\n\n return resource\n }\n","import { XmlDocument } from \"xmldoc\"\nimport { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst hasCalibreCoverMeta = (doc: XmlDocument) => {\n const metaElm = doc\n .descendantWithPath(\"head\")\n ?.childrenNamed(\"meta\")\n .find((node) => node.attr.name === \"calibre:cover\")\n\n return !!(metaElm && metaElm.attr.name === \"calibre:cover\")\n}\n\nconst getBuggyCoverSvg = (doc: XmlDocument) => {\n return doc\n .descendantWithPath(\"body\")\n ?.descendantWithPath(\"div\")\n ?.childrenNamed(\"svg\")\n ?.find(\n (node) =>\n node.attr.width === \"100%\" && node.attr.preserveAspectRatio === \"none\",\n )\n}\n\nexport const calibreFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const opfXmlDoc = new XmlDocument(bodyToParse)\n\n if (!hasCalibreCoverMeta(opfXmlDoc)) {\n return undefined\n }\n\n const buggySvg = getBuggyCoverSvg(opfXmlDoc)\n\n if (buggySvg) {\n delete buggySvg.attr.preserveAspectRatio\n }\n\n return opfXmlDoc.toString()\n },\n)\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nexport const cssFixHook = createTextResourceHook(\".css\", (body) =>\n /**\n * Fix the potentially invalid writing mode present on some vertical book.\n * This has the benefit of making it compatible with firefox as well.\n */\n body.replaceAll(`-webkit-writing-mode`, `writing-mode`),\n)\n","import {\n type Archive,\n getArchiveFileRecordByUri,\n readArchiveOpf,\n} from \"@prose-reader/archive-reader\"\nimport type { HookResource } from \"./types\"\n\n/**\n * Resolves `contentType` from the package OPF when possible, else from the URI.\n * Uses `readArchiveOpf` each time — see `generateResourceFromArchive` JSDoc for\n * why that can mean repeated OPF work on the resource path.\n */\nconst getMetadata = async (archive: Archive, resourcePath: string) => {\n const parsed = await readArchiveOpf(archive)\n\n if (parsed) {\n const { opf, basePath } = parsed\n\n // we are comparing opf items container-relative path in epub archive\n // against resourcePath (which is an absolute path in archive).\n // They should in theory match.\n const foundMediaType = opf.manifestItems.find((item) => {\n const containerHref = basePath ? `${basePath}/${item.href}` : item.href\n\n return resourcePath.endsWith(containerHref)\n })?.mediaType\n\n if (foundMediaType) {\n return {\n mediaType: foundMediaType,\n }\n }\n }\n\n return {\n mediaType: getContentTypeFromExtension(resourcePath),\n }\n}\n\nconst getContentTypeFromExtension = (uri: string) => {\n if (uri.endsWith(`.css`)) {\n return `text/css; charset=UTF-8`\n }\n if (uri.endsWith(`.jpg`)) {\n return `image/jpg`\n }\n if (uri.endsWith(`.xhtml`)) {\n return `application/xhtml+xml`\n }\n if (uri.endsWith(`.mp4`)) {\n return `video/mp4`\n }\n if (uri.endsWith(`.svg`)) {\n return `image/svg+xml`\n }\n}\n\nexport const defaultHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) return resource\n\n const metadata = await getMetadata(archive, resourcePath)\n\n return {\n ...resource,\n params: {\n ...resource.params,\n ...(file?.encodingFormat && {\n contentType: file.encodingFormat,\n }),\n ...(metadata.mediaType && {\n contentType: metadata.mediaType,\n }),\n },\n }\n }\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst invalidSelfClosingTags = [\n \"div\",\n \"span\",\n \"p\",\n \"a\",\n \"li\",\n \"ul\",\n \"ol\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"table\",\n \"tr\",\n \"td\",\n \"th\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"section\",\n \"article\",\n \"header\",\n \"footer\",\n \"nav\",\n \"aside\",\n \"main\",\n \"figure\",\n \"figcaption\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"form\",\n \"textarea\",\n \"select\",\n \"option\",\n \"button\",\n \"label\",\n \"fieldset\",\n \"legend\",\n \"caption\",\n \"dl\",\n \"dt\",\n \"dd\",\n \"iframe\",\n \"video\",\n \"audio\",\n \"canvas\",\n \"script\",\n \"style\",\n]\n\n/**\n * Some books uses xhtml files but also includes wrong self closin tags. This happens\n * a lot with kobo epub which have a lot of self closing <script ... />. This breaks on some\n * browser such as webkit. We use a regex to replace and fix them. There\n * is a first lighter regex which check if any such tag exist in the first place before running\n * the full replace regex empty.\n */\nexport const selfClosingTagsFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const tagCheckPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})[\\\\s/>]`,\n \"i\",\n )\n if (!tagCheckPattern.test(bodyToParse)) {\n return undefined\n }\n\n const tagPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})(\\\\s[^>]*)?\\\\s*/>`,\n \"gi\",\n )\n\n return bodyToParse.replace(tagPattern, (_, tagName, attributes = \"\") => {\n // Convert to an opening and closing tag\n return `<${tagName} ${attributes.trim()}></${tagName}>`\n })\n },\n)\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { getArchiveFileRecordByUri } from \"@prose-reader/archive-reader\"\nimport type { StreamerResourceHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { calibreFixHook } from \"./hooks/calibreFixHook\"\nimport { cssFixHook } from \"./hooks/cssFixHook\"\nimport { defaultHook } from \"./hooks/defaultHook\"\nimport { selfClosingTagsFixHook } from \"./hooks/selfClosingTagsFixHook\"\nimport type { HookResource } from \"./hooks/types\"\n\n/**\n * Builds one streamed asset from `archive` (hot path: typically once per\n * fetched file in the reader).\n *\n * **OPF limitation:** Unlike manifest generation, this API does not take\n * `ArchiveOpfParsed`. `defaultHook` calls `readArchiveOpf(archive)` to resolve\n * `media-type` from the package document, so EPUBs repeat OPF I/O + parse per\n * resource. Usually fine (small OPF); if profiling shows regressions, address\n * explicitly—e.g. optional `archiveOpf` (or equivalent) threaded from\n * `Streamer.fetchResource` / manifest, or an opt-in cache on `Archive` wired at\n * construction time—rather than hidden process-wide memoization.\n */\nexport const generateResourceFromArchive = async (\n archive: Archive,\n resourcePath: string,\n { hooks = [] }: { hooks?: StreamerResourceHookFactory[] } = {},\n) => {\n const defaultResource: HookResource = {\n params: {},\n }\n const externalHooks = hooks.map((hook) => hook({ archive, resourcePath }))\n\n /**\n * EPUB: `defaultHook` calls `readArchiveOpf` → repeated OPF I/O + parse per\n * resource until we thread `ArchiveOpfParsed` here (see export JSDoc).\n */\n const resourceHooks = [\n ...externalHooks,\n defaultHook({ archive, resourcePath }),\n selfClosingTagsFixHook({ archive, resourcePath }),\n cssFixHook({ archive, resourcePath }),\n calibreFixHook({ archive, resourcePath }),\n ]\n\n try {\n const resource = await resourceHooks.reduce(async (manifest, gen) => {\n return await gen(await manifest)\n }, Promise.resolve(defaultResource))\n\n Report.log(\"Generated resource\", resourcePath, resource)\n\n if (resource.body !== undefined) {\n return resource\n }\n\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) {\n throw new Error(`no file found for resourcePath:${resourcePath}`)\n }\n\n return {\n ...resource,\n body: await file.blob(),\n }\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport {\n BehaviorSubject,\n catchError,\n distinctUntilChanged,\n EMPTY,\n filter,\n first,\n from,\n ignoreElements,\n map,\n merge,\n mergeMap,\n NEVER,\n type ObservedValueOf,\n pairwise,\n Subject,\n shareReplay,\n startWith,\n switchMap,\n takeUntil,\n tap,\n timer,\n} from \"rxjs\"\nimport { Report } from \"./report\"\n\nclass ArchiveEntry {\n state$ = new BehaviorSubject<{\n status: \"idle\" | \"loading\" | \"success\" | \"error\"\n error?: unknown | undefined\n archive?: undefined | Archive\n locks: number\n }>({\n status: `idle`,\n locks: 0,\n })\n\n constructor(private cleanArchiveAfter: number) {}\n\n update(update: Partial<ObservedValueOf<typeof this.state$>>) {\n this.state$.next({ ...this.state$.getValue(), ...update })\n }\n\n get locks$() {\n return this.state$.pipe(map(({ locks }) => locks))\n }\n\n get state() {\n return this.state$.getValue()\n }\n\n get isUnlocked$() {\n return this.locks$.pipe(\n map((locks) => locks <= 0),\n distinctUntilChanged(),\n shareReplay(),\n )\n }\n\n get overTTL$() {\n return this.isUnlocked$.pipe(\n switchMap((isUnlocked) =>\n !isUnlocked\n ? NEVER\n : this.cleanArchiveAfter === Infinity\n ? NEVER\n : timer(this.cleanArchiveAfter),\n ),\n )\n }\n}\n\nexport const createArchiveLoader = ({\n getArchive,\n cleanArchiveAfter = 5 * 60 * 1000, // 5mn\n}: {\n getArchive: (key: string) => Promise<Archive>\n cleanArchiveAfter?: number\n}) => {\n const loadSubject = new Subject<string>()\n const destroySubject = new Subject<void>()\n const purgeSubject = new Subject<void>()\n const archives: Record<string, ArchiveEntry> = {}\n\n const loadArchive$ = loadSubject.pipe(\n mergeMap((key) => {\n const archiveEntry = archives[key]\n\n if (archiveEntry?.state.status !== \"idle\") return EMPTY\n\n let isClosed = false\n\n const cleanupArchive = (key: string) => {\n Report.debug(`Cleaning up archive with key: ${key}`)\n\n const entry = archives[key]\n\n delete archives[key]\n\n if (!isClosed) {\n entry?.state.archive?.close()\n isClosed = true\n }\n }\n\n archiveEntry.update({\n status: \"loading\",\n })\n\n const locks$ = archiveEntry.locks$\n const isUnlocked$ = archiveEntry.isUnlocked$\n\n const newAccess$ = locks$.pipe(\n pairwise(),\n filter(([prev, curent]) => curent > prev),\n startWith(true),\n )\n\n const archive$ = from(getArchive(key))\n\n return archive$.pipe(\n tap((archive) => {\n archiveEntry.update({\n archive,\n status: \"success\",\n })\n }),\n catchError((error) => {\n cleanupArchive(key)\n\n archiveEntry.update({\n status: \"error\",\n error,\n })\n\n return EMPTY\n }),\n switchMap(() => {\n const readyForPurge$ = newAccess$.pipe(\n switchMap(() => purgeSubject),\n switchMap(() => isUnlocked$),\n filter((isUnlocked) => isUnlocked),\n )\n\n const toCleanup$ = merge(readyForPurge$, archiveEntry.overTTL$)\n\n return toCleanup$.pipe(\n first(),\n tap(() => {\n cleanupArchive(key)\n }),\n )\n }),\n )\n }),\n takeUntil(destroySubject),\n )\n\n const access = (key: string) => {\n let releaseCalled = false\n\n const archiveEntry = archives[key] ?? new ArchiveEntry(cleanArchiveAfter)\n\n archives[key] = archiveEntry\n\n archiveEntry.update({\n locks: archiveEntry.state.locks + 1,\n })\n\n const release = () => {\n if (releaseCalled) return\n\n releaseCalled = true\n\n archiveEntry.update({\n locks: archiveEntry.state.locks - 1,\n })\n }\n\n loadSubject.next(key)\n\n const archive$ = archiveEntry.state$.pipe(\n map(({ archive }) => archive),\n filter((archive) => !!archive),\n )\n\n const error$ = archiveEntry.state$.pipe(\n tap(({ error }) => {\n if (error) {\n throw error\n }\n }),\n ignoreElements(),\n )\n\n return merge(archive$, error$).pipe(\n first(),\n map((archive) => ({ archive, release })),\n catchError((error) => {\n release()\n\n throw error\n }),\n )\n }\n\n /**\n * Will purge immediatly archives as soon as they are released\n */\n const purge = () => {\n purgeSubject.next()\n }\n\n loadArchive$.subscribe()\n\n return {\n access,\n purge,\n _archives: archives,\n }\n}\n","const parseByteRangePart = (value: string) => {\n if (!value) {\n return {\n valid: true,\n value: undefined,\n }\n }\n\n if (!/^\\d+$/.test(value)) {\n return {\n valid: false,\n value: undefined,\n }\n }\n\n return {\n valid: true,\n value: Number.parseInt(value, 10),\n }\n}\n\nconst parseSingleByteRange = (rangeHeader: string) => {\n if (!rangeHeader.toLowerCase().startsWith(\"bytes=\")) {\n return {\n kind: \"missing\" as const,\n }\n }\n\n const rangeValue = rangeHeader.slice(\"bytes=\".length).trim()\n\n if (!rangeValue) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n if (rangeValue.includes(\",\")) {\n return {\n kind: \"multi\" as const,\n }\n }\n\n const matches = /^(\\d*)-(\\d*)$/.exec(rangeValue)\n\n if (!matches) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n const [, rawStart = \"\", rawEnd = \"\"] = matches\n const parsedStart = parseByteRangePart(rawStart.trim())\n const parsedEnd = parseByteRangePart(rawEnd.trim())\n\n if (!parsedStart.valid || !parsedEnd.valid) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n return {\n kind: \"single\" as const,\n start: parsedStart.value,\n end: parsedEnd.value,\n }\n}\n\n/**\n * Normalize the body into a sliceable form that avoids passing a\n * `Blob.slice()` result directly to `new Response()`. Some Node.js\n * versions do not recognise sliced Blobs as valid `BodyInit`,\n * producing `\"[object Blob]\"` instead of the actual content.\n */\nconst materializeForSlicing = (body: Blob | string) => {\n if (body instanceof Blob) {\n return {\n size: body.size,\n slice: (start: number, endExclusive: number) => {\n const part = body.slice(start, endExclusive)\n return { content: part, length: part.size }\n },\n }\n }\n\n const bytes = new TextEncoder().encode(body)\n\n return {\n size: bytes.byteLength,\n slice: (start: number, endExclusive: number) => {\n const part = bytes.slice(start, endExclusive)\n return { content: part, length: part.byteLength }\n },\n }\n}\n\nexport const createRangeResponse = ({\n body,\n contentType,\n rangeHeader,\n}: {\n body: Blob | string\n contentType?: string\n rangeHeader?: string | null\n}) => {\n const headers = new Headers()\n\n if (contentType) {\n headers.set(\"Content-Type\", contentType)\n }\n\n headers.set(\"Accept-Ranges\", \"bytes\")\n\n if (!rangeHeader) {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const parsedRange = parseSingleByteRange(rangeHeader)\n\n if (parsedRange.kind === \"missing\" || parsedRange.kind === \"multi\") {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const rangeBody = materializeForSlicing(body)\n const size = rangeBody.size\n\n if (parsedRange.kind === \"invalid\") {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n let start = parsedRange.start\n let end = parsedRange.end\n\n if (start === undefined && end === undefined) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n if (start === undefined) {\n const suffixLength = Math.min(end ?? 0, size)\n start = Math.max(0, size - suffixLength)\n end = size - 1\n } else if (end === undefined || end >= size) {\n end = size - 1\n }\n\n if (start < 0 || end < 0 || start >= size || end >= size || start > end) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n const partial = rangeBody.slice(start, end + 1)\n\n headers.set(\"Content-Length\", String(partial.length))\n headers.set(\"Content-Range\", `bytes ${start}-${end}/${size}`)\n\n return new Response(partial.content, {\n status: 206,\n headers,\n })\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport {\n catchError,\n finalize,\n from,\n lastValueFrom,\n map,\n mergeMap,\n type Observable,\n of,\n switchMap,\n} from \"rxjs\"\nimport { createArchiveLoader } from \"./archiveLoader\"\nimport { generateManifestFromArchive } from \"./generators/manifest\"\nimport { generateResourceFromArchive } from \"./generators/resources\"\nimport type { StreamerHooks } from \"./hooks\"\nimport { createRangeResponse } from \"./utils/createRangeResponse\"\n\ntype OnError = (error: unknown) => Response\ntype OnManifestSuccess = (params: {\n manifest: Manifest\n archive: Archive\n}) => Observable<Manifest> | Promise<Manifest>\ntype WithArchiveResponse = (\n archive: Archive,\n) => Observable<Response> | Promise<Response>\n\nconst fileProtocol = `file://`\nconst httpProtocolPattern = /^https?:\\/\\//\n\nconst decodeResourcePath = (resourcePath: string) => {\n try {\n return decodeURIComponent(resourcePath)\n } catch {\n return resourcePath\n }\n}\n\nconst stripFileProtocol = (resourcePath: string) =>\n resourcePath.startsWith(fileProtocol)\n ? resourcePath.slice(fileProtocol.length)\n : resourcePath\n\nconst normalizeResourcePath = (resourcePath: string) => {\n const resourcePathWithoutFileProtocol = stripFileProtocol(resourcePath)\n\n if (httpProtocolPattern.test(resourcePathWithoutFileProtocol)) {\n return resourcePathWithoutFileProtocol\n }\n\n return stripFileProtocol(decodeResourcePath(resourcePathWithoutFileProtocol))\n}\n\nexport class Streamer {\n protected archiveLoader: ReturnType<typeof createArchiveLoader>\n protected onError: OnError = (error) => {\n console.error(error)\n\n return new Response(String(error), { status: 500 })\n }\n protected hooks: StreamerHooks\n protected onManifestSuccess: OnManifestSuccess\n protected lastAccessedKey: string | undefined\n\n constructor({\n hooks,\n onError,\n onManifestSuccess,\n ...rest\n }: Parameters<typeof createArchiveLoader>[0] & {\n hooks?: StreamerHooks\n onError?: OnError\n onManifestSuccess?: OnManifestSuccess\n }) {\n this.archiveLoader = createArchiveLoader(rest)\n this.hooks = hooks ?? {}\n\n this.onManifestSuccess =\n onManifestSuccess ?? (({ manifest }) => Promise.resolve(manifest))\n this.onError = onError ?? this.onError\n }\n\n public prune() {\n this.archiveLoader.purge()\n }\n\n public accessArchive(key: string) {\n if (this.lastAccessedKey !== undefined && this.lastAccessedKey !== key) {\n this.archiveLoader.purge()\n }\n\n this.lastAccessedKey = key\n\n return this.archiveLoader.access(key)\n }\n\n public accessArchiveWithoutLock(key: string) {\n return this.accessArchive(key).pipe(\n map(({ archive, release }) => {\n release()\n\n return archive\n }),\n )\n }\n\n protected withArchiveResponse({\n key,\n getResponse,\n }: {\n key: string\n getResponse: WithArchiveResponse\n }) {\n const response$ = this.accessArchive(key).pipe(\n mergeMap(({ archive, release }) =>\n from(getResponse(archive)).pipe(\n finalize(() => {\n release()\n }),\n ),\n ),\n catchError((error) => {\n return of(this.onError(error))\n }),\n )\n\n return lastValueFrom(response$)\n }\n\n public fetchManifest({ key, baseUrl }: { key: string; baseUrl?: string }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n const manifest$ = from(\n generateManifestFromArchive(archive, {\n baseUrl,\n hooks: this.hooks.manifest,\n }),\n )\n\n return manifest$.pipe(\n switchMap((manifest) =>\n from(this.onManifestSuccess({ manifest, archive })),\n ),\n map(\n (manifest) =>\n new Response(JSON.stringify(manifest satisfies Manifest), {\n status: 200,\n }),\n ),\n )\n },\n })\n }\n\n public fetchResource({\n key,\n resourcePath,\n request,\n }: {\n key: string\n resourcePath: string\n request?: Request\n }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n /**\n * We commonly use file:// for manifest without baseUrl. This ensure we\n * have valid URL for the reader while flagging them as local.\n *\n * However, we obviously don't have the file:// prefix on the archive.\n */\n const cleanedResourcePath = normalizeResourcePath(resourcePath)\n\n /**\n * Reader hot path: one `generateResourceFromArchive` per request. For\n * EPUBs, `defaultHook` re-reads and re-parses the package OPF each time\n * (no `archiveOpf` threading yet). See `generateResourceFromArchive` JSDoc.\n */\n const resource$ = from(\n generateResourceFromArchive(archive, cleanedResourcePath, {\n hooks: this.hooks.resource,\n }),\n )\n\n return resource$.pipe(\n map((resource) =>\n createRangeResponse({\n body: resource.body ?? \"\",\n contentType: resource.params.contentType,\n rangeHeader: request?.headers.get(\"range\"),\n }),\n ),\n )\n },\n })\n }\n}\n","import { removeTrailingSlash } from \"@prose-reader/shared\"\nimport { Streamer } from \"./Streamer\"\n\ntype ConflictFreeWebWorkerFetchEvent = {\n readonly request: Request\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */\n respondWith(r: Response | PromiseLike<Response>): void\n}\n\nexport class ServiceWorkerStreamer extends Streamer {\n protected getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n\n constructor({\n getUriInfo,\n ...rest\n }: ConstructorParameters<typeof Streamer>[0] & {\n getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n }) {\n super(rest)\n\n this.getUriInfo = getUriInfo\n this.fetchEventListener = this.fetchEventListener.bind(this)\n }\n\n fetchEventListener(event: ConflictFreeWebWorkerFetchEvent) {\n try {\n const uriInfo = this.getUriInfo(event)\n\n if (!uriInfo) return\n\n const baseUrl = removeTrailingSlash(uriInfo.baseUrl)\n const streamerPath = event.request.url.substring(\n baseUrl.length + `/`.length,\n )\n const [key = ``] = streamerPath.split(\"/\")\n const resourcePath = removeTrailingSlash(\n streamerPath.substring(key.length + `/`.length),\n )\n\n if (streamerPath.endsWith(`/manifest`)) {\n event.respondWith(\n this.fetchManifest({ key, baseUrl: `${baseUrl}/${key}/` }),\n )\n } else {\n event.respondWith(\n this.fetchResource({\n key,\n resourcePath,\n request: event.request,\n }),\n )\n }\n } catch (e) {\n event.respondWith(new Response(String(e), { status: 500 }))\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["Report","SharedReport","Report","Report$1","Report","configure","enableReport","normalizeFilenameAsTitle","filename","buildAudiobookToc","manifest","parseContentType","item","detectMimeTypeFromName","getUriBasename","encodeResourcePath","resourcePath","createManifestResourceHref","baseUrl","hrefBaseUrl","toManifestTocItem","urlJoin","manifestGuideType","v","raw","isHttpUrl","value","firstDirectoryTitle","archive","dir","epubManifest","resolved","opfBasePath","opf","guide","reference","type","createSafeId","createXmlSafeIdFactory","mainTitle","metadata","readingOrder","toContainerHref","recordsManifest","files","file","idByUri","toc","manifestFromResolvedArchive","normalizeBaseUrl","generateManifestFromArchive","normalizedBaseUrl","resolveArchive","hookFactory","hooks","manifestStr","createTextResourceHook","getArchiveFileRecordByUri","extension","newBody","transform","resource","readRecordAsText","hasCalibreCoverMeta","metaElm","doc","node","getBuggyCoverSvg","calibreFixHook","opfXmlDoc","XmlDocument","bodyToParse","buggySvg","cssFixHook","body","getMetadata","parsed","readArchiveOpf","foundMediaType","toContainerUri","basePath","getContentTypeFromExtension","uri","defaultHook","invalidSelfClosingTags","selfClosingTagsFixHook","tagPattern","tagName","attributes","generateResourceFromArchive","defaultResource","resourceHooks","hook","gen","ArchiveEntry","cleanArchiveAfter","BehaviorSubject","update","map","locks","distinctUntilChanged","shareReplay","switchMap","isUnlocked","NEVER","timer","createArchiveLoader","loadSubject","Subject","destroySubject","purgeSubject","archives","mergeMap","archiveEntry","key","EMPTY","isClosed","cleanupArchive","entry","locks$","isUnlocked$","newAccess$","pairwise","filter","prev","curent","startWith","from","getArchive","tap","catchError","readyForPurge$","merge","first","takeUntil","releaseCalled","release","archive$","error$","error","ignoreElements","parseByteRangePart","parseSingleByteRange","rangeHeader","rangeValue","matches","rawStart","rawEnd","parsedStart","parsedEnd","materializeForSlicing","part","start","endExclusive","bytes","createRangeResponse","headers","contentType","parsedRange","rangeBody","size","end","partial","fileProtocol","httpProtocolPattern","decodeResourcePath","stripFileProtocol","normalizeResourcePath","resourcePathWithoutFileProtocol","Streamer","rest","onManifestSuccess","onError","response$","getResponse","finalize","of","lastValueFrom","cleanedResourcePath","request","ServiceWorkerStreamer","getUriInfo","uriInfo","event","removeTrailingSlash","streamerPath","e"],"sources":["../../package.json","../../src/report.ts","../../src/configure.ts","../../src/generators/manifest/audiobookToc.ts","../../src/generators/manifest/createManifestResourceHref.ts","../../src/generators/manifest/manifestFromResolvedArchive.ts","../../src/generators/manifest/index.ts","../../src/generators/resources/hooks/createTextResourceHook.ts","../../src/generators/resources/hooks/calibreFixHook.ts","../../src/generators/resources/hooks/cssFixHook.ts","../../src/generators/resources/hooks/defaultHook.ts","../../src/generators/resources/hooks/selfClosingTagsFixHook.ts","../../src/generators/resources/index.ts","../../src/archiveLoader.ts","../../src/utils/createRangeResponse.ts","../../src/Streamer.ts","../../src/ServiceWorkerStreamer.ts"],"sourcesContent":["{\n \"name\": \"@prose-reader/streamer\",\n \"version\": \"28.1.0\",\n \"type\": \"module\",\n \"main\": \"./dist/index/index.cjs\",\n \"module\": \"./dist/index/index.js\",\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index/index.js\",\n \"require\": \"./dist/index/index.cjs\"\n }\n },\n \"types\": \"./dist/index.d.ts\",\n \"license\": \"MIT\",\n \"files\": [\n \"/dist\"\n ],\n \"scripts\": {\n \"start\": \"vite build --watch --mode development\",\n \"build\": \"tsc && vite build\",\n \"test\": \"vitest run --coverage\",\n \"tsc\": \"tsc\",\n \"test:watch\": \"vitest watch\"\n },\n \"dependencies\": {\n \"@prose-reader/archive-reader\": \"workspace:^\",\n \"@prose-reader/shared\": \"workspace:^\",\n \"xmldoc\": \"^3.0.0\"\n },\n \"peerDependencies\": {\n \"rxjs\": \"^7.5.5\"\n },\n \"gitHead\": \"4601e14dcacf50b2295cb343582a7ef2c7e1eedc\",\n \"engines\": {\n \"node\": \">=22.12\"\n }\n}\n","import { Report as SharedReport } from \"@prose-reader/shared\"\nimport { name } from \"../package.json\"\n\n// No explicit `enabled`: inherit the shared root, which auto-configures from\n// the `globalThis.__PROSE_READER_DEBUG` flag (works in window, workers, node).\n// `configure()` remains as an explicit override for setups where the flag\n// cannot be set before the module evaluates.\nexport const Report = SharedReport.namespace(name, undefined, {\n color: \"#ffae42\",\n})\n","import { Report } from \"./report\"\n\nexport const configure = ({\n enableReport,\n}: {\n enableReport?: boolean\n} = {}) => {\n Report.enable(!!enableReport)\n}\n","import {\n detectMimeTypeFromName,\n getUriBasename,\n type Manifest,\n parseContentType,\n} from \"@prose-reader/shared\"\n\ntype Toc = NonNullable<Manifest[\"nav\"]>[\"toc\"]\n\nexport const normalizeFilenameAsTitle = (filename: string) =>\n filename\n .replace(/\\.[^.]+$/, \"\")\n .replace(/[_-]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n\n/**\n * One entry per track for a book made only of audio, each titled after the\n * archive record it plays.\n */\nexport const buildAudiobookToc = (manifest: Manifest): Toc | undefined => {\n if (manifest.spineItems.length === 0) return undefined\n\n const allAudio = manifest.spineItems.every((item) => {\n const mimeType =\n parseContentType(item.mediaType ?? \"\") ||\n detectMimeTypeFromName(item.containerUri)\n\n return mimeType?.startsWith(\"audio/\")\n })\n\n if (!allAudio) return undefined\n\n return manifest.spineItems.map((item) => ({\n title: normalizeFilenameAsTitle(getUriBasename(item.containerUri)),\n href: item.href,\n path: item.containerUri,\n contents: [],\n }))\n}\n","// encodeURI leaves `#` and `?` untouched (reserved URI delimiters), but in\n// a raw archive filename they are data and would otherwise be parsed as\n// fragment/query by whatever fetches the href (a fragment never even reaches\n// the streamer). Same escaping as the archive-reader toc containerHref.\nconst encodeResourcePath = (resourcePath: string) =>\n encodeURI(resourcePath).replace(/#/g, `%23`).replace(/\\?/g, `%3F`)\n\nexport const createManifestResourceHref = ({\n baseUrl = ``,\n resourcePath,\n}: {\n baseUrl?: string\n resourcePath: string\n}) => {\n if (!baseUrl && /^https?:\\/\\//.test(resourcePath)) {\n return encodeURI(resourcePath)\n }\n\n const hrefBaseUrl = baseUrl\n ? `${baseUrl}${baseUrl.endsWith(`/`) ? `` : `/`}`\n : `file://`\n\n return `${encodeURI(hrefBaseUrl)}${encodeResourcePath(resourcePath)}`\n}\n","import {\n type Archive,\n type ArchiveTocItem,\n mainTitle,\n type ResolvedArchive,\n toContainerHref,\n} from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport { createXmlSafeIdFactory, urlJoin } from \"@prose-reader/shared\"\nimport { buildAudiobookToc } from \"./audiobookToc\"\nimport { createManifestResourceHref } from \"./createManifestResourceHref\"\n\ntype TocItem = NonNullable<Manifest[\"nav\"]>[\"toc\"][number]\ntype ManifestGuideEntry = NonNullable<Manifest[\"guide\"]>[number]\n\n/**\n * The archive reader resolves references within the container, the streamer\n * owns where they are served from.\n */\nconst toManifestTocItem =\n (baseUrl: string) =>\n (item: ArchiveTocItem): TocItem => ({\n title: item.title,\n path: item.path,\n // rebase the container-relative ref into serving space; entries without a\n // target (eg nav heading without link) keep an empty href\n href: item.containerHref ? urlJoin(baseUrl, item.containerHref) : ``,\n contents: item.contents.map(toManifestTocItem(baseUrl)),\n })\n\nconst manifestGuideType = (\n raw: string | undefined,\n): ManifestGuideEntry[\"type\"] | undefined => {\n const v = raw?.trim()\n if (\n v === `cover` ||\n v === `title-page` ||\n v === `copyright-page` ||\n v === `text`\n ) {\n return v\n }\n return undefined\n}\n\nconst isHttpUrl = (value: string) => /^https?:\\/\\//.test(value)\n\nconst firstDirectoryTitle = (archive: Archive): string | undefined =>\n archive.records.find(({ dir }) => dir)?.basename.replace(/\\/$/, ``)\n\n/**\n * The subset of {@link ResolvedArchive} the manifest mapping consumes: the\n * normalized publication plus `sources` — the OPF manifest (all package\n * resources, not just the spine) and the legacy guide only exist there.\n */\ntype ResolvedArchiveForManifest = Pick<\n ResolvedArchive,\n \"mediaType\" | \"metadata\" | \"readingOrder\" | \"toc\" | \"sources\"\n>\n\nconst epubManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n opf: NonNullable<ResolvedArchiveForManifest[\"sources\"][\"opf\"]>,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n const opfBasePath = opf.basePath\n\n const guide: ManifestGuideEntry[] = []\n for (const reference of opf.opf.guide) {\n const type = manifestGuideType(reference.type)\n if (type === undefined) continue\n // guide hrefs stay as authored (opf-relative), matching the legacy\n // EPUB 2 contract consumers already handle\n guide.push({ href: reference.href, title: reference.title, type })\n }\n\n const createSafeId = createXmlSafeIdFactory()\n\n return {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title: mainTitle(metadata) || firstDirectoryTitle(archive) || ``,\n renditionLayout: metadata.renditionLayout,\n renditionFlow: metadata.renditionFlow ?? `auto`,\n renditionSpread: metadata.renditionSpread,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: item.id ?? createSafeId(item.uri),\n index,\n // remote resources pass through; local ones get the serving base, or\n // the file:// scheme when the manifest is not served from anywhere, and\n // the reference the book makes to them, which its navigation and links\n // match as written\n href: isHttpUrl(item.uri)\n ? item.uri\n : `${baseUrl || `file://`}${item.containerHref ?? item.uri}`,\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n ...(item.renditionFlow !== undefined\n ? { renditionFlow: item.renditionFlow }\n : {}),\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: item.pageSpreadLeft,\n pageSpreadRight: item.pageSpreadRight,\n mediaType: item.mediaType,\n })),\n items: opf.opf.manifestItems.map((item) => ({\n id: item.id,\n // rebased as the spine item of the same resource is\n href: isHttpUrl(item.href)\n ? item.href\n : `${baseUrl || `file://`}${toContainerHref(item.href, opfBasePath)}`,\n mediaType: item.mediaType,\n })),\n guide: guide.length > 0 ? guide : undefined,\n nav: {\n // EPUB-like containers keep an explicit (possibly empty) toc\n toc: (resolved.toc ?? []).map(toManifestTocItem(baseUrl)),\n },\n }\n}\n\nconst recordsManifest = (\n archive: Archive,\n baseUrl: string,\n resolved: ResolvedArchiveForManifest,\n): Manifest => {\n const { metadata, readingOrder } = resolved\n\n const files = archive.records.filter((file) => !file.dir)\n const createSafeId = createXmlSafeIdFactory()\n const idByUri = new Map(\n files.map((file) => [file.uri, createSafeId(file.uri)]),\n )\n\n const manifest: Manifest = {\n filename: archive.filename ?? ``,\n mediaType: resolved.mediaType,\n title:\n mainTitle(metadata) ||\n firstDirectoryTitle(archive) ||\n archive.filename ||\n ``,\n renditionLayout: metadata.renditionLayout,\n renditionSpread: `auto`,\n readingDirection: metadata.readingDirection,\n spineItems: readingOrder.map((item, index) => ({\n id: idByUri.get(item.uri) ?? createSafeId(item.uri),\n index,\n href: createManifestResourceHref({ baseUrl, resourcePath: item.uri }),\n containerUri: item.uri,\n renditionLayout: item.renditionLayout,\n progressionWeight: item.progressionWeight,\n pageSpreadLeft: undefined,\n pageSpreadRight: undefined,\n mediaType: item.mediaType,\n })),\n // every container file stays addressable, sidecars included — items is\n // the resource map, the reading order is the curation\n items: files.map((file) => ({\n id: idByUri.get(file.uri) ?? createSafeId(file.uri),\n href: encodeURI(`${baseUrl}${file.uri}`),\n })),\n }\n\n const audiobookToc = buildAudiobookToc(manifest)\n const toc = audiobookToc ?? resolved.toc?.map(toManifestTocItem(baseUrl))\n\n return toc !== undefined ? { ...manifest, nav: { toc } } : manifest\n}\n\n/**\n * Maps a resolved archive into the streamer's serving space: hrefs get the\n * base url (or `file://`) baked in, ids become XML-safe, defaults are the\n * manifest's own. The container understanding itself (format detection,\n * metadata precedence, spine rules) all happened in\n * `@prose-reader/archive-reader`.\n */\nexport const manifestFromResolvedArchive = ({\n archive,\n baseUrl,\n resolved,\n}: {\n archive: Archive\n baseUrl: string\n resolved: ResolvedArchiveForManifest\n}): Manifest => {\n const opf = resolved.sources.opf\n\n if (opf) return epubManifest(archive, baseUrl, resolved, opf)\n\n return recordsManifest(archive, baseUrl, resolved)\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { resolveArchive } from \"@prose-reader/archive-reader\"\nimport type { StreamerManifestHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { manifestFromResolvedArchive } from \"./manifestFromResolvedArchive\"\n\nconst normalizeBaseUrl = (baseUrl: string | undefined) => {\n if (!baseUrl) return ``\n\n return baseUrl.endsWith(`/`) ? baseUrl : `${baseUrl}/`\n}\n\n/**\n * Generates the prose `Manifest` for an archive: `resolveArchive` does the\n * book understanding (metadata precedence, reading order, toc, the viewport\n * layout scan), then the result is mapped into serving space and handed to\n * the consumer `hooks` (ordered `Manifest` transforms). The final defaults\n * (`readingDirection` falling back to `ltr`) apply after the hooks, so a\n * hook can still observe \"no source decided it\".\n */\nexport const generateManifestFromArchive = async (\n archive: Archive,\n {\n baseUrl = ``,\n hooks = [],\n }: { baseUrl?: string; hooks?: StreamerManifestHookFactory[] } = {},\n) => {\n Report.log(\"Generating manifest from archive\", archive)\n\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl)\n\n try {\n const resolved = await resolveArchive(archive, {\n include: [`metadata`, `readingOrder`, `toc`, `sources`],\n layoutScan: true,\n })\n\n let manifest = manifestFromResolvedArchive({\n archive,\n baseUrl: normalizedBaseUrl,\n resolved,\n })\n\n for (const hookFactory of hooks) {\n manifest = await hookFactory({\n archive,\n baseUrl: normalizedBaseUrl,\n })(manifest)\n }\n\n manifest = {\n ...manifest,\n readingDirection: manifest.readingDirection ?? \"ltr\",\n }\n\n Report.log(\"Generated manifest\", manifest)\n\n if (process.env.NODE_ENV === \"development\") {\n if (Report.isEnabled()) {\n const manifestStr = JSON.stringify(manifest, null, 2)\n Report.groupCollapsed(...Report.getGroupArgs(\"Generated manifest\"))\n Report.log(`\\n${manifestStr}`)\n Report.groupEnd()\n }\n }\n\n return manifest\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import {\n getArchiveFileRecordByUri,\n readRecordAsText,\n} from \"@prose-reader/archive-reader\"\nimport type {\n StreamerResourceHook,\n StreamerResourceHookContext,\n StreamerResourceHookFactory,\n} from \"../../../hooks\"\n\n/**\n * Shared scaffolding for resource hooks that rewrite the text body of a single\n * archive file matching a given extension.\n *\n * Resolves the archive record for `resourcePath` and, when its basename ends\n * with `extension`, reads the resource body as text (reusing an already-string\n * body, otherwise reading the record) and passes it to `transform`. When\n * `transform` returns a string the resource body is replaced; when it returns\n * `undefined` the resource is returned untouched (so a non-string body is\n * preserved as-is).\n */\nexport const createTextResourceHook =\n (\n extension: string,\n transform: (\n body: string,\n ) => string | undefined | Promise<string | undefined>,\n ): StreamerResourceHookFactory =>\n ({\n archive,\n resourcePath,\n }: StreamerResourceHookContext): StreamerResourceHook =>\n async (resource) => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (file?.basename.endsWith(extension)) {\n const bodyToParse =\n typeof resource.body === `string`\n ? resource.body\n : await readRecordAsText(file)\n\n const newBody = await transform(bodyToParse)\n\n if (newBody !== undefined) {\n return {\n ...resource,\n body: newBody,\n }\n }\n }\n\n return resource\n }\n","import { XmlDocument } from \"xmldoc\"\nimport { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst hasCalibreCoverMeta = (doc: XmlDocument) => {\n const metaElm = doc\n .descendantWithPath(\"head\")\n ?.childrenNamed(\"meta\")\n .find((node) => node.attr.name === \"calibre:cover\")\n\n return !!(metaElm && metaElm.attr.name === \"calibre:cover\")\n}\n\nconst getBuggyCoverSvg = (doc: XmlDocument) => {\n return doc\n .descendantWithPath(\"body\")\n ?.descendantWithPath(\"div\")\n ?.childrenNamed(\"svg\")\n ?.find(\n (node) =>\n node.attr.width === \"100%\" && node.attr.preserveAspectRatio === \"none\",\n )\n}\n\nexport const calibreFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const opfXmlDoc = new XmlDocument(bodyToParse)\n\n if (!hasCalibreCoverMeta(opfXmlDoc)) {\n return undefined\n }\n\n const buggySvg = getBuggyCoverSvg(opfXmlDoc)\n\n if (buggySvg) {\n delete buggySvg.attr.preserveAspectRatio\n }\n\n return opfXmlDoc.toString()\n },\n)\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nexport const cssFixHook = createTextResourceHook(\".css\", (body) =>\n /**\n * Fix the potentially invalid writing mode present on some vertical book.\n * This has the benefit of making it compatible with firefox as well.\n */\n body.replaceAll(`-webkit-writing-mode`, `writing-mode`),\n)\n","import {\n type Archive,\n getArchiveFileRecordByUri,\n readArchiveOpf,\n toContainerUri,\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 // the manifest item whose href names the requested record\n const foundMediaType = opf.manifestItems.find(\n (item) => toContainerUri(item.href, basePath) === resourcePath,\n )?.mediaType\n\n if (foundMediaType) {\n return {\n mediaType: foundMediaType,\n }\n }\n }\n\n return {\n mediaType: getContentTypeFromExtension(resourcePath),\n }\n}\n\nconst getContentTypeFromExtension = (uri: string) => {\n if (uri.endsWith(`.css`)) {\n return `text/css; charset=UTF-8`\n }\n if (uri.endsWith(`.jpg`)) {\n return `image/jpg`\n }\n if (uri.endsWith(`.xhtml`)) {\n return `application/xhtml+xml`\n }\n if (uri.endsWith(`.mp4`)) {\n return `video/mp4`\n }\n if (uri.endsWith(`.svg`)) {\n return `image/svg+xml`\n }\n}\n\nexport const defaultHook =\n ({ archive, resourcePath }: { archive: Archive; resourcePath: string }) =>\n async (resource: HookResource): Promise<HookResource> => {\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) return resource\n\n const metadata = await getMetadata(archive, resourcePath)\n\n return {\n ...resource,\n params: {\n ...resource.params,\n ...(file?.encodingFormat && {\n contentType: file.encodingFormat,\n }),\n ...(metadata.mediaType && {\n contentType: metadata.mediaType,\n }),\n },\n }\n }\n","import { createTextResourceHook } from \"./createTextResourceHook\"\n\nconst invalidSelfClosingTags = [\n \"div\",\n \"span\",\n \"p\",\n \"a\",\n \"li\",\n \"ul\",\n \"ol\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"table\",\n \"tr\",\n \"td\",\n \"th\",\n \"thead\",\n \"tbody\",\n \"tfoot\",\n \"section\",\n \"article\",\n \"header\",\n \"footer\",\n \"nav\",\n \"aside\",\n \"main\",\n \"figure\",\n \"figcaption\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"form\",\n \"textarea\",\n \"select\",\n \"option\",\n \"button\",\n \"label\",\n \"fieldset\",\n \"legend\",\n \"caption\",\n \"dl\",\n \"dt\",\n \"dd\",\n \"iframe\",\n \"video\",\n \"audio\",\n \"canvas\",\n \"script\",\n \"style\",\n]\n\n/**\n * Some books uses xhtml files but also includes wrong self closin tags. This happens\n * a lot with kobo epub which have a lot of self closing <script ... />. This breaks on some\n * browser such as webkit. We use a regex to replace and fix them. There\n * is a first lighter regex which check if any such tag exist in the first place before running\n * the full replace regex empty.\n */\nexport const selfClosingTagsFixHook = createTextResourceHook(\n \".xhtml\",\n (bodyToParse) => {\n const tagCheckPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})[\\\\s/>]`,\n \"i\",\n )\n if (!tagCheckPattern.test(bodyToParse)) {\n return undefined\n }\n\n const tagPattern = new RegExp(\n `<(${invalidSelfClosingTags.join(\"|\")})(\\\\s[^>]*)?\\\\s*/>`,\n \"gi\",\n )\n\n return bodyToParse.replace(tagPattern, (_, tagName, attributes = \"\") => {\n // Convert to an opening and closing tag\n return `<${tagName} ${attributes.trim()}></${tagName}>`\n })\n },\n)\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport { getArchiveFileRecordByUri } from \"@prose-reader/archive-reader\"\nimport type { StreamerResourceHookFactory } from \"../../hooks\"\nimport { Report } from \"../../report\"\nimport { calibreFixHook } from \"./hooks/calibreFixHook\"\nimport { cssFixHook } from \"./hooks/cssFixHook\"\nimport { defaultHook } from \"./hooks/defaultHook\"\nimport { selfClosingTagsFixHook } from \"./hooks/selfClosingTagsFixHook\"\nimport type { HookResource } from \"./hooks/types\"\n\n/**\n * Builds one streamed asset from `archive` (hot path: typically once per\n * fetched file in the reader).\n *\n * **OPF limitation:** Unlike manifest generation, this API does not take\n * `ArchiveOpfParsed`. `defaultHook` calls `readArchiveOpf(archive)` to resolve\n * `media-type` from the package document, so EPUBs repeat OPF I/O + parse per\n * resource. Usually fine (small OPF); if profiling shows regressions, address\n * explicitly—e.g. optional `archiveOpf` (or equivalent) threaded from\n * `Streamer.fetchResource` / manifest, or an opt-in cache on `Archive` wired at\n * construction time—rather than hidden process-wide memoization.\n */\nexport const generateResourceFromArchive = async (\n archive: Archive,\n resourcePath: string,\n { hooks = [] }: { hooks?: StreamerResourceHookFactory[] } = {},\n) => {\n const defaultResource: HookResource = {\n params: {},\n }\n const externalHooks = hooks.map((hook) => hook({ archive, resourcePath }))\n\n /**\n * EPUB: `defaultHook` calls `readArchiveOpf` → repeated OPF I/O + parse per\n * resource until we thread `ArchiveOpfParsed` here (see export JSDoc).\n */\n const resourceHooks = [\n ...externalHooks,\n defaultHook({ archive, resourcePath }),\n selfClosingTagsFixHook({ archive, resourcePath }),\n cssFixHook({ archive, resourcePath }),\n calibreFixHook({ archive, resourcePath }),\n ]\n\n try {\n const resource = await resourceHooks.reduce(async (manifest, gen) => {\n return await gen(await manifest)\n }, Promise.resolve(defaultResource))\n\n Report.log(\"Generated resource\", resourcePath, resource)\n\n if (resource.body !== undefined) {\n return resource\n }\n\n const file = getArchiveFileRecordByUri(archive, resourcePath)\n\n if (!file) {\n throw new Error(`no file found for resourcePath:${resourcePath}`)\n }\n\n return {\n ...resource,\n body: await file.blob(),\n }\n } catch (e) {\n Report.error(e)\n\n throw e\n }\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport {\n BehaviorSubject,\n catchError,\n distinctUntilChanged,\n EMPTY,\n filter,\n first,\n from,\n ignoreElements,\n map,\n merge,\n mergeMap,\n NEVER,\n type ObservedValueOf,\n pairwise,\n Subject,\n shareReplay,\n startWith,\n switchMap,\n takeUntil,\n tap,\n timer,\n} from \"rxjs\"\nimport { Report } from \"./report\"\n\nclass ArchiveEntry {\n state$ = new BehaviorSubject<{\n status: \"idle\" | \"loading\" | \"success\" | \"error\"\n error?: unknown | undefined\n archive?: undefined | Archive\n locks: number\n }>({\n status: `idle`,\n locks: 0,\n })\n\n constructor(private cleanArchiveAfter: number) {}\n\n update(update: Partial<ObservedValueOf<typeof this.state$>>) {\n this.state$.next({ ...this.state$.getValue(), ...update })\n }\n\n get locks$() {\n return this.state$.pipe(map(({ locks }) => locks))\n }\n\n get state() {\n return this.state$.getValue()\n }\n\n get isUnlocked$() {\n return this.locks$.pipe(\n map((locks) => locks <= 0),\n distinctUntilChanged(),\n shareReplay(),\n )\n }\n\n get overTTL$() {\n return this.isUnlocked$.pipe(\n switchMap((isUnlocked) =>\n !isUnlocked\n ? NEVER\n : this.cleanArchiveAfter === Infinity\n ? NEVER\n : timer(this.cleanArchiveAfter),\n ),\n )\n }\n}\n\nexport const createArchiveLoader = ({\n getArchive,\n cleanArchiveAfter = 5 * 60 * 1000, // 5mn\n}: {\n getArchive: (key: string) => Promise<Archive>\n cleanArchiveAfter?: number\n}) => {\n const loadSubject = new Subject<string>()\n const destroySubject = new Subject<void>()\n const purgeSubject = new Subject<void>()\n const archives: Record<string, ArchiveEntry> = {}\n\n const loadArchive$ = loadSubject.pipe(\n mergeMap((key) => {\n const archiveEntry = archives[key]\n\n if (archiveEntry?.state.status !== \"idle\") return EMPTY\n\n let isClosed = false\n\n const cleanupArchive = (key: string) => {\n Report.debug(`Cleaning up archive with key: ${key}`)\n\n const entry = archives[key]\n\n delete archives[key]\n\n if (!isClosed) {\n entry?.state.archive?.close()\n isClosed = true\n }\n }\n\n archiveEntry.update({\n status: \"loading\",\n })\n\n const locks$ = archiveEntry.locks$\n const isUnlocked$ = archiveEntry.isUnlocked$\n\n const newAccess$ = locks$.pipe(\n pairwise(),\n filter(([prev, curent]) => curent > prev),\n startWith(true),\n )\n\n const archive$ = from(getArchive(key))\n\n return archive$.pipe(\n tap((archive) => {\n archiveEntry.update({\n archive,\n status: \"success\",\n })\n }),\n catchError((error) => {\n cleanupArchive(key)\n\n archiveEntry.update({\n status: \"error\",\n error,\n })\n\n return EMPTY\n }),\n switchMap(() => {\n const readyForPurge$ = newAccess$.pipe(\n switchMap(() => purgeSubject),\n switchMap(() => isUnlocked$),\n filter((isUnlocked) => isUnlocked),\n )\n\n const toCleanup$ = merge(readyForPurge$, archiveEntry.overTTL$)\n\n return toCleanup$.pipe(\n first(),\n tap(() => {\n cleanupArchive(key)\n }),\n )\n }),\n )\n }),\n takeUntil(destroySubject),\n )\n\n const access = (key: string) => {\n let releaseCalled = false\n\n const archiveEntry = archives[key] ?? new ArchiveEntry(cleanArchiveAfter)\n\n archives[key] = archiveEntry\n\n archiveEntry.update({\n locks: archiveEntry.state.locks + 1,\n })\n\n const release = () => {\n if (releaseCalled) return\n\n releaseCalled = true\n\n archiveEntry.update({\n locks: archiveEntry.state.locks - 1,\n })\n }\n\n loadSubject.next(key)\n\n const archive$ = archiveEntry.state$.pipe(\n map(({ archive }) => archive),\n filter((archive) => !!archive),\n )\n\n const error$ = archiveEntry.state$.pipe(\n tap(({ error }) => {\n if (error) {\n throw error\n }\n }),\n ignoreElements(),\n )\n\n return merge(archive$, error$).pipe(\n first(),\n map((archive) => ({ archive, release })),\n catchError((error) => {\n release()\n\n throw error\n }),\n )\n }\n\n /**\n * Will purge immediatly archives as soon as they are released\n */\n const purge = () => {\n purgeSubject.next()\n }\n\n loadArchive$.subscribe()\n\n return {\n access,\n purge,\n _archives: archives,\n }\n}\n","const parseByteRangePart = (value: string) => {\n if (!value) {\n return {\n valid: true,\n value: undefined,\n }\n }\n\n if (!/^\\d+$/.test(value)) {\n return {\n valid: false,\n value: undefined,\n }\n }\n\n return {\n valid: true,\n value: Number.parseInt(value, 10),\n }\n}\n\nconst parseSingleByteRange = (rangeHeader: string) => {\n if (!rangeHeader.toLowerCase().startsWith(\"bytes=\")) {\n return {\n kind: \"missing\" as const,\n }\n }\n\n const rangeValue = rangeHeader.slice(\"bytes=\".length).trim()\n\n if (!rangeValue) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n if (rangeValue.includes(\",\")) {\n return {\n kind: \"multi\" as const,\n }\n }\n\n const matches = /^(\\d*)-(\\d*)$/.exec(rangeValue)\n\n if (!matches) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n const [, rawStart = \"\", rawEnd = \"\"] = matches\n const parsedStart = parseByteRangePart(rawStart.trim())\n const parsedEnd = parseByteRangePart(rawEnd.trim())\n\n if (!parsedStart.valid || !parsedEnd.valid) {\n return {\n kind: \"invalid\" as const,\n }\n }\n\n return {\n kind: \"single\" as const,\n start: parsedStart.value,\n end: parsedEnd.value,\n }\n}\n\n/**\n * Normalize the body into a sliceable form that avoids passing a\n * `Blob.slice()` result directly to `new Response()`. Some Node.js\n * versions do not recognise sliced Blobs as valid `BodyInit`,\n * producing `\"[object Blob]\"` instead of the actual content.\n */\nconst materializeForSlicing = (body: Blob | string) => {\n if (body instanceof Blob) {\n return {\n size: body.size,\n slice: (start: number, endExclusive: number) => {\n const part = body.slice(start, endExclusive)\n return { content: part, length: part.size }\n },\n }\n }\n\n const bytes = new TextEncoder().encode(body)\n\n return {\n size: bytes.byteLength,\n slice: (start: number, endExclusive: number) => {\n const part = bytes.slice(start, endExclusive)\n return { content: part, length: part.byteLength }\n },\n }\n}\n\nexport const createRangeResponse = ({\n body,\n contentType,\n rangeHeader,\n}: {\n body: Blob | string\n contentType?: string\n rangeHeader?: string | null\n}) => {\n const headers = new Headers()\n\n if (contentType) {\n headers.set(\"Content-Type\", contentType)\n }\n\n headers.set(\"Accept-Ranges\", \"bytes\")\n\n if (!rangeHeader) {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const parsedRange = parseSingleByteRange(rangeHeader)\n\n if (parsedRange.kind === \"missing\" || parsedRange.kind === \"multi\") {\n if (body instanceof Blob) {\n headers.set(\"Content-Length\", String(body.size))\n }\n\n return new Response(body, {\n status: 200,\n headers,\n })\n }\n\n const rangeBody = materializeForSlicing(body)\n const size = rangeBody.size\n\n if (parsedRange.kind === \"invalid\") {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n let start = parsedRange.start\n let end = parsedRange.end\n\n if (start === undefined && end === undefined) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n if (start === undefined) {\n const suffixLength = Math.min(end ?? 0, size)\n start = Math.max(0, size - suffixLength)\n end = size - 1\n } else if (end === undefined || end >= size) {\n end = size - 1\n }\n\n if (start < 0 || end < 0 || start >= size || end >= size || start > end) {\n return new Response(null, {\n status: 416,\n headers: {\n \"Content-Range\": `bytes */${size}`,\n },\n })\n }\n\n const partial = rangeBody.slice(start, end + 1)\n\n headers.set(\"Content-Length\", String(partial.length))\n headers.set(\"Content-Range\", `bytes ${start}-${end}/${size}`)\n\n return new Response(partial.content, {\n status: 206,\n headers,\n })\n}\n","import type { Archive } from \"@prose-reader/archive-reader\"\nimport type { Manifest } from \"@prose-reader/shared\"\nimport {\n catchError,\n finalize,\n from,\n lastValueFrom,\n map,\n mergeMap,\n type Observable,\n of,\n switchMap,\n} from \"rxjs\"\nimport { createArchiveLoader } from \"./archiveLoader\"\nimport { generateManifestFromArchive } from \"./generators/manifest\"\nimport { generateResourceFromArchive } from \"./generators/resources\"\nimport type { StreamerHooks } from \"./hooks\"\nimport { createRangeResponse } from \"./utils/createRangeResponse\"\n\ntype OnError = (error: unknown) => Response\ntype OnManifestSuccess = (params: {\n manifest: Manifest\n archive: Archive\n}) => Observable<Manifest> | Promise<Manifest>\ntype WithArchiveResponse = (\n archive: Archive,\n) => Observable<Response> | Promise<Response>\n\nconst fileProtocol = `file://`\nconst httpProtocolPattern = /^https?:\\/\\//\n\nconst decodeResourcePath = (resourcePath: string) => {\n try {\n return decodeURIComponent(resourcePath)\n } catch {\n return resourcePath\n }\n}\n\nconst stripFileProtocol = (resourcePath: string) =>\n resourcePath.startsWith(fileProtocol)\n ? resourcePath.slice(fileProtocol.length)\n : resourcePath\n\nconst normalizeResourcePath = (resourcePath: string) => {\n const resourcePathWithoutFileProtocol = stripFileProtocol(resourcePath)\n\n if (httpProtocolPattern.test(resourcePathWithoutFileProtocol)) {\n return resourcePathWithoutFileProtocol\n }\n\n return stripFileProtocol(decodeResourcePath(resourcePathWithoutFileProtocol))\n}\n\nexport class Streamer {\n protected archiveLoader: ReturnType<typeof createArchiveLoader>\n protected onError: OnError = (error) => {\n console.error(error)\n\n return new Response(String(error), { status: 500 })\n }\n protected hooks: StreamerHooks\n protected onManifestSuccess: OnManifestSuccess\n protected lastAccessedKey: string | undefined\n\n constructor({\n hooks,\n onError,\n onManifestSuccess,\n ...rest\n }: Parameters<typeof createArchiveLoader>[0] & {\n hooks?: StreamerHooks\n onError?: OnError\n onManifestSuccess?: OnManifestSuccess\n }) {\n this.archiveLoader = createArchiveLoader(rest)\n this.hooks = hooks ?? {}\n\n this.onManifestSuccess =\n onManifestSuccess ?? (({ manifest }) => Promise.resolve(manifest))\n this.onError = onError ?? this.onError\n }\n\n public prune() {\n this.archiveLoader.purge()\n }\n\n public accessArchive(key: string) {\n if (this.lastAccessedKey !== undefined && this.lastAccessedKey !== key) {\n this.archiveLoader.purge()\n }\n\n this.lastAccessedKey = key\n\n return this.archiveLoader.access(key)\n }\n\n public accessArchiveWithoutLock(key: string) {\n return this.accessArchive(key).pipe(\n map(({ archive, release }) => {\n release()\n\n return archive\n }),\n )\n }\n\n protected withArchiveResponse({\n key,\n getResponse,\n }: {\n key: string\n getResponse: WithArchiveResponse\n }) {\n const response$ = this.accessArchive(key).pipe(\n mergeMap(({ archive, release }) =>\n from(getResponse(archive)).pipe(\n finalize(() => {\n release()\n }),\n ),\n ),\n catchError((error) => {\n return of(this.onError(error))\n }),\n )\n\n return lastValueFrom(response$)\n }\n\n public fetchManifest({ key, baseUrl }: { key: string; baseUrl?: string }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n const manifest$ = from(\n generateManifestFromArchive(archive, {\n baseUrl,\n hooks: this.hooks.manifest,\n }),\n )\n\n return manifest$.pipe(\n switchMap((manifest) =>\n from(this.onManifestSuccess({ manifest, archive })),\n ),\n map(\n (manifest) =>\n new Response(JSON.stringify(manifest satisfies Manifest), {\n status: 200,\n }),\n ),\n )\n },\n })\n }\n\n public fetchResource({\n key,\n resourcePath,\n request,\n }: {\n key: string\n resourcePath: string\n request?: Request\n }) {\n return this.withArchiveResponse({\n key,\n getResponse: (archive) => {\n /**\n * We commonly use file:// for manifest without baseUrl. This ensure we\n * have valid URL for the reader while flagging them as local.\n *\n * However, we obviously don't have the file:// prefix on the archive.\n */\n const cleanedResourcePath = normalizeResourcePath(resourcePath)\n\n /**\n * Reader hot path: one `generateResourceFromArchive` per request. For\n * EPUBs, `defaultHook` re-reads and re-parses the package OPF each time\n * (no `archiveOpf` threading yet). See `generateResourceFromArchive` JSDoc.\n */\n const resource$ = from(\n generateResourceFromArchive(archive, cleanedResourcePath, {\n hooks: this.hooks.resource,\n }),\n )\n\n return resource$.pipe(\n map((resource) =>\n createRangeResponse({\n body: resource.body ?? \"\",\n contentType: resource.params.contentType,\n rangeHeader: request?.headers.get(\"range\"),\n }),\n ),\n )\n },\n })\n }\n}\n","import { removeTrailingSlash } from \"@prose-reader/shared\"\nimport { Streamer } from \"./Streamer\"\n\ntype ConflictFreeWebWorkerFetchEvent = {\n readonly request: Request\n /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */\n respondWith(r: Response | PromiseLike<Response>): void\n}\n\nexport class ServiceWorkerStreamer extends Streamer {\n protected getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n\n constructor({\n getUriInfo,\n ...rest\n }: ConstructorParameters<typeof Streamer>[0] & {\n getUriInfo: (event: ConflictFreeWebWorkerFetchEvent) =>\n | {\n baseUrl: string\n }\n | undefined\n }) {\n super(rest)\n\n this.getUriInfo = getUriInfo\n this.fetchEventListener = this.fetchEventListener.bind(this)\n }\n\n fetchEventListener(event: ConflictFreeWebWorkerFetchEvent) {\n try {\n const uriInfo = this.getUriInfo(event)\n\n if (!uriInfo) return\n\n const baseUrl = removeTrailingSlash(uriInfo.baseUrl)\n const streamerPath = event.request.url.substring(\n baseUrl.length + `/`.length,\n )\n const [key = ``] = streamerPath.split(\"/\")\n const resourcePath = removeTrailingSlash(\n streamerPath.substring(key.length + `/`.length),\n )\n\n if (streamerPath.endsWith(`/manifest`)) {\n event.respondWith(\n this.fetchManifest({ key, baseUrl: `${baseUrl}/${key}/` }),\n )\n } else {\n event.respondWith(\n this.fetchResource({\n key,\n resourcePath,\n request: event.request,\n }),\n )\n }\n } catch (e) {\n event.respondWith(new Response(String(e), { status: 500 }))\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prose-reader/streamer",
3
- "version": "28.0.0",
3
+ "version": "28.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index/index.cjs",
6
6
  "module": "./dist/index/index.js",
@@ -28,14 +28,14 @@
28
28
  "test:watch": "vitest watch"
29
29
  },
30
30
  "dependencies": {
31
- "@prose-reader/archive-reader": "^28.0.0",
31
+ "@prose-reader/archive-reader": "^28.1.0",
32
32
  "@prose-reader/shared": "^28.0.0",
33
33
  "xmldoc": "^3.0.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "rxjs": "^7.5.5"
37
37
  },
38
- "gitHead": "889074a8f68ffa7c3cde305514e9564af4b65a22",
38
+ "gitHead": "cad16e3ee6dbb015033dc0a7c82327046b8f104e",
39
39
  "engines": {
40
40
  "node": ">=22.12"
41
41
  }