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