Compare commits

...

37 Commits

Author SHA1 Message Date
Book Pauk
8c9fd7678d Merge branch 'release/1.2.8' 2025-06-04 09:28:35 +07:00
Book Pauk
01313d66b2 Версия 1.2.8 2025-06-04 09:28:15 +07:00
Book Pauk
eaeacbfb1b Улучшено форматирование текста при копировании из окна 2025-06-04 09:23:13 +07:00
Book Pauk
5328998c21 Merge tag '1.2.7' into develop
1.2.7
2025-02-22 14:44:22 +07:00
Book Pauk
ee066c7c4b Merge branch 'release/1.2.7' 2025-02-22 14:44:18 +07:00
Book Pauk
130aebb514 Версия 1.2.7 2025-02-22 14:43:18 +07:00
Book Pauk
dbec1e630e Отключена форма для сбора донатов 2025-02-22 14:39:29 +07:00
Book Pauk
583b966616 Мелкая оптимизация, чтобы не отдавал большой конфиг каждый раз при обновлении страницы 2025-02-22 14:31:19 +07:00
Book Pauk
9e509ac845 Обновление caniuse-lite 2025-02-22 13:49:57 +07:00
Book Pauk
4ea2d8918e Merge tag '1.2.6' into develop
1.2.6
2024-10-03 15:43:48 +07:00
Book Pauk
6667688193 Merge branch 'release/1.2.6' 2024-10-03 15:43:44 +07:00
Book Pauk
30a1629f23 Исправления из-за нарушения авторских прав 2024-10-03 15:38:16 +07:00
Book Pauk
ba50faeebb Merge tag '1.2.5' into develop
1.2.5
2024-10-03 11:51:40 +07:00
Book Pauk
3c0d784e3d Merge branch 'release/1.2.5' 2024-10-03 11:51:36 +07:00
Book Pauk
3e75310e1f Исправления из-за нарушения авторских прав 2024-10-03 11:51:09 +07:00
Book Pauk
2b01d6d8d7 Merge tag '1.2.4' into develop
1.2.4
2024-08-27 12:59:44 +07:00
Book Pauk
be6d60d7a9 Merge branch 'release/1.2.4' 2024-08-27 12:59:41 +07:00
Book Pauk
3c0815d55b 1.2.4 2024-08-27 12:59:28 +07:00
Book Pauk
abd8584cb8 1.2.4 2024-08-27 12:59:20 +07:00
Book Pauk
5a910f80b3 Поправлена реакция на клик в строке статуса в режиме clickControl 2024-08-27 12:58:07 +07:00
Book Pauk
67bdfd853e Merge tag '1.2.3' into develop
1.2.3
2024-08-02 15:22:27 +07:00
Book Pauk
fc8e986acb Merge branch 'release/1.2.3' 2024-08-02 15:22:24 +07:00
Book Pauk
64539785c2 1.2.3 2024-08-02 15:22:07 +07:00
Book Pauk
f530455146 Версия 1.2.3 2024-08-02 15:21:43 +07:00
Book Pauk
70dc66e1ae Исправление мелких багов при прокрутке 2024-08-02 15:15:54 +07:00
Book Pauk
3e5894d9e0 Исправление багов 2024-07-31 11:44:07 +07:00
Book Pauk
d7ac9d1bfc Улучшение отображения примечаний 2024-07-31 11:30:31 +07:00
Book Pauk
5160c5fb75 Мелкая поправка текста 2024-07-30 21:29:02 +07:00
Book Pauk
d9c7964410 Поправки багов 2024-07-30 21:28:27 +07:00
Book Pauk
110952b4c4 К предыдущему 2024-07-30 18:41:21 +07:00
Book Pauk
ece17dc0dd Улучшение отображения сносок 2024-07-30 18:23:52 +07:00
Book Pauk
35e1087531 Merge tag '1.2.2' into develop
1.2.2
2024-07-28 20:22:59 +07:00
Book Pauk
59c4b62770 Merge branch 'release/1.2.2' 2024-07-28 20:22:54 +07:00
Book Pauk
4be9ce5ff3 Версия 1.2.2 2024-07-28 20:22:33 +07:00
Book Pauk
92a811cabd Поправки парсинга примечаний 2024-07-28 20:20:45 +07:00
Book Pauk
897cdc8ac7 Исправление парсинга примечаний 2024-07-28 20:13:35 +07:00
Book Pauk
418ff482ae Merge tag '1.2.1' into develop
1.2.1
2024-07-28 17:55:11 +07:00
18 changed files with 240 additions and 67 deletions

