r2-streamer-js
Version:
Readium 2 'streamer' for NodeJS (TypeScript)
399 lines • 20.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.serverManifestJson = serverManifestJson;
var tslib_1 = require("tslib");
var crypto = require("crypto");
var css2json = require("css2json");
var debug_ = require("debug");
var DotProp = require("dot-prop");
var express = require("express");
var jsonMarkup = require("json-markup");
var path = require("path");
var serializable_1 = require("r2-lcp-js/dist/es5/src/serializable");
var epub_1 = require("r2-shared-js/dist/es5/src/parser/epub");
var UrlUtils_1 = require("r2-utils-js/dist/es5/src/_utils/http/UrlUtils");
var JsonUtils_1 = require("r2-utils-js/dist/es5/src/_utils/JsonUtils");
var json_schema_validate_1 = require("../utils/json-schema-validate");
var request_ext_1 = require("./request-ext");
var url_signed_expiry_1 = require("./url-signed-expiry");
var debug = debug_("r2:streamer#http/server-manifestjson");
function serverManifestJson(server, routerPathBase64) {
var _this = this;
var jsonStyle = "\n.json-markup {\n line-height: 17px;\n font-size: 13px;\n font-family: monospace;\n white-space: pre;\n}\n.json-markup-key {\n font-weight: bold;\n}\n.json-markup-bool {\n color: firebrick;\n}\n.json-markup-string {\n color: green;\n}\n.json-markup-null {\n color: gray;\n}\n.json-markup-number {\n color: blue;\n}\n";
var routerManifestJson = express.Router({ strict: false });
routerManifestJson.get(["/", "/" + request_ext_1._show + "{/:" + request_ext_1._jsonPath + "}"], function (req, res) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
function absoluteURL(href) {
return rootUrl + "/" + href;
}
function absolutizeURLs(jsonObj) {
(0, JsonUtils_1.traverseJsonObjects)(jsonObj, function (obj) {
if (obj.href && typeof obj.href === "string"
&& !(0, UrlUtils_1.isHTTP)(obj.href)) {
obj.href = absoluteURL(obj.href);
}
if (obj["media-overlay"] && typeof obj["media-overlay"] === "string"
&& !(0, UrlUtils_1.isHTTP)(obj["media-overlay"])) {
obj["media-overlay"] = absoluteURL(obj["media-overlay"]);
}
});
}
var reqparams, isShow, isHead, isCanonical, isSecureHttp, pathBase64Str, publication, err_1, lcpPass, err_2, errMsg, rootUrl, manifestURL, contentType, selfLink, hasMO, link, moLink, moURL, objToSerialize, _a, err_3, jsonObj, validationStr, doValidate, jsonSchemasRootpath, jsonSchemasNames, validationErrors, _i, validationErrors_1, err, val, valueStr, title, jsonPretty, regex, coverImage, findCover, publicationJsonObj, publicationJsonStr, checkSum, hash, match, links, n, prefetch, _b, links_1, l, href;
var _c;
return tslib_1.__generator(this, function (_d) {
switch (_d.label) {
case 0:
reqparams = req.params;
if (!reqparams.pathBase64) {
reqparams.pathBase64 = req.pathBase64;
}
if (!reqparams.lcpPass64) {
reqparams.lcpPass64 = req.lcpPass64;
}
isShow = req.url.indexOf("/show") >= 0 || req.query.show;
if (!reqparams.jsonPath && req.query.show) {
reqparams.jsonPath = req.query.show;
}
isHead = req.method.toLowerCase() === "head";
if (isHead) {
debug("HEAD !!!!!!!!!!!!!!!!!!!");
}
isCanonical = req.query.canonical &&
req.query.canonical === "true";
isSecureHttp = req.secure ||
req.protocol === "https" ||
req.get("X-Forwarded-Proto") === "https";
pathBase64Str = Buffer.from(reqparams.pathBase64, "base64").toString("utf8");
_d.label = 1;
case 1:
_d.trys.push([1, 3, , 4]);
return [4, server.loadOrGetCachedPublication(pathBase64Str)];
case 2:
publication = _d.sent();
return [3, 4];
case 3:
err_1 = _d.sent();
debug(err_1);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err_1 + "</p></body></html>");
return [2];
case 4:
if (!(reqparams.lcpPass64 && !server.disableDecryption)) return [3, 8];
lcpPass = Buffer.from(reqparams.lcpPass64, "base64").toString("utf8");
if (!publication.LCP) return [3, 8];
_d.label = 5;
case 5:
_d.trys.push([5, 7, , 8]);
return [4, publication.LCP.tryUserKeys([lcpPass])];
case 6:
_d.sent();
return [3, 8];
case 7:
err_2 = _d.sent();
publication.LCP.ContentKey = undefined;
debug(err_2);
errMsg = "FAIL publication.LCP.tryUserKeys(): " + err_2;
debug(errMsg);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ errMsg + "</p></body></html>");
return [2];
case 8:
rootUrl = (isSecureHttp ? "https://" : "http://")
+ req.headers.host + "/pub/"
+ (reqparams.lcpPass64 ?
(server.lcpBeginToken + (0, UrlUtils_1.encodeURIComponent_RFC3986)(reqparams.lcpPass64) + server.lcpEndToken) :
"")
+ (0, UrlUtils_1.encodeURIComponent_RFC3986)(reqparams.pathBase64);
manifestURL = rootUrl + "/" + "manifest.json";
contentType = (publication.Metadata && publication.Metadata.RDFType &&
/https?:\/\/schema\.org\/Audiobook$/.test(publication.Metadata.RDFType)) ?
"application/audiobook+json" : ((publication.Metadata && publication.Metadata.RDFType &&
(/https?:\/\/schema\.org\/ComicStory$/.test(publication.Metadata.RDFType) ||
/https?:\/\/schema\.org\/VisualNarrative$/.test(publication.Metadata.RDFType))) ? "application/divina+json" :
"application/webpub+json");
selfLink = publication.searchLinkByRel("self");
if (!selfLink) {
publication.AddLink(contentType, ["self"], manifestURL, undefined);
}
hasMO = false;
if (publication.Spine) {
link = publication.Spine.find(function (l) {
if (l.Properties && l.Properties.MediaOverlay) {
return true;
}
return false;
});
if (link) {
hasMO = true;
}
}
if (hasMO) {
moLink = publication.searchLinkByRel("media-overlay");
if (!moLink) {
moURL = epub_1.mediaOverlayURLPath +
"?" + epub_1.mediaOverlayURLParam + "={path}";
publication.AddLink("application/vnd.syncnarr+json", ["media-overlay"], moURL, true);
}
}
if (!isShow) return [3, 26];
objToSerialize = null;
if (!reqparams.jsonPath) return [3, 24];
_a = reqparams.jsonPath;
switch (_a) {
case "all": return [3, 9];
case "cover": return [3, 10];
case "mediaoverlays": return [3, 11];
case "spine": return [3, 15];
case "pagelist": return [3, 16];
case "landmarks": return [3, 17];
case "links": return [3, 18];
case "resources": return [3, 19];
case "toc": return [3, 20];
case "metadata": return [3, 21];
}
return [3, 22];
case 9:
{
objToSerialize = publication;
return [3, 23];
}
_d.label = 10;
case 10:
{
objToSerialize = publication.GetCover();
return [3, 23];
}
_d.label = 11;
case 11:
_d.trys.push([11, 13, , 14]);
return [4, (0, epub_1.getAllMediaOverlays)(publication)];
case 12:
objToSerialize = _d.sent();
return [3, 14];
case 13:
err_3 = _d.sent();
debug(err_3);
res.status(500).send("<html><body><p>Internal Server Error</p><p>"
+ err_3 + "</p></body></html>");
return [2];
case 14: return [3, 23];
case 15:
{
objToSerialize = publication.Spine;
return [3, 23];
}
_d.label = 16;
case 16:
{
objToSerialize = publication.PageList;
return [3, 23];
}
_d.label = 17;
case 17:
{
objToSerialize = publication.Landmarks;
return [3, 23];
}
_d.label = 18;
case 18:
{
objToSerialize = publication.Links;
return [3, 23];
}
_d.label = 19;
case 19:
{
objToSerialize = publication.Resources;
return [3, 23];
}
_d.label = 20;
case 20:
{
objToSerialize = publication.TOC;
return [3, 23];
}
_d.label = 21;
case 21:
{
objToSerialize = publication.Metadata;
return [3, 23];
}
_d.label = 22;
case 22:
{
objToSerialize = null;
}
_d.label = 23;
case 23: return [3, 25];
case 24:
objToSerialize = publication;
_d.label = 25;
case 25:
if (!objToSerialize) {
objToSerialize = {};
}
jsonObj = (0, serializable_1.TaJsonSerialize)(objToSerialize);
if (server.enableSignedExpiry) {
(0, url_signed_expiry_1.signExpiringResourceURLs)(rootUrl, pathBase64Str, jsonObj);
}
validationStr = void 0;
doValidate = !reqparams.jsonPath || reqparams.jsonPath === "all";
if (doValidate) {
jsonSchemasRootpath = path.join(process.cwd(), "misc", "json-schema");
jsonSchemasNames = [
"webpub-manifest/publication",
"webpub-manifest/contributor-object",
"webpub-manifest/contributor",
"webpub-manifest/link",
"webpub-manifest/a11y",
"webpub-manifest/metadata",
"webpub-manifest/subcollection",
"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",
];
validationErrors = (0, json_schema_validate_1.jsonSchemaValidate)(jsonSchemasRootpath, jsonSchemasNames, jsonObj);
if (validationErrors) {
validationStr = "";
for (_i = 0, validationErrors_1 = validationErrors; _i < validationErrors_1.length; _i++) {
err = validationErrors_1[_i];
debug("JSON Schema validation FAIL.");
debug(err);
val = err.jsonPath ? DotProp.get(jsonObj, err.jsonPath) : "";
valueStr = (typeof val === "string") ?
"".concat(val) :
((val instanceof Array || typeof val === "object") ?
"".concat(JSON.stringify(val)) :
"");
debug(valueStr);
title = DotProp.get(jsonObj, "metadata.title");
debug(JSON.stringify(title));
validationStr +=
"\n\"".concat(JSON.stringify(title), "\"\n\n").concat(err.ajvMessage, ": ").concat(valueStr, "\n\n'").concat((_c = err.ajvDataPath) === null || _c === void 0 ? void 0 : _c.replace(/^\./, ""), "' (").concat(err.ajvSchemaPath, ")\n\n");
}
}
}
absolutizeURLs(jsonObj);
jsonPretty = jsonMarkup(jsonObj, css2json(jsonStyle));
regex = new RegExp(">" + rootUrl + "/([^<]+</a>)", "g");
jsonPretty = jsonPretty.replace(regex, ">$1");
jsonPretty = jsonPretty.replace(/>manifest.json<\/a>/, ">" + rootUrl + "/manifest.json</a>");
coverImage = void 0;
findCover = function (arr) {
var coverHref;
for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) {
var link = arr_1[_i];
if (link && typeof link === "object" && !Array.isArray(link) && link.rel === "cover" && link.href && typeof link.href === "string") {
coverHref = link.href;
break;
}
}
return coverHref;
};
if (jsonObj.resources && Array.isArray(jsonObj.resources)) {
coverImage = findCover(jsonObj.resources);
}
if (!coverImage) {
if (jsonObj.links && Array.isArray(jsonObj.links)) {
coverImage = findCover(jsonObj.links);
}
}
if (!coverImage) {
if (jsonObj.readingOrder && Array.isArray(jsonObj.readingOrder)) {
coverImage = findCover(jsonObj.readingOrder);
}
}
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>");
return [3, 27];
case 26:
server.setResponseCORS(res);
res.set("Content-Type", "".concat(contentType, "; charset=utf-8"));
publicationJsonObj = (0, serializable_1.TaJsonSerialize)(publication);
if (server.enableSignedExpiry) {
(0, url_signed_expiry_1.signExpiringResourceURLs)(rootUrl, pathBase64Str, publicationJsonObj);
}
if (isCanonical) {
if (publicationJsonObj.links) {
delete publicationJsonObj.links;
}
}
publicationJsonStr = isCanonical ?
global.JSON.stringify((0, JsonUtils_1.sortObject)(publicationJsonObj), null, "") :
global.JSON.stringify(publicationJsonObj, null, " ");
checkSum = crypto.createHash("sha256");
checkSum.update(publicationJsonStr);
hash = checkSum.digest("hex");
match = req.header("If-None-Match");
if (match === hash) {
debug("manifest.json cache");
res.status(304);
res.end();
return [2];
}
res.setHeader("ETag", hash);
links = getPreFetchResources(publication);
if (links && links.length) {
n = 0;
prefetch = "";
for (_b = 0, links_1 = links; _b < links_1.length; _b++) {
l = links_1[_b];
n++;
if (n > server.maxPrefetchLinks) {
break;
}
href = absoluteURL(l.Href);
prefetch += "<" + href + ">;" + "rel=prefetch,";
}
res.setHeader("Link", prefetch);
}
res.status(200);
if (isHead) {
res.end();
}
else {
res.send(publicationJsonStr);
}
_d.label = 27;
case 27: return [2];
}
});
}); });
routerPathBase64.use("/:" + request_ext_1._pathBase64 + "/manifest.json", routerManifestJson);
}
function getPreFetchResources(publication) {
var links = [];
if (publication.Resources) {
var 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 (var _i = 0, mediaTypes_1 = mediaTypes; _i < mediaTypes_1.length; _i++) {
var mediaType = mediaTypes_1[_i];
for (var _a = 0, _b = publication.Resources; _a < _b.length; _a++) {
var link = _b[_a];
if (link.TypeLink === mediaType) {
links.push(link);
}
}
}
}
return links;
}
//# sourceMappingURL=server-manifestjson.js.map