14 Commits

Author SHA1 Message Date
a5ed64f866 ssb: Fixing private messaging yourself, and delete a failing not-quite-relevant private message test.
Some checks are pending
Build Tilde Friends / Build-All (push) Waiting to run
Build Tilde Friends / Build-Docs (push) Successful in 2m24s
2025-12-17 20:05:14 -05:00
88e3494dcf ssb: Finish moving private message encrypt/decrypt to C. 2025-12-17 19:46:26 -05:00
abe16dcf66 linux: Request no new privileges: https://docs.kernel.org/userspace-api/no_new_privs.html.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m34s
Build Tilde Friends / Build-All (push) Successful in 10m7s
2025-12-17 19:05:43 -05:00
03a32ca371 ssb: Stop following yourself.
Some checks failed
Build Tilde Friends / Build-Docs (push) Successful in 2m24s
Build Tilde Friends / Build-All (push) Has been cancelled
2025-12-17 18:58:09 -05:00
09a4fae432 ssb: Consolidate/fix following and blocking messaging. 2025-12-17 18:55:07 -05:00
5c173b2695 prettier 2025-12-17 18:48:38 -05:00
71493aac51 bookclub: Initial commit. Very simple view of all local bookclub messages for now. 2025-12-17 18:47:30 -05:00
8fb1850044 update: CodeMirror. 2025-12-17 18:13:56 -05:00
bbfcbfcae6 ssb: Avoid one last superfluous reload.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m49s
Build Tilde Friends / Build-All (push) Successful in 10m35s
2025-12-17 12:38:24 -05:00
cd2903c0df ssb: Embiggen the search input box when focused. 2025-12-17 12:24:58 -05:00
d873d99b23 ssb: Handful of URL encoding issues.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m25s
Build Tilde Friends / Build-All (push) Successful in 10m9s
2025-12-15 20:42:57 -05:00
1a5392d942 ssb: Avoid an unnecessary messages load.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m30s
Build Tilde Friends / Build-All (push) Successful in 10m54s
2025-12-15 12:30:27 -05:00
ef80c0910c intro: Scroll to top when switching pages.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m34s
Build Tilde Friends / Build-All (push) Successful in 15m36s
2025-12-13 09:01:17 -05:00
6c641acdd3 ssb: Put the hamburger menu on the same line as the welcome text. 2025-12-13 08:57:06 -05:00
26 changed files with 980 additions and 560 deletions

5
apps/bookclub.json Normal file
View File

@@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📚",
"previous": "&yLHlvKirJEqrekP5lf5BydvzIo/vN+z7K2ACQacxJXE=.sha256"
}

181
apps/bookclub/app.js Normal file
View 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

File diff suppressed because one or more lines are too long

6
apps/bookclub/index.html Normal file
View File

@@ -0,0 +1,6 @@
<!doctype html>
<html>
<body>
${BODY}
</body>
</html>

251
apps/bookclub/w3.css Normal file
View 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}

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "💡", "emoji": "💡",
"previous": "&eN6DNPpQUNhGvxneLuLPgsOXR6qyFZ7u+MAz0b4fa7k=.sha256" "previous": "&FGkkfFLaEID3V4lUjPbgCOwgEvNXkcVkzs0zzwD/gQ8=.sha256"
} }

View File

@@ -34,6 +34,7 @@
class="w3-flex w3-dark-gray w3-center" class="w3-flex w3-dark-gray w3-center"
> >
<div <div
id="scrollbox"
style=" style="
flex: 1 1 auto; flex: 1 1 auto;
overflow: auto; overflow: auto;
@@ -251,6 +252,7 @@
index == 0 ? 'hidden' : 'visible'; index == 0 ? 'hidden' : 'visible';
document.getElementById('right').style.visibility = document.getElementById('right').style.visibility =
index == slides.length - 1 ? 'hidden' : 'visible'; index == slides.length - 1 ? 'hidden' : 'visible';
document.getElementById('scrollbox').scrollTo(0, 0);
} }
let dots = [...document.getElementsByClassName('dot')]; let dots = [...document.getElementsByClassName('dot')];

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🦀", "emoji": "🦀",
"previous": "&/flKW9HptO5lHG19rMwc4GT9rGBFiRqVD87VDbWvsgc=.sha256" "previous": "&eqeAxU0q6n0RZDSd68j44hQ4UtssESqgohsCXN/otwY=.sha256"
} }

View File

@@ -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

View File

@@ -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}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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._private_messages !== (this.hash.startsWith('#🔐') &&
JSON.stringify([ this._private_messages !==
this.private_messages, JSON.stringify([
this.grouped_private_messages, this.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();
} }

View File

@@ -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 != '#@'
@@ -428,18 +436,18 @@ class TfTabNewsElement extends LitElement {
</p> </p>
<div> <div>
<div <div
id="show_sidebar" style="width: 100%; max-width: 100%; white-space: nowrap; overflow: hidden"
class="w3-button w3-hide-large"
@click=${this.show_sidebar}
>
${this.unread_status()}&#9776;
</div>
<span
style="display: inline-block; width: 100%; max-width: 100%; white-space: nowrap; overflow: hidden"
> >
<button
id="show_sidebar"
class="w3-button w3-hide-large"
@click=${this.show_sidebar}
>
${this.unread_status()}&#9776;
</button>
Welcome, Welcome,
<tf-user id=${this.whoami} .users=${this.users}></tf-user>! <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
</span> </div>
${edit_profile} ${edit_profile}
</div> </div>
<div> <div>
@@ -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}

View File

@@ -44,36 +44,37 @@ class TfTabSearchElement extends LitElement {
this.error = undefined; this.error = undefined;
this.results = []; this.results = [];
this.messages = []; this.messages = [];
if (query.startsWith('sql:')) { try {
this.messages = []; if (query.startsWith('sql:')) {
try { this.messages = [];
this.results = await tfrpc.rpc.query( this.results = await tfrpc.rpc.query(
query.substring('sql:'.length), query.substring('sql:'.length),
[] []
); );
} catch (e) { } else {
this.results = []; let results = await tfrpc.rpc.query(
this.error = e; `
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value
ORDER BY timestamp DESC limit 100
`,
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
);
search = this.renderRoot.getElementById('search');
if (search) {
search.value = query;
search.focus();
search.select();
}
this.messages = results;
} }
} else { } catch (e) {
let results = await tfrpc.rpc.query( this.messages = [];
` this.results = [];
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature this.error = e;
FROM messages_fts(?) console.log(e);
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value
ORDER BY timestamp DESC limit 100
`,
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
);
console.log('Done.');
search = this.renderRoot.getElementById('search');
if (search) {
search.value = query;
search.focus();
search.select();
}
this.messages = results;
} }
} }

View File

@@ -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) {

View File

@@ -486,33 +486,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 &&

File diff suppressed because one or more lines are too long

194
deps/codemirror_src/package-lock.json generated vendored
View File

@@ -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"
} }
}, },

View File

@@ -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");

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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));

View File

@@ -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)
{ {

View File

@@ -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.

View File

@@ -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);