1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.serverManifestJson = void 0;
|
4 | var tslib_1 = require("tslib");
|
5 | var crypto = require("crypto");
|
6 | var css2json = require("css2json");
|
7 | var debug_ = require("debug");
|
8 | var DotProp = require("dot-prop");
|
9 | var express = require("express");
|
10 | var jsonMarkup = require("json-markup");
|
11 | var path = require("path");
|
12 | var serializable_1 = require("r2-lcp-js/dist/es5/src/serializable");
|
13 | var epub_1 = require("r2-shared-js/dist/es5/src/parser/epub");
|
14 | var UrlUtils_1 = require("r2-utils-js/dist/es5/src/_utils/http/UrlUtils");
|
15 | var JsonUtils_1 = require("r2-utils-js/dist/es5/src/_utils/JsonUtils");
|
16 | var json_schema_validate_1 = require("../utils/json-schema-validate");
|
17 | var request_ext_1 = require("./request-ext");
|
18 | var debug = debug_("r2:streamer#http/server-manifestjson");
|
19 | function 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 | }
|
353 | exports.serverManifestJson = serverManifestJson;
|
354 | function 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 |
|
\ | No newline at end of file |