1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.serverOPDS_local_feed = exports.serverOPDS_local_feed_PATH_ = exports.serverOPDS_local_feed_PATH = void 0;
|
4 | var crypto = require("crypto");
|
5 | var css2json = require("css2json");
|
6 | var debug_ = require("debug");
|
7 | var DotProp = require("dot-prop");
|
8 | var express = require("express");
|
9 | var jsonMarkup = require("json-markup");
|
10 | var path = require("path");
|
11 | var serializable_1 = require("r2-lcp-js/dist/es5/src/serializable");
|
12 | var opds2_link_1 = require("r2-opds-js/dist/es5/src/opds/opds2/opds2-link");
|
13 | var UrlUtils_1 = require("r2-utils-js/dist/es5/src/_utils/http/UrlUtils");
|
14 | var JsonUtils_1 = require("r2-utils-js/dist/es5/src/_utils/JsonUtils");
|
15 | var json_schema_validate_1 = require("../utils/json-schema-validate");
|
16 | var request_ext_1 = require("./request-ext");
|
17 | var server_trailing_slash_redirect_1 = require("./server-trailing-slash-redirect");
|
18 | var debug = debug_("r2:streamer#http/server-opds-local-feed");
|
19 | exports.serverOPDS_local_feed_PATH = "/opds2";
|
20 | exports.serverOPDS_local_feed_PATH_ = "/publications.json";
|
21 | function serverOPDS_local_feed(server, topRouter) {
|
22 | 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";
|
23 | var routerOPDS_local_feed = express.Router({ strict: false });
|
24 | routerOPDS_local_feed.get(["/", "/" + request_ext_1._show + "/:" + request_ext_1._jsonPath + "?"], function (req, res) {
|
25 | var reqparams = req.params;
|
26 | var isShow = req.url.indexOf("/show") >= 0 || req.query.show;
|
27 | if (!reqparams.jsonPath && req.query.show) {
|
28 | reqparams.jsonPath = req.query.show;
|
29 | }
|
30 | var isCanonical = req.query.canonical &&
|
31 | req.query.canonical === "true";
|
32 | var isSecureHttp = req.secure ||
|
33 | req.protocol === "https" ||
|
34 | req.get("X-Forwarded-Proto") === "https";
|
35 | var rootUrl = (isSecureHttp ? "https://" : "http://")
|
36 | + req.headers.host;
|
37 | var selfURL = rootUrl + exports.serverOPDS_local_feed_PATH + exports.serverOPDS_local_feed_PATH_;
|
38 | var feed = server.publicationsOPDS();
|
39 | if (!feed) {
|
40 | var err = "Publications OPDS2 feed not available yet, try again later.";
|
41 | debug(err);
|
42 | res.status(503).send("<html><body><p>Resource temporarily unavailable</p><p>"
|
43 | + err + "</p></body></html>");
|
44 | return;
|
45 | }
|
46 | if (!feed.findFirstLinkByRel("self")) {
|
47 | feed.Links = [];
|
48 | var selfLink = new opds2_link_1.OPDSLink();
|
49 | selfLink.Href = selfURL;
|
50 | selfLink.TypeLink = "application/opds+json";
|
51 | selfLink.AddRel("self");
|
52 | feed.Links.push(selfLink);
|
53 | }
|
54 | function absoluteURL(href) {
|
55 | return rootUrl + "/pub/" + href;
|
56 | }
|
57 | function absolutizeURLs(jsonObj) {
|
58 | JsonUtils_1.traverseJsonObjects(jsonObj, function (obj) {
|
59 | if (obj.href && typeof obj.href === "string") {
|
60 | if (!UrlUtils_1.isHTTP(obj.href)) {
|
61 | obj.href = absoluteURL(obj.href);
|
62 | }
|
63 | if (isShow &&
|
64 | (obj.type === "application/webpub+json"
|
65 | || obj.type === "application/audiobook+json"
|
66 | || obj.type === "application/divina+json") &&
|
67 | obj.rel === "http://opds-spec.org/acquisition" &&
|
68 | obj.href.endsWith("/manifest.json")) {
|
69 | obj.href += "/show";
|
70 | }
|
71 | }
|
72 | });
|
73 | }
|
74 | if (isShow) {
|
75 | var objToSerialize = null;
|
76 | if (reqparams.jsonPath) {
|
77 | switch (reqparams.jsonPath) {
|
78 | case "all": {
|
79 | objToSerialize = feed;
|
80 | break;
|
81 | }
|
82 | case "metadata": {
|
83 | objToSerialize = feed.Metadata;
|
84 | break;
|
85 | }
|
86 | case "links": {
|
87 | objToSerialize = feed.Links;
|
88 | break;
|
89 | }
|
90 | case "publications": {
|
91 | objToSerialize = feed.Publications;
|
92 | break;
|
93 | }
|
94 | default: {
|
95 | objToSerialize = null;
|
96 | }
|
97 | }
|
98 | }
|
99 | else {
|
100 | objToSerialize = feed;
|
101 | }
|
102 | if (!objToSerialize) {
|
103 | objToSerialize = {};
|
104 | }
|
105 | var jsonObj = serializable_1.TaJsonSerialize(objToSerialize);
|
106 | var validationStr = void 0;
|
107 | var doValidate = !reqparams.jsonPath || reqparams.jsonPath === "all";
|
108 | if (doValidate) {
|
109 | var jsonSchemasRootpath = path.join(process.cwd(), "misc", "json-schema");
|
110 | var jsonSchemasNames = [
|
111 | "opds/feed",
|
112 | "opds/publication",
|
113 | "opds/acquisition-object",
|
114 | "opds/catalog-entry",
|
115 | "opds/feed-metadata",
|
116 | "opds/properties",
|
117 | "webpub-manifest/publication",
|
118 | "webpub-manifest/contributor-object",
|
119 | "webpub-manifest/contributor",
|
120 | "webpub-manifest/link",
|
121 | "webpub-manifest/metadata",
|
122 | "webpub-manifest/subcollection",
|
123 | "webpub-manifest/properties",
|
124 | "webpub-manifest/subject",
|
125 | "webpub-manifest/subject-object",
|
126 | "webpub-manifest/extensions/epub/metadata",
|
127 | "webpub-manifest/extensions/epub/subcollections",
|
128 | "webpub-manifest/extensions/epub/properties",
|
129 | "webpub-manifest/extensions/presentation/metadata",
|
130 | "webpub-manifest/extensions/presentation/properties",
|
131 | "webpub-manifest/language-map",
|
132 | ];
|
133 | var validationErrors = json_schema_validate_1.jsonSchemaValidate(jsonSchemasRootpath, jsonSchemasNames, jsonObj);
|
134 | if (validationErrors) {
|
135 | validationStr = "";
|
136 | for (var _i = 0, validationErrors_1 = validationErrors; _i < validationErrors_1.length; _i++) {
|
137 | var err = validationErrors_1[_i];
|
138 | debug("JSON Schema validation FAIL.");
|
139 | debug(err);
|
140 | var val = DotProp.get(jsonObj, err.jsonPath);
|
141 | var valueStr = (typeof val === "string") ?
|
142 | "" + val :
|
143 | ((val instanceof Array || typeof val === "object") ?
|
144 | "" + JSON.stringify(val) :
|
145 | "");
|
146 | debug(valueStr);
|
147 | var title = "";
|
148 | var pubIndex = "";
|
149 | if (/^publications\.[0-9]+/.test(err.jsonPath)) {
|
150 | var jsonPubTitlePath = err.jsonPath.replace(/^(publications\.[0-9]+).*/, "$1.metadata.title");
|
151 | debug(jsonPubTitlePath);
|
152 | title = DotProp.get(jsonObj, jsonPubTitlePath);
|
153 | debug(title);
|
154 | pubIndex = err.jsonPath.replace(/^publications\.([0-9]+).*/, "$1");
|
155 | debug(pubIndex);
|
156 | }
|
157 | validationStr +=
|
158 | "\n___________INDEX___________ #" + pubIndex + " \"" + title + "\"\n\n" + err.ajvMessage + ": " + valueStr + "\n\n'" + err.ajvDataPath.replace(/^\./, "") + "' (" + err.ajvSchemaPath + ")\n\n";
|
159 | }
|
160 | }
|
161 | }
|
162 | absolutizeURLs(jsonObj);
|
163 | if (jsonObj.publications && jsonObj.publications.length) {
|
164 | var i_1 = 0;
|
165 | jsonObj.publications.forEach(function (pub) {
|
166 | pub.___________INDEX___________ = i_1++;
|
167 | });
|
168 | }
|
169 | var jsonPretty = jsonMarkup(jsonObj, css2json(jsonStyle));
|
170 | res.status(200).send("<html><body>" +
|
171 | "<h1>OPDS2 JSON feed</h1>" +
|
172 | "<hr><p><pre>" + jsonPretty + "</pre></p>" +
|
173 | (doValidate ? (validationStr ? ("<hr><p><pre>" + validationStr + "</pre></p>") : ("<hr><p>JSON SCHEMA OK.</p>")) : "") +
|
174 | "</body></html>");
|
175 | }
|
176 | else {
|
177 | server.setResponseCORS(res);
|
178 | res.set("Content-Type", "application/opds+json; charset=utf-8");
|
179 | var publicationsJsonObj = serializable_1.TaJsonSerialize(feed);
|
180 | absolutizeURLs(publicationsJsonObj);
|
181 | var publicationsJsonStr = isCanonical ?
|
182 | global.JSON.stringify(JsonUtils_1.sortObject(publicationsJsonObj), null, "") :
|
183 | global.JSON.stringify(publicationsJsonObj, null, " ");
|
184 | var checkSum = crypto.createHash("sha256");
|
185 | checkSum.update(publicationsJsonStr);
|
186 | var hash = checkSum.digest("hex");
|
187 | var match = req.header("If-None-Match");
|
188 | if (match === hash) {
|
189 | debug("opds2 publications.json cache");
|
190 | res.status(304);
|
191 | res.end();
|
192 | return;
|
193 | }
|
194 | res.setHeader("ETag", hash);
|
195 | res.status(200).send(publicationsJsonStr);
|
196 | }
|
197 | });
|
198 | var routerOPDS_local_feed_ = express.Router({ strict: false });
|
199 | routerOPDS_local_feed_.use(server_trailing_slash_redirect_1.trailingSlashRedirect);
|
200 | routerOPDS_local_feed_.get("/", function (req, res) {
|
201 | var i = req.originalUrl.indexOf("?");
|
202 | var pathWithoutQuery = req.originalUrl;
|
203 | if (i >= 0) {
|
204 | pathWithoutQuery = pathWithoutQuery.substr(0, i);
|
205 | }
|
206 | var redirect = pathWithoutQuery +
|
207 | exports.serverOPDS_local_feed_PATH_ + "/show";
|
208 | redirect = redirect.replace("//", "/");
|
209 | if (i >= 0) {
|
210 | redirect += req.originalUrl.substr(i);
|
211 | }
|
212 | debug("REDIRECT: " + req.originalUrl + " ==> " + redirect);
|
213 | res.redirect(301, redirect);
|
214 | });
|
215 | routerOPDS_local_feed_.use(exports.serverOPDS_local_feed_PATH_, routerOPDS_local_feed);
|
216 | topRouter.use(exports.serverOPDS_local_feed_PATH, routerOPDS_local_feed_);
|
217 | }
|
218 | exports.serverOPDS_local_feed = serverOPDS_local_feed;
|
219 |
|
\ | No newline at end of file |