UNPKG

19.2 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.serverManifestJson = void 0;
4var tslib_1 = require("tslib");
5var crypto = require("crypto");
6var css2json = require("css2json");
7var debug_ = require("debug");
8var DotProp = require("dot-prop");
9var express = require("express");
10var jsonMarkup = require("json-markup");
11var path = require("path");
12var serializable_1 = require("r2-lcp-js/dist/es5/src/serializable");
13var epub_1 = require("r2-shared-js/dist/es5/src/parser/epub");
14var UrlUtils_1 = require("r2-utils-js/dist/es5/src/_utils/http/UrlUtils");
15var JsonUtils_1 = require("r2-utils-js/dist/es5/src/_utils/JsonUtils");
16var json_schema_validate_1 = require("../utils/json-schema-validate");
17var request_ext_1 = require("./request-ext");
18var debug = debug_("r2:streamer#http/server-manifestjson");
19function serverManifestJson(server, routerPathBase64) {
20 var _this = this;
21 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";
22 var routerManifestJson = express.Router({ strict: false });
23 routerManifestJson.get(["/", "/" + request_ext_1._show + "/:" + request_ext_1._jsonPath + "?"], function (req, res) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
24 function absoluteURL(href) {
25 return rootUrl + "/" + href;
26 }
27 function absolutizeURLs(jsonObj) {
28 JsonUtils_1.traverseJsonObjects(jsonObj, function (obj) {
29 if (obj.href && typeof obj.href === "string"
30 && !UrlUtils_1.isHTTP(obj.href)) {
31 obj.href = absoluteURL(obj.href);
32 }
33 if (obj["media-overlay"] && typeof obj["media-overlay"] === "string"
34 && !UrlUtils_1.isHTTP(obj["media-overlay"])) {
35 obj["media-overlay"] = absoluteURL(obj["media-overlay"]);
36 }
37 });
38 }
39 var reqparams, isShow, isHead, isCanonical, isSecureHttp, pathBase64Str, publication, err_1, lcpPass, err_2, errMsg, rootUrl, manifestURL, contentType, selfLink, hasMO, link, moLink, moURL, coverImage, coverLink, objToSerialize, _a, err_3, jsonObj, validationStr, doValidate, jsonSchemasRootpath, jsonSchemasNames, validationErrors, _i, validationErrors_1, err, val, valueStr, title, jsonPretty, regex, publicationJsonObj, publicationJsonStr, checkSum, hash, match, links, n, prefetch, _b, links_1, l, href;
40 return tslib_1.__generator(this, function (_c) {
41 switch (_c.label) {
42 case 0:
43 reqparams = req.params;
44 if (!reqparams.pathBase64) {
45 reqparams.pathBase64 = req.pathBase64;
46 }
47 if (!reqparams.lcpPass64) {
48 reqparams.lcpPass64 = req.lcpPass64;
49 }
50 isShow = req.url.indexOf("/show") >= 0 || req.query.show;
51 if (!reqparams.jsonPath && req.query.show) {
52 reqparams.jsonPath = req.query.show;
53 }
54 isHead = req.method.toLowerCase() === "head";
55 if (isHead) {
56 debug("HEAD !!!!!!!!!!!!!!!!!!!");
57 }
58 isCanonical = req.query.canonical &&
59 req.query.canonical === "true";
60 isSecureHttp = req.secure ||
61 req.protocol === "https" ||
62 req.get("X-Forwarded-Proto") === "https";
63 pathBase64Str = Buffer.from(reqparams.pathBase64, "base64").toString("utf8");
64 _c.label = 1;
65 case 1:
66 _c.trys.push([1, 3, , 4]);
67 return [4, server.loadOrGetCachedPublication(pathBase64Str)];
68 case 2:
69 publication = _c.sent();
70 return [3, 4];
71 case 3:
72 err_1 = _c.sent();
73 debug(err_1);
74 res.status(500).send("<html><body><p>Internal Server Error</p><p>"
75 + err_1 + "</p></body></html>");
76 return [2];
77 case 4:
78 if (!(reqparams.lcpPass64 && !server.disableDecryption)) return [3, 8];
79 lcpPass = Buffer.from(reqparams.lcpPass64, "base64").toString("utf8");
80 if (!publication.LCP) return [3, 8];
81 _c.label = 5;
82 case 5:
83 _c.trys.push([5, 7, , 8]);
84 return [4, publication.LCP.tryUserKeys([lcpPass])];
85 case 6:
86 _c.sent();
87 return [3, 8];
88 case 7:
89 err_2 = _c.sent();
90 publication.LCP.ContentKey = undefined;
91 debug(err_2);
92 errMsg = "FAIL publication.LCP.tryUserKeys(): " + err_2;
93 debug(errMsg);
94 res.status(500).send("<html><body><p>Internal Server Error</p><p>"
95 + errMsg + "</p></body></html>");
96 return [2];
97 case 8:
98 rootUrl = (isSecureHttp ? "https://" : "http://")
99 + req.headers.host + "/pub/"
100 + (reqparams.lcpPass64 ?
101 (server.lcpBeginToken + UrlUtils_1.encodeURIComponent_RFC3986(reqparams.lcpPass64) + server.lcpEndToken) :
102 "")
103 + UrlUtils_1.encodeURIComponent_RFC3986(reqparams.pathBase64);
104 manifestURL = rootUrl + "/" + "manifest.json";
105 contentType = (publication.Metadata && publication.Metadata.RDFType &&
106 /http[s]?:\/\/schema\.org\/Audiobook$/.test(publication.Metadata.RDFType)) ?
107 "application/audiobook+json" : ((publication.Metadata && publication.Metadata.RDFType &&
108 (/http[s]?:\/\/schema\.org\/ComicStory$/.test(publication.Metadata.RDFType) ||
109 /http[s]?:\/\/schema\.org\/VisualNarrative$/.test(publication.Metadata.RDFType))) ? "application/divina+json" :
110 "application/webpub+json");
111 selfLink = publication.searchLinkByRel("self");
112 if (!selfLink) {
113 publication.AddLink(contentType, ["self"], manifestURL, undefined);
114 }
115 hasMO = false;
116 if (publication.Spine) {
117 link = publication.Spine.find(function (l) {
118 if (l.Properties && l.Properties.MediaOverlay) {
119 return true;
120 }
121 return false;
122 });
123 if (link) {
124 hasMO = true;
125 }
126 }
127 if (hasMO) {
128 moLink = publication.searchLinkByRel("media-overlay");
129 if (!moLink) {
130 moURL = epub_1.mediaOverlayURLPath +
131 "?" + epub_1.mediaOverlayURLParam + "={path}";
132 publication.AddLink("application/vnd.syncnarr+json", ["media-overlay"], moURL, true);
133 }
134 }
135 coverLink = publication.GetCover();
136 if (coverLink) {
137 coverImage = coverLink.Href;
138 if (coverImage && !UrlUtils_1.isHTTP(coverImage)) {
139 coverImage = absoluteURL(coverImage);
140 }
141 }
142 if (!isShow) return [3, 26];
143 objToSerialize = null;
144 if (!reqparams.jsonPath) return [3, 24];
145 _a = reqparams.jsonPath;
146 switch (_a) {
147 case "all": return [3, 9];
148 case "cover": return [3, 10];
149 case "mediaoverlays": return [3, 11];
150 case "spine": return [3, 15];
151 case "pagelist": return [3, 16];
152 case "landmarks": return [3, 17];
153 case "links": return [3, 18];
154 case "resources": return [3, 19];
155 case "toc": return [3, 20];
156 case "metadata": return [3, 21];
157 }
158 return [3, 22];
159 case 9:
160 {
161 objToSerialize = publication;
162 return [3, 23];
163 }
164 _c.label = 10;
165 case 10:
166 {
167 objToSerialize = publication.GetCover();
168 return [3, 23];
169 }
170 _c.label = 11;
171 case 11:
172 _c.trys.push([11, 13, , 14]);
173 return [4, epub_1.getAllMediaOverlays(publication)];
174 case 12:
175 objToSerialize = _c.sent();
176 return [3, 14];
177 case 13:
178 err_3 = _c.sent();
179 debug(err_3);
180 res.status(500).send("<html><body><p>Internal Server Error</p><p>"
181 + err_3 + "</p></body></html>");
182 return [2];
183 case 14: return [3, 23];
184 case 15:
185 {
186 objToSerialize = publication.Spine;
187 return [3, 23];
188 }
189 _c.label = 16;
190 case 16:
191 {
192 objToSerialize = publication.PageList;
193 return [3, 23];
194 }
195 _c.label = 17;
196 case 17:
197 {
198 objToSerialize = publication.Landmarks;
199 return [3, 23];
200 }
201 _c.label = 18;
202 case 18:
203 {
204 objToSerialize = publication.Links;
205 return [3, 23];
206 }
207 _c.label = 19;
208 case 19:
209 {
210 objToSerialize = publication.Resources;
211 return [3, 23];
212 }
213 _c.label = 20;
214 case 20:
215 {
216 objToSerialize = publication.TOC;
217 return [3, 23];
218 }
219 _c.label = 21;
220 case 21:
221 {
222 objToSerialize = publication.Metadata;
223 return [3, 23];
224 }
225 _c.label = 22;
226 case 22:
227 {
228 objToSerialize = null;
229 }
230 _c.label = 23;
231 case 23: return [3, 25];
232 case 24:
233 objToSerialize = publication;
234 _c.label = 25;
235 case 25:
236 if (!objToSerialize) {
237 objToSerialize = {};
238 }
239 jsonObj = serializable_1.TaJsonSerialize(objToSerialize);
240 validationStr = void 0;
241 doValidate = !reqparams.jsonPath || reqparams.jsonPath === "all";
242 if (doValidate) {
243 jsonSchemasRootpath = path.join(process.cwd(), "misc", "json-schema");
244 jsonSchemasNames = [
245 "webpub-manifest/publication",
246 "webpub-manifest/contributor-object",
247 "webpub-manifest/contributor",
248 "webpub-manifest/link",
249 "webpub-manifest/metadata",
250 "webpub-manifest/subcollection",
251 "webpub-manifest/properties",
252 "webpub-manifest/subject",
253 "webpub-manifest/subject-object",
254 "webpub-manifest/extensions/epub/metadata",
255 "webpub-manifest/extensions/epub/subcollections",
256 "webpub-manifest/extensions/epub/properties",
257 "webpub-manifest/extensions/presentation/metadata",
258 "webpub-manifest/extensions/presentation/properties",
259 "webpub-manifest/language-map",
260 "opds/acquisition-object",
261 "opds/catalog-entry",
262 "opds/properties",
263 ];
264 validationErrors = json_schema_validate_1.jsonSchemaValidate(jsonSchemasRootpath, jsonSchemasNames, jsonObj);
265 if (validationErrors) {
266 validationStr = "";
267 for (_i = 0, validationErrors_1 = validationErrors; _i < validationErrors_1.length; _i++) {
268 err = validationErrors_1[_i];
269 debug("JSON Schema validation FAIL.");
270 debug(err);
271 val = DotProp.get(jsonObj, err.jsonPath);
272 valueStr = (typeof val === "string") ?
273 "" + val :
274 ((val instanceof Array || typeof val === "object") ?
275 "" + JSON.stringify(val) :
276 "");
277 debug(valueStr);
278 title = DotProp.get(jsonObj, "metadata.title");
279 debug(title);
280 validationStr +=
281 "\n\"" + title + "\"\n\n" + err.ajvMessage + ": " + valueStr + "\n\n'" + err.ajvDataPath.replace(/^\./, "") + "' (" + err.ajvSchemaPath + ")\n\n";
282 }
283 }
284 }
285 absolutizeURLs(jsonObj);
286 jsonPretty = jsonMarkup(jsonObj, css2json(jsonStyle));
287 regex = new RegExp(">" + rootUrl + "/([^<]+</a>)", "g");
288 jsonPretty = jsonPretty.replace(regex, ">$1");
289 jsonPretty = jsonPretty.replace(/>manifest.json<\/a>/, ">" + rootUrl + "/manifest.json</a>");
290 res.status(200).send("<html>" +
291 "<head><script type=\"application/ld+json\" href=\"" +
292 manifestURL +
293 "\"></script></head>" +
294 "<body>" +
295 "<h1>" + path.basename(pathBase64Str) + "</h1>" +
296 (coverImage ? "<a href=\"" + coverImage + "\"><div style=\"width: 400px;\"><img src=\"" + coverImage + "\" alt=\"\" style=\"display: block; width: 100%; height: auto;\"/></div></a>" : "") +
297 "<hr><p><pre>" + jsonPretty + "</pre></p>" +
298 (doValidate ? (validationStr ? ("<hr><p><pre>" + validationStr + "</pre></p>") : ("<hr><p>JSON SCHEMA OK.</p>")) : "") +
299 "</body></html>");
300 return [3, 27];
301 case 26:
302 server.setResponseCORS(res);
303 res.set("Content-Type", contentType + "; charset=utf-8");
304 publicationJsonObj = serializable_1.TaJsonSerialize(publication);
305 if (isCanonical) {
306 if (publicationJsonObj.links) {
307 delete publicationJsonObj.links;
308 }
309 }
310 publicationJsonStr = isCanonical ?
311 global.JSON.stringify(JsonUtils_1.sortObject(publicationJsonObj), null, "") :
312 global.JSON.stringify(publicationJsonObj, null, " ");
313 checkSum = crypto.createHash("sha256");
314 checkSum.update(publicationJsonStr);
315 hash = checkSum.digest("hex");
316 match = req.header("If-None-Match");
317 if (match === hash) {
318 debug("manifest.json cache");
319 res.status(304);
320 res.end();
321 return [2];
322 }
323 res.setHeader("ETag", hash);
324 links = getPreFetchResources(publication);
325 if (links && links.length) {
326 n = 0;
327 prefetch = "";
328 for (_b = 0, links_1 = links; _b < links_1.length; _b++) {
329 l = links_1[_b];
330 n++;
331 if (n > server.maxPrefetchLinks) {
332 break;
333 }
334 href = absoluteURL(l.Href);
335 prefetch += "<" + href + ">;" + "rel=prefetch,";
336 }
337 res.setHeader("Link", prefetch);
338 }
339 res.status(200);
340 if (isHead) {
341 res.end();
342 }
343 else {
344 res.send(publicationJsonStr);
345 }
346 _c.label = 27;
347 case 27: return [2];
348 }
349 });
350 }); });
351 routerPathBase64.use("/:" + request_ext_1._pathBase64 + "/manifest.json", routerManifestJson);
352}
353exports.serverManifestJson = serverManifestJson;
354function getPreFetchResources(publication) {
355 var links = [];
356 if (publication.Resources) {
357 var mediaTypes = ["text/css",
358 "text/javascript", "application/javascript",
359 "application/vnd.ms-opentype", "font/otf", "application/font-sfnt",
360 "font/ttf", "application/font-sfnt",
361 "font/woff", "application/font-woff", "font/woff2"];
362 for (var _i = 0, mediaTypes_1 = mediaTypes; _i < mediaTypes_1.length; _i++) {
363 var mediaType = mediaTypes_1[_i];
364 for (var _a = 0, _b = publication.Resources; _a < _b.length; _a++) {
365 var link = _b[_a];
366 if (link.TypeLink === mediaType) {
367 links.push(link);
368 }
369 }
370 }
371 }
372 return links;
373}
374//# sourceMappingURL=server-manifestjson.js.map
\No newline at end of file