mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
修复一些小错误
This commit is contained in:
1 parent
8cd84f29e8
commit
a596231a06
15 files changed
+125
-124
No files matched your search
@@ -73,8 +73,8 @@ class TrainingMainHandler extends TrainingHandler {
|
|||||||
$or: [{ _id: { $in: Array.from(tids) } }, { enroll: 1 }],
|
$or: [{ _id: { $in: Array.from(tids) } }, { enroll: 1 }],
|
||||||
}).toArray();
|
}).toArray();
|
||||||
for (const tsdoc of tsdocs) {
|
for (const tsdoc of tsdocs) {
|
||||||
tsdict[tsdoc._id] = tsdoc;
|
tsdict[tsdoc.tid] = tsdoc;
|
||||||
enrolledTids.add(tsdoc._id);
|
enrolledTids.add(tsdoc.tid);
|
||||||
}
|
}
|
||||||
for (const tid of tids) enrolledTids.delete(tid);
|
for (const tid of tids) enrolledTids.delete(tid);
|
||||||
if (enrolledTids.size) tdict = await training.getList(Array.from(enrolledTids));
|
if (enrolledTids.size) tdict = await training.getList(Array.from(enrolledTids));
|
||||||
@@ -117,7 +117,7 @@ class TrainingDetailHandler extends TrainingHandler {
|
|||||||
for (const node of tdoc.dag) {
|
for (const node of tdoc.dag) {
|
||||||
ndict[node._id] = node;
|
ndict[node._id] = node;
|
||||||
const totalCount = node.pids.length;
|
const totalCount = node.pids.length;
|
||||||
const doneCount = Set.union(new Set(node.pids), new Set(donePids));
|
const doneCount = Set.union(new Set(node.pids), new Set(donePids)).size;
|
||||||
const nsdoc = {
|
const nsdoc = {
|
||||||
progress: totalCount ? parseInt(100 * (doneCount / totalCount)) : 100,
|
progress: totalCount ? parseInt(100 * (doneCount / totalCount)) : 100,
|
||||||
isDone: training.isDone(node, doneNids, donePids),
|
isDone: training.isDone(node, doneNids, donePids),
|
||||||
@@ -172,8 +172,6 @@ class TrainingCreateHandler extends TrainingHandler {
|
|||||||
}) {
|
}) {
|
||||||
dag = await _parseDagJson(dag);
|
dag = await _parseDagJson(dag);
|
||||||
const pids = training.getPids({ dag });
|
const pids = training.getPids({ dag });
|
||||||
console.log(pids);
|
|
||||||
console.log(pids.length, pids.size);
|
|
||||||
assert(pids.length, new ValidationError('dag'));
|
assert(pids.length, new ValidationError('dag'));
|
||||||
const pdocs = await problem.getMulti({
|
const pdocs = await problem.getMulti({
|
||||||
$or: [{ _id: { $in: pids } }, { pid: { $in: pids } }],
|
$or: [{ _id: { $in: pids } }, { pid: { $in: pids } }],
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ async function setStatus(tid, uid, $set) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getPids(tdoc) {
|
getPids(tdoc) {
|
||||||
console.log(tdoc.dag);
|
|
||||||
const pids = new Set();
|
const pids = new Set();
|
||||||
for (const node of tdoc.dag) {
|
for (const node of tdoc.dag) {
|
||||||
for (const pid of node.pids) pids.add(pid);
|
for (const pid of node.pids) pids.add(pid);
|
||||||
|
|||||||
+8
-23
@@ -303,19 +303,11 @@ class ConnectionHandler {
|
|||||||
* @param {import('sockjs').Connection} conn
|
* @param {import('sockjs').Connection} conn
|
||||||
*/
|
*/
|
||||||
constructor(conn) {
|
constructor(conn) {
|
||||||
const that = this;
|
|
||||||
this.conn = conn;
|
this.conn = conn;
|
||||||
this.request = {
|
this.request = {
|
||||||
cookies: {
|
|
||||||
get(name) {
|
|
||||||
return that.request.cookies[name];
|
|
||||||
},
|
|
||||||
set() { },
|
|
||||||
},
|
|
||||||
params: {},
|
params: {},
|
||||||
headers: conn.headers,
|
headers: conn.headers,
|
||||||
};
|
};
|
||||||
this._handler = {};
|
|
||||||
const p = (conn.url.split('?')[1] || '').split('&');
|
const p = (conn.url.split('?')[1] || '').split('&');
|
||||||
for (const i in p) p[i] = p[i].split('=');
|
for (const i in p) p[i] = p[i].split('=');
|
||||||
for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]);
|
for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]);
|
||||||
@@ -342,21 +334,14 @@ class ConnectionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ___prepare() {
|
async ___prepare() {
|
||||||
await new Promise((resolve, reject) => {
|
try {
|
||||||
this.conn.once('data', (msg) => {
|
this.session = {
|
||||||
for (const i of msg.split(';')) {
|
uid: await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN),
|
||||||
const [k, v] = i.trim().split('=');
|
};
|
||||||
this.request.cookies[k] = v;
|
await token.delete(this.request.params.token, token.TYPE_CSRF_TOKEN);
|
||||||
}
|
} catch (e) {
|
||||||
resolve();
|
this.session = { uid: 1 };
|
||||||
});
|
}
|
||||||
setTimeout(reject, 5000);
|
|
||||||
});
|
|
||||||
this._handler.sid = this.request.cookies.get('sid');
|
|
||||||
this.session = this._handler.sid
|
|
||||||
? await token.get(this._handler.sid, token.TYPE_SESSION)
|
|
||||||
: { uid: 1 };
|
|
||||||
if (!this.session) this.session = { uid: 1 };
|
|
||||||
const bdoc = await blacklist.get(this.request.ip);
|
const bdoc = await blacklist.get(this.request.ip);
|
||||||
if (bdoc) throw new BlacklistedError(this.request.ip);
|
if (bdoc) throw new BlacklistedError(this.request.ip);
|
||||||
this.user = await user.getById(this.session.uid);
|
this.user = await user.getById(this.session.uid);
|
||||||
|
|||||||
+24
-14
@@ -1,16 +1,26 @@
|
|||||||
global.Hydro.lib.html2md = module.exports = function html2md(str) {
|
const RE_IMG = /<img.*?src="(.*?)".*?>(.*?)<\/img>/i;
|
||||||
|
const RE_A = /<a.*?href="(.*?)".*?>(.*?)<\/a>/i;
|
||||||
|
const RE_BLOCKQUOTE = /<blockquote.*?>(.*?)<\/blockquote>/i;
|
||||||
|
|
||||||
|
function replacer(RE, cb) { // eslint-disable-line no-unused-vars
|
||||||
|
// eslint-disable-next-line no-eval
|
||||||
|
return eval(`(substr)=>{
|
||||||
|
const s = RE.exec(substr);
|
||||||
|
return cb(s);
|
||||||
|
}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function html2md(str) {
|
||||||
return str.replace(/<p>/gmi, '').replace(/<\/p>/gmi, ' ')
|
return str.replace(/<p>/gmi, '').replace(/<\/p>/gmi, ' ')
|
||||||
.replace(/<h1>/gmi, '# ')
|
.replace(/<h[1-6]>/gmi, (substr) => {
|
||||||
.replace(/<h2>/gmi, '## ')
|
const s = /<h[1-6]>/i.exec(substr);
|
||||||
.replace(/<h3>/gmi, '### ')
|
return `${'#'.repeat(parseInt(s[1]))} `;
|
||||||
.replace(/<h4>/gmi, '#### ')
|
|
||||||
.replace(/<h5>/gmi, '##### ')
|
|
||||||
.replace(/<h6>/gmi, '###### ')
|
|
||||||
.replace(/<\/?code>/, '\n```\n')
|
|
||||||
.replace(/<img.*?src=".*?".*?>.*?<\/img>/gmi, (substr) => {
|
|
||||||
const RE_IMG = /<img.*?src="(.*?)".*?>(.*?)<\/img>/i;
|
|
||||||
const res = RE_IMG.exec(substr);
|
|
||||||
return `[${res[2]}](${res[1]})`;
|
|
||||||
})
|
})
|
||||||
.replace(/<\/h[0-6]>/, '\n');
|
.replace(/<\/h[0-6]>/, '\n')
|
||||||
};
|
.replace(/<\/?code>/, '\n```\n')
|
||||||
|
.replace(/<img.*?src=".*?".*?>.*?<\/img>/gmi, replacer(RE_IMG, (s) => `![${s[2]}](${s[1]})`))
|
||||||
|
.replace(/<a.*?href=".*?".*?>.*?<\/a>/gmi, replacer(RE_A, (s) => `[${s[2]}](${s[1]})`))
|
||||||
|
.replace(/<blockquote.*?>.*?<\/blockquote>/gmi, replacer(RE_BLOCKQUOTE, (s) => `> ${s[1]}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
global.Hydro.lib.html2md = module.exports = html2md;
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
const Mongo = global.Hydro.nodeModules.mongodb;
|
const Mongo = global.Hydro.nodeModules.mongodb;
|
||||||
const { ObjectID } = global.Hydro.nodeModules.bson;
|
const { ObjectID } = global.Hydro.nodeModules.bson;
|
||||||
const dst = global.Hydro.service.db;
|
const dst = global.Hydro.service.db;
|
||||||
|
const { problem } = global.Hydro.model.problem;
|
||||||
|
|
||||||
function parseSolution(doc) {
|
function parseSolution(doc) {
|
||||||
return {
|
return {
|
||||||
@@ -12,72 +13,75 @@ function parseSolution(doc) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function problem(src, report) {
|
const tasks = {
|
||||||
let count = await src.collection('document').find({ doc_type: 10 }).count();
|
async problem(src, report) {
|
||||||
await report({ progress: 1, message: `Found ${count} problems.` });
|
let count = await src.collection('document').find({ doc_type: 10 }).count();
|
||||||
for (let i = 0; i <= Math.floor(count / 50); i++) {
|
await report({ progress: 1, message: `Found ${count} problems.` });
|
||||||
const docs = await src.collection('document').find({ doc_type: 10 })
|
for (let i = 0; i <= Math.floor(count / 50); i++) {
|
||||||
.skip(i * 50).limit(50)
|
const docs = await src.collection('document').find({ doc_type: 10 })
|
||||||
.toArray();
|
.skip(i * 50).limit(50)
|
||||||
const problems = [];
|
.toArray();
|
||||||
for (const doc of docs) {
|
const problems = [];
|
||||||
problems.push({
|
for (const doc of docs) {
|
||||||
_id: doc._id,
|
problems.push({
|
||||||
pid: doc.doc_id || new ObjectID(),
|
_id: doc._id,
|
||||||
title: doc.title,
|
pid: doc.doc_id || new ObjectID(),
|
||||||
content: doc.content,
|
title: doc.title,
|
||||||
owner: doc.owner_uid,
|
content: doc.content,
|
||||||
data: doc.data,
|
owner: doc.owner_uid,
|
||||||
category: doc.category,
|
data: doc.data,
|
||||||
tag: doc.tag,
|
category: doc.category,
|
||||||
nSubmit: doc.num_submit,
|
tag: doc.tag,
|
||||||
nAccept: doc.num_accept,
|
nSubmit: doc.num_submit,
|
||||||
});
|
nAccept: doc.num_accept,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await dst.collection('problem').insertMany(problems);
|
||||||
}
|
}
|
||||||
await dst.collection('problem').insertMany(problems);
|
count = await src.collection('document.status').find({ doc_type: 10 }).count();
|
||||||
}
|
for (let i = 0; i <= Math.floor(count / 50); i++) {
|
||||||
count = await src.collection('document.status').find({ doc_type: 10 }).count();
|
const docs = await src.collection('document.status').find({ doc_type: 10 })
|
||||||
for (let i = 0; i <= Math.floor(count / 50); i++) {
|
.skip(i * 50).limit(50)
|
||||||
const docs = await src.collection('document.status').find({ doc_type: 10 })
|
.toArray();
|
||||||
.skip(i * 50).limit(50)
|
const problems = [];
|
||||||
.toArray();
|
for (const doc of docs) {
|
||||||
const problems = [];
|
problems.push({
|
||||||
for (const doc of docs) {
|
pid: await problem.get(doc.pid),
|
||||||
problems.push({
|
rid: doc.rid,
|
||||||
pid: doc.pid,
|
uid: doc.uid,
|
||||||
rid: doc.rid,
|
status: doc.status,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
await dst.collection('problem.status').insertMany(problems);
|
||||||
}
|
}
|
||||||
await dst.collection('problem.status').insertMany(problems);
|
await report({ progress: 1, message: `Found ${count} problems.` });
|
||||||
}
|
},
|
||||||
await report({ progress: 1, message: `Found ${count} problems.` });
|
async contest(src, report) {
|
||||||
}
|
const RULES = ['', 'oi', 'acm'];
|
||||||
|
const count = await src.collection('document').find({ doc_type: 30 }).count();
|
||||||
async function contest(src, report) {
|
await report({ progress: 1, message: `Found ${count} contests.` });
|
||||||
const RULES = ['', 'oi', 'acm'];
|
for (let i = 0; i <= Math.floor(count / 50); i++) {
|
||||||
const count = await src.collection('document').find({ doc_type: 30 }).count();
|
const docs = await src.collection('document').find({ doc_type: 30 })
|
||||||
await report({ progress: 1, message: `Found ${count} contests.` });
|
.skip(i * 50).limit(50)
|
||||||
for (let i = 0; i <= Math.floor(count / 50); i++) {
|
.toArray();
|
||||||
const docs = await src.collection('document').find({ doc_type: 30 })
|
const contests = [];
|
||||||
.skip(i * 50).limit(50)
|
for (const doc of docs) {
|
||||||
.toArray();
|
contests.push({
|
||||||
const contests = [];
|
_id: doc._id,
|
||||||
for (const doc of docs) {
|
title: doc.title,
|
||||||
contests.push({
|
content: doc.content,
|
||||||
_id: doc._id,
|
owner: doc.owner_uid,
|
||||||
title: doc.title,
|
rule: RULES[doc.rule],
|
||||||
content: doc.content,
|
beginAt: doc.begin_at,
|
||||||
owner: doc.owner_uid,
|
endAt: doc.end_at,
|
||||||
rule: RULES[doc.rule],
|
pids: doc.pids,
|
||||||
beginAt: doc.begin_at,
|
attend: doc.attend,
|
||||||
endAt: doc.end_at,
|
});
|
||||||
pids: doc.pids,
|
}
|
||||||
attend: doc.attend,
|
await dst.collection('contest').insertMany(contests);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await dst.collection('contests').insertMany(contests);
|
},
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
async function migrateVijos({
|
async function migrateVijos({
|
||||||
host, port, name, username, password,
|
host, port, name, username, password,
|
||||||
@@ -95,8 +99,8 @@ async function migrateVijos({
|
|||||||
value: (await src.collection('system').findOne({ _id: 'user_counter' })).value,
|
value: (await src.collection('system').findOne({ _id: 'user_counter' })).value,
|
||||||
});
|
});
|
||||||
await report({ progress: 1, message: 'Collection:system done.' });
|
await report({ progress: 1, message: 'Collection:system done.' });
|
||||||
await problem(src, report);
|
await tasks.problem(src, report);
|
||||||
await contest(src, report);
|
await tasks.contest(src, report);
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.script.migrateVijos = module.exports = migrateVijos;
|
global.Hydro.script.migrateVijos = module.exports = migrateVijos;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[
|
||||||
|
"introduction"
|
||||||
|
]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Introduction
|
||||||
|
|
||||||
|
为什么使用 Hydro ?
|
||||||
|
|
||||||
|
- 安全:使用 cgroup 进行隔离,杜绝卡评测;
|
||||||
|
- 高效:Hydro 使用了沙箱复用技术,拥有极高的评测效率;
|
||||||
|
- 扩展:Hydro 支持安装额外模块进行扩展;
|
||||||
|
- 强大:配合 Judger 模块(或 HydroJudger 独立评测机),可支持 spj,交互题,提交答案题,文件IO等多种特性;
|
||||||
|
- 简便:提供了多种迁移脚本,可从 Vijos 等快速升级至 Hydro;
|
||||||
|
- 自定:所有权限节点均可自由设置;
|
||||||
|
- 社区:Hydro 正在持续维护中;
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
<ol class="menu">
|
<ol class="menu">
|
||||||
{{ sidemenu.render_item('info', 'wiki_about', '/wiki/about') }}
|
{{ sidemenu.render_item('info', 'wiki_about', '/wiki/about') }}
|
||||||
{{ sidemenu.render_item('help', 'wiki_help', '/wiki/help') }}
|
{{ sidemenu.render_item('help', 'wiki_help', '/wiki/help') }}
|
||||||
|
{{ sidemenu.render_item('help', 'wiki_gettingstarted', '/wiki/gettingstarted') }}
|
||||||
{{ sidemenu.render_item('help', 'wiki_development', '/wiki/development') }}
|
{{ sidemenu.render_item('help', 'wiki_development', '/wiki/development') }}
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<script>
|
<script>
|
||||||
var Context = {
|
var Context = {
|
||||||
'socketUrl': "/record-detail-conn?rid={{ rdoc._id }}"
|
'socketUrl': "/record-detail-conn?rid={{ rdoc._id }}&token={{ handler.csrfToken }}"
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<script>
|
<script>
|
||||||
var Context = {
|
var Context = {
|
||||||
'socketUrl': "/record-conn",
|
'socketUrl': "/record-conn?token={{ handler.csrfToken }}",
|
||||||
'rids': [
|
'rids': [
|
||||||
{% for rdoc in rdocs %}"{{ rdoc._id.toString() }}",{% endfor %}
|
{% for rdoc in rdocs %}"{{ rdoc._id.toString() }}",{% endfor %}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
{{ tdoc['content'] }}
|
{{ tdoc['content'] }}
|
||||||
</div>
|
</div>
|
||||||
<div class="section__body typo">
|
<div class="section__body typo">
|
||||||
{{ tdoc['desc']|default('')|markdown }}
|
{{ tdoc['desc']|default('')|markdown|safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for node in tdoc['dag'] %}
|
{% for node in tdoc['dag'] %}
|
||||||
<div class="training__section {% if nsdict[node['_id']]['isDone'] %}done{% elif nsdict[node['_id']]['isProgress'] %}progress{% elif nsdict[node['_id']]['isOpen'] %}open{% else %}invalid{% endif %} {% if nsdict[node['_id']]['isProgress'] or nsdict[node['_id']]['isOpen'] %}expanded{% else %}collapsed{% endif %}">
|
<div class="training__section {% if nsdict[node['_id']]['isDone'] %}done{% elif nsdict[node['_id']]['isProgress'] %}progress{% elif nsdict[node['_id']]['isOpen'] %}open{% else %}invalid{% endif %} {% if nsdict[node['_id']]['isProgress'] or nsdict[node['_id']]['isOpen'] %}expanded{% else %}collapsed{% endif %}">
|
||||||
<div class="section__header clearfix">
|
<div class="section__header clearfix">
|
||||||
<div class="float-left">
|
<div class="float-left">
|
||||||
<h1 class="section__title">{{ _('Section') }} {{ node['_id'] }}. {{ node['title'] }}</h1>
|
<h1 class="section__title">{{ _('Section') }} {{ node['_id'] }}. {{ node['title'] }}</h1>
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import * as recordEnum from 'vj/constant/record';
|
|||||||
const page = new NamedPage('judge_playground', async () => {
|
const page = new NamedPage('judge_playground', async () => {
|
||||||
const { default: SockJs } = await import('sockjs-client');
|
const { default: SockJs } = await import('sockjs-client');
|
||||||
|
|
||||||
const sock = new SockJs('/judge/consume-conn');
|
const sock = new SockJs(`/judge/consume-conn?token=${ UIContext.csrfToken }`);
|
||||||
sock.onopen = () => {
|
sock.onopen = () => {
|
||||||
sock.send(document.cookie);
|
|
||||||
const div = $('<div class="section visible">').appendTo('#messages');
|
const div = $('<div class="section visible">').appendTo('#messages');
|
||||||
$('<div class="section__header"><h1 class="section__title">Connection opened.</h1></div>')
|
$('<div class="section__header"><h1 class="section__title">Connection opened.</h1></div>')
|
||||||
.appendTo(div);
|
.appendTo(div);
|
||||||
|
|||||||
@@ -168,9 +168,6 @@ const page = new NamedPage(['problem_detail', 'contest_detail_problem', 'homewor
|
|||||||
} = await loadReactRedux(ScratchpadReducer);
|
} = await loadReactRedux(ScratchpadReducer);
|
||||||
|
|
||||||
const sock = new SockJs(Context.socketUrl);
|
const sock = new SockJs(Context.socketUrl);
|
||||||
sock.onopen = () => {
|
|
||||||
sock.send(document.cookie);
|
|
||||||
};
|
|
||||||
sock.onmessage = (message) => {
|
sock.onmessage = (message) => {
|
||||||
const msg = JSON.parse(message.data);
|
const msg = JSON.parse(message.data);
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ const page = new NamedPage('record_detail', async () => {
|
|||||||
|
|
||||||
const sock = new SockJs(Context.socketUrl);
|
const sock = new SockJs(Context.socketUrl);
|
||||||
const dd = new DiffDOM();
|
const dd = new DiffDOM();
|
||||||
sock.onopen = () => {
|
|
||||||
sock.send(document.cookie);
|
|
||||||
};
|
|
||||||
sock.onmessage = (message) => {
|
sock.onmessage = (message) => {
|
||||||
const msg = JSON.parse(message.data);
|
const msg = JSON.parse(message.data);
|
||||||
const newStatus = $(msg.status_html);
|
const newStatus = $(msg.status_html);
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ const page = new NamedPage('record_main', async () => {
|
|||||||
const dd = new DiffDOM();
|
const dd = new DiffDOM();
|
||||||
|
|
||||||
sock.onopen = () => {
|
sock.onopen = () => {
|
||||||
sock.send(document.cookie);
|
sock.send(JSON.stringify({ rids: Context.rids }));
|
||||||
setTimeout(() => {
|
|
||||||
sock.send(JSON.stringify({ rids: Context.rids }));
|
|
||||||
}, 300);
|
|
||||||
};
|
};
|
||||||
sock.onmessage = (message) => {
|
sock.onmessage = (message) => {
|
||||||
const msg = JSON.parse(message.data);
|
const msg = JSON.parse(message.data);
|
||||||
|
|||||||
Reference in new issue
Block a user