View File

@@ -1,11 +1,15 @@
import wsc from './webSocketConnection';
class Misc {
async loadConfig() {
async loadConfig(_configHash) {
const query = {params: [
'name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'acceptFileExt', 'bucEnabled', 'branch', 'networkLibraryLink',
]};
const query = {
params: [
'name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter',
'acceptFileExt', 'bucEnabled', 'branch', 'networkLibraryLink', 'restricted'
],
_configHash,
};
const config = await wsc.message(await wsc.send(Object.assign({action: 'get-config'}, query)));
if (config.error)

View File

@@ -154,8 +154,11 @@ class App {
(async() => {
//загрузим конфиг сервера
try {
const config = await miscApi.loadConfig();
this.commit('config/setConfig', config);
const config = await miscApi.loadConfig(this.config._configHash);
if (!config._useCached)
this.commit('config/setConfig', config);
this.showPage = true;
} catch(e) {
//проверим, не получен ли конфиг ранее

View File

@@ -52,18 +52,21 @@ class CopyTextPage {
from = (from < 0 ? 0 : from);
to = paraIndex + 100;
to = (to > parsed.para.length ? parsed.para.length : to);
cut = '<p>..... Текст вырезан. Если хотите скопировать больше, поставьте в настройках галочку "Загружать весь текст"';
cut = '<dd>..... Текст вырезан. Если хотите скопировать больше, поставьте в настройках галочку "Загружать весь текст"';
}
if (from > 0)
text += cut;
for (let i = from; i < to; i++) {
const p = parsed.para[i];
if (p.addIndex > 0)
continue;
const parts = parsed.splitToStyle(p.text);
if (this.stopInit)
return;
text += `<p id="p${i}" class="copyPara">`;
text += `<dd id="p${i}" class="copyPara">&nbsp;&nbsp;`;
for (const part of parts)
text += part.text;

View File

@@ -51,7 +51,7 @@ const tabs = [
['MouseHelpPage', 'Мышь/тачскрин'],
['HotkeysHelpPage', 'Клавиатура'],
['VersionHistoryPage', 'История версий'],
['DonateHelpPage', 'Помочь проекту'],
//['DonateHelpPage', 'Помочь проекту'],
];
const componentOptions = {

View File

@@ -770,6 +770,10 @@ class Reader {
return this.$store.state.config.bucEnabled && this.bucEnabled;
}
get restricted() {
return this.$store.state.config.restricted;
}
get routeParamUrl() {
let result = '';
const path = this.$route.fullPath;
@@ -1263,6 +1267,19 @@ class Reader {
return result;
}
isUrlAllowed(url) {
const restrictedSites = this.restricted?.sites;
if (restrictedSites) {
url = url.toLowerCase();
for (const site of restrictedSites) {
if (url.indexOf(site) === 0)
return false;
}
}
return true;
}
async _loadBook(opts) {
if (!opts || !opts.url) {
this.mostRecentBook();
@@ -1273,6 +1290,11 @@ class Reader {
let url = encodeURI(decodeURI(opts.url));
if (!this.isUrlAllowed(url)) {
this.$root.stdDialog.alert('Книга не загружена, причина: нарушение авторских прав.<br>Приносим извинения за неудобство.', '', {color: 'negative'});
return;
}
if ((url.indexOf('http://') != 0) && (url.indexOf('https://') != 0) &&
(url.indexOf('disk://') != 0))
url = 'http://' + url;

View File

@@ -131,7 +131,7 @@ class ReaderDialogs {
async init() {
await this.showWhatsNew();
await this.showDonation();
//await this.showDonation();
}
loadSettings() {

View File

@@ -594,7 +594,7 @@ class RecentBooksPage {
}
async handleDel(item) {
if (item.group) {
if (item.group?.length) {
const keys = [{key: item.key}];
for (const book of item.group)
keys.push({key: book.key});
@@ -615,14 +615,14 @@ class RecentBooksPage {
} else {
if (await this.$root.stdDialog.confirm('Подтвердите удаление книги из архива:', ' ')) {
await bookManager.delRecentBooks([{key: item.key}], 2);
this.$root.notify.info('Книга удалено безвозвратно');
this.$root.notify.info('Книга удалена безвозвратно');
}
}
}
}
async handleRestore(item) {
if (item.group) {
if (item.group?.length) {
const keys = [{key: item.key}];
for (const book of item.group)
keys.push({key: book.key});
@@ -637,7 +637,7 @@ class RecentBooksPage {
async loadBook(item, force = false) {
if (item.deleted)
await this.handleRestore(item.key);
await this.handleRestore(item);
this.$emit('load-book', {url: item.url, path: item.path, force});
this.close();

View File

@@ -53,7 +53,7 @@
</q-checkbox>
</div>
<div class="sets-item row">
<!--div class="sets-item row">
<div class="sets-label label">
Уведомление
</div>
@@ -63,7 +63,7 @@
Показывать диалог для сбора пожертвований
</q-tooltip>
</q-checkbox>
</div>
</div-->
<!---------------------------------------------->
<div class="sets-part-header">

View File

@@ -14,6 +14,11 @@ export default class DrawHelper {
return this.context.measureText(text).width;
}
measureTextMetrics(text, style) {// eslint-disable-line no-unused-vars
this.context.font = this.fontByStyle(style);
return this.context.measureText(text);
}
measureTextFont(text, font) {// eslint-disable-line no-unused-vars
this.context.font = font;
return this.context.measureText(text).width;
@@ -39,7 +44,6 @@ export default class DrawHelper {
let center = false;
let space = 0;
let j = 0;
const pad = this.fontSize/2;
//формируем строку
for (const part of line.parts) {
let tOpen = '';
@@ -47,10 +51,20 @@ export default class DrawHelper {
tOpen += (part.style.italic ? '<i>' : '');
tOpen += (part.style.sup ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: -0.3em">' : '');
tOpen += (part.style.sub ? '<span style="vertical-align: baseline; position: relative; line-height: 0; top: 0.3em">' : '');
tOpen += (part.style.note ? `<span style="position: relative;">` +
`<span style="position: absolute; background-color: ${this.textColor}; opacity: 0.1; cursor: pointer; pointer-events: auto; ` +
`height: ${this.fontSize + pad*2}px; padding: ${pad}px; left: -${pad}px; top: -${pad*0.9}px; border-radius: ${this.fontSize}px;" ` +
`onclick="onNoteClickLiberama('${part.style.note.id}', ${part.style.note.orig ? 1 : 0})"><span style="visibility: hidden;">__TEXT</span></span>` : '');
if (part.style.note) {
const t = part.text;
const m = this.measureTextMetrics(t, part.style);
const d = this.fontSize - 1.1*m.fontBoundingBoxAscent;
const w = m.width;
const size = (this.fontSize > 18 ? this.fontSize : 18);
const pad = size/2;
const btnW = (w >= size ? w : size) + pad*2;
tOpen += `<span style="position: relative;">` +
`<span style="position: absolute; background-color: ${this.textColor}; opacity: 0.1; cursor: pointer; pointer-events: auto; ` +
`height: ${this.fontSize + pad*2}px; padding: ${pad}px; left: -${(btnW - w)/2 - pad*0.05}px; top: -${pad + d}px; width: ${btnW}px; border-radius: ${size}px;" ` +
`onclick="onNoteClickLiberama('${part.style.note.id}', ${part.style.note.orig ? 1 : 0})"><span style="visibility: hidden;" class="dborder">${t}</span></span>`;
}
let tClose = '';
tClose += (part.style.note ? '</span>' : '');
tClose += (part.style.sub ? '</span>' : '');
@@ -70,9 +84,6 @@ export default class DrawHelper {
if (text && text.trim() == '')
text = `<span style="white-space: pre">${text}</span>`;
if (part.style.note)
tOpen = tOpen.replace('__TEXT', text);
lineText += `${tOpen}${text}${tClose}`;
center = center || part.style.center;

View File

@@ -14,13 +14,14 @@
<div @copy.prevent="copyText" v-html="page2"></div>
</div>
</div>
<div v-show="showStatusBar" ref="statusBar" class="layout">
<div v-show="showStatusBar" ref="statusBar" class="layout" :class="{'no-events': clickControl}">
<div v-html="statusBar"></div>
</div>
<div
v-show="clickControl" ref="layoutEvents" class="layout events"
oncontextmenu="return false;"
@mousedown.prevent.stop="onMouseDown" @mouseup.prevent.stop="onMouseUp"
@mouseover.prevent.stop="onMouseEvent" @mouseout.prevent.stop="onMouseEvent" @mousemove.prevent.stop="onMouseEvent"
@wheel.prevent.stop="onMouseWheel"
@touchstart.stop="onTouchStart" @touchend.stop="onTouchEnd" @touchmove.stop="onTouchMove" @touchcancel.prevent.stop="onTouchCancel"
>
@@ -38,9 +39,9 @@
<!-- Примечание -->
<Dialog ref="dialog1" v-model="noteDialogVisible">
<!--template #header>
Примечание
</template-->
<template #header>
{{ noteTitle }}
</template>
<div class="column col" style="line-height: 20px; max-width: 400px; max-height: 200px; overflow-x: hidden; overflow-y: auto">
<div v-html="noteHtml"></div>
@@ -49,7 +50,7 @@
<template #footer>
<div class="row col">
<q-btn class="q-px-md q-mr-md" color="btn2" text-color="app" dense no-caps @click="goToNotes">
В примечаниях
В примечания
</q-btn>
</div>
@@ -148,6 +149,7 @@ class TextPage {
noteDialogVisible = false;
noteId = '';
noteTitle = '';
noteHtml = '';
created() {
@@ -1072,6 +1074,7 @@ class TextPage {
if (this.startTouch) {
event.preventDefault();
}
this.endClickRepeat();
}
onTouchEnd(event) {
@@ -1156,6 +1159,9 @@ class TextPage {
onMouseWheel(event) {
if (this.$root.isMobileDevice)
return;
this.endClickRepeat();
if (event.deltaY > 0) {
this.doDown();
} else if (event.deltaY < 0) {
@@ -1163,6 +1169,12 @@ class TextPage {
}
}
onMouseEvent() {
if (this.$root.isMobileDevice)
return;
this.endClickRepeat();
}
onStatusBarClick() {
const url = this.meta.url;
if (url && url.indexOf('disk://') != 0) {
@@ -1271,8 +1283,15 @@ class TextPage {
if (note) {
if (orig) {//show dialog
this.noteId = noteId;
const pad = (note.para.length > 1 ? 20 : 0);
this.noteHtml = note.para.map(p => `<p style="margin: 0; padding-left: ${pad}px">${p}</p>`).join('');
this.noteTitle = `[${note.title?.trim()}]`;
this.noteHtml = note.xml
.replace(/<p>/g, '<p class="note-para">')
.replace(/<stanza>/g, '<br>').replace(/<\/stanza>/g, '')
.replace(/<v>/g, '<p style="margin: 0">').replace(/<\/v>/g, '</p>')
.replace(/<emphasis>/g, '<em>').replace(/<\/emphasis>/g, '</em>')
.replace(/<text-author>/g, '<br>').replace(/<\/text-author>/g, '')
;
this.noteDialogVisible = true;
} else {//go to orig
this.goToOrigNote(noteId);
@@ -1334,4 +1353,14 @@ export default vueComponent(TextPage);
background-color: rgba(0,0,0,0);
}
.no-events {
pointer-events: none;
}
</style>
<style>
.note-para {
margin: 0;
padding: 0;
margin-bottom: 10px;
}
</style>

View File

@@ -94,6 +94,7 @@ export default class BookParser {
let inNote = false;
let noteId = '';
let inNotesBody = false;
const noteTags = new Set(['p', 'poem', 'stanza', 'v', 'text-author', 'emphasis']);
//оглавление
this.contents = [];
@@ -401,17 +402,6 @@ export default class BookParser {
bodyIndex++;
}
if (tag == 'title') {
newParagraph();
isFirstTitlePara = true;
bold = true;
center = true;
inTitle = true;
curTitle = {paraIndex, title: '', inset: sectionLevel, bodyIndex, subtitles: []};
this.contents.push(curTitle);
}
if (tag == 'section') {
if (!isFirstSection)
newParagraph();
@@ -429,13 +419,25 @@ export default class BookParser {
}
note.noteParaIndex = paraIndex;
note.para = [];
note.xml = '';
note.title = '';
noteId = id;
}
}
}
if (tag == 'title') {
newParagraph();
isFirstTitlePara = true;
bold = true;
center = true;
inTitle = true;
curTitle = {paraIndex, title: '', inset: sectionLevel, bodyIndex, subtitles: []};
this.contents.push(curTitle);
}
if (tag == 'emphasis' || tag == 'strong' || tag == 'sup' || tag == 'sub') {
growParagraph(`<${tag}>`, 0);
}
@@ -447,12 +449,8 @@ export default class BookParser {
inPara = true;
isFirstTitlePara = false;
if (inNotesBody && noteId) {
if (!inTitle) {
this.notes[noteId].para.push('');
} else {
growParagraph(`<note href="${noteId}">`, 0);
}
if (inTitle && inNotesBody && noteId) {
growParagraph(`<note href="${noteId}">`, 0);
}
}
}
@@ -487,6 +485,10 @@ export default class BookParser {
bold = true;
space += 1;
}
if (!inTitle && inNotesBody && noteId && noteTags.has(tag)) {
this.notes[noteId].xml += `<${tag}>`;
}
}
};
@@ -551,6 +553,10 @@ export default class BookParser {
bold = false;
space -= 1;
}
if (!inTitle && inNotesBody && noteId && noteTags.has(tag)) {
this.notes[noteId].xml += `</${tag}>`;
}
}
let i = path.lastIndexOf(tag);
@@ -637,10 +643,12 @@ export default class BookParser {
else
growParagraph(' ', 1);
if (!inTitle && inPara && inNotesBody && noteId) {
const p = this.notes[noteId].para;
if (p.length)
p[p.length - 1] = p[p.length - 1] + text;
if (inNotesBody && noteId) {
if (inTitle) {
this.notes[noteId].title += text;
} else {
this.notes[noteId].xml += text;
}
}
}
};

View File

@@ -1,8 +1,74 @@
export const versionHistory = [
{
version: '1.2.1',
version: '1.2.8',
releaseDate: '2025-06-04',
showUntil: '2025-06-03',
content:
`
<ul>
<li>исправление багов</li>
</ul>
`
},
{
version: '1.2.7',
releaseDate: '2025-02-22',
showUntil: '2025-02-21',
content:
`
<ul>
<li>отключена форма для сбора донатов</li>
<li>мелкие оптимизации</li>
</ul>
`
},
{
version: '1.2.6',
releaseDate: '2024-10-03',
showUntil: '2024-10-02',
content:
`
<ul>
<li>исправления из-за нарушения авторских прав</li>
</ul>
`
},
{
version: '1.2.4',
releaseDate: '2024-08-27',
showUntil: '2024-08-26',
content:
`
<ul>
<li>исправление багов</li>
</ul>
`
},
{
version: '1.2.3',
releaseDate: '2024-08-02',
showUntil: '2024-08-01',
content:
`
<ul>
<li>исправление багов</li>
</ul>
`
},
{
version: '1.2.2',
releaseDate: '2024-07-28',
showUntil: '2024-08-04',
showUntil: '2024-07-27',
content:
`
<ul>

View File

@@ -1,4 +1,3 @@
import miscApi from '../../api/misc';
// initial state
const state = {
name: null,

16
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "liberama",
"version": "1.2.0",
"version": "1.2.6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "liberama",
"version": "1.2.0",
"version": "1.2.6",
"hasInstallScript": true,
"license": "CC0-1.0",
"dependencies": {
@@ -3364,9 +3364,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001643",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz",
"integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==",
"version": "1.0.30001700",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
"integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
"dev": true,
"funding": [
{
@@ -13709,9 +13709,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001643",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz",
"integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==",
"version": "1.0.30001700",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
"integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
"dev": true
},
"chalk": {

View File

@@ -1,6 +1,6 @@
{
"name": "liberama",
"version": "1.2.1",
"version": "1.2.8",
"author": "Book Pauk <bookpauk@gmail.com>",
"license": "CC0-1.0",
"repository": "bookpauk/liberama",

View File

@@ -18,7 +18,8 @@ module.exports = {
useExternalBookConverter: false,
acceptFileExt: '.fb2, .fb3, .html, .txt, .zip, .bz2, .gz, .rar, .epub, .mobi, .rtf, .doc, .docx, .pdf, .djvu, .jpg, .jpeg, .png',
webConfigParams: ['name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'acceptFileExt', 'bucEnabled', 'branch', 'networkLibraryLink'],
restricted: {},
webConfigParams: ['name', 'version', 'mode', 'maxUploadFileSize', 'useExternalBookConverter', 'acceptFileExt', 'bucEnabled', 'branch', 'networkLibraryLink', 'restricted'],
jembaDb: [
{

View File

@@ -56,6 +56,7 @@ class ConfigManager {
await fs.ensureDir(config.dataDir);
this._userConfigFile = `${config.dataDir}/config.json`;
this._restrictedFile = `${config.dataDir}/restricted.json`;
this._config = config;
this.inited = true;
@@ -75,6 +76,10 @@ class ConfigManager {
return this._userConfigFile;
}
get restrictedFile() {
return this._restrictedFile;
}
set userConfigFile(value) {
if (value)
this._userConfigFile = value;
@@ -100,6 +105,12 @@ class ConfigManager {
} else {
await this.save();
}
if (await fs.pathExists(this.restrictedFile)) {
const data = JSON.parse(await fs.readFile(this.restrictedFile, 'utf8'));
this.config = {restricted: data};
}
} catch(e) {
throw new Error(`Error while loading "${this.userConfigFile}": ${e.message}`);
}

View File

@@ -20,6 +20,8 @@ class WebSocketController {
this.readerWorker = new ReaderWorker(config);
this.workerState = new WorkerState();
this.configHash = '';
if (config.bucEnabled) {
this.bucClient = new BUCClient(config);
}
@@ -119,8 +121,22 @@ class WebSocketController {
async getConfig(req, ws) {
if (Array.isArray(req.params)) {
const paramsSet = new Set(req.params);
const _configHash = req._configHash;
this.send(_.pick(this.config, this.config.webConfigParams.filter(x => paramsSet.has(x))), req, ws);
let response = {_useCached: true};
//оптимизация, чтобы не отдавал большой конфиг каждый раз при обновлении страницы
if (!_configHash || _configHash !== this.configHash) {
if (!this.configHash) {
const webConfig = _.pick(this.config, this.config.webConfigParams);
this.configHash = await utils.getBufHash(Buffer.from(JSON.stringify(webConfig)), 'sha256', 'hex');
}
response = _.pick(this.config, this.config.webConfigParams.filter(x => paramsSet.has(x)));
response._configHash = this.configHash;
}
this.send(response, req, ws);
} else {
throw new Error('params is not an array');
}