UNPKG

r2-navigator-js

Version:

Readium 2 'navigator' for NodeJS (TypeScript)

649 lines 25.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.clearSessions = exports.clearDefaultSession = exports.clearWebviewSession = exports.getWebViewSession = exports.clearSession = exports.initSessions = exports.secureSessions = void 0; var tslib_1 = require("tslib"); var debug_ = require("debug"); var electron_1 = require("electron"); var request = require("request"); var requestPromise = require("request-promise-native"); var transformer_1 = require("r2-shared-js/dist/es5/src/transform/transformer"); var transformer_html_1 = require("r2-shared-js/dist/es5/src/transform/transformer-html"); var dom_1 = require("../common/dom"); var sessions_1 = require("../common/sessions"); var url_params_1 = require("../renderer/common/url-params"); var debug = debug_("r2:navigator#electron/main/sessions"); var USE_STREAM_PROTOCOL_INSTEAD_OF_HTTP = true; function promiseAllSettled(promises) { return tslib_1.__awaiter(this, void 0, void 0, function () { var promises_; var _this = this; return tslib_1.__generator(this, function (_a) { promises_ = promises.map(function (promise) { return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) { return [2, promise .then(function (value) { return { status: "fulfilled", value: value, }; }) .catch(function (reason) { return { reason: reason, status: "rejected", }; })]; }); }); }); return [2, Promise.all(promises_)]; }); }); } var _server; function secureSessions(server) { _server = server; var filter = { urls: ["*://*/*"] }; var onHeadersReceivedCB = function (details, callback) { if (!details.url) { callback({}); return; } var serverUrl = server.serverUrl(); if ((serverUrl && details.url.startsWith(serverUrl)) || details.url.startsWith(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL + "://")) { callback({ cancel: false, responseHeaders: tslib_1.__assign(tslib_1.__assign({}, details.responseHeaders), { "Content-Security-Policy": ["default-src 'self' 'unsafe-inline' 'unsafe-eval' data: http: https: ".concat(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL, ": ").concat(serverUrl)] }), }); } else { callback({ cancel: false, responseHeaders: tslib_1.__assign({}, details.responseHeaders), }); } }; var onBeforeSendHeadersCB = function (details, callback) { if (!details.url) { callback({}); return; } var serverUrl = server.serverUrl(); if (server.isSecured() && ((serverUrl && details.url.startsWith(serverUrl)) || details.url.startsWith(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL + "://"))) { var header = server.getSecureHTTPHeader(details.url); if (header) { details.requestHeaders[header.name] = header.value; } callback({ cancel: false, requestHeaders: tslib_1.__assign({}, details.requestHeaders), }); } else { callback({ cancel: false, requestHeaders: tslib_1.__assign({}, details.requestHeaders), }); } }; var setCertificateVerifyProcCB = function (req, callback) { if (server.isSecured()) { var info = server.serverInfo(); if (info) { if (req.hostname === info.urlHost) { callback(0); return; } } } callback(-3); }; if (electron_1.session.defaultSession) { electron_1.session.defaultSession.webRequest.onHeadersReceived(filter, onHeadersReceivedCB); electron_1.session.defaultSession.webRequest.onBeforeSendHeaders(filter, onBeforeSendHeadersCB); electron_1.session.defaultSession.setCertificateVerifyProc(setCertificateVerifyProcCB); } var webViewSession = getWebViewSession(); if (webViewSession) { webViewSession.webRequest.onHeadersReceived(filter, onHeadersReceivedCB); webViewSession.webRequest.onBeforeSendHeaders(filter, onBeforeSendHeadersCB); webViewSession.setCertificateVerifyProc(setCertificateVerifyProcCB); } electron_1.app.on("certificate-error", function (event, _webContents, url, _error, _certificate, callback) { if (server.isSecured()) { var info = server.serverInfo(); if (info) { if (url.indexOf(info.urlScheme + "://" + info.urlHost) === 0) { event.preventDefault(); callback(true); return; } } } callback(false); }); } exports.secureSessions = secureSessions; var _customUrlProtocolSchemeHandlerWasCalled = false; var streamProtocolHandler = function (req, callback) { return tslib_1.__awaiter(void 0, void 0, void 0, function () { var url, u, ref, failure, success, reqHeaders, serverUrl, header, needsStreamingResponse, response, err_1; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: _customUrlProtocolSchemeHandlerWasCalled = true; url = (0, sessions_1.convertCustomSchemeToHttpUrl)(req.url); u = new URL(url); ref = u.origin; if (req.referrer && req.referrer.trim()) { ref = req.referrer; } failure = function (err) { debug(err); callback({}); }; success = function (response) { var headers = {}; Object.keys(response.headers).forEach(function (header) { var val = response.headers[header]; if (val) { headers[header] = val; } }); if (!headers.referer) { headers.referer = ref; } if (response.statusCode && (response.statusCode < 200 || response.statusCode >= 300)) { failure("HTTP CODE " + response.statusCode); return; } response .on("error", function h() { debug("RESPONSE ERROR " + url); }); var obj = { data: response, headers: headers, statusCode: response.statusCode, }; callback(obj); }; reqHeaders = req.headers; if (_server) { serverUrl = _server.serverUrl(); if (_server.isSecured() && ((serverUrl && url.startsWith(serverUrl)) || url.startsWith(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL + "://"))) { header = _server.getSecureHTTPHeader(url); if (header) { reqHeaders[header.name] = header.value; } } } needsStreamingResponse = true; if (!needsStreamingResponse) return [3, 1]; request.get({ headers: reqHeaders, method: "GET", rejectUnauthorized: false, uri: url, }) .on("response", function (response) { success(response); }) .on("error", function (err) { failure(err); }); return [3, 5]; case 1: response = void 0; _a.label = 2; case 2: _a.trys.push([2, 4, , 5]); return [4, requestPromise({ headers: reqHeaders, method: "GET", rejectUnauthorized: false, resolveWithFullResponse: true, uri: url, })]; case 3: response = _a.sent(); success(response); return [3, 5]; case 4: err_1 = _a.sent(); failure(err_1); return [3, 5]; case 5: return [2]; } }); }); }; var httpProtocolHandler = function (req, callback) { _customUrlProtocolSchemeHandlerWasCalled = true; var url = (0, sessions_1.convertCustomSchemeToHttpUrl)(req.url); callback({ method: req.method, session: getWebViewSession(), url: url, }); }; var transformerAudioVideo = function (_publication, link, url, htmlStr, _sessionInfo) { if (!_customUrlProtocolSchemeHandlerWasCalled) { return htmlStr; } if (!url) { return htmlStr; } if (htmlStr.indexOf("<audio") < 0 && htmlStr.indexOf("<video") < 0) { return htmlStr; } var iHtmlStart = htmlStr.indexOf("<html"); if (iHtmlStart < 0) { return htmlStr; } var iBodyStart = htmlStr.indexOf("<body"); if (iBodyStart < 0) { return htmlStr; } var parseableChunk = htmlStr.substr(iHtmlStart); var htmlStrToParse = "<?xml version=\"1.0\" encoding=\"utf-8\"?>".concat(parseableChunk); var mediaType = "application/xhtml+xml"; if (link && link.TypeLink) { mediaType = link.TypeLink; } var documant = (0, dom_1.parseDOM)(htmlStrToParse, mediaType); var urlHttp = url; if (urlHttp.startsWith(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL + "://")) { urlHttp = (0, sessions_1.convertCustomSchemeToHttpUrl)(urlHttp); } var url_ = new URL(urlHttp); url_.search = ""; url_.hash = ""; var urlStr = url_.toString(); var patchElementSrc = function (el) { var src = el.getAttribute("src"); if (!src || src[0] === "/" || /^https?:\/\//.test(src) || /^data:\/\//.test(src)) { return; } var src_ = src; if (src_.startsWith("./")) { src_ = src_.substr(2); } src_ = "".concat(urlStr, "/../").concat(src_); debug("VIDEO/AUDIO SRC PATCH: ".concat(src, " ==> ").concat(src_)); el.setAttribute("src", src_); }; var processTree = function (el) { var elName = el.nodeName.toLowerCase(); if (elName === "audio" || elName === "video") { patchElementSrc(el); if (!el.childNodes) { return; } for (var i = 0; i < el.childNodes.length; i++) { var childNode = el.childNodes[i]; if (childNode.nodeType === 1) { elName = childNode.nodeName.toLowerCase(); if (elName === "source") { patchElementSrc(childNode); } } } } else { if (!el.childNodes) { return; } for (var i = 0; i < el.childNodes.length; i++) { var childNode = el.childNodes[i]; if (childNode.nodeType === 1) { processTree(childNode); } } } }; processTree(documant.body); var serialized = (0, dom_1.serializeDOM)(documant); var prefix = htmlStr.substr(0, iHtmlStart); var iHtmlStart_ = serialized.indexOf("<html"); if (iHtmlStart_ < 0) { return htmlStr; } var remaining = serialized.substr(iHtmlStart_); var newStr = "".concat(prefix).concat(remaining); return newStr; }; var transformerHttpBaseIframes = function (_publication, link, url, htmlStr, _sessionInfo) { if (!_customUrlProtocolSchemeHandlerWasCalled) { return htmlStr; } if (!url) { return htmlStr; } if (htmlStr.indexOf("<iframe") < 0) { return htmlStr; } var iHtmlStart = htmlStr.indexOf("<html"); if (iHtmlStart < 0) { return htmlStr; } var iBodyStart = htmlStr.indexOf("<body"); if (iBodyStart < 0) { return htmlStr; } var parseableChunk = htmlStr.substr(iHtmlStart); var htmlStrToParse = "<?xml version=\"1.0\" encoding=\"utf-8\"?>".concat(parseableChunk); var mediaType = "application/xhtml+xml"; if (link && link.TypeLink) { mediaType = link.TypeLink; } var documant = (0, dom_1.parseDOM)(htmlStrToParse, mediaType); var urlHttp = url; if (!urlHttp.startsWith(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL + "://")) { urlHttp = (0, sessions_1.convertHttpUrlToCustomScheme)(urlHttp); } var url_ = new URL(urlHttp); var r2CSS = url_.searchParams.get(url_params_1.URL_PARAM_CSS); var r2ERS = url_.searchParams.get(url_params_1.URL_PARAM_EPUBREADINGSYSTEM); var r2DEBUG = url_.searchParams.get(url_params_1.URL_PARAM_DEBUG_VISUALS); var r2CLIPBOARDINTERCEPT = url_.searchParams.get(url_params_1.URL_PARAM_CLIPBOARD_INTERCEPT); var r2SESSIONINFO = url_.searchParams.get(url_params_1.URL_PARAM_SESSION_INFO); var r2WEBVIEWSLOT = url_.searchParams.get(url_params_1.URL_PARAM_WEBVIEW_SLOT); var r2SECONDWEBVIEW = url_.searchParams.get(url_params_1.URL_PARAM_SECOND_WEBVIEW); url_.search = ""; url_.hash = ""; var urlStr = url_.toString(); var patchElementSrc = function (el) { var src = el.getAttribute("src"); if (!src || src[0] === "/" || /^https?:\/\//.test(src) || /^data:\/\//.test(src)) { return; } var src_ = src; if (src_.startsWith("./")) { src_ = src_.substr(2); } src_ = "".concat(urlStr, "/../").concat(src_); var iframeUrl = new URL(src_); if (r2CLIPBOARDINTERCEPT) { iframeUrl.searchParams.append(url_params_1.URL_PARAM_CLIPBOARD_INTERCEPT, r2CLIPBOARDINTERCEPT); } if (r2SESSIONINFO) { iframeUrl.searchParams.append(url_params_1.URL_PARAM_SESSION_INFO, r2SESSIONINFO); } if (r2DEBUG) { iframeUrl.searchParams.append(url_params_1.URL_PARAM_DEBUG_VISUALS, r2DEBUG); } if (r2ERS) { iframeUrl.searchParams.append(url_params_1.URL_PARAM_EPUBREADINGSYSTEM, r2ERS); } if (r2CSS) { iframeUrl.searchParams.append(url_params_1.URL_PARAM_CSS, r2CSS); } if (r2WEBVIEWSLOT) { iframeUrl.searchParams.append(url_params_1.URL_PARAM_WEBVIEW_SLOT, r2WEBVIEWSLOT); } if (r2SECONDWEBVIEW) { iframeUrl.searchParams.append(url_params_1.URL_PARAM_SECOND_WEBVIEW, r2SECONDWEBVIEW); } iframeUrl.searchParams.append(url_params_1.URL_PARAM_IS_IFRAME, "1"); src_ = iframeUrl.toString(); debug("IFRAME SRC PATCH: ".concat(src, " ==> ").concat(src_)); el.setAttribute("src", src_); }; var processTree = function (el) { var elName = el.nodeName.toLowerCase(); if (elName === "iframe") { patchElementSrc(el); } else { if (!el.childNodes) { return; } for (var i = 0; i < el.childNodes.length; i++) { var childNode = el.childNodes[i]; if (childNode.nodeType === 1) { processTree(childNode); } } } }; processTree(documant.body); var serialized = (0, dom_1.serializeDOM)(documant); var prefix = htmlStr.substr(0, iHtmlStart); var iHtmlStart_ = serialized.indexOf("<html"); if (iHtmlStart_ < 0) { return htmlStr; } var remaining = serialized.substr(iHtmlStart_); var newStr = "".concat(prefix).concat(remaining); return newStr; }; var transformerHttpBase = function (publication, link, url, htmlStr, sessionInfo) { if (!_customUrlProtocolSchemeHandlerWasCalled) { return htmlStr; } if (!url) { return htmlStr; } var iHead = htmlStr.indexOf("</head>"); if (iHead < 0) { return htmlStr; } var urlHttp = url; if (urlHttp.startsWith(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL + "://")) { urlHttp = (0, sessions_1.convertCustomSchemeToHttpUrl)(urlHttp); } var url_ = new URL(urlHttp); url_.search = ""; url_.hash = ""; var urlStr = url_.toString(); var baseStr = "\n<base href=\"".concat(urlStr, "\" />\n"); var newStr = htmlStr.substr(0, iHead) + baseStr + htmlStr.substr(iHead); newStr = newStr.replace(/<(audio|video)/g, "<$1 data-r2-crossorigin=\"true\" crossorigin=\"anonymous\" "); newStr = transformerHttpBaseIframes(publication, link, url, newStr, sessionInfo); return newStr; }; var INJECT_HTTP_BASE = true; function initSessions() { var _this = this; electron_1.app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); if (INJECT_HTTP_BASE) { transformer_1.Transformers.instance().add(new transformer_html_1.TransformerHTML(transformerHttpBase)); } else { transformer_1.Transformers.instance().add(new transformer_html_1.TransformerHTML(transformerAudioVideo)); } if (electron_1.protocol.registerStandardSchemes) { electron_1.protocol.registerStandardSchemes([sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL], { secure: true }); } else { electron_1.protocol.registerSchemesAsPrivileged([{ privileges: { allowServiceWorkers: false, bypassCSP: false, corsEnabled: true, secure: true, standard: true, stream: true, supportFetchAPI: true, }, scheme: sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL, }]); } electron_1.app.on("ready", function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { var err_2, webViewSession; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: debug("app ready"); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4, clearSessions()]; case 2: _a.sent(); return [3, 4]; case 3: err_2 = _a.sent(); debug(err_2); return [3, 4]; case 4: if (electron_1.session.defaultSession) { if (USE_STREAM_PROTOCOL_INSTEAD_OF_HTTP) { electron_1.session.defaultSession.protocol.registerStreamProtocol(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL, streamProtocolHandler); } else { electron_1.session.defaultSession.protocol.registerHttpProtocol(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL, httpProtocolHandler); } } webViewSession = getWebViewSession(); if (webViewSession) { if (USE_STREAM_PROTOCOL_INSTEAD_OF_HTTP) { webViewSession.protocol.registerStreamProtocol(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL, streamProtocolHandler); } else { webViewSession.protocol.registerHttpProtocol(sessions_1.READIUM2_ELECTRON_HTTP_PROTOCOL, httpProtocolHandler); } webViewSession.setPermissionRequestHandler(function (wc, permission, callback) { debug("setPermissionRequestHandler"); debug(wc.getURL()); debug(permission); callback(true); }); } return [2]; } }); }); }); } exports.initSessions = initSessions; function clearSession(sess, str) { return tslib_1.__awaiter(this, void 0, void 0, function () { var prom1, prom2, results, results_1, results_1_1, result, err_3; var e_1, _a; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: prom1 = sess.clearCache(); prom2 = sess.clearStorageData({ origin: "*", quotas: [ "temporary", "persistent", "syncable", ], storages: [ "appcache", "serviceworkers", "cachestorage", ], }); _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); return [4, promiseAllSettled([prom1, prom2])]; case 2: results = _b.sent(); try { for (results_1 = tslib_1.__values(results), results_1_1 = results_1.next(); !results_1_1.done; results_1_1 = results_1.next()) { result = results_1_1.value; debug("SESSION CACHE + STORAGE DATA CLEARED - ".concat(str, " => ").concat(result.status)); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (results_1_1 && !results_1_1.done && (_a = results_1.return)) _a.call(results_1); } finally { if (e_1) throw e_1.error; } } return [3, 4]; case 3: err_3 = _b.sent(); debug(err_3); return [3, 4]; case 4: return [2, Promise.resolve()]; } }); }); } exports.clearSession = clearSession; function getWebViewSession() { return electron_1.session.fromPartition(sessions_1.R2_SESSION_WEBVIEW, { cache: true }); } exports.getWebViewSession = getWebViewSession; function clearWebviewSession() { return tslib_1.__awaiter(this, void 0, void 0, function () { var sess, err_4; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: sess = getWebViewSession(); if (!sess) return [3, 4]; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4, clearSession(sess, "[" + sessions_1.R2_SESSION_WEBVIEW + "]")]; case 2: _a.sent(); return [3, 4]; case 3: err_4 = _a.sent(); debug(err_4); return [3, 4]; case 4: return [2, Promise.resolve()]; } }); }); } exports.clearWebviewSession = clearWebviewSession; function clearDefaultSession() { return tslib_1.__awaiter(this, void 0, void 0, function () { var err_5; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: if (!electron_1.session.defaultSession) return [3, 4]; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4, clearSession(electron_1.session.defaultSession, "[default]")]; case 2: _a.sent(); return [3, 4]; case 3: err_5 = _a.sent(); debug(err_5); return [3, 4]; case 4: return [2, Promise.resolve()]; } }); }); } exports.clearDefaultSession = clearDefaultSession; function clearSessions() { return tslib_1.__awaiter(this, void 0, void 0, function () { var err_6; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4, promiseAllSettled([clearDefaultSession(), clearWebviewSession()])]; case 1: _a.sent(); return [3, 3]; case 2: err_6 = _a.sent(); debug(err_6); return [3, 3]; case 3: return [2, Promise.resolve()]; } }); }); } exports.clearSessions = clearSessions; //# sourceMappingURL=sessions.js.map