UNPKG

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