Compare commits
14 Commits
ef80c0910c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 95d263e139 | |||
| 782013f3a3 | |||
| a5ed64f866 | |||
| 88e3494dcf | |||
| abe16dcf66 | |||
| 03a32ca371 | |||
| 09a4fae432 | |||
| 5c173b2695 | |||
| 71493aac51 | |||
| 8fb1850044 | |||
| bbfcbfcae6 | |||
| cd2903c0df | |||
| d873d99b23 | |||
| 1a5392d942 |
5
apps/bookclub.json
Normal file
5
apps/bookclub.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"type": "tildefriends-app",
|
||||||
|
"emoji": "📚",
|
||||||
|
"previous": "&yLHlvKirJEqrekP5lf5BydvzIo/vN+z7K2ACQacxJXE=.sha256"
|
||||||
|
}
|
||||||
181
apps/bookclub/app.js
Normal file
181
apps/bookclub/app.js
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import * as commonmark from './commonmark.min.js';
|
||||||
|
|
||||||
|
async function query(sql, args) {
|
||||||
|
let result = [];
|
||||||
|
await ssb.sqlAsync(sql, args, function (row) {
|
||||||
|
result.push(row);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function image(node, entering) {
|
||||||
|
if (
|
||||||
|
node.firstChild?.type === 'text' &&
|
||||||
|
node.firstChild.literal.startsWith('video:')
|
||||||
|
) {
|
||||||
|
if (entering) {
|
||||||
|
this.lit(
|
||||||
|
'<video style="max-width: 100%; max-height: 480px" title="' +
|
||||||
|
this.esc(node.firstChild?.literal) +
|
||||||
|
'" controls>'
|
||||||
|
);
|
||||||
|
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
||||||
|
this.disableTags += 1;
|
||||||
|
} else {
|
||||||
|
this.disableTags -= 1;
|
||||||
|
this.lit('</video>');
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
node.firstChild?.type === 'text' &&
|
||||||
|
node.firstChild.literal.startsWith('audio:')
|
||||||
|
) {
|
||||||
|
if (entering) {
|
||||||
|
this.lit(
|
||||||
|
'<audio style="height: 32px; max-width: 100%" title="' +
|
||||||
|
this.esc(node.firstChild?.literal) +
|
||||||
|
'" controls>'
|
||||||
|
);
|
||||||
|
this.lit('<source src="' + this.esc(node.destination) + '"></source>');
|
||||||
|
this.disableTags += 1;
|
||||||
|
} else {
|
||||||
|
this.disableTags -= 1;
|
||||||
|
this.lit('</audio>');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (entering) {
|
||||||
|
if (this.disableTags === 0) {
|
||||||
|
this.lit(
|
||||||
|
'<div class="img_caption">' +
|
||||||
|
this.esc(node.firstChild?.literal || node.destination) +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
||||||
|
this.lit('<img src="" title="');
|
||||||
|
} else {
|
||||||
|
this.lit('<img src="' + this.esc(node.destination) + '" title="');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.disableTags += 1;
|
||||||
|
} else {
|
||||||
|
this.disableTags -= 1;
|
||||||
|
if (this.disableTags === 0) {
|
||||||
|
if (node.title) {
|
||||||
|
this.lit('" title="' + this.esc(node.title));
|
||||||
|
}
|
||||||
|
this.lit('" />');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function code(node) {
|
||||||
|
let attrs = this.attrs(node);
|
||||||
|
attrs.push(['class', k_code_classes]);
|
||||||
|
this.tag('code', attrs);
|
||||||
|
this.out(node.literal);
|
||||||
|
this.tag('/code');
|
||||||
|
}
|
||||||
|
|
||||||
|
function attrs(node) {
|
||||||
|
let result = commonmark.HtmlRenderer.prototype.attrs.bind(this)(node);
|
||||||
|
if (node.type == 'block_quote') {
|
||||||
|
result.push(['class', 'w3-theme-d1']);
|
||||||
|
} else if (node.type == 'code_block') {
|
||||||
|
result.push(['class', k_code_classes]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function markdown(md) {
|
||||||
|
let reader = new commonmark.Parser();
|
||||||
|
let writer = new commonmark.HtmlRenderer({safe: true});
|
||||||
|
//writer.image = image;
|
||||||
|
writer.code = code;
|
||||||
|
writer.attrs = attrs;
|
||||||
|
let parsed = reader.parse(md || '');
|
||||||
|
let walker = parsed.walker();
|
||||||
|
let event, node;
|
||||||
|
while ((event = walker.next())) {
|
||||||
|
node = event.node;
|
||||||
|
if (event.entering) {
|
||||||
|
if (node.type == 'link') {
|
||||||
|
if (
|
||||||
|
node.destination.startsWith('@') &&
|
||||||
|
node.destination.endsWith('.ed25519')
|
||||||
|
) {
|
||||||
|
node.destination = '#' + encodeURIComponent(node.destination);
|
||||||
|
} else if (
|
||||||
|
node.destination.startsWith('%') &&
|
||||||
|
node.destination.endsWith('.sha256')
|
||||||
|
) {
|
||||||
|
node.destination = '#' + encodeURIComponent(node.destination);
|
||||||
|
} else if (
|
||||||
|
node.destination.startsWith('&') &&
|
||||||
|
node.destination.endsWith('.sha256')
|
||||||
|
) {
|
||||||
|
node.destination = '/' + node.destination + '/view';
|
||||||
|
}
|
||||||
|
} else if (node.type == 'image') {
|
||||||
|
if (node.destination.startsWith('&')) {
|
||||||
|
node.destination = '/' + node.destination + '/view';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return writer.render(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
let data = await query(`
|
||||||
|
SELECT
|
||||||
|
content ->> 'title' AS title,
|
||||||
|
content ->> '$.image.link' AS image,
|
||||||
|
content ->> 'description' AS description
|
||||||
|
FROM messages
|
||||||
|
WHERE
|
||||||
|
content ->> 'type' = 'bookclub' AND
|
||||||
|
title IS NOT NULL AND
|
||||||
|
image IS NOT NULL AND
|
||||||
|
description IS NOT NULL
|
||||||
|
`);
|
||||||
|
if (!data?.length) {
|
||||||
|
await app.setDocument(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body style="background-color: #fff">
|
||||||
|
<p>No bookclub messages found.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await app.setDocument(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="w3.css">
|
||||||
|
</head>
|
||||||
|
<body class="w3-grid" style="background-color: #fff; gap:8px;grid-template-columns:repeat(auto-fit, minmax(4in,1fr))">
|
||||||
|
${data
|
||||||
|
.map(
|
||||||
|
(x) => `
|
||||||
|
<div class="w3-card-4">
|
||||||
|
<header class="w3-container w3-center">
|
||||||
|
<h1>${markdown(x.title)}</h1>
|
||||||
|
</header>
|
||||||
|
<div class="w3-container w3-center">
|
||||||
|
<img src="/${x.image}/view" style="max-height: 2in; max-width: 2in">
|
||||||
|
</div>
|
||||||
|
<div class="w3-container">
|
||||||
|
<p>${markdown(x.description)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join('\n')}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
1
apps/bookclub/commonmark.min.js
vendored
Normal file
1
apps/bookclub/commonmark.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
apps/bookclub/index.html
Normal file
6
apps/bookclub/index.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
${BODY}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
251
apps/bookclub/w3.css
Normal file
251
apps/bookclub/w3.css
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/* W3.CSS 5.02 March 31 2025 by Jan Egil and Borge Refsnes */
|
||||||
|
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||||
|
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||||
|
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||||
|
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
|
||||||
|
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
|
||||||
|
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
|
||||||
|
a{background-color:transparent}a:active,a:hover{outline-width:0}
|
||||||
|
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
|
||||||
|
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
|
||||||
|
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
|
||||||
|
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
|
||||||
|
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
|
||||||
|
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
|
||||||
|
button,input{overflow:visible}button,select{text-transform:none}
|
||||||
|
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
|
||||||
|
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
|
||||||
|
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
|
||||||
|
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
|
||||||
|
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
|
||||||
|
[type=checkbox],[type=radio]{padding:0}
|
||||||
|
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
|
||||||
|
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
|
||||||
|
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
|
||||||
|
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
|
||||||
|
/* End extract */
|
||||||
|
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
|
||||||
|
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
|
||||||
|
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
|
||||||
|
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
|
||||||
|
hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||||
|
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
|
||||||
|
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
|
||||||
|
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
|
||||||
|
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
|
||||||
|
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
|
||||||
|
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
|
||||||
|
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
||||||
|
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
||||||
|
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
||||||
|
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||||
|
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
||||||
|
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
||||||
|
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
||||||
|
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
|
||||||
|
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
|
||||||
|
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
|
||||||
|
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
|
||||||
|
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
|
||||||
|
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
|
||||||
|
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
|
||||||
|
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
|
||||||
|
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
|
||||||
|
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
|
||||||
|
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
|
||||||
|
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
|
||||||
|
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
|
||||||
|
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
|
||||||
|
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
|
||||||
|
.w3-main,#main{transition:margin-left .4s}
|
||||||
|
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
|
||||||
|
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
|
||||||
|
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
|
||||||
|
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
|
||||||
|
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
|
||||||
|
.w3-bar .w3-button{white-space:normal}
|
||||||
|
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
|
||||||
|
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
|
||||||
|
.w3-responsive{display:block;overflow-x:auto}
|
||||||
|
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
|
||||||
|
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
|
||||||
|
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
|
||||||
|
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
|
||||||
|
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
|
||||||
|
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
|
||||||
|
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
|
||||||
|
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
|
||||||
|
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
|
||||||
|
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
|
||||||
|
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
|
||||||
|
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
|
||||||
|
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
|
||||||
|
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
|
||||||
|
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
|
||||||
|
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
|
||||||
|
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
||||||
|
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
||||||
|
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
||||||
|
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||||
|
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
||||||
|
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
||||||
|
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
||||||
|
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
|
||||||
|
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
|
||||||
|
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
|
||||||
|
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
|
||||||
|
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
|
||||||
|
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
|
||||||
|
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
|
||||||
|
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
|
||||||
|
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
|
||||||
|
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
|
||||||
|
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||||
|
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
|
||||||
|
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
|
||||||
|
.w3-display-position{position:absolute}
|
||||||
|
.w3-circle{border-radius:50%}
|
||||||
|
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||||
|
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||||
|
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||||
|
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||||
|
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||||
|
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||||
|
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||||
|
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||||
|
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
|
||||||
|
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
|
||||||
|
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
|
||||||
|
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
|
||||||
|
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
|
||||||
|
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
|
||||||
|
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
|
||||||
|
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
|
||||||
|
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
|
||||||
|
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
|
||||||
|
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
|
||||||
|
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
|
||||||
|
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
|
||||||
|
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
|
||||||
|
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
|
||||||
|
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
|
||||||
|
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
|
||||||
|
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
|
||||||
|
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
|
||||||
|
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
|
||||||
|
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
|
||||||
|
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
|
||||||
|
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
|
||||||
|
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
|
||||||
|
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
|
||||||
|
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
|
||||||
|
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
|
||||||
|
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
|
||||||
|
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
|
||||||
|
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
|
||||||
|
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
|
||||||
|
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
|
||||||
|
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
|
||||||
|
.w3-left{float:left!important}.w3-right{float:right!important}
|
||||||
|
.w3-button:hover{color:#000!important;background-color:#ccc!important}
|
||||||
|
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||||
|
.w3-hover-none:hover{box-shadow:none!important}
|
||||||
|
.w3-rtl{direction:rtl}.w3-ltr{direction:ltr}
|
||||||
|
/* Colors */
|
||||||
|
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||||
|
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||||
|
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||||
|
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||||
|
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||||
|
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||||
|
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
|
||||||
|
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
|
||||||
|
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
|
||||||
|
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
|
||||||
|
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
|
||||||
|
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
|
||||||
|
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
|
||||||
|
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
|
||||||
|
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||||
|
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||||
|
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||||
|
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||||
|
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||||
|
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||||
|
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||||
|
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||||
|
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||||
|
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||||
|
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||||
|
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||||
|
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}
|
||||||
|
.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||||
|
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||||
|
.w3-emerald,.w3-hover-emerald:hover{color:#fff!important;background-color:#008a00!important}
|
||||||
|
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||||
|
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}
|
||||||
|
.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||||
|
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||||
|
.w3-danger{color:#fff!important;background-color:#dd0000!important}
|
||||||
|
.w3-note{color:#000!important;background-color:#fff599!important}
|
||||||
|
.w3-info{color:#fff!important;background-color:#0a6fc2!important}
|
||||||
|
.w3-warning{color:#000!important;background-color:#ffb305!important}
|
||||||
|
.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||||
|
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||||
|
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||||
|
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||||
|
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
|
||||||
|
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
|
||||||
|
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
|
||||||
|
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
|
||||||
|
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
|
||||||
|
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
|
||||||
|
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
|
||||||
|
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
|
||||||
|
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
|
||||||
|
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
|
||||||
|
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
|
||||||
|
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
|
||||||
|
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
|
||||||
|
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
|
||||||
|
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
|
||||||
|
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
|
||||||
|
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
|
||||||
|
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
|
||||||
|
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
|
||||||
|
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
|
||||||
|
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
|
||||||
|
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
|
||||||
|
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
|
||||||
|
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
|
||||||
|
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
|
||||||
|
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
|
||||||
|
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
|
||||||
|
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
|
||||||
|
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
|
||||||
|
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
|
||||||
|
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
|
||||||
|
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
|
||||||
|
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
|
||||||
|
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
|
||||||
|
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
|
||||||
|
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
|
||||||
|
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
|
||||||
|
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
|
||||||
|
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
|
||||||
|
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
|
||||||
|
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
|
||||||
|
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
|
||||||
|
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
|
||||||
|
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
|
||||||
|
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
|
||||||
|
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
|
||||||
|
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
|
||||||
|
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
|
||||||
|
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
|
||||||
|
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
|
||||||
|
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
|
||||||
|
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||||
|
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||||
|
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||||
|
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🦀",
|
"emoji": "🦀",
|
||||||
"previous": "&JDcwV6jqSMWEgYqq2qRmYDPVhVS5yoasohZnH077RW4=.sha256"
|
"previous": "&eqeAxU0q6n0RZDSd68j44hQ4UtssESqgohsCXN/otwY=.sha256"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -612,7 +612,7 @@ class TfElement extends LitElement {
|
|||||||
by_count.push({count: v.of, id: id});
|
by_count.push({count: v.of, id: id});
|
||||||
}
|
}
|
||||||
let reactions = this.load_recent_reactions();
|
let reactions = this.load_recent_reactions();
|
||||||
this.load_channels_latest(Object.keys(following));
|
let channels = this.load_channels_latest(Object.keys(following));
|
||||||
this.channels_unread = JSON.parse(
|
this.channels_unread = JSON.parse(
|
||||||
(await tfrpc.rpc.databaseGet('unread')) ?? '{}'
|
(await tfrpc.rpc.databaseGet('unread')) ?? '{}'
|
||||||
);
|
);
|
||||||
@@ -625,6 +625,7 @@ class TfElement extends LitElement {
|
|||||||
self.users = result;
|
self.users = result;
|
||||||
});
|
});
|
||||||
await reactions;
|
await reactions;
|
||||||
|
await channels;
|
||||||
this.whoami = whoami;
|
this.whoami = whoami;
|
||||||
this.loaded = whoami;
|
this.loaded = whoami;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -707,9 +708,7 @@ class TfElement extends LitElement {
|
|||||||
.following=${this.following}
|
.following=${this.following}
|
||||||
whoami=${this.whoami}
|
whoami=${this.whoami}
|
||||||
.users=${this.users}
|
.users=${this.users}
|
||||||
query=${this.hash?.startsWith('#q=')
|
query=${this.search_text()}
|
||||||
? decodeURIComponent(this.hash.substring(3))
|
|
||||||
: null}
|
|
||||||
></tf-tab-search>
|
></tf-tab-search>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -758,7 +757,7 @@ class TfElement extends LitElement {
|
|||||||
search_text.focus();
|
search_text.focus();
|
||||||
this.set_tab('search');
|
this.set_tab('search');
|
||||||
} else {
|
} else {
|
||||||
this.set_hash('#q=' + search_text.value);
|
this.set_hash('#q=' + encodeURIComponent(search_text.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,6 +767,16 @@ class TfElement extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
search_text() {
|
||||||
|
if (this.hash.startsWith('#q=')) {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(this.hash.substring('#q='.length));
|
||||||
|
} catch {
|
||||||
|
return this.hash.substring('#q='.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
@@ -784,6 +793,12 @@ class TfElement extends LitElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let tabs = html`
|
let tabs = html`
|
||||||
|
<style>
|
||||||
|
#search_text:focus {
|
||||||
|
float: none !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<div
|
<div
|
||||||
class="w3-bar w3-theme-l1"
|
class="w3-bar w3-theme-l1"
|
||||||
style="position: static; top: 0; z-index: 10"
|
style="position: static; top: 0; z-index: 10"
|
||||||
@@ -832,7 +847,7 @@ class TfElement extends LitElement {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
<button class="w3-bar-item w3-button w3-right" @click=${this.search}>🔍<span class="w3-hide-small">Search</span></button>
|
<button class="w3-bar-item w3-button w3-right" @click=${this.search}>🔍<span class="w3-hide-small">Search</span></button>
|
||||||
<input type="text" class=${'w3-input w3-bar-item w3-right w3-theme-d1' + (this.tab == 'search' ? ' w3-mobile' : ' w3-hide-small')} placeholder="keywords, @id, #channel" id="search_text" @keydown=${this.search_keydown}></input>
|
<input type="text" class=${'w3-input w3-bar-item w3-right w3-theme-d1' + (this.tab == 'search' ? ' w3-mobile' : ' w3-hide-small')} placeholder="keywords, @id, #channel" id="search_text" @keydown=${this.search_keydown} value=${this.search_text()}></input>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
let contents = this.guest
|
let contents = this.guest
|
||||||
|
|||||||
@@ -648,21 +648,25 @@ class TfMessageElement extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contact_description(content) {
|
||||||
|
return content.following && content.blocking
|
||||||
|
? 'following and blocking'
|
||||||
|
: content.following
|
||||||
|
? 'following'
|
||||||
|
: content.blocking
|
||||||
|
? 'blocking'
|
||||||
|
: content.blocking !== undefined
|
||||||
|
? 'no longer blocking'
|
||||||
|
: content.following !== undefined
|
||||||
|
? 'no longer following'
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
content_group_by_author() {
|
content_group_by_author() {
|
||||||
let sorted = this.message.messages
|
let sorted = this.message.messages
|
||||||
.map((x) => [
|
.map((x) => [
|
||||||
x.author,
|
x.author,
|
||||||
x.content.following && x.content.blocking
|
this.contact_description(x.content),
|
||||||
? 'is following and blocking'
|
|
||||||
: x.content.following
|
|
||||||
? 'is following'
|
|
||||||
: x.content.blocking
|
|
||||||
? 'is blocking'
|
|
||||||
: x.content.blocking !== undefined
|
|
||||||
? 'is no longer blocking'
|
|
||||||
: x.content.following !== undefined
|
|
||||||
? 'is no longer following'
|
|
||||||
: '',
|
|
||||||
x.content.contact,
|
x.content.contact,
|
||||||
x,
|
x,
|
||||||
])
|
])
|
||||||
@@ -969,16 +973,7 @@ class TfMessageElement extends LitElement {
|
|||||||
id=${this.message.author}
|
id=${this.message.author}
|
||||||
.users=${this.users}
|
.users=${this.users}
|
||||||
></tf-user>
|
></tf-user>
|
||||||
is
|
is ${this.contact_description(content)}
|
||||||
${content.blocking === true
|
|
||||||
? 'blocking'
|
|
||||||
: content.blocking === false
|
|
||||||
? 'no longer blocking'
|
|
||||||
: content.following === true
|
|
||||||
? 'following'
|
|
||||||
: content.following === false
|
|
||||||
? 'no longer following'
|
|
||||||
: '?'}
|
|
||||||
<tf-user
|
<tf-user
|
||||||
id=${this.message.content.contact}
|
id=${this.message.content.contact}
|
||||||
.users=${this.users}
|
.users=${this.users}
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ class TfNewsElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function link_message(message) {
|
function link_message(message) {
|
||||||
|
if (!message.content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (message.content.type === 'vote') {
|
if (message.content.type === 'vote') {
|
||||||
let parent = ensure_message(message.content.vote.link, message.rowid);
|
let parent = ensure_message(message.content.vote.link, message.rowid);
|
||||||
if (!parent.votes) {
|
if (!parent.votes) {
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ class TfProfileElement extends LitElement {
|
|||||||
|
|
||||||
async load_follows() {
|
async load_follows() {
|
||||||
let accounts = await tfrpc.rpc.following([this.id], 1);
|
let accounts = await tfrpc.rpc.following([this.id], 1);
|
||||||
|
delete accounts[this.id];
|
||||||
return html`
|
return html`
|
||||||
<div class="w3-container">
|
<div class="w3-container">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -398,12 +398,19 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
make_messages_key() {
|
||||||
|
return JSON.stringify([
|
||||||
|
this.hash,
|
||||||
|
Object.keys(this.channels_latest ?? {}).filter((x) => x != '🔐'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
async load_messages() {
|
async load_messages() {
|
||||||
let start_time = new Date();
|
let start_time = new Date();
|
||||||
let self = this;
|
let self = this;
|
||||||
this.loading++;
|
this.loading++;
|
||||||
let messages = [];
|
let messages = [];
|
||||||
let original_key = JSON.stringify([this.hash, this.channels_latest ?? {}]);
|
let original_key = this.make_messages_key();
|
||||||
try {
|
try {
|
||||||
if (this._messages_key !== original_key) {
|
if (this._messages_key !== original_key) {
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
@@ -429,7 +436,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
} finally {
|
} finally {
|
||||||
this.loading--;
|
this.loading--;
|
||||||
}
|
}
|
||||||
let current_key = JSON.stringify([this.hash, this.channels_latest ?? {}]);
|
let current_key = this.make_messages_key();
|
||||||
if (current_key === original_key) {
|
if (current_key === original_key) {
|
||||||
this.messages = this.merge_messages(this.messages, messages);
|
this.messages = this.merge_messages(this.messages, messages);
|
||||||
}
|
}
|
||||||
@@ -486,19 +493,17 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
render() {
|
render() {
|
||||||
if (
|
if (
|
||||||
!this.messages ||
|
!this.messages ||
|
||||||
this._messages_key !==
|
this._messages_key !== this.make_messages_key() ||
|
||||||
JSON.stringify([this.hash, this.channels_latest ?? {}]) ||
|
|
||||||
this._messages_following !== JSON.stringify(this.following) ||
|
this._messages_following !== JSON.stringify(this.following) ||
|
||||||
|
(this.hash.startsWith('#🔐') &&
|
||||||
this._private_messages !==
|
this._private_messages !==
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
this.private_messages,
|
this.private_messages,
|
||||||
this.grouped_private_messages,
|
this.grouped_private_messages,
|
||||||
]) ||
|
]))
|
||||||
this._channels_latest !==
|
|
||||||
JSON.stringify(Object.keys(this.channels_latest))
|
|
||||||
) {
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
`loading messages for ${this.whoami} (messages=${!this.messages},${this._messages_hash != this.hash} following=${this._messages_following !== JSON.stringify(this.following)}, channels=${this._channels_latest !== JSON.stringify(Object.keys(this.channels_latest))}, private=${this._private_messages !== JSON.stringify([this.private_messages, this.grouped_private_messages])},${this.private_messages?.length},${Object.keys(this.grouped_private_messages ?? {}).length})`
|
`loading messages for ${this.whoami} (messages=${!this.messages},${this._messages_key != this.make_messages_key()} following=${this._messages_following !== JSON.stringify(this.following)}, private=${this._private_messages !== JSON.stringify([this.private_messages, this.grouped_private_messages])},${this.private_messages?.length},${Object.keys(this.grouped_private_messages ?? {}).length})`
|
||||||
);
|
);
|
||||||
this.load_messages();
|
this.load_messages();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,6 +375,14 @@ class TfTabNewsElement extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
recipients() {
|
||||||
|
if (this.hash == '#🔐') {
|
||||||
|
return [this.whoami];
|
||||||
|
} else if (this.hash.startsWith('#🔐')) {
|
||||||
|
return this.hash.substring('#🔐'.length).split(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let profile =
|
let profile =
|
||||||
this.hash.startsWith('#@') && this.hash != '#@'
|
this.hash.startsWith('#@') && this.hash != '#@'
|
||||||
@@ -450,9 +458,7 @@ class TfTabNewsElement extends LitElement {
|
|||||||
.drafts=${this.drafts}
|
.drafts=${this.drafts}
|
||||||
@tf-draft=${this.draft}
|
@tf-draft=${this.draft}
|
||||||
.channel=${this.channel()}
|
.channel=${this.channel()}
|
||||||
.recipients=${this.hash.startsWith('#🔐')
|
.recipients=${this.recipients()}
|
||||||
? this.hash.substring('#🔐'.length).split(',')
|
|
||||||
: undefined}
|
|
||||||
></tf-compose>
|
></tf-compose>
|
||||||
</div>
|
</div>
|
||||||
${profile}
|
${profile}
|
||||||
|
|||||||
@@ -44,17 +44,13 @@ class TfTabSearchElement extends LitElement {
|
|||||||
this.error = undefined;
|
this.error = undefined;
|
||||||
this.results = [];
|
this.results = [];
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
|
try {
|
||||||
if (query.startsWith('sql:')) {
|
if (query.startsWith('sql:')) {
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
try {
|
|
||||||
this.results = await tfrpc.rpc.query(
|
this.results = await tfrpc.rpc.query(
|
||||||
query.substring('sql:'.length),
|
query.substring('sql:'.length),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
} catch (e) {
|
|
||||||
this.results = [];
|
|
||||||
this.error = e;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let results = await tfrpc.rpc.query(
|
let results = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
@@ -66,7 +62,6 @@ class TfTabSearchElement extends LitElement {
|
|||||||
`,
|
`,
|
||||||
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
|
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
|
||||||
);
|
);
|
||||||
console.log('Done.');
|
|
||||||
search = this.renderRoot.getElementById('search');
|
search = this.renderRoot.getElementById('search');
|
||||||
if (search) {
|
if (search) {
|
||||||
search.value = query;
|
search.value = query;
|
||||||
@@ -75,6 +70,12 @@ class TfTabSearchElement extends LitElement {
|
|||||||
}
|
}
|
||||||
this.messages = results;
|
this.messages = results;
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.messages = [];
|
||||||
|
this.results = [];
|
||||||
|
this.error = e;
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
on_expand(event) {
|
on_expand(event) {
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ class TfUserElement extends LitElement {
|
|||||||
name = this.icon_only
|
name = this.icon_only
|
||||||
? undefined
|
? undefined
|
||||||
: !this.nolink
|
: !this.nolink
|
||||||
? html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`
|
? html`<a target="_top" href=${'#' + encodeURIComponent(this.id)}
|
||||||
|
>${name_string}</a
|
||||||
|
>`
|
||||||
: html`<span>${name_string}</span>`;
|
: html`<span>${name_string}</span>`;
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|||||||
164
core/core.js
164
core/core.js
@@ -14,64 +14,6 @@ let g_handler_index = 0;
|
|||||||
/** Whether updating accounts information is currently scheduled. */
|
/** Whether updating accounts information is currently scheduled. */
|
||||||
let g_update_accounts_scheduled;
|
let g_update_accounts_scheduled;
|
||||||
|
|
||||||
/**
|
|
||||||
** App constructor.
|
|
||||||
** @return An app instance.
|
|
||||||
*/
|
|
||||||
function App() {
|
|
||||||
this._send_queue = [];
|
|
||||||
this.calls = {};
|
|
||||||
this._next_call_id = 1;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
** Create a function wrapper that when called invokes a function on the app
|
|
||||||
** itself.
|
|
||||||
** @param api The function and argument names.
|
|
||||||
** @return A function.
|
|
||||||
*/
|
|
||||||
App.prototype.makeFunction = function (api) {
|
|
||||||
let self = this;
|
|
||||||
let result = function () {
|
|
||||||
let id = self._next_call_id++;
|
|
||||||
while (!id || self.calls[id]) {
|
|
||||||
id = self._next_call_id++;
|
|
||||||
}
|
|
||||||
let promise = new Promise(function (resolve, reject) {
|
|
||||||
self.calls[id] = {resolve: resolve, reject: reject};
|
|
||||||
});
|
|
||||||
let message = {
|
|
||||||
action: 'tfrpc',
|
|
||||||
method: api[0],
|
|
||||||
params: [...arguments],
|
|
||||||
id: id,
|
|
||||||
};
|
|
||||||
self.send(message);
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
Object.defineProperty(result, 'name', {value: api[0], writable: false});
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
** Send a message to the app.
|
|
||||||
** @param message The message to send.
|
|
||||||
*/
|
|
||||||
App.prototype.send = function (message) {
|
|
||||||
if (this._send_queue) {
|
|
||||||
if (this._on_output) {
|
|
||||||
this._send_queue.forEach((x) => this._on_output(x));
|
|
||||||
this._send_queue = null;
|
|
||||||
} else if (message) {
|
|
||||||
this._send_queue.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (message && this._on_output) {
|
|
||||||
this._on_output(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print an error.
|
* Print an error.
|
||||||
* @param error The error.
|
* @param error The error.
|
||||||
@@ -233,7 +175,58 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
process.url = options?.url;
|
process.url = options?.url;
|
||||||
process.eventHandlers = {};
|
process.eventHandlers = {};
|
||||||
if (!options?.script || options?.script === 'app.js') {
|
if (!options?.script || options?.script === 'app.js') {
|
||||||
process.app = new App();
|
process._send_queue = [];
|
||||||
|
process._calls = {};
|
||||||
|
process._next_call_id = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Create a function wrapper that when called invokes a function on the app
|
||||||
|
** itself.
|
||||||
|
** @param api The function and argument names.
|
||||||
|
** @return A function.
|
||||||
|
*/
|
||||||
|
process.makeFunction = function (api) {
|
||||||
|
let result = function () {
|
||||||
|
let id = process._next_call_id++;
|
||||||
|
while (!id || process._calls[id]) {
|
||||||
|
id = process._next_call_id++;
|
||||||
|
}
|
||||||
|
let promise = new Promise(function (resolve, reject) {
|
||||||
|
process._calls[id] = {resolve: resolve, reject: reject};
|
||||||
|
});
|
||||||
|
let message = {
|
||||||
|
action: 'tfrpc',
|
||||||
|
method: api[0],
|
||||||
|
params: [...arguments],
|
||||||
|
id: id,
|
||||||
|
};
|
||||||
|
process.send(message);
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
Object.defineProperty(result, 'name', {
|
||||||
|
value: api[0],
|
||||||
|
writable: false,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Send a message to the app.
|
||||||
|
** @param message The message to send.
|
||||||
|
*/
|
||||||
|
process.send = function (message) {
|
||||||
|
if (process._send_queue) {
|
||||||
|
if (process._on_output) {
|
||||||
|
process._send_queue.forEach((x) => process._on_output(x));
|
||||||
|
process._send_queue = null;
|
||||||
|
} else if (message) {
|
||||||
|
process._send_queue.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (message && process._on_output) {
|
||||||
|
process._on_output(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
process.ready = new Promise(function (resolve, reject) {
|
process.ready = new Promise(function (resolve, reject) {
|
||||||
resolveReady = resolve;
|
resolveReady = resolve;
|
||||||
@@ -286,8 +279,8 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
} else {
|
} else {
|
||||||
throw Error(`Permission denied: ${permission}.`);
|
throw Error(`Permission denied: ${permission}.`);
|
||||||
}
|
}
|
||||||
} else if (process.app) {
|
} else if (process.makeFunction) {
|
||||||
return process.app
|
return process
|
||||||
.makeFunction(['requestPermission'])(permission, description)
|
.makeFunction(['requestPermission'])(permission, description)
|
||||||
.then(async function (value) {
|
.then(async function (value) {
|
||||||
if (value == 'allow') {
|
if (value == 'allow') {
|
||||||
@@ -331,7 +324,7 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
);
|
);
|
||||||
let json = JSON.stringify(identities);
|
let json = JSON.stringify(identities);
|
||||||
if (process._last_sent_identities !== json) {
|
if (process._last_sent_identities !== json) {
|
||||||
process.app.send(
|
process.send(
|
||||||
Object.assign(
|
Object.assign(
|
||||||
{
|
{
|
||||||
action: 'identities',
|
action: 'identities',
|
||||||
@@ -393,7 +386,7 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
imports.app = {};
|
imports.app = {};
|
||||||
for (let i in options.api) {
|
for (let i in options.api) {
|
||||||
let api = options.api[i];
|
let api = options.api[i];
|
||||||
imports.app[api[0]] = process.app.makeFunction(api);
|
imports.app[api[0]] = process.makeFunction(api);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let [name, f] of Object.entries(options?.imports || {})) {
|
for (let [name, f] of Object.entries(options?.imports || {})) {
|
||||||
@@ -406,8 +399,8 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
};
|
};
|
||||||
process.task.onError = function (error) {
|
process.task.onError = function (error) {
|
||||||
try {
|
try {
|
||||||
if (process.app) {
|
if (process.makeFunction) {
|
||||||
process.app.makeFunction(['error'])(error);
|
process.makeFunction(['error'])(error);
|
||||||
} else {
|
} else {
|
||||||
printError(error);
|
printError(error);
|
||||||
}
|
}
|
||||||
@@ -486,33 +479,6 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
imports.ssb.privateMessageEncrypt = function (id, recipients, message) {
|
|
||||||
if (
|
|
||||||
process.credentials &&
|
|
||||||
process.credentials.session &&
|
|
||||||
process.credentials.session.name
|
|
||||||
) {
|
|
||||||
return ssb.privateMessageEncrypt(
|
|
||||||
process.credentials.session.name,
|
|
||||||
id,
|
|
||||||
recipients,
|
|
||||||
message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
imports.ssb.privateMessageDecrypt = function (id, message) {
|
|
||||||
if (
|
|
||||||
process.credentials &&
|
|
||||||
process.credentials.session &&
|
|
||||||
process.credentials.session.name
|
|
||||||
) {
|
|
||||||
return ssb.privateMessageDecrypt(
|
|
||||||
process.credentials.session.name,
|
|
||||||
id,
|
|
||||||
message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (
|
if (
|
||||||
process.credentials &&
|
process.credentials &&
|
||||||
process.credentials.session &&
|
process.credentials.session &&
|
||||||
@@ -562,7 +528,7 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
process.sendPermissions = async function sendPermissions() {
|
process.sendPermissions = async function sendPermissions() {
|
||||||
process.app.send({
|
process.send({
|
||||||
action: 'permissions',
|
action: 'permissions',
|
||||||
permissions: await imports.core.permissionsGranted(),
|
permissions: await imports.core.permissionsGranted(),
|
||||||
});
|
});
|
||||||
@@ -616,8 +582,8 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
printError(e);
|
printError(e);
|
||||||
}
|
}
|
||||||
if (process.app) {
|
if (process.send) {
|
||||||
process.app.send({action: 'ready', version: version()});
|
process.send({action: 'ready', version: version()});
|
||||||
await process.sendPermissions();
|
await process.sendPermissions();
|
||||||
}
|
}
|
||||||
await process.task.execute({name: appSourceName, source: appSource});
|
await process.task.execute({name: appSourceName, source: appSource});
|
||||||
@@ -700,13 +666,11 @@ async function loadSettings() {
|
|||||||
* Send periodic stats to all clients.
|
* Send periodic stats to all clients.
|
||||||
*/
|
*/
|
||||||
function sendStats() {
|
function sendStats() {
|
||||||
let apps = Object.values(gProcesses)
|
let apps = Object.values(gProcesses).filter((process) => process.send);
|
||||||
.filter((process) => process.app)
|
|
||||||
.map((process) => process.app);
|
|
||||||
if (apps.length) {
|
if (apps.length) {
|
||||||
let stats = getStats();
|
let stats = getStats();
|
||||||
for (let app of apps) {
|
for (let process of apps) {
|
||||||
app.send({action: 'stats', stats: stats});
|
process.send({action: 'stats', stats: stats});
|
||||||
}
|
}
|
||||||
setTimeout(sendStats, 1000);
|
setTimeout(sendStats, 1000);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
194
deps/codemirror_src/package-lock.json
generated
vendored
194
deps/codemirror_src/package-lock.json
generated
vendored
@@ -33,9 +33,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/commands": {
|
"node_modules/@codemirror/commands": {
|
||||||
"version": "6.10.0",
|
"version": "6.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.1.tgz",
|
||||||
"integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==",
|
"integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "^6.0.0",
|
"@codemirror/language": "^6.0.0",
|
||||||
@@ -186,9 +186,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/view": {
|
"node_modules/@codemirror/view": {
|
||||||
"version": "6.39.3",
|
"version": "6.39.4",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.3.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz",
|
||||||
"integrity": "sha512-ZR32LYnPMpf7XZcrYJpSrHJUHNZPTj73/amTtZLhAwzYhSKiDI2OZmCiXbTRvxL1T8X7QTHnCG+KfnRJvH/QsA==",
|
"integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.5.0",
|
"@codemirror/state": "^6.5.0",
|
||||||
@@ -412,9 +412,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz",
|
||||||
"integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
|
"integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -425,9 +425,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz",
|
||||||
"integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
|
"integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -438,9 +438,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz",
|
||||||
"integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
|
"integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -451,9 +451,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz",
|
||||||
"integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
|
"integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -464,9 +464,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz",
|
||||||
"integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
|
"integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -477,9 +477,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz",
|
||||||
"integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
|
"integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -490,9 +490,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz",
|
||||||
"integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
|
"integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -503,9 +503,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz",
|
||||||
"integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
|
"integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -516,9 +516,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
|
"integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -529,9 +529,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz",
|
||||||
"integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
|
"integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -542,9 +542,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
|
"integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -555,9 +555,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
|
"integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -568,9 +568,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
|
"integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -581,9 +581,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz",
|
||||||
"integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
|
"integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -594,9 +594,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
|
"integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -607,9 +607,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
|
"integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -620,9 +620,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz",
|
||||||
"integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
|
"integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -633,9 +633,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz",
|
||||||
"integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
|
"integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -646,9 +646,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz",
|
||||||
"integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
|
"integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -659,9 +659,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz",
|
||||||
"integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
|
"integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -672,9 +672,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
|
"integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -685,9 +685,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz",
|
||||||
"integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
|
"integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -877,9 +877,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.53.3",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz",
|
||||||
"integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
|
"integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
@@ -892,28 +892,28 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.53.3",
|
"@rollup/rollup-android-arm-eabi": "4.53.5",
|
||||||
"@rollup/rollup-android-arm64": "4.53.3",
|
"@rollup/rollup-android-arm64": "4.53.5",
|
||||||
"@rollup/rollup-darwin-arm64": "4.53.3",
|
"@rollup/rollup-darwin-arm64": "4.53.5",
|
||||||
"@rollup/rollup-darwin-x64": "4.53.3",
|
"@rollup/rollup-darwin-x64": "4.53.5",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.53.3",
|
"@rollup/rollup-freebsd-arm64": "4.53.5",
|
||||||
"@rollup/rollup-freebsd-x64": "4.53.3",
|
"@rollup/rollup-freebsd-x64": "4.53.5",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.53.5",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.53.3",
|
"@rollup/rollup-linux-arm-musleabihf": "4.53.5",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.53.3",
|
"@rollup/rollup-linux-arm64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.53.3",
|
"@rollup/rollup-linux-arm64-musl": "4.53.5",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.53.3",
|
"@rollup/rollup-linux-loong64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.53.3",
|
"@rollup/rollup-linux-ppc64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.53.3",
|
"@rollup/rollup-linux-riscv64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.53.3",
|
"@rollup/rollup-linux-riscv64-musl": "4.53.5",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.53.3",
|
"@rollup/rollup-linux-s390x-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.53.3",
|
"@rollup/rollup-linux-x64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.53.3",
|
"@rollup/rollup-linux-x64-musl": "4.53.5",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.53.3",
|
"@rollup/rollup-openharmony-arm64": "4.53.5",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.53.3",
|
"@rollup/rollup-win32-arm64-msvc": "4.53.5",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.53.3",
|
"@rollup/rollup-win32-ia32-msvc": "4.53.5",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.53.3",
|
"@rollup/rollup-win32-x64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.53.3",
|
"@rollup/rollup-win32-x64-msvc": "4.53.5",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
6
metadata/en-US/changelogs/49.txt
Normal file
6
metadata/en-US/changelogs/49.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
* Crash fixes.
|
||||||
|
* Fix channels with hyphens and various other characters not working correctly.
|
||||||
|
* Navigation bar and search UI improvements.
|
||||||
|
* Faster loads, though the first launch may be particularly slow as indexes are rebuilt.
|
||||||
|
* Fixed various broken links.
|
||||||
|
* Update CodeMirror, c-ares 1.34.6, speedscope 1.25.0, and sqlite 3.51.1.
|
||||||
316
src/api.js.c
316
src/api.js.c
@@ -7,8 +7,17 @@
|
|||||||
#include "task.h"
|
#include "task.h"
|
||||||
#include "util.js.h"
|
#include "util.js.h"
|
||||||
|
|
||||||
#include <quickjs.h>
|
#include "quickjs.h"
|
||||||
|
#include "sodium/crypto_box.h"
|
||||||
|
#include "sodium/crypto_scalarmult.h"
|
||||||
|
#include "sodium/crypto_scalarmult_curve25519.h"
|
||||||
|
#include "sodium/crypto_scalarmult_ed25519.h"
|
||||||
|
#include "sodium/crypto_secretbox.h"
|
||||||
|
#include "sodium/crypto_sign.h"
|
||||||
|
#include "sodium/randombytes.h"
|
||||||
|
#include "sqlite3.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
@@ -1219,6 +1228,309 @@ static JSValue _tf_ssb_swap_with_server_identity(JSContext* context, JSValueCons
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool _tf_ssb_get_private_key_curve25519_internal(sqlite3* db, const char* user, const char* identity, uint8_t out_private_key[static crypto_sign_SECRETKEYBYTES])
|
||||||
|
{
|
||||||
|
if (!user || !identity)
|
||||||
|
{
|
||||||
|
tf_printf("user=%p identity=%p out_private_key=%p\n", user, identity, out_private_key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
sqlite3_stmt* statement = NULL;
|
||||||
|
if (sqlite3_prepare_v2(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK)
|
||||||
|
{
|
||||||
|
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, *identity == '@' ? identity + 1 : identity, -1, NULL) == SQLITE_OK)
|
||||||
|
{
|
||||||
|
while (sqlite3_step(statement) == SQLITE_ROW)
|
||||||
|
{
|
||||||
|
uint8_t key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||||
|
int length = tf_base64_decode((const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0) - strlen(".ed25519"), key, sizeof(key));
|
||||||
|
if (length == crypto_sign_SECRETKEYBYTES)
|
||||||
|
{
|
||||||
|
success = crypto_sign_ed25519_sk_to_curve25519(out_private_key, key) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _tf_ssb_get_private_key_curve25519(tf_ssb_t* ssb, sqlite3* db, const char* user, const char* identity, uint8_t out_private_key[static crypto_sign_SECRETKEYBYTES])
|
||||||
|
{
|
||||||
|
if (_tf_ssb_get_private_key_curve25519_internal(db, user, identity, out_private_key))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tf_ssb_db_user_has_permission(ssb, db, user, "administration"))
|
||||||
|
{
|
||||||
|
return _tf_ssb_get_private_key_curve25519_internal(db, ":admin", identity, out_private_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct _private_message_encrypt_t
|
||||||
|
{
|
||||||
|
const char* signer_user;
|
||||||
|
const char* signer_identity;
|
||||||
|
const char* recipients[k_max_private_message_recipients];
|
||||||
|
int recipient_count;
|
||||||
|
const char* message;
|
||||||
|
size_t message_size;
|
||||||
|
JSValue promise[2];
|
||||||
|
bool error_id_not_found;
|
||||||
|
char* encrypted;
|
||||||
|
size_t encrypted_length;
|
||||||
|
} private_message_encrypt_t;
|
||||||
|
|
||||||
|
static void _tf_ssb_private_message_encrypt_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
private_message_encrypt_t* work = user_data;
|
||||||
|
|
||||||
|
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||||
|
bool found = _tf_ssb_get_private_key_curve25519(ssb, db, work->signer_user, work->signer_identity, private_key);
|
||||||
|
tf_ssb_release_db_reader(ssb, db);
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
work->encrypted = tf_ssb_private_message_encrypt(private_key, work->recipients, work->recipient_count, work->message, work->message_size);
|
||||||
|
work->encrypted_length = work->encrypted ? strlen(work->encrypted) : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
work->error_id_not_found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_private_message_encrypt_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
private_message_encrypt_t* work = user_data;
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
bool success = false;
|
||||||
|
if (work->error_id_not_found)
|
||||||
|
{
|
||||||
|
result = JS_ThrowInternalError(context, "Unable to get key for ID %s of user %s.", work->signer_identity, work->signer_user);
|
||||||
|
}
|
||||||
|
else if (!work->encrypted)
|
||||||
|
{
|
||||||
|
result = JS_ThrowInternalError(context, "Encrypt failed.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = JS_NewStringLen(context, work->encrypted, work->encrypted_length);
|
||||||
|
tf_free((void*)work->encrypted);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < work->recipient_count; i++)
|
||||||
|
{
|
||||||
|
tf_free((void*)work->recipients[i]);
|
||||||
|
}
|
||||||
|
JSValue error = JS_Call(context, work->promise[success ? 0 : 1], JS_UNDEFINED, 1, &result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
tf_util_report_error(context, error);
|
||||||
|
JS_FreeValue(context, error);
|
||||||
|
JS_FreeValue(context, work->promise[0]);
|
||||||
|
JS_FreeValue(context, work->promise[1]);
|
||||||
|
JS_FreeCString(context, work->signer_user);
|
||||||
|
JS_FreeCString(context, work->signer_identity);
|
||||||
|
JS_FreeCString(context, work->message);
|
||||||
|
tf_free(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_ssb_private_message_encrypt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||||
|
{
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
JSValue process = data[0];
|
||||||
|
int recipient_count = tf_util_get_length(context, argv[1]);
|
||||||
|
if (recipient_count < 1 || recipient_count > k_max_private_message_recipients)
|
||||||
|
{
|
||||||
|
return JS_ThrowRangeError(context, "Number of recipients must be between 1 and %d.", k_max_private_message_recipients);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* session_name_string = _tf_ssb_get_process_credentials_session_name(context, process);
|
||||||
|
char* recipients[k_max_private_message_recipients] = { 0 };
|
||||||
|
for (int i = 0; i < recipient_count && JS_IsUndefined(result); i++)
|
||||||
|
{
|
||||||
|
JSValue recipient = JS_GetPropertyUint32(context, argv[1], i);
|
||||||
|
const char* id = JS_ToCString(context, recipient);
|
||||||
|
if (id)
|
||||||
|
{
|
||||||
|
recipients[i] = tf_strdup(id);
|
||||||
|
JS_FreeCString(context, id);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JS_IsUndefined(result))
|
||||||
|
{
|
||||||
|
const char* signer_identity = JS_ToCString(context, argv[0]);
|
||||||
|
size_t message_size = 0;
|
||||||
|
const char* message = JS_ToCStringLen(context, &message_size, argv[2]);
|
||||||
|
|
||||||
|
tf_task_t* task = tf_task_get(context);
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
private_message_encrypt_t* work = tf_malloc(sizeof(private_message_encrypt_t));
|
||||||
|
*work = (private_message_encrypt_t) {
|
||||||
|
.signer_user = session_name_string,
|
||||||
|
.signer_identity = signer_identity,
|
||||||
|
.recipient_count = recipient_count,
|
||||||
|
.message = message,
|
||||||
|
.message_size = message_size,
|
||||||
|
};
|
||||||
|
static_assert(sizeof(work->recipients) == sizeof(recipients), "size mismatch");
|
||||||
|
memcpy(work->recipients, recipients, sizeof(recipients));
|
||||||
|
result = JS_NewPromiseCapability(context, work->promise);
|
||||||
|
tf_ssb_run_work(ssb, _tf_ssb_private_message_encrypt_work, _tf_ssb_private_message_encrypt_after_work, work);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct _private_message_decrypt_t
|
||||||
|
{
|
||||||
|
const char* user;
|
||||||
|
const char* identity;
|
||||||
|
size_t message_size;
|
||||||
|
const char* message;
|
||||||
|
const char* decrypted;
|
||||||
|
size_t decrypted_size;
|
||||||
|
const char* error;
|
||||||
|
JSValue promise[2];
|
||||||
|
} private_message_decrypt_t;
|
||||||
|
|
||||||
|
static void _tf_ssb_private_message_decrypt_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
private_message_decrypt_t* work = user_data;
|
||||||
|
|
||||||
|
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||||
|
bool found = _tf_ssb_get_private_key_curve25519(ssb, db, work->user, work->identity, private_key);
|
||||||
|
tf_ssb_release_db_reader(ssb, db);
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
if (work->message_size >= strlen(".box") && memcmp(work->message + work->message_size - strlen(".box"), ".box", strlen(".box")) == 0)
|
||||||
|
{
|
||||||
|
uint8_t* decoded = tf_malloc(work->message_size);
|
||||||
|
int decoded_length = tf_base64_decode(work->message, work->message_size - strlen(".box"), decoded, work->message_size);
|
||||||
|
uint8_t* nonce = decoded;
|
||||||
|
uint8_t* public_key = decoded + crypto_box_NONCEBYTES;
|
||||||
|
if (public_key + crypto_secretbox_KEYBYTES < decoded + decoded_length)
|
||||||
|
{
|
||||||
|
uint8_t shared_secret[crypto_secretbox_KEYBYTES] = { 0 };
|
||||||
|
if (crypto_scalarmult(shared_secret, private_key, public_key) == 0)
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
k_recipient_header_bytes = crypto_secretbox_MACBYTES + sizeof(uint8_t) + crypto_secretbox_KEYBYTES
|
||||||
|
};
|
||||||
|
for (uint8_t* p = decoded + crypto_box_NONCEBYTES + crypto_secretbox_KEYBYTES; p <= decoded + decoded_length - k_recipient_header_bytes;
|
||||||
|
p += k_recipient_header_bytes)
|
||||||
|
{
|
||||||
|
uint8_t out[k_recipient_header_bytes] = { 0 };
|
||||||
|
int opened = crypto_secretbox_open_easy(out, p, k_recipient_header_bytes, nonce, shared_secret);
|
||||||
|
if (opened != -1)
|
||||||
|
{
|
||||||
|
int recipients = (int)out[0];
|
||||||
|
uint8_t* body = decoded + crypto_box_NONCEBYTES + crypto_secretbox_KEYBYTES + k_recipient_header_bytes * recipients;
|
||||||
|
size_t body_size = decoded + decoded_length - body;
|
||||||
|
uint8_t* decrypted = tf_malloc(body_size);
|
||||||
|
uint8_t* key = out + 1;
|
||||||
|
if (crypto_secretbox_open_easy(decrypted, body, body_size, nonce, key) != -1)
|
||||||
|
{
|
||||||
|
work->decrypted = (const char*)decrypted;
|
||||||
|
work->decrypted_size = body_size - crypto_secretbox_MACBYTES;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
work->error = "Received key to open secret box containing message body, but it did not work.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
work->error = "crypto_scalarmult failed.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
work->error = "Encrypted message was not long enough to contain its one-time public key.";
|
||||||
|
}
|
||||||
|
tf_free(decoded);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
work->error = "Message does not end in \".box\".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
work->error = "Private key not found for user.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _tf_ssb_private_message_decrypt_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
private_message_decrypt_t* work = user_data;
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue error = JS_UNDEFINED;
|
||||||
|
if (work->error)
|
||||||
|
{
|
||||||
|
JSValue result = JS_ThrowInternalError(context, "%s", work->error);
|
||||||
|
error = JS_Call(context, work->promise[1], JS_UNDEFINED, 1, &result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
}
|
||||||
|
else if (work->decrypted)
|
||||||
|
{
|
||||||
|
JSValue result = JS_NewStringLen(context, work->decrypted, work->decrypted_size);
|
||||||
|
error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JSValue result = JS_UNDEFINED;
|
||||||
|
error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||||
|
}
|
||||||
|
tf_util_report_error(context, error);
|
||||||
|
JS_FreeValue(context, error);
|
||||||
|
JS_FreeValue(context, work->promise[0]);
|
||||||
|
JS_FreeValue(context, work->promise[1]);
|
||||||
|
JS_FreeCString(context, work->user);
|
||||||
|
JS_FreeCString(context, work->identity);
|
||||||
|
JS_FreeCString(context, work->message);
|
||||||
|
tf_free((void*)work->decrypted);
|
||||||
|
tf_free(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue _tf_ssb_private_message_decrypt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||||
|
{
|
||||||
|
tf_task_t* task = tf_task_get(context);
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
JSValue process = data[0];
|
||||||
|
const char* identity = JS_ToCString(context, argv[0]);
|
||||||
|
size_t message_size = 0;
|
||||||
|
const char* message = JS_ToCStringLen(context, &message_size, argv[1]);
|
||||||
|
const char* session_name_string = _tf_ssb_get_process_credentials_session_name(context, process);
|
||||||
|
|
||||||
|
private_message_decrypt_t* work = tf_malloc(sizeof(private_message_decrypt_t));
|
||||||
|
*work = (private_message_decrypt_t) {
|
||||||
|
.user = session_name_string,
|
||||||
|
.identity = identity,
|
||||||
|
.message_size = message_size,
|
||||||
|
.message = message,
|
||||||
|
};
|
||||||
|
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||||
|
tf_ssb_run_work(ssb, _tf_ssb_private_message_decrypt_work, _tf_ssb_private_message_decrypt_after_work, work);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||||
{
|
{
|
||||||
JSValue imports = argv[0];
|
JSValue imports = argv[0];
|
||||||
@@ -1245,6 +1557,8 @@ static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_va
|
|||||||
JS_SetPropertyStr(context, ssb, "getActiveIdentity", JS_NewCFunctionData(context, _tf_ssb_getActiveIdentity, 0, 0, 1, &process));
|
JS_SetPropertyStr(context, ssb, "getActiveIdentity", JS_NewCFunctionData(context, _tf_ssb_getActiveIdentity, 0, 0, 1, &process));
|
||||||
JS_SetPropertyStr(context, ssb, "getIdentities", JS_NewCFunctionData(context, _tf_ssb_getIdentities, 0, 0, 1, &process));
|
JS_SetPropertyStr(context, ssb, "getIdentities", JS_NewCFunctionData(context, _tf_ssb_getIdentities, 0, 0, 1, &process));
|
||||||
JS_SetPropertyStr(context, ssb, "getOwnerIdentities", JS_NewCFunctionData(context, _tf_ssb_getOwnerIdentities, 0, 0, 1, &process));
|
JS_SetPropertyStr(context, ssb, "getOwnerIdentities", JS_NewCFunctionData(context, _tf_ssb_getOwnerIdentities, 0, 0, 1, &process));
|
||||||
|
JS_SetPropertyStr(context, ssb, "privateMessageEncrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_encrypt, 3, 0, 1, &process));
|
||||||
|
JS_SetPropertyStr(context, ssb, "privateMessageDecrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_decrypt, 2, 0, 1, &process));
|
||||||
JS_FreeValue(context, ssb);
|
JS_FreeValue(context, ssb);
|
||||||
|
|
||||||
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");
|
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");
|
||||||
|
|||||||
@@ -308,8 +308,7 @@ static JSValue _httpd_app_on_tfrpc(JSContext* context, JSValueConst this_val, in
|
|||||||
{
|
{
|
||||||
JSClassID class_id = 0;
|
JSClassID class_id = 0;
|
||||||
app_t* app = JS_GetAnyOpaque(func_data[0], &class_id);
|
app_t* app = JS_GetAnyOpaque(func_data[0], &class_id);
|
||||||
JSValue process_app = JS_GetPropertyStr(context, app->process, "app");
|
JSValue calls = JS_IsObject(app->process) ? JS_GetPropertyStr(context, app->process, "_calls") : JS_UNDEFINED;
|
||||||
JSValue calls = JS_IsObject(process_app) ? JS_GetPropertyStr(context, process_app, "calls") : JS_UNDEFINED;
|
|
||||||
JSValue call = JS_IsObject(calls) ? JS_GetPropertyStr(context, calls, id) : JS_UNDEFINED;
|
JSValue call = JS_IsObject(calls) ? JS_GetPropertyStr(context, calls, id) : JS_UNDEFINED;
|
||||||
if (!JS_IsUndefined(call))
|
if (!JS_IsUndefined(call))
|
||||||
{
|
{
|
||||||
@@ -336,7 +335,6 @@ static JSValue _httpd_app_on_tfrpc(JSContext* context, JSValueConst this_val, in
|
|||||||
}
|
}
|
||||||
JS_FreeValue(context, call);
|
JS_FreeValue(context, call);
|
||||||
JS_FreeValue(context, calls);
|
JS_FreeValue(context, calls);
|
||||||
JS_FreeValue(context, process_app);
|
|
||||||
}
|
}
|
||||||
JS_FreeCString(context, id);
|
JS_FreeCString(context, id);
|
||||||
return JS_UNDEFINED;
|
return JS_UNDEFINED;
|
||||||
@@ -364,13 +362,11 @@ static JSValue _httpd_app_on_process_start(JSContext* context, JSValueConst this
|
|||||||
JS_SetPropertyStr(context, client_api, "tfrpc", tfrpc);
|
JS_SetPropertyStr(context, client_api, "tfrpc", tfrpc);
|
||||||
JS_FreeValue(context, client_api);
|
JS_FreeValue(context, client_api);
|
||||||
|
|
||||||
JSValue process_app = JS_GetPropertyStr(context, app->process, "app");
|
|
||||||
JSValue on_output = JS_NewCFunctionData(context, _httpd_app_on_output, 1, 0, 1, func_data);
|
JSValue on_output = JS_NewCFunctionData(context, _httpd_app_on_output, 1, 0, 1, func_data);
|
||||||
JS_SetPropertyStr(context, process_app, "_on_output", on_output);
|
JS_SetPropertyStr(context, app->process, "_on_output", on_output);
|
||||||
|
|
||||||
JSValue send = JS_GetPropertyStr(context, process_app, "send");
|
JSValue send = JS_GetPropertyStr(context, app->process, "send");
|
||||||
JSValue result = JS_Call(context, send, process_app, 0, NULL);
|
JSValue result = JS_Call(context, send, app->process, 0, NULL);
|
||||||
JS_FreeValue(context, process_app);
|
|
||||||
JS_FreeValue(context, send);
|
JS_FreeValue(context, send);
|
||||||
tf_util_report_error(context, result);
|
tf_util_report_error(context, result);
|
||||||
JS_FreeValue(context, result);
|
JS_FreeValue(context, result);
|
||||||
|
|||||||
@@ -2055,6 +2055,13 @@ void tf_run_thread_start(const char* zip_path)
|
|||||||
#else
|
#else
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
|
#if defined(__linux__)
|
||||||
|
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0)
|
||||||
|
{
|
||||||
|
tf_printf("Unable to set no new privileges flag: %s\n", strerror(errno));
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
setvbuf(stdout, NULL, _IONBF, 0);
|
setvbuf(stdout, NULL, _IONBF, 0);
|
||||||
_startup(argc, argv);
|
_startup(argc, argv);
|
||||||
ares_library_init(0);
|
ares_library_init(0);
|
||||||
|
|||||||
12
src/ssb.c
12
src/ssb.c
@@ -4766,6 +4766,7 @@ char* tf_ssb_private_message_encrypt(uint8_t* private_key, const char** recipien
|
|||||||
|
|
||||||
uint8_t* payload = tf_malloc(payload_size);
|
uint8_t* payload = tf_malloc(payload_size);
|
||||||
|
|
||||||
|
char* encoded = NULL;
|
||||||
uint8_t* p = payload;
|
uint8_t* p = payload;
|
||||||
memcpy(p, nonce, sizeof(nonce));
|
memcpy(p, nonce, sizeof(nonce));
|
||||||
p += sizeof(nonce);
|
p += sizeof(nonce);
|
||||||
@@ -4780,17 +4781,17 @@ char* tf_ssb_private_message_encrypt(uint8_t* private_key, const char** recipien
|
|||||||
tf_ssb_id_str_to_bin(key, recipients[i]);
|
tf_ssb_id_str_to_bin(key, recipients[i]);
|
||||||
if (crypto_sign_ed25519_pk_to_curve25519(recipient, key) != 0)
|
if (crypto_sign_ed25519_pk_to_curve25519(recipient, key) != 0)
|
||||||
{
|
{
|
||||||
return NULL;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t shared_secret[crypto_secretbox_KEYBYTES] = { 0 };
|
uint8_t shared_secret[crypto_secretbox_KEYBYTES] = { 0 };
|
||||||
if (crypto_scalarmult(shared_secret, secret_key, recipient) != 0)
|
if (crypto_scalarmult(shared_secret, secret_key, recipient) != 0)
|
||||||
{
|
{
|
||||||
return NULL;
|
goto fail;
|
||||||
}
|
}
|
||||||
if (crypto_secretbox_easy(p, length_and_key, sizeof(length_and_key), nonce, shared_secret) != 0)
|
if (crypto_secretbox_easy(p, length_and_key, sizeof(length_and_key), nonce, shared_secret) != 0)
|
||||||
{
|
{
|
||||||
return NULL;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
p += crypto_secretbox_MACBYTES + sizeof(length_and_key);
|
p += crypto_secretbox_MACBYTES + sizeof(length_and_key);
|
||||||
@@ -4798,16 +4799,17 @@ char* tf_ssb_private_message_encrypt(uint8_t* private_key, const char** recipien
|
|||||||
|
|
||||||
if (crypto_secretbox_easy(p, (const uint8_t*)message, message_size, nonce, body_key) != 0)
|
if (crypto_secretbox_easy(p, (const uint8_t*)message, message_size, nonce, body_key) != 0)
|
||||||
{
|
{
|
||||||
return NULL;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
p += crypto_secretbox_MACBYTES + message_size;
|
p += crypto_secretbox_MACBYTES + message_size;
|
||||||
assert((size_t)(p - payload) == payload_size);
|
assert((size_t)(p - payload) == payload_size);
|
||||||
|
|
||||||
char* encoded = tf_malloc(payload_size * 2 + 5);
|
encoded = tf_malloc(payload_size * 2 + 5);
|
||||||
size_t encoded_length = tf_base64_encode(payload, payload_size, encoded, payload_size * 2 + 5);
|
size_t encoded_length = tf_base64_encode(payload, payload_size, encoded, payload_size * 2 + 5);
|
||||||
memcpy(encoded + encoded_length, ".box", 5);
|
memcpy(encoded + encoded_length, ".box", 5);
|
||||||
|
|
||||||
|
fail:
|
||||||
tf_free(payload);
|
tf_free(payload);
|
||||||
return encoded;
|
return encoded;
|
||||||
}
|
}
|
||||||
|
|||||||
306
src/ssb.js.c
306
src/ssb.js.c
@@ -7,18 +7,11 @@
|
|||||||
#include "ssb.h"
|
#include "ssb.h"
|
||||||
#include "util.js.h"
|
#include "util.js.h"
|
||||||
|
|
||||||
#include "sodium/crypto_box.h"
|
|
||||||
#include "sodium/crypto_scalarmult.h"
|
|
||||||
#include "sodium/crypto_scalarmult_curve25519.h"
|
|
||||||
#include "sodium/crypto_scalarmult_ed25519.h"
|
|
||||||
#include "sodium/crypto_secretbox.h"
|
|
||||||
#include "sodium/crypto_sign.h"
|
#include "sodium/crypto_sign.h"
|
||||||
#include "sodium/randombytes.h"
|
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
#include "string.h"
|
#include "string.h"
|
||||||
#include "uv.h"
|
#include "uv.h"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
|
||||||
static const int k_sql_async_timeout_ms = 60 * 1000;
|
static const int k_sql_async_timeout_ms = 60 * 1000;
|
||||||
@@ -1545,303 +1538,6 @@ static JSValue _tf_ssb_createTunnel(JSContext* context, JSValueConst this_val, i
|
|||||||
return result ? JS_TRUE : JS_FALSE;
|
return result ? JS_TRUE : JS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _tf_ssb_get_private_key_curve25519_internal(sqlite3* db, const char* user, const char* identity, uint8_t out_private_key[static crypto_sign_SECRETKEYBYTES])
|
|
||||||
{
|
|
||||||
if (!user || !identity)
|
|
||||||
{
|
|
||||||
tf_printf("user=%p identity=%p out_private_key=%p\n", user, identity, out_private_key);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool success = false;
|
|
||||||
sqlite3_stmt* statement = NULL;
|
|
||||||
if (sqlite3_prepare_v2(db, "SELECT private_key FROM identities WHERE user = ? AND public_key = ?", -1, &statement, NULL) == SQLITE_OK)
|
|
||||||
{
|
|
||||||
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, *identity == '@' ? identity + 1 : identity, -1, NULL) == SQLITE_OK)
|
|
||||||
{
|
|
||||||
while (sqlite3_step(statement) == SQLITE_ROW)
|
|
||||||
{
|
|
||||||
uint8_t key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
|
||||||
int length = tf_base64_decode((const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0) - strlen(".ed25519"), key, sizeof(key));
|
|
||||||
if (length == crypto_sign_SECRETKEYBYTES)
|
|
||||||
{
|
|
||||||
success = crypto_sign_ed25519_sk_to_curve25519(out_private_key, key) == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sqlite3_finalize(statement);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool _tf_ssb_get_private_key_curve25519(tf_ssb_t* ssb, sqlite3* db, const char* user, const char* identity, uint8_t out_private_key[static crypto_sign_SECRETKEYBYTES])
|
|
||||||
{
|
|
||||||
if (_tf_ssb_get_private_key_curve25519_internal(db, user, identity, out_private_key))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tf_ssb_db_user_has_permission(ssb, db, user, "administration"))
|
|
||||||
{
|
|
||||||
return _tf_ssb_get_private_key_curve25519_internal(db, ":admin", identity, out_private_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct _private_message_encrypt_t
|
|
||||||
{
|
|
||||||
const char* signer_user;
|
|
||||||
const char* signer_identity;
|
|
||||||
const char* recipients[k_max_private_message_recipients];
|
|
||||||
int recipient_count;
|
|
||||||
const char* message;
|
|
||||||
size_t message_size;
|
|
||||||
JSValue promise[2];
|
|
||||||
bool error_id_not_found;
|
|
||||||
char* encrypted;
|
|
||||||
size_t encrypted_length;
|
|
||||||
} private_message_encrypt_t;
|
|
||||||
|
|
||||||
static void _tf_ssb_private_message_encrypt_work(tf_ssb_t* ssb, void* user_data)
|
|
||||||
{
|
|
||||||
private_message_encrypt_t* work = user_data;
|
|
||||||
|
|
||||||
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
|
||||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
|
||||||
bool found = _tf_ssb_get_private_key_curve25519(ssb, db, work->signer_user, work->signer_identity, private_key);
|
|
||||||
tf_ssb_release_db_reader(ssb, db);
|
|
||||||
|
|
||||||
if (found)
|
|
||||||
{
|
|
||||||
work->encrypted = tf_ssb_private_message_encrypt(private_key, work->recipients, work->recipient_count, work->message, work->message_size);
|
|
||||||
work->encrypted_length = work->encrypted ? strlen(work->encrypted) : 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
work->error_id_not_found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _tf_ssb_private_message_encrypt_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
|
||||||
{
|
|
||||||
private_message_encrypt_t* work = user_data;
|
|
||||||
JSContext* context = tf_ssb_get_context(ssb);
|
|
||||||
JSValue result = JS_UNDEFINED;
|
|
||||||
if (!work->encrypted)
|
|
||||||
{
|
|
||||||
result = JS_ThrowInternalError(context, "Encrypt failed.");
|
|
||||||
}
|
|
||||||
else if (work->error_id_not_found)
|
|
||||||
{
|
|
||||||
result = JS_ThrowInternalError(context, "Unable to get key for ID %s of user %s.", work->signer_identity, work->signer_user);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = JS_NewStringLen(context, work->encrypted, work->encrypted_length);
|
|
||||||
tf_free((void*)work->encrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < work->recipient_count; i++)
|
|
||||||
{
|
|
||||||
tf_free((void*)work->recipients[i]);
|
|
||||||
}
|
|
||||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
|
||||||
JS_FreeValue(context, result);
|
|
||||||
tf_util_report_error(context, error);
|
|
||||||
JS_FreeValue(context, error);
|
|
||||||
JS_FreeValue(context, work->promise[0]);
|
|
||||||
JS_FreeValue(context, work->promise[1]);
|
|
||||||
JS_FreeCString(context, work->signer_user);
|
|
||||||
JS_FreeCString(context, work->signer_identity);
|
|
||||||
JS_FreeCString(context, work->message);
|
|
||||||
tf_free(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue _tf_ssb_private_message_encrypt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
||||||
{
|
|
||||||
JSValue result = JS_UNDEFINED;
|
|
||||||
int recipient_count = tf_util_get_length(context, argv[2]);
|
|
||||||
if (recipient_count < 1 || recipient_count > k_max_private_message_recipients)
|
|
||||||
{
|
|
||||||
return JS_ThrowRangeError(context, "Number of recipients must be between 1 and %d.", k_max_private_message_recipients);
|
|
||||||
}
|
|
||||||
|
|
||||||
char* recipients[k_max_private_message_recipients] = { 0 };
|
|
||||||
for (int i = 0; i < recipient_count && JS_IsUndefined(result); i++)
|
|
||||||
{
|
|
||||||
JSValue recipient = JS_GetPropertyUint32(context, argv[2], i);
|
|
||||||
const char* id = JS_ToCString(context, recipient);
|
|
||||||
if (id)
|
|
||||||
{
|
|
||||||
recipients[i] = tf_strdup(id);
|
|
||||||
JS_FreeCString(context, id);
|
|
||||||
}
|
|
||||||
JS_FreeValue(context, recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JS_IsUndefined(result))
|
|
||||||
{
|
|
||||||
const char* signer_user = JS_ToCString(context, argv[0]);
|
|
||||||
const char* signer_identity = JS_ToCString(context, argv[1]);
|
|
||||||
size_t message_size = 0;
|
|
||||||
const char* message = JS_ToCStringLen(context, &message_size, argv[3]);
|
|
||||||
|
|
||||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
|
||||||
private_message_encrypt_t* work = tf_malloc(sizeof(private_message_encrypt_t));
|
|
||||||
*work = (private_message_encrypt_t) {
|
|
||||||
.signer_user = signer_user,
|
|
||||||
.signer_identity = signer_identity,
|
|
||||||
.recipient_count = recipient_count,
|
|
||||||
.message = message,
|
|
||||||
.message_size = message_size,
|
|
||||||
};
|
|
||||||
static_assert(sizeof(work->recipients) == sizeof(recipients), "size mismatch");
|
|
||||||
memcpy(work->recipients, recipients, sizeof(recipients));
|
|
||||||
result = JS_NewPromiseCapability(context, work->promise);
|
|
||||||
tf_ssb_run_work(ssb, _tf_ssb_private_message_encrypt_work, _tf_ssb_private_message_encrypt_after_work, work);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct _private_message_decrypt_t
|
|
||||||
{
|
|
||||||
const char* user;
|
|
||||||
const char* identity;
|
|
||||||
size_t message_size;
|
|
||||||
const char* message;
|
|
||||||
const char* decrypted;
|
|
||||||
size_t decrypted_size;
|
|
||||||
const char* error;
|
|
||||||
JSValue promise[2];
|
|
||||||
} private_message_decrypt_t;
|
|
||||||
|
|
||||||
static void _tf_ssb_private_message_decrypt_work(tf_ssb_t* ssb, void* user_data)
|
|
||||||
{
|
|
||||||
private_message_decrypt_t* work = user_data;
|
|
||||||
|
|
||||||
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
|
|
||||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
|
||||||
bool found = _tf_ssb_get_private_key_curve25519(ssb, db, work->user, work->identity, private_key);
|
|
||||||
tf_ssb_release_db_reader(ssb, db);
|
|
||||||
|
|
||||||
if (found)
|
|
||||||
{
|
|
||||||
if (work->message_size >= strlen(".box") && memcmp(work->message + work->message_size - strlen(".box"), ".box", strlen(".box")) == 0)
|
|
||||||
{
|
|
||||||
uint8_t* decoded = tf_malloc(work->message_size);
|
|
||||||
int decoded_length = tf_base64_decode(work->message, work->message_size - strlen(".box"), decoded, work->message_size);
|
|
||||||
uint8_t* nonce = decoded;
|
|
||||||
uint8_t* public_key = decoded + crypto_box_NONCEBYTES;
|
|
||||||
if (public_key + crypto_secretbox_KEYBYTES < decoded + decoded_length)
|
|
||||||
{
|
|
||||||
uint8_t shared_secret[crypto_secretbox_KEYBYTES] = { 0 };
|
|
||||||
if (crypto_scalarmult(shared_secret, private_key, public_key) == 0)
|
|
||||||
{
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
k_recipient_header_bytes = crypto_secretbox_MACBYTES + sizeof(uint8_t) + crypto_secretbox_KEYBYTES
|
|
||||||
};
|
|
||||||
for (uint8_t* p = decoded + crypto_box_NONCEBYTES + crypto_secretbox_KEYBYTES; p <= decoded + decoded_length - k_recipient_header_bytes;
|
|
||||||
p += k_recipient_header_bytes)
|
|
||||||
{
|
|
||||||
uint8_t out[k_recipient_header_bytes] = { 0 };
|
|
||||||
int opened = crypto_secretbox_open_easy(out, p, k_recipient_header_bytes, nonce, shared_secret);
|
|
||||||
if (opened != -1)
|
|
||||||
{
|
|
||||||
int recipients = (int)out[0];
|
|
||||||
uint8_t* body = decoded + crypto_box_NONCEBYTES + crypto_secretbox_KEYBYTES + k_recipient_header_bytes * recipients;
|
|
||||||
size_t body_size = decoded + decoded_length - body;
|
|
||||||
uint8_t* decrypted = tf_malloc(body_size);
|
|
||||||
uint8_t* key = out + 1;
|
|
||||||
if (crypto_secretbox_open_easy(decrypted, body, body_size, nonce, key) != -1)
|
|
||||||
{
|
|
||||||
work->decrypted = (const char*)decrypted;
|
|
||||||
work->decrypted_size = body_size - crypto_secretbox_MACBYTES;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
work->error = "Received key to open secret box containing message body, but it did not work.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
work->error = "crypto_scalarmult failed.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
work->error = "Encrypted message was not long enough to contain its one-time public key.";
|
|
||||||
}
|
|
||||||
tf_free(decoded);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
work->error = "Message does not end in \".box\".";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
work->error = "Private key not found for user.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _tf_ssb_private_message_decrypt_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
|
||||||
{
|
|
||||||
private_message_decrypt_t* work = user_data;
|
|
||||||
JSContext* context = tf_ssb_get_context(ssb);
|
|
||||||
JSValue error = JS_UNDEFINED;
|
|
||||||
if (work->error)
|
|
||||||
{
|
|
||||||
JSValue result = JS_ThrowInternalError(context, "%s", work->error);
|
|
||||||
error = JS_Call(context, work->promise[1], JS_UNDEFINED, 1, &result);
|
|
||||||
JS_FreeValue(context, result);
|
|
||||||
}
|
|
||||||
else if (work->decrypted)
|
|
||||||
{
|
|
||||||
JSValue result = JS_NewStringLen(context, work->decrypted, work->decrypted_size);
|
|
||||||
error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
|
||||||
JS_FreeValue(context, result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
JSValue result = JS_UNDEFINED;
|
|
||||||
error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
|
||||||
}
|
|
||||||
tf_util_report_error(context, error);
|
|
||||||
JS_FreeValue(context, error);
|
|
||||||
JS_FreeValue(context, work->promise[0]);
|
|
||||||
JS_FreeValue(context, work->promise[1]);
|
|
||||||
JS_FreeCString(context, work->user);
|
|
||||||
JS_FreeCString(context, work->identity);
|
|
||||||
JS_FreeCString(context, work->message);
|
|
||||||
tf_free((void*)work->decrypted);
|
|
||||||
tf_free(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue _tf_ssb_private_message_decrypt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
||||||
{
|
|
||||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
|
||||||
const char* user = JS_ToCString(context, argv[0]);
|
|
||||||
const char* identity = JS_ToCString(context, argv[1]);
|
|
||||||
size_t message_size = 0;
|
|
||||||
const char* message = JS_ToCStringLen(context, &message_size, argv[2]);
|
|
||||||
|
|
||||||
private_message_decrypt_t* work = tf_malloc(sizeof(private_message_decrypt_t));
|
|
||||||
*work = (private_message_decrypt_t) {
|
|
||||||
.user = user,
|
|
||||||
.identity = identity,
|
|
||||||
.message_size = message_size,
|
|
||||||
.message = message,
|
|
||||||
};
|
|
||||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
|
||||||
tf_ssb_run_work(ssb, _tf_ssb_private_message_decrypt_work, _tf_ssb_private_message_decrypt_after_work, work);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct _following_t
|
typedef struct _following_t
|
||||||
{
|
{
|
||||||
JSContext* context;
|
JSContext* context;
|
||||||
@@ -2086,8 +1782,6 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
|
|||||||
JS_SetPropertyStr(context, object, "addIdentity", JS_NewCFunction(context, _tf_ssb_addIdentity, "addIdentity", 2));
|
JS_SetPropertyStr(context, object, "addIdentity", JS_NewCFunction(context, _tf_ssb_addIdentity, "addIdentity", 2));
|
||||||
JS_SetPropertyStr(context, object, "deleteIdentity", JS_NewCFunction(context, _tf_ssb_deleteIdentity, "deleteIdentity", 2));
|
JS_SetPropertyStr(context, object, "deleteIdentity", JS_NewCFunction(context, _tf_ssb_deleteIdentity, "deleteIdentity", 2));
|
||||||
JS_SetPropertyStr(context, object, "getPrivateKey", JS_NewCFunction(context, _tf_ssb_getPrivateKey, "getPrivateKey", 2));
|
JS_SetPropertyStr(context, object, "getPrivateKey", JS_NewCFunction(context, _tf_ssb_getPrivateKey, "getPrivateKey", 2));
|
||||||
JS_SetPropertyStr(context, object, "privateMessageEncrypt", JS_NewCFunction(context, _tf_ssb_private_message_encrypt, "privateMessageEncrypt", 4));
|
|
||||||
JS_SetPropertyStr(context, object, "privateMessageDecrypt", JS_NewCFunction(context, _tf_ssb_private_message_decrypt, "privateMessageDecrypt", 3));
|
|
||||||
JS_SetPropertyStr(context, object, "setUserPermission", JS_NewCFunction(context, _tf_ssb_set_user_permission, "setUserPermission", 5));
|
JS_SetPropertyStr(context, object, "setUserPermission", JS_NewCFunction(context, _tf_ssb_set_user_permission, "setUserPermission", 5));
|
||||||
/* Write. */
|
/* Write. */
|
||||||
JS_SetPropertyStr(context, object, "appendMessageWithIdentity", JS_NewCFunction(context, _tf_ssb_appendMessageWithIdentity, "appendMessageWithIdentity", 3));
|
JS_SetPropertyStr(context, object, "appendMessageWithIdentity", JS_NewCFunction(context, _tf_ssb_appendMessageWithIdentity, "appendMessageWithIdentity", 3));
|
||||||
|
|||||||
@@ -922,45 +922,8 @@ void tf_ssb_test_go_ssb_room(const tf_test_options_t* options)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
static void _write_file(const char* path, const char* contents)
|
|
||||||
{
|
|
||||||
FILE* file = fopen(path, "w");
|
|
||||||
if (!file)
|
|
||||||
{
|
|
||||||
tf_printf("Unable to write %s: %s.\n", path, strerror(errno));
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
fputs(contents, file);
|
|
||||||
fclose(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define TEST_ARGS " --args=ssb_port=0,http_port=0"
|
#define TEST_ARGS " --args=ssb_port=0,http_port=0"
|
||||||
|
|
||||||
void tf_ssb_test_encrypt(const tf_test_options_t* options)
|
|
||||||
{
|
|
||||||
_write_file("out/test.js",
|
|
||||||
"async function main() {\n"
|
|
||||||
" let a = await ssb.createIdentity('test');\n"
|
|
||||||
" let b = await ssb.createIdentity('test');\n"
|
|
||||||
" let c = await ssb.privateMessageEncrypt('test', a, [a, b], \"{'foo': 1}\");\n"
|
|
||||||
" if (!c.endsWith('.box')) {\n"
|
|
||||||
" exit(1);\n"
|
|
||||||
" }\n"
|
|
||||||
" print(await ssb.privateMessageDecrypt('test', a, c));\n"
|
|
||||||
"}\n"
|
|
||||||
"main().catch(() => exit(2));\n");
|
|
||||||
|
|
||||||
unlink("out/testdb.sqlite");
|
|
||||||
char command[256];
|
|
||||||
snprintf(command, sizeof(command), "%s run --db-path=out/testdb.sqlite -s out/test.js" TEST_ARGS, options->exe_path);
|
|
||||||
tf_printf("%s\n", command);
|
|
||||||
int result = system(command);
|
|
||||||
(void)result;
|
|
||||||
assert(WIFEXITED(result));
|
|
||||||
tf_printf("returned %d\n", WEXITSTATUS(result));
|
|
||||||
assert(WEXITSTATUS(result) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _count_broadcasts_callback(
|
static void _count_broadcasts_callback(
|
||||||
const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data)
|
const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,12 +47,6 @@ void tf_ssb_test_bench(const tf_test_options_t* options);
|
|||||||
*/
|
*/
|
||||||
void tf_ssb_test_go_ssb_room(const tf_test_options_t* options);
|
void tf_ssb_test_go_ssb_room(const tf_test_options_t* options);
|
||||||
|
|
||||||
/**
|
|
||||||
** Test encrypting a private message.
|
|
||||||
** @param options The test options.
|
|
||||||
*/
|
|
||||||
void tf_ssb_test_encrypt(const tf_test_options_t* options);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** Test peer exchange.
|
** Test peer exchange.
|
||||||
** @param options The test options.
|
** @param options The test options.
|
||||||
|
|||||||
@@ -997,7 +997,6 @@ void tf_tests(const tf_test_options_t* options)
|
|||||||
_tf_test_run(options, "rooms", tf_ssb_test_rooms, false);
|
_tf_test_run(options, "rooms", tf_ssb_test_rooms, false);
|
||||||
_tf_test_run(options, "bench", tf_ssb_test_bench, false);
|
_tf_test_run(options, "bench", tf_ssb_test_bench, false);
|
||||||
_tf_test_run(options, "go-ssb-room", tf_ssb_test_go_ssb_room, true);
|
_tf_test_run(options, "go-ssb-room", tf_ssb_test_go_ssb_room, true);
|
||||||
_tf_test_run(options, "encrypt", tf_ssb_test_encrypt, false);
|
|
||||||
_tf_test_run(options, "peer_exchange", tf_ssb_test_peer_exchange, false);
|
_tf_test_run(options, "peer_exchange", tf_ssb_test_peer_exchange, false);
|
||||||
_tf_test_run(options, "publish", tf_ssb_test_publish, false);
|
_tf_test_run(options, "publish", tf_ssb_test_publish, false);
|
||||||
_tf_test_run(options, "replicate", tf_ssb_test_replicate, false);
|
_tf_test_run(options, "replicate", tf_ssb_test_replicate, false);
|
||||||
|
|||||||
Reference in New Issue
Block a user