UNPKG

dita-streamer-js

Version:

Readium 2 'streamer' for NodeJS (TypeScript)

344 lines 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.serverManifestJson = void 0; const crypto = require("crypto"); const css2json = require("css2json"); const debug_ = require("debug"); const DotProp = require("dot-prop"); const express = require("express"); const jsonMarkup = require("json-markup"); const path = require("path"); const serializable_1 = require("r2-lcp-js/dist/es8-es2017/src/serializable"); const epub_1 = require("r2-shared-js/dist/es8-es2017/src/parser/epub"); const UrlUtils_1 = require("r2-utils-js/dist/es8-es2017/src/_utils/http/UrlUtils"); const JsonUtils_1 = require("r2-utils-js/dist/es8-es2017/src/_utils/JsonUtils"); const json_schema_validate_1 = require("../utils/json-schema-validate"); const request_ext_1 = require("./request-ext"); const debug = debug_("r2:streamer#http/server-manifestjson"); function serverManifestJson(server, routerPathBase64) { const jsonStyle = ` .json-markup { line-height: 17px; font-size: 13px; font-family: monospace; white-space: pre; } .json-markup-key { font-weight: bold; } .json-markup-bool { color: firebrick; } .json-markup-string { color: green; } .json-markup-null { color: gray; } .json-markup-number { color: blue; } `; const routerManifestJson = express.Router({ strict: false }); routerManifestJson.get(["/", "/" + request_ext_1._show + "/:" + request_ext_1._jsonPath + "?"], async (req, res) => { var _a; const reqparams = req.params; if (!reqparams.pathBase64) { reqparams.pathBase64 = req.pathBase64; } if (!reqparams.lcpPass64) { reqparams.lcpPass64 = req.lcpPass64; } const isShow = req.url.indexOf("/show") >= 0 || req.query.show; if (!reqparams.jsonPath && req.query.show) { reqparams.jsonPath = req.query.show; } const isHead = req.method.toLowerCase() === "head"; if (isHead) { debug("HEAD !!!!!!!!!!!!!!!!!!!"); } const isCanonical = req.query.canonical && req.query.canonical === "true"; const isSecureHttp = req.secure || req.protocol === "https" || req.get("X-Forwarded-Proto") === "https"; const pathBase64Str = Buffer.from(reqparams.pathBase64, "base64").toString("utf8"); let publication; try { publication = await server.loadOrGetCachedPublication(pathBase64Str); } catch (err) { debug(err); res.status(500).send("<html><body><p>Internal Server Error</p><p>" + err + "</p></body></html>"); return; } if (reqparams.lcpPass64 && !server.disableDecryption) { const lcpPass = Buffer.from(reqparams.lcpPass64, "base64").toString("utf8"); if (publication.LCP) { try { await publication.LCP.tryUserKeys([lcpPass]); } catch (err) { publication.LCP.ContentKey = undefined; debug(err); const errMsg = "FAIL publication.LCP.tryUserKeys(): " + err; debug(errMsg); res.status(500).send("<html><body><p>Internal Server Error</p><p>" + errMsg + "</p></body></html>"); return; } } } const rootUrl = (isSecureHttp ? "https://" : "http://") + req.headers.host + "/pub/" + (reqparams.lcpPass64 ? (server.lcpBeginToken + UrlUtils_1.encodeURIComponent_RFC3986(reqparams.lcpPass64) + server.lcpEndToken) : "") + UrlUtils_1.encodeURIComponent_RFC3986(reqparams.pathBase64); const manifestURL = rootUrl + "/" + "manifest.json"; const contentType = (publication.Metadata && publication.Metadata.RDFType && /http[s]?:\/\/schema\.org\/Audiobook$/.test(publication.Metadata.RDFType)) ? "application/audiobook+json" : ((publication.Metadata && publication.Metadata.RDFType && (/http[s]?:\/\/schema\.org\/ComicStory$/.test(publication.Metadata.RDFType) || /http[s]?:\/\/schema\.org\/VisualNarrative$/.test(publication.Metadata.RDFType))) ? "application/divina+json" : "application/webpub+json"); const selfLink = publication.searchLinkByRel("self"); if (!selfLink) { publication.AddLink(contentType, ["self"], manifestURL, undefined); } function absoluteURL(href) { return rootUrl + "/" + href; } function absolutizeURLs(jsonObj) { JsonUtils_1.traverseJsonObjects(jsonObj, (obj) => { if (obj.href && typeof obj.href === "string" && !UrlUtils_1.isHTTP(obj.href)) { obj.href = absoluteURL(obj.href); } if (obj["media-overlay"] && typeof obj["media-overlay"] === "string" && !UrlUtils_1.isHTTP(obj["media-overlay"])) { obj["media-overlay"] = absoluteURL(obj["media-overlay"]); } }); } let hasMO = false; if (publication.Spine) { const link = publication.Spine.find((l) => { if (l.Properties && l.Properties.MediaOverlay) { return true; } return false; }); if (link) { hasMO = true; } } if (hasMO) { const moLink = publication.searchLinkByRel("media-overlay"); if (!moLink) { const moURL = epub_1.mediaOverlayURLPath + "?" + epub_1.mediaOverlayURLParam + "={path}"; publication.AddLink("application/vnd.syncnarr+json", ["media-overlay"], moURL, true); } } let coverImage; const coverLink = publication.GetCover(); if (coverLink) { coverImage = coverLink.Href; if (coverImage && !UrlUtils_1.isHTTP(coverImage)) { coverImage = absoluteURL(coverImage); } } if (isShow) { let objToSerialize = null; if (reqparams.jsonPath) { switch (reqparams.jsonPath) { case "all": { objToSerialize = publication; break; } case "cover": { objToSerialize = publication.GetCover(); break; } case "mediaoverlays": { try { objToSerialize = await epub_1.getAllMediaOverlays(publication); } catch (err) { debug(err); res.status(500).send("<html><body><p>Internal Server Error</p><p>" + err + "</p></body></html>"); return; } break; } case "spine": { objToSerialize = publication.Spine; break; } case "pagelist": { objToSerialize = publication.PageList; break; } case "landmarks": { objToSerialize = publication.Landmarks; break; } case "links": { objToSerialize = publication.Links; break; } case "resources": { objToSerialize = publication.Resources; break; } case "toc": { objToSerialize = publication.TOC; break; } case "metadata": { objToSerialize = publication.Metadata; break; } default: { objToSerialize = null; } } } else { objToSerialize = publication; } if (!objToSerialize) { objToSerialize = {}; } const jsonObj = serializable_1.TaJsonSerialize(objToSerialize); let validationStr; const doValidate = !reqparams.jsonPath || reqparams.jsonPath === "all"; if (doValidate) { const jsonSchemasRootpath = path.join(process.cwd(), "misc", "json-schema"); const jsonSchemasNames = [ "webpub-manifest/publication", "webpub-manifest/contributor-object", "webpub-manifest/contributor", "webpub-manifest/link", "webpub-manifest/metadata", "webpub-manifest/subcollection", "webpub-manifest/properties", "webpub-manifest/subject", "webpub-manifest/subject-object", "webpub-manifest/extensions/epub/metadata", "webpub-manifest/extensions/epub/subcollections", "webpub-manifest/extensions/epub/properties", "webpub-manifest/extensions/presentation/metadata", "webpub-manifest/extensions/presentation/properties", "webpub-manifest/language-map", "opds/acquisition-object", "opds/catalog-entry", "opds/properties", ]; const validationErrors = json_schema_validate_1.jsonSchemaValidate(jsonSchemasRootpath, jsonSchemasNames, jsonObj); if (validationErrors) { validationStr = ""; for (const err of validationErrors) { debug("JSON Schema validation FAIL."); debug(err); const val = err.jsonPath ? DotProp.get(jsonObj, err.jsonPath) : ""; const valueStr = (typeof val === "string") ? `${val}` : ((val instanceof Array || typeof val === "object") ? `${JSON.stringify(val)}` : ""); debug(valueStr); const title = DotProp.get(jsonObj, "metadata.title"); debug(title); validationStr += `\n"${title}"\n\n${err.ajvMessage}: ${valueStr}\n\n'${(_a = err.ajvDataPath) === null || _a === void 0 ? void 0 : _a.replace(/^\./, "")}' (${err.ajvSchemaPath})\n\n`; } } } absolutizeURLs(jsonObj); let jsonPretty = jsonMarkup(jsonObj, css2json(jsonStyle)); const regex = new RegExp(">" + rootUrl + "/([^<]+</a>)", "g"); jsonPretty = jsonPretty.replace(regex, ">$1"); jsonPretty = jsonPretty.replace(/>manifest.json<\/a>/, ">" + rootUrl + "/manifest.json</a>"); res.status(200).send("<html>" + "<head><script type=\"application/ld+json\" href=\"" + manifestURL + "\"></script></head>" + "<body>" + "<h1>" + path.basename(pathBase64Str) + "</h1>" + (coverImage ? "<a href=\"" + coverImage + "\"><div style=\"width: 400px;\"><img src=\"" + coverImage + "\" alt=\"\" style=\"display: block; width: 100%; height: auto;\"/></div></a>" : "") + "<hr><p><pre>" + jsonPretty + "</pre></p>" + (doValidate ? (validationStr ? ("<hr><p><pre>" + validationStr + "</pre></p>") : ("<hr><p>JSON SCHEMA OK.</p>")) : "") + "</body></html>"); } else { server.setResponseCORS(res); res.set("Content-Type", `${contentType}; charset=utf-8`); const publicationJsonObj = serializable_1.TaJsonSerialize(publication); if (isCanonical) { if (publicationJsonObj.links) { delete publicationJsonObj.links; } } const publicationJsonStr = isCanonical ? global.JSON.stringify(JsonUtils_1.sortObject(publicationJsonObj), null, "") : global.JSON.stringify(publicationJsonObj, null, " "); const checkSum = crypto.createHash("sha256"); checkSum.update(publicationJsonStr); const hash = checkSum.digest("hex"); const match = req.header("If-None-Match"); if (match === hash) { debug("manifest.json cache"); res.status(304); res.end(); return; } res.setHeader("ETag", hash); const links = getPreFetchResources(publication); if (links && links.length) { let n = 0; let prefetch = ""; for (const l of links) { n++; if (n > server.maxPrefetchLinks) { break; } const href = absoluteURL(l.Href); prefetch += "<" + href + ">;" + "rel=prefetch,"; } res.setHeader("Link", prefetch); } res.status(200); if (isHead) { res.end(); } else { res.send(publicationJsonStr); } } }); routerPathBase64.use("/:" + request_ext_1._pathBase64 + "/manifest.json", routerManifestJson); } exports.serverManifestJson = serverManifestJson; function getPreFetchResources(publication) { const links = []; if (publication.Resources) { const mediaTypes = ["text/css", "text/javascript", "application/javascript", "application/vnd.ms-opentype", "font/otf", "application/font-sfnt", "font/ttf", "application/font-sfnt", "font/woff", "application/font-woff", "font/woff2"]; for (const mediaType of mediaTypes) { for (const link of publication.Resources) { if (link.TypeLink === mediaType) { links.push(link); } } } } return links; } //# sourceMappingURL=server-manifestjson.js.map