50 Commits

Author SHA1 Message Date
abde709e54 prettier.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m7s
2025-04-16 19:27:24 -04:00
27f2d319ab storage: Faster. 2025-04-16 19:21:57 -04:00
66234b14bc test: Add some coverage of storing and resetting permissions by clicking the approve/deny/remember/reset buttons.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m32s
2025-04-16 12:32:34 -04:00
6a9167e565 ssb: More logging to help track down an issue I've seen on rare occasion when saving app changes from the editor.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 34m43s
2025-04-15 12:49:40 -04:00
3c60f8ca06 ssb: Whitespace around nested messages.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-15 12:42:13 -04:00
c26bf5c112 core: Setting and resetting user app permissions was all sorts of broken.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m21s
2025-04-14 12:41:35 -04:00
41cbde934a ssb: Layout the message expand/collapse buttons better.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m47s
2025-04-13 21:38:56 -04:00
946941d95e format
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-13 21:18:28 -04:00
50f0104239 ssb: Add size to messages_stats. Forces an already-necessary rebuild and provides information I'm keen on having readily available.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-13 21:16:26 -04:00
40fa7edadf ssb: Show message count when viewing an account profile.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-13 20:48:55 -04:00
d6926569c6 verify: Add an option to dump a specific message in the format that its signature validates as well as a hex representation of the bytes for good measure.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m2s
2025-04-13 13:28:48 -04:00
a8bba324ca ssb: Fix removing a mention discarding a draft.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m39s
2025-04-13 07:53:15 -04:00
5bba5776b3 core: Get rid of the httpd JS object. Potential progress on #108.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m37s
2025-04-12 09:43:55 -04:00
8104f6f228 ssb: Fix and test the messages_stats trigger.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m59s
2025-04-12 08:40:36 -04:00
3f4738e593 ssb: Fall back to hostnames for connections in the sidebar.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m36s
2025-04-12 07:05:37 -04:00
1516e17f5d update: Lit 3.3.0.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m33s
2025-04-11 19:32:26 -04:00
676d2702b7 ssb: Read back-pressure on a tunnel connection affects the parent connection. This was causing spurrious disconnects.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-11 19:29:10 -04:00
5d39548964 ssb: Why is this faster?
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m0s
2025-04-09 22:47:23 -04:00
67d458bd38 ssb: prettier.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-09 22:15:51 -04:00
d9684c7d62 ssb: Fix culling feeds again. 2025-04-09 22:15:01 -04:00
5a818d2119 ssb: More incremental profile loading? 2025-04-09 22:08:33 -04:00
6f96d4ce65 ssb: More different feed culling logic. 2025-04-09 20:45:23 -04:00
f72395756a ssb: If things time out because we're following a million accounts...recover ungracefully. 2025-04-09 20:02:16 -04:00
38d746b310 ssb: Dust off the verify command.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m53s
2025-04-09 19:32:38 -04:00
f7270987ea http: Trying to track down a rare 404 I get on save. 2025-04-09 19:08:28 -04:00
6f565c0f0a ssb: Move reply and react to the message % menu. 2025-04-09 19:02:02 -04:00
7f252e79b6 ssb: Faster channel loads. 2025-04-09 18:50:14 -04:00
ba2bb17638 ssb: Gather all the votes for messages seen. 2025-04-09 18:29:37 -04:00
bc7c658293 update: CoreMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m12s
2025-04-09 12:28:56 -04:00
4d84e69bb5 update: libbacktrace. 2025-04-09 12:26:44 -04:00
03fac74908 update: c-ares 1.34.5. 2025-04-09 12:24:55 -04:00
5252ff1ecf update: OpenSSL 3.5.0. 2025-04-09 12:22:12 -04:00
20100d3fd4 ssb: Oops, that event was Firefox-specific.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m36s
2025-04-07 22:35:40 -04:00
dd3b2656ad ssb: Message % menu tweaks. Show on click. Hide when clicking off. Bury the message ID further.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-04-07 21:39:14 -04:00
657f25e22b ssb: Bring back the date. Whoops.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m37s
2025-04-05 22:05:26 -04:00
8be354fc49 ssb: It is troubling to me that querying like this is so much faster.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m37s
2025-04-05 18:40:00 -04:00
e574758340 ssb: Make a message % context menu with some new options to begin to declutter the message view itself.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m42s
2025-04-05 17:53:47 -04:00
40cf519492 http: I caught an http listener lingering on shutdown. Clean them up more correctly. Possible progress on #108.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m38s
2025-04-05 09:53:47 -04:00
0e4fda54e9 fdroid: Lean into SSB in the description.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m45s
2025-04-03 12:31:59 -04:00
868f91e1ef docs: Noticed some errors in the readme.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m38s
2025-04-02 20:01:26 -04:00
b9000c154f apps: Add a very wip web app.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m8s
2025-04-02 18:07:25 -04:00
894c72a82f ssb: Still chasing faster loads of the default news query.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m59s
2025-04-02 12:44:17 -04:00
c128cfc25c ssb: This makes the channels query sub-second for me.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m39s
2025-04-01 19:50:13 -04:00
36c88b463c buttfeed: Add PatchFox.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m41s
2025-04-01 08:13:54 -04:00
8a66e74074 ssb: Faster *and* correct. I think.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m43s
2025-03-30 13:18:16 -04:00
ea60b165da ssb: Had my refs backward.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m17s
2025-03-30 12:27:36 -04:00
1011e0026b ssb: Rearrange indexes for an overall faster load on mobile for me. Tidy up some things along the way.
Some checks are pending
Build Tilde Friends / Build-All (push) Has started running
2025-03-30 12:16:23 -04:00
9462521287 update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 29m53s
2025-03-27 12:37:13 -04:00
576022c41a build: 0.0.30-wip, let's gooooo.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 30m42s
2025-03-26 20:19:28 -04:00
70c38b7ea8 build: nix => 0.0.29.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-03-26 18:57:05 -04:00
58 changed files with 1145 additions and 561 deletions

View File

@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker.
## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 34
VERSION_CODE_IOS := 11
VERSION_NUMBER := 0.0.29
VERSION_CODE := 35
VERSION_CODE_IOS := 12
VERSION_NUMBER := 0.0.30-wip
VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0

View File

@ -38,7 +38,7 @@ dependencies in the right places.
### Requirements
System OpenSSL libraries are assumed to be available on Haiku and OpenSSL.
System OpenSSL libraries are assumed to be available on Haiku and OpenBSD.
On MacOS, Xcode's command-line tools are expected to be available.
@ -55,9 +55,8 @@ standard.
## Running
By default, running the built `out/debug/tildefriends` executable will start a
web server at <http://localhost:12345/>. It expects to be run with the
repository root as the current working directory. `tildefriends -h` lists
further options.
web server at <http://localhost:12345/>. `tildefriends -h` lists further
options.
The first user to create an account and log in will be granted administrative
privileges. Further administration can be done in the `admin` app at

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦀",
"previous": "&wOd/+1l5wpywBlfxC1Lm0i+HhYidrgSfrn9LRX7qy2w=.sha256"
"previous": "&jko2iokTaY2t/pD9m4ekMr0wsLjov3LVl9ShysXEjkE=.sha256"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -175,63 +175,98 @@ class TfElement extends LitElement {
}
}
let abouts = await tfrpc.rpc.query(
`
SELECT
messages.author, json(messages.content) AS content, messages.sequence
FROM
messages,
json_each(?1) AS following
WHERE
messages.author = following.value AND
messages.rowid > ?3 AND
messages.rowid <= ?4 AND
json_extract(messages.content, '$.type') = 'about'
UNION
SELECT
messages.author, json(messages.content) AS content, messages.sequence
FROM
messages,
json_each(?2) AS following
WHERE
messages.author = following.value AND
messages.rowid <= ?4 AND
json_extract(messages.content, '$.type') = 'about'
ORDER BY messages.author, messages.sequence
`,
[
JSON.stringify(ids.filter((id) => cache.about[id])),
JSON.stringify(ids.filter((id) => !cache.about[id])),
cache.last_row_id,
max_row_id,
]
const k_chunk_size = 1024;
let min_row_id = 0;
console.log(
'loading about for',
ids.length,
'accounts',
cache.last_row_id,
'=>',
max_row_id
);
for (let about of abouts) {
let content = JSON.parse(about.content);
if (content.about === about.author) {
delete content.type;
delete content.about;
cache.about[about.author] = Object.assign(
cache.about[about.author] || {},
content
try {
while (true) {
let abouts = await tfrpc.rpc.query(
`
SELECT * FROM (
SELECT
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence
FROM
messages,
json_each(?1) AS following
WHERE
messages.author = following.value AND
messages.content ->> 'type' = 'about' AND
messages.rowid > ?3 AND
messages.rowid <= ?4
UNION
SELECT
messages.rowid AS rowid, messages.author, json(messages.content) AS content, messages.sequence
FROM
messages,
json_each(?2) AS following
WHERE
messages.author = following.value AND
messages.content ->> 'type' = 'about' AND
messages.rowid > ?6 AND
messages.rowid <= ?4
)
ORDER BY rowid LIMIT ?5
`,
[
JSON.stringify(ids.filter((id) => cache.about[id])),
JSON.stringify(ids.filter((id) => !cache.about[id])),
cache.last_row_id,
max_row_id,
k_chunk_size,
min_row_id,
]
);
let max_seen;
for (let about of abouts) {
let content = JSON.parse(about.content);
if (content.about === about.author) {
delete content.type;
delete content.about;
cache.about[about.author] = Object.assign(
cache.about[about.author] || {},
content
);
}
max_seen = about.rowid;
}
console.log(
'cache =',
cache.last_row_id,
'seen =',
max_seen,
'max =',
max_row_id
);
cache.last_row_id = Math.max(cache.last_row_id, max_seen ?? max_row_id);
min_row_id = Math.max(min_row_id, max_seen ?? max_row_id);
let new_cache = JSON.stringify(cache);
if (new_cache !== original_cache) {
let start_time = new Date();
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
console.log('saving about took', (new Date() - start_time) / 1000);
});
}
users = users || {};
for (let id of Object.keys(cache.about)) {
users[id] = Object.assign(
{follow_depth: following[id]?.d},
users[id] || {},
cache.about[id]
);
}
if (cache.last_row_id >= max_row_id) {
break;
}
}
}
cache.last_row_id = max_row_id;
let new_cache = JSON.stringify(cache);
if (new_cache !== original_cache) {
let start_time = new Date();
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
console.log('saving about took', (new Date() - start_time) / 1000);
});
}
users = users || {};
for (let id of Object.keys(cache.about)) {
users[id] = Object.assign(
{follow_depth: following[id]?.d},
users[id] || {},
cache.about[id]
);
} catch (e) {
console.log(e);
}
return Object.assign({}, users);
}
@ -382,7 +417,6 @@ class TfElement extends LitElement {
'🔐': latest[0],
});
console.log('private took', (new Date() - start_time) / 1000.0);
console.log(latest);
self.private_messages = latest[1];
});
}

View File

@ -359,7 +359,7 @@ class TfComposeElement extends LitElement {
remove_mention(id) {
let draft = this.get_draft();
delete draft.mentions[id];
setTimeout(() => this.notify(), 0);
setTimeout(() => this.notify(draft), 0);
}
render_mention(mention) {

View File

@ -33,6 +33,25 @@ class TfMessageElement extends LitElement {
this.channel_unread = -1;
}
connectedCallback() {
super.connectedCallback();
this._click_callback = this.document_click.bind(this);
document.body.addEventListener('mouseup', this._click_callback);
}
disconnectedCallback() {
super.disconnectedCallback();
document.body.removeEventListener('mouseup', this._click_callback);
}
document_click(event) {
let content = this.renderRoot.querySelector('.w3-dropdown-content');
let target = event.target;
if (content && !content.contains(target)) {
content.classList.remove('w3-show');
}
}
show_reply() {
let event = new CustomEvent('tf-draft', {
bubbles: true,
@ -291,32 +310,39 @@ class TfMessageElement extends LitElement {
let self = this;
if (this.message.child_messages?.length) {
if (!this.expanded[this.message.id]) {
return html`<button
class="w3-button w3-theme-d1"
@click=${() => self.set_expanded(true)}
>
+ ${this.total_child_messages(this.message) + ' More'}
</button>`;
return html`
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(true)}
>
+ ${this.total_child_messages(this.message) + ' More'}
</button>
`;
} else {
return html`<button
class="w3-button w3-theme-d1"
return html` <div class="w3-container w3-margin-bottom">
${repeat(
this.message.child_messages || [],
(x) => x.id,
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>`
)}
</div>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse</button
>${repeat(
this.message.child_messages || [],
(x) => x.id,
(x) =>
html`<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>`
)}`;
Collapse
</button>`;
}
} else {
return undefined;
@ -371,62 +397,74 @@ class TfMessageElement extends LitElement {
return content;
}
render_raw_button() {
copy_id(event) {
navigator.clipboard.writeText(this.message?.id);
}
toggle_menu(event) {
event.srcElement.parentNode
.querySelector('.w3-dropdown-content')
.classList.toggle('w3-show');
}
render_menu() {
let content = this.get_content();
let raw_button;
switch (this.format) {
case 'raw':
if (content?.type == 'post' || content?.type == 'blog') {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'md')}
>
Markdown
</button>`;
} else {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'message')}
>
Message
</button>`;
}
break;
case 'md':
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'message')}
>
Message
</button>`;
break;
case 'decrypted':
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'raw')}
>
Raw
</button>`;
break;
default:
if (this.message.decrypted) {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'decrypted')}
>
Decrypted
</button>`;
} else {
raw_button = html`<button
class="w3-button w3-theme-d1"
@click=${() => (this.format = 'raw')}
>
Raw
</button>`;
}
break;
let formats = [['message', 'Message']];
if (content?.type == 'post' || content?.type == 'blog') {
formats.push(['md', 'Markdown']);
}
return raw_button;
if (this.message?.decrypted) {
formats.push(['decrypted', 'Decrypted']);
}
formats.push(['raw', 'Raw']);
return html`
<div class="w3-bar-item w3-right">
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
%
</button>
<div
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
style="right: 48px"
>
<a
target="_top"
class="w3-button w3-bar-item"
href=${'#' + encodeURIComponent(this.message?.id)}
>View Message</a
>
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.copy_id}
>
Copy ID
</button>
${this.drafts[this.message?.id] === undefined
? html`
<button class="w3-button w3-bar-item" @click=${this.show_reply}>
⮢ Reply
</button>
`
: undefined}
<button
class="w3-button w3-bar-item w3-border-bottom"
@click=${this.react}
>
👍 React
</button>
${formats.map(
([format, name]) => html`
<button
class="w3-button w3-bar-item"
style=${format == this.format ? 'font-weight: bold' : ''}
@click=${() => (this.format = format)}
>
${name}
</button>
`
)}
</div>
</div>
`;
}
render_header() {
@ -440,14 +478,10 @@ class TfMessageElement extends LitElement {
<span class="w3-bar-item">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
</span>
${is_encrypted}
<span class="w3-bar-item w3-right">${this.render_raw_button()}</span>
<span class="w3-bar-item w3-right" style="text-wrap: nowrap"
><a target="_top" href=${'#' + encodeURIComponent(this.message.id)}
>%</a
>
${new Date(this.message.timestamp).toLocaleString()}</span
>
${is_encrypted} ${this.render_menu()}
<div class="w3-bar-item w3-right" style="text-wrap: nowrap">
${new Date(this.message.timestamp).toLocaleString()}
</div>
</header>
`;
}
@ -516,19 +550,10 @@ class TfMessageElement extends LitElement {
author=${this.message.author}
></tf-compose>
`
: html`
<button class="w3-button w3-theme-d1" @click=${this.show_reply}>
Reply
</button>
`;
: undefined;
return html`
<div class="w3-section w3-container">
${reply}
<button class="w3-button w3-theme-d1" @click=${this.react}>
React
</button>
${this.render_children()}
</div>
<div class="w3-section w3-container">${reply}</div>
<footer>${this.render_children()}</footer>
`;
}

View File

@ -11,6 +11,7 @@ class TfProfileElement extends LitElement {
id: {type: String},
users: {type: Object},
size: {type: Number},
sequence: {type: Number},
following: {type: Boolean},
blocking: {type: Boolean},
};
@ -26,6 +27,7 @@ class TfProfileElement extends LitElement {
this.id = null;
this.users = {};
this.size = 0;
this.sequence = 0;
}
async load() {
@ -170,11 +172,12 @@ class TfProfileElement extends LitElement {
let profile = this.users[this.id] || {};
tfrpc.rpc
.query(
`SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`,
`SELECT SUM(LENGTH(content)) AS size, MAX(sequence) AS sequence FROM messages WHERE author = ?`,
[this.id]
)
.then(function (result) {
self.size = result[0].size;
self.sequence = result[0].sequence;
});
let edit;
let follow;
@ -245,7 +248,7 @@ class TfProfileElement extends LitElement {
let description = this.editing?.description ?? profile.description;
return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
<header class="w3-container">
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})</p>
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
</header>
<div class="w3-container">
<div class="w3-margin-bottom" style="display: flex; flex-direction: row">

View File

@ -46,6 +46,63 @@ class TfTabNewsFeedElement extends LitElement {
: this.hash.substring(1);
}
async _fetch_related_messages(messages) {
let refs = await tfrpc.rpc.query(
`
WITH
news AS (
SELECT value AS id FROM json_each(?)
)
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
UNION
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
`,
[JSON.stringify(messages.map((x) => x.id))]
);
let related_messages = await tfrpc.rpc.query(
`
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?2) refs ON messages.id = refs.value
JOIN json_each(?1) AS following ON messages.author = following.value
`,
[JSON.stringify(this.following), JSON.stringify(refs.map((x) => x.ref))]
);
let combined = [].concat(messages, related_messages);
let refs2 = await tfrpc.rpc.query(
`
WITH
news AS (
SELECT value AS id FROM json_each(?)
)
SELECT refs_out.ref AS ref FROM messages_refs refs_out JOIN news ON refs_out.message = news.id
UNION
SELECT refs_in.message AS ref FROM messages_refs refs_in JOIN news ON refs_in.ref = news.id
`,
[JSON.stringify(combined.map((x) => x.id))]
);
let t0 = new Date();
let result = [].concat(
combined,
await tfrpc.rpc.query(
`
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM json_each(?2) refs
JOIN messages ON messages.id = refs.value
JOIN json_each(?1) following ON messages.author = following.value
WHERE messages.content ->> 'type' != 'post'
`,
[
JSON.stringify(this.following),
JSON.stringify(refs2.map((x) => x.ref)),
]
)
);
let t1 = new Date();
console.log((t1 - t0) / 1000);
return result;
}
async fetch_messages(start_time, end_time) {
this.time_loading = [start_time, end_time];
let result;
@ -107,7 +164,8 @@ class TfTabNewsFeedElement extends LitElement {
[this.hash.substring(1)]
);
} else if (this.hash.startsWith('##')) {
result = await tfrpc.rpc.query(
let t0 = new Date();
let initial_messages = await tfrpc.rpc.query(
`
WITH
all_news AS (
@ -120,20 +178,11 @@ class TfTabNewsFeedElement extends LitElement {
FROM messages_fts(?5)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?1) AS following ON messages.author = following.value
JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4),
JOIN json_tree(messages.content, '$.mentions') AS mention ON mention.value = '#' || ?4
),
news AS (SELECT * FROM all_news
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
ORDER BY all_news.timestamp DESC LIMIT 20)
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
UNION
SELECT TRUE AS is_primary, news.* FROM news
`,
[
@ -144,6 +193,12 @@ class TfTabNewsFeedElement extends LitElement {
'"#' + this.hash.substring(2).replace('"', '""') + '"',
]
);
let t1 = new Date();
result = await this._fetch_related_messages(initial_messages);
let t2 = new Date();
console.log(
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
);
} else if (this.hash == '#🔐') {
result = await tfrpc.rpc.query(
`
@ -159,33 +214,30 @@ class TfTabNewsFeedElement extends LitElement {
);
result = (await this.decrypt(result)).filter((x) => x.decrypted);
} else {
result = await tfrpc.rpc.query(
let t0 = new Date();
let initial_messages = await tfrpc.rpc.query(
`
WITH
all_news AS (
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE timestamp >= 0 AND timestamp < ?3),
),
news AS (
SELECT * FROM all_news
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
WHERE all_news.timestamp < ?3 AND (?2 IS NULL OR all_news.timestamp >= ?2)
ORDER BY timestamp DESC LIMIT 20
)
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
UNION
SELECT TRUE AS is_primary, news.* FROM news
`,
[JSON.stringify(this.following), start_time, end_time]
);
let t1 = new Date();
result = await this._fetch_related_messages(initial_messages);
let t2 = new Date();
console.log(
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
);
}
this.time_loading = undefined;
return result;
@ -211,7 +263,11 @@ class TfTabNewsFeedElement extends LitElement {
try {
let more = [];
let last_start_time = this.time_range[0];
more = await this.fetch_messages(null, last_start_time);
try {
more = await this.fetch_messages(null, last_start_time);
} catch (e) {
console.log(e);
}
this.update_time_range_from_messages(
more.filter((x) => x.timestamp < last_start_time)
);
@ -311,7 +367,7 @@ class TfTabNewsFeedElement extends LitElement {
this.messages = this.merge_messages(this.messages, messages);
this.time_loading = undefined;
console.log(
`loading messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
);
}

View File

@ -232,6 +232,7 @@ class TfTabNewsElement extends LitElement {
class="w3-bar-item"
style="max-width: 100%"
id=${x.id}
fallback_name=${x.host}
.users=${this.users}
></tf-user>
`
@ -285,7 +286,7 @@ class TfTabNewsElement extends LitElement {
return cache(html`
${this.render_sidebar()}
<div
style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto"
style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto; contain: layout"
id="main"
class="w3-main"
>

View File

@ -6,6 +6,7 @@ class TfUserElement extends LitElement {
static get properties() {
return {
id: {type: String},
fallback_name: {type: String},
users: {type: Object},
};
}
@ -15,6 +16,7 @@ class TfUserElement extends LitElement {
constructor() {
super();
this.id = null;
this.fallback_name = null;
this.users = {};
}
@ -31,7 +33,7 @@ class TfUserElement extends LitElement {
>`;
let name = this.users?.[this.id]?.name;
name = html`<a target="_top" href=${'#' + this.id}
>${name !== undefined ? name : this.id}</a
>${name ?? this.fallback_name ?? this.id}</a
>`;
if (user) {

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "💾",
"previous": "&mvGTlWKFR5QM/3nb4fJ2WQq0n/gNKvBmhGDkAvb8ki8=.sha256"
"previous": "&tzZFIe7Y54O4sx1QtAPdemkXh+p5qHXSG/dlS7NP6OQ=.sha256"
}

View File

@ -8,7 +8,7 @@ async function query(sql, args) {
async function get_biggest() {
return query(`
select author, sum(length(content)) as size from messages group by author order by size desc limit 10;
select author, size from messages_stats group by author order by size desc limit 10;
`);
}
@ -62,15 +62,14 @@ function nice_size(bytes) {
}
async function main() {
await app.setDocument(
'<p style="color: #fff">Finding the top 10 largest feeds...</p>'
);
let most_follows = await get_most_follows();
await app.setDocument('<p style="color: #fff">Analyzing feeds...</p>');
let most_follows = get_most_follows();
let total = await get_total();
let identities = await ssb.getAllIdentities();
let following1 = await ssb.following(identities, 1);
let following2 = await ssb.following(identities, 2);
let biggest = await get_biggest();
most_follows = await most_follows;
let names = await get_names(
[].concat(
biggest.map((x) => x.author),
@ -94,7 +93,7 @@ async function main() {
}
let html = `<body style="color: #000; background-color: #ddd">\n
<h1>Storage Summary</h1>
<h2>Top 10 Accounts by Size</h2>
<h2>Top Accounts by Size</h2>
<ol>`;
for (let item of biggest) {
html += `<li>
@ -105,7 +104,7 @@ async function main() {
}
html += `
</ol>
<h2>Top 10 Accounts by Follows</h2>
<h2>Top Accounts by Follows</h2>
<ol>`;
for (let item of most_follows) {
html += `<li>

5
apps/web.json Normal file
View File

@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🕸",
"previous": "&n7hu5b8/TsfiG6FDlCRG5nPCrIdCr96+xpIJ/aQT/uM=.sha256"
}

100
apps/web/app.js Normal file
View File

@ -0,0 +1,100 @@
let g_hash;
async function query(sql, params) {
let results = [];
await ssb.sqlAsync(sql, params, function (row) {
results.push(row);
});
return results;
}
async function resolve(id) {
try {
let blob = await ssb.blobGet(id);
if (blob) {
let json;
try {
json = JSON.parse(utf8Decode(blob));
} catch {
return {id: utf8Decode(blob)};
}
if (json?.links) {
for (let [key, value] of Object.entries(json.links)) {
json.links[key] = await resolve(value);
}
return json;
} else {
return 'huh?' + json;
}
} else {
return `missing<${id}>`;
}
} catch (e) {
return id + ': ' + e.message;
}
}
async function get_names(identities) {
return Object.fromEntries(
(
await query(
`
SELECT author, name FROM (
SELECT
messages.author,
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
messages.content ->> 'name' AS name
FROM messages
JOIN json_each(?) AS identities ON identities.value = messages.author
WHERE
json_extract(messages.content, '$.type') = 'about' AND
content ->> 'about' = messages.author AND name IS NOT NULL)
WHERE author_rank = 1
`,
[JSON.stringify(identities)]
)
).map((x) => [x.author, x.name])
);
}
async function render(hash) {
g_hash = hash;
if (!hash) {
let sites = await query(
`
SELECT site.author, site.id
FROM messages site
WHERE site.content ->> 'type' = 'web-init'
`,
[]
);
let names = await get_names(sites.map((x) => x.author));
if (hash === g_hash) {
await app.setDocument(
`<ul style="background-color: #ddd">${sites.map((x) => `<li><a target="_top" href="#${encodeURIComponent(x.id)}">${names[x.author] ?? x.author} - ${x.id}</a></li>`).join('\n')}</ul>`
);
}
} else {
let site_id =
hash.charAt(0) == '#'
? decodeURIComponent(hash.substring(1))
: decodeURIComponent(hash);
await app.setDocument(`<html style="margin: 0; padding: 0; width: 100vw; height: 100vh; margin: 0; padding: 0">
<body style="display: flex; flex-direction: column; width: 100vw; height: 100vh">
<iframe src="${encodeURIComponent(site_id)}/index.html" style="flex: 1 1; border: 0; background-color: #fff"></iframe>
</body>
</html>`);
}
}
core.register('message', async function message_handler(message) {
if (message.event == 'hashChange') {
await render(message.hash);
}
});
async function main() {
render(null);
}
main();

63
apps/web/handler.js Normal file
View File

@ -0,0 +1,63 @@
async function query(sql, params) {
let results = [];
await ssb.sqlAsync(sql, params, function (row) {
results.push(row);
});
return results;
}
function guess_content_type(name) {
if (name.endsWith('.html')) {
return 'text/html; charset=UTF-8';
} else if (name.endsWith('.js') || name.endsWith('.mjs')) {
return 'text/javascript; charset=UTF-8';
} else if (name.endsWith('.css')) {
return 'text/stylesheet; charset=UTF-8';
} else {
return 'application/binary';
}
}
async function main() {
let path = request.path.replaceAll(/(%[0-9a-fA-F]{2})/g, (x) =>
String.fromCharCode(parseInt(x.substring(1), 16))
);
let match = path.match(/^(%.{44}\.sha256)(?:\/)?(.*)$/);
let content_type = guess_content_type(request.path);
let root = await query(
`
SELECT root.content ->> 'root' AS root
FROM messages site
JOIN messages root
ON site.id = ? AND root.author = site.author AND root.content ->> 'site' = site.id
ORDER BY root.sequence DESC LIMIT 1
`,
[match[1]]
);
let root_id = root[0]['root'];
let last_id = root_id;
let blob = await ssb.blobGet(root_id);
try {
for (let part of match[2]?.split('/')) {
let dir = JSON.parse(utf8Decode(blob));
last_id = dir?.links[part];
blob = await ssb.blobGet(dir?.links[part]);
content_type = guess_content_type(part);
}
} catch {}
respond({
status_code: 200,
data: blob ? utf8Decode(blob) : `${last_id} not found`,
content_type: content_type,
});
}
main().catch(function (e) {
respond({
status_code: 200,
data: `${e.message}\n${e.stack}`,
content_type: 'text/plain',
});
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -266,6 +266,7 @@ class TfNavigationElement extends LitElement {
<button
@click=${() => this.reset_permission(key)}
class="w3-button w3-red"
id=${'permission_reset:' + key}
>
Reset
</button>
@ -275,6 +276,7 @@ class TfNavigationElement extends LitElement {
<button
@click=${() => (this.show_permissions = false)}
class="w3-button w3-blue"
id="permissions_close"
>
Close
</button>

View File

@ -25,14 +25,14 @@
}:
pkgs.stdenv.mkDerivation rec {
pname = "tildefriends";
version = "0.0.28";
version = "0.0.29";
src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net";
owner = "cory";
repo = "tildefriends";
rev = "v${version}";
hash = "sha256-vcLJCXgIrjC37t9oavK2QMRcMJljghuzKDYxQ4nyTcE=";
hash = "sha256-bQXFpocOYOlFmVj9OZeQhNrgFuTJ8sx2RSw1tgmelOM=";
fetchSubmodules = true;
};

2
deps/c-ares vendored

File diff suppressed because one or more lines are too long

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

@ -30,9 +30,9 @@
}
},
"node_modules/@codemirror/commands": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.0.tgz",
"integrity": "sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==",
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
@ -92,9 +92,9 @@
}
},
"node_modules/@codemirror/language": {
"version": "6.10.8",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.8.tgz",
"integrity": "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==",
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz",
"integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
@ -105,9 +105,9 @@
}
},
"node_modules/@codemirror/lint": {
"version": "6.8.4",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz",
"integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==",
"version": "6.8.5",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.35.0",
@ -144,9 +144,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.36.4",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.4.tgz",
"integrity": "sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==",
"version": "6.36.5",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.5.tgz",
"integrity": "sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==",
"dependencies": {
"@codemirror/state": "^6.5.0",
"style-mod": "^4.1.0",
@ -217,9 +217,9 @@
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
},
"node_modules/@lezer/css": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.10.tgz",
"integrity": "sha512-V5/89eDapjeAkWPBpWEfQjZ1Hag3aYUUJOL8213X0dFRuXJ4BXa5NKl9USzOnaLod4AOpmVCkduir2oKwZYZtg==",
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.11.tgz",
"integrity": "sha512-FuAnusbLBl1SEAtfN8NdShxYJiESKw9LAFysfea1T96jD3ydBn12oYjaSG1a04BQRIUd93/0D8e5CV1cUMkmQg==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
@ -344,9 +344,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz",
"integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz",
"integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==",
"cpu": [
"arm"
],
@ -356,9 +356,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz",
"integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz",
"integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==",
"cpu": [
"arm64"
],
@ -368,9 +368,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz",
"integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz",
"integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==",
"cpu": [
"arm64"
],
@ -380,9 +380,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz",
"integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz",
"integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==",
"cpu": [
"x64"
],
@ -392,9 +392,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz",
"integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz",
"integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==",
"cpu": [
"arm64"
],
@ -404,9 +404,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz",
"integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz",
"integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==",
"cpu": [
"x64"
],
@ -416,9 +416,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz",
"integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz",
"integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==",
"cpu": [
"arm"
],
@ -428,9 +428,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz",
"integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz",
"integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==",
"cpu": [
"arm"
],
@ -440,9 +440,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz",
"integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz",
"integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==",
"cpu": [
"arm64"
],
@ -452,9 +452,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz",
"integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz",
"integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==",
"cpu": [
"arm64"
],
@ -464,9 +464,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz",
"integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz",
"integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==",
"cpu": [
"loong64"
],
@ -476,9 +476,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz",
"integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz",
"integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==",
"cpu": [
"ppc64"
],
@ -488,9 +488,21 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz",
"integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz",
"integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz",
"integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==",
"cpu": [
"riscv64"
],
@ -500,9 +512,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz",
"integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz",
"integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==",
"cpu": [
"s390x"
],
@ -512,9 +524,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz",
"integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz",
"integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==",
"cpu": [
"x64"
],
@ -524,9 +536,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz",
"integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz",
"integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==",
"cpu": [
"x64"
],
@ -536,9 +548,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz",
"integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz",
"integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==",
"cpu": [
"arm64"
],
@ -548,9 +560,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz",
"integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz",
"integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==",
"cpu": [
"ia32"
],
@ -560,9 +572,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz",
"integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz",
"integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==",
"cpu": [
"x64"
],
@ -572,9 +584,9 @@
]
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
},
"node_modules/@types/resolve": {
"version": "1.20.2",
@ -733,11 +745,11 @@
}
},
"node_modules/rollup": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz",
"integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==",
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
"dependencies": {
"@types/estree": "1.0.6"
"@types/estree": "1.0.7"
},
"bin": {
"rollup": "dist/bin/rollup"
@ -747,25 +759,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.34.9",
"@rollup/rollup-android-arm64": "4.34.9",
"@rollup/rollup-darwin-arm64": "4.34.9",
"@rollup/rollup-darwin-x64": "4.34.9",
"@rollup/rollup-freebsd-arm64": "4.34.9",
"@rollup/rollup-freebsd-x64": "4.34.9",
"@rollup/rollup-linux-arm-gnueabihf": "4.34.9",
"@rollup/rollup-linux-arm-musleabihf": "4.34.9",
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
"@rollup/rollup-linux-arm64-musl": "4.34.9",
"@rollup/rollup-linux-loongarch64-gnu": "4.34.9",
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.9",
"@rollup/rollup-linux-riscv64-gnu": "4.34.9",
"@rollup/rollup-linux-s390x-gnu": "4.34.9",
"@rollup/rollup-linux-x64-gnu": "4.34.9",
"@rollup/rollup-linux-x64-musl": "4.34.9",
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
"@rollup/rollup-win32-ia32-msvc": "4.34.9",
"@rollup/rollup-win32-x64-msvc": "4.34.9",
"@rollup/rollup-android-arm-eabi": "4.40.0",
"@rollup/rollup-android-arm64": "4.40.0",
"@rollup/rollup-darwin-arm64": "4.40.0",
"@rollup/rollup-darwin-x64": "4.40.0",
"@rollup/rollup-freebsd-arm64": "4.40.0",
"@rollup/rollup-freebsd-x64": "4.40.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.40.0",
"@rollup/rollup-linux-arm-musleabihf": "4.40.0",
"@rollup/rollup-linux-arm64-gnu": "4.40.0",
"@rollup/rollup-linux-arm64-musl": "4.40.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.40.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.40.0",
"@rollup/rollup-linux-riscv64-gnu": "4.40.0",
"@rollup/rollup-linux-riscv64-musl": "4.40.0",
"@rollup/rollup-linux-s390x-gnu": "4.40.0",
"@rollup/rollup-linux-x64-gnu": "4.40.0",
"@rollup/rollup-linux-x64-musl": "4.40.0",
"@rollup/rollup-win32-arm64-msvc": "4.40.0",
"@rollup/rollup-win32-ia32-msvc": "4.40.0",
"@rollup/rollup-win32-x64-msvc": "4.40.0",
"fsevents": "~2.3.2"
}
},

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
A tool for making and sharing
A Secure Scuttlebutt decentralized social network client

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends"
android:versionCode="34"
android:versionName="0.0.29">
android:versionCode="35"
android:versionName="0.0.30-wip">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

View File

@ -347,7 +347,6 @@ public class TildeFriendsActivity extends Activity {
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (java.io.FileNotFoundException e) {
Log.w("tildefriends", "Port file does not yet exist.");
} catch (java.io.IOException e) {
e.printStackTrace();
}

View File

@ -785,8 +785,25 @@ void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_
static void _http_free_listener_on_close(uv_handle_t* handle)
{
tf_http_listener_t* listener = handle->data;
tf_http_t* http = listener->http;
for (int i = 0; i < http->listeners_count; i++)
{
if (http->listeners[i] == listener)
{
http->listeners[i] = http->listeners[http->listeners_count - 1];
http->listeners_count--;
break;
}
}
handle->data = NULL;
tf_free(listener);
if (!http->listeners_count)
{
tf_free(http->listeners);
http->listeners = NULL;
tf_http_destroy(http);
}
}
void tf_http_destroy(tf_http_t* http)
@ -812,6 +829,10 @@ void tf_http_destroy(tf_http_t* http)
listener->cleanup(listener->user_data);
listener->cleanup = NULL;
}
if (listener->tcp.data && !uv_is_closing((uv_handle_t*)&listener->tcp))
{
uv_close((uv_handle_t*)&listener->tcp, _http_free_listener_on_close);
}
}
for (int i = 0; i < http->handlers_count; i++)
@ -829,20 +850,11 @@ void tf_http_destroy(tf_http_t* http)
http->user_data = NULL;
}
if (http->connections_count == 0)
if (http->connections_count == 0 && http->listeners_count == 0)
{
tf_free(http->connections);
http->connections = NULL;
for (int i = 0; i < http->listeners_count; i++)
{
tf_http_listener_t* listener = http->listeners[i];
uv_close((uv_handle_t*)&listener->tcp, _http_free_listener_on_close);
}
tf_free(http->listeners);
http->listeners = NULL;
http->listeners_count = 0;
for (int i = 0; i < http->handlers_count; i++)
{
if (http->handlers[i].pattern)

View File

@ -47,7 +47,6 @@ static const char* _make_set_session_cookie_header(tf_http_request_t* request, c
const char** _form_data_decode(const char* data, int length);
const char* _form_data_get(const char** form_data, const char* key);
static JSClassID _httpd_class_id;
static JSClassID _httpd_request_class_id;
typedef struct _http_user_data_t
@ -538,12 +537,6 @@ static const char* _ext_to_content_type(const char* ext, bool use_fallback)
return use_fallback ? "application/binary" : NULL;
}
static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
{
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
tf_http_destroy(http);
}
static void _httpd_request_finalizer(JSRuntime* runtime, JSValue value)
{
tf_http_request_t* request = JS_GetOpaque(value, _httpd_request_class_id);
@ -1324,6 +1317,11 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id);
save->response = 200;
}
else
{
tf_printf("Blob store or property set failed.\n");
save->response = 500;
}
JS_FreeCString(context, new_app_str);
JS_FreeValue(context, new_app_json);
@ -1340,7 +1338,7 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
}
else
{
save->response = 401;
save->response = 403;
}
tf_free(user_app);
}
@ -1352,12 +1350,21 @@ static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
snprintf(save->blob_id, sizeof(save->blob_id), "%s", blob_id);
save->response = 200;
}
else
{
tf_printf("Blob store failed.\n");
save->response = 500;
}
}
else
{
save->response = 400;
}
}
else
{
save->response = 401;
}
tf_free((void*)session);
JS_FreeCString(context, user_string);
@ -2348,16 +2355,7 @@ static const char* _httpd_read_file(tf_task_t* task, const char* path)
void tf_httpd_register(JSContext* context)
{
JS_NewClassID(&_httpd_class_id);
JS_NewClassID(&_httpd_request_class_id);
JSClassDef httpd_def = {
.class_name = "Httpd",
.finalizer = &_httpd_finalizer,
};
if (JS_NewClass(JS_GetRuntime(context), _httpd_class_id, &httpd_def) != 0)
{
fprintf(stderr, "Failed to register Httpd.\n");
}
JSClassDef request_def = {
.class_name = "Request",
.finalizer = &_httpd_request_finalizer,
@ -2368,14 +2366,19 @@ void tf_httpd_register(JSContext* context)
}
JSValue global = JS_GetGlobalObject(context);
JSValue httpd = JS_NewObjectClass(context, _httpd_class_id);
JSValue httpd = JS_NewObject(context);
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global);
}
tf_http_t* tf_httpd_create(JSContext* context)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
uv_loop_t* loop = tf_task_get_loop(task);
tf_http_t* http = tf_http_create(loop);
tf_http_set_trace(http, tf_task_get_trace(task));
JS_SetOpaque(httpd, http);
int64_t http_port = 0;
int64_t https_port = 0;
@ -2440,10 +2443,6 @@ void tf_httpd_register(JSContext* context)
tf_http_add_handler(http, "/app/socket", _httpd_endpoint_app_socket, NULL, task);
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global);
if (http_port > 0 || *out_http_port_file)
{
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
@ -2488,4 +2487,10 @@ void tf_httpd_register(JSContext* context)
tf_free((char*)private_key);
}
}
return http;
}
void tf_httpd_destroy(tf_http_t* http)
{
tf_http_destroy(http);
}

View File

@ -13,6 +13,11 @@
#include "quickjs.h"
/**
** An HTTP server instance.
*/
typedef struct _tf_http_t tf_http_t;
/**
** Register the HTTP script interface. Also registers a number of built-in
** request handlers. An ongoing project is to move the JS request handlers
@ -21,4 +26,16 @@
*/
void tf_httpd_register(JSContext* context);
/**
** Create the HTTP server instance.
** @param context The JS context.
*/
tf_http_t* tf_httpd_create(JSContext* context);
/**
** Destroy the HTTP server instance.
** @param http The HTTP server instance.
*/
void tf_httpd_destroy(tf_http_t* http);
/** @} */

View File

@ -13,13 +13,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.0.29</string>
<string>0.0.30</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>11</string>
<string>12</string>
<key>DTPlatformName</key>
<string>iphoneos</string>
<key>LSRequiresIPhoneOS</key>

View File

@ -1161,17 +1161,19 @@ static int _tf_command_verify(const char* file, int argc, char* argv[])
const char* identity = NULL;
const char* default_db_path = _get_db_path();
const char* db_path = default_db_path;
int64_t sequence = 0;
bool show_usage = false;
while (!show_usage)
{
static const struct option k_options[] = {
{ "id", required_argument, NULL, 'i' },
{ "sequence", required_argument, NULL, 's' },
{ "db-path", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ 0 },
};
int c = getopt_long(argc, argv, "i:d:h", k_options, NULL);
int c = getopt_long(argc, argv, "i:s:d:h", k_options, NULL);
if (c == -1)
{
break;
@ -1187,6 +1189,9 @@ static int _tf_command_verify(const char* file, int argc, char* argv[])
case 'i':
identity = optarg;
break;
case 's':
sequence = atoll(optarg);
break;
case 'd':
db_path = optarg;
break;
@ -1198,15 +1203,45 @@ static int _tf_command_verify(const char* file, int argc, char* argv[])
tf_printf("\n%s import [options] [paths...]\n\n", file);
tf_printf("options:\n");
tf_printf(" -i, --identity identity Identity to verify.\n");
tf_printf(" -s, --sequence sequence Sequence number to debug.\n");
tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -h, --help Show this usage information.\n");
tf_free((void*)default_db_path);
return EXIT_FAILURE;
}
tf_printf("Verifying %s...\n", identity);
bool verified = false;
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
bool verified = tf_ssb_db_verify(ssb, identity);
if (identity)
{
tf_printf("Verifying %s...\n", identity);
verified = tf_ssb_db_verify(ssb, identity, sequence, true);
}
else
{
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT DISTINCT author FROM messages ORDER BY author", -1, &statement, NULL) == SQLITE_OK)
{
verified = true;
while (sqlite3_step(statement) == SQLITE_ROW)
{
const char* identity = (const char*)sqlite3_column_text(statement, 0);
tf_printf("Verifying %s...", identity);
if (tf_ssb_db_verify(ssb, identity, sequence, true))
{
tf_printf("success.\n");
}
else
{
tf_printf("fail.\n");
verified = false;
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
tf_ssb_destroy(ssb);
tf_free((void*)default_db_path);
return verified ? EXIT_SUCCESS : EXIT_FAILURE;

View File

@ -37,6 +37,8 @@
#define PRE_CALLBACK(ssb, cb) uint64_t pre_callback_hrtime_ns = _tf_ssb_callback_pre(ssb)
#define POST_CALLBACK(ssb, cb) _tf_ssb_callback_post(ssb, cb, pre_callback_hrtime_ns)
const int k_read_back_pressure_threshold = 256;
static_assert(k_id_base64_len == sodium_base64_ENCODED_LEN(9 + crypto_box_PUBLICKEYBYTES, sodium_base64_VARIANT_ORIGINAL), "k_id_base64_len");
static_assert(k_id_bin_len == crypto_box_PUBLICKEYBYTES, "k_id_bin_len");
static_assert(k_blob_id_len == (sodium_base64_ENCODED_LEN(crypto_hash_sha256_BYTES, sodium_base64_VARIANT_ORIGINAL) + 8), "k_blob_id_len");
@ -1074,7 +1076,8 @@ void tf_ssb_calculate_message_id(JSContext* context, JSValue message, char* out_
JS_FreeValue(context, idval);
}
static bool _tf_ssb_verify_and_strip_signature_internal(JSContext* context, JSValue val, char* out_id, size_t out_id_size, char* out_signature, size_t out_signature_size)
static bool _tf_ssb_verify_and_strip_signature_internal(
JSContext* context, JSValue val, int verify_flags, char* out_id, size_t out_id_size, char* out_signature, size_t out_signature_size)
{
JSValue signature = JS_GetPropertyStr(context, val, "signature");
if (JS_IsUndefined(signature))
@ -1122,6 +1125,16 @@ static bool _tf_ssb_verify_and_strip_signature_internal(JSContext* context, JSVa
{
r = crypto_sign_verify_detached(binsig, (const uint8_t*)sigstr, strlen(sigstr), publickey);
verified = r == 0;
if (verify_flags & k_tf_ssb_verify_flag_debug)
{
tf_printf("verifying author=%s id=%s signature=%s success=%d\n", author, out_id, str, verified);
tf_printf("signed string:\n%s\n\n", sigstr);
for (int i = 0; sigstr[i]; i++)
{
tf_printf("%s%02x", (i && (i % 32) == 0) ? "\n" : (i && (i % 8) == 0) ? " " : (i ? " " : ""), sigstr[i]);
}
tf_printf("\n");
}
if (!verified)
{
// tf_printf("crypto_sign_verify_detached fail (r=%d)\n", r);
@ -1159,7 +1172,8 @@ static bool _tf_ssb_verify_and_strip_signature_internal(JSContext* context, JSVa
return verified;
}
bool tf_ssb_verify_and_strip_signature(JSContext* context, JSValue val, char* out_id, size_t out_id_size, char* out_signature, size_t out_signature_size, int* out_flags)
bool tf_ssb_verify_and_strip_signature(
JSContext* context, JSValue val, int verify_flags, char* out_id, size_t out_id_size, char* out_signature, size_t out_signature_size, int* out_flags)
{
JSValue reordered = JS_NewObject(context);
JS_SetPropertyStr(context, reordered, "previous", JS_GetPropertyStr(context, val, "previous"));
@ -1169,7 +1183,7 @@ bool tf_ssb_verify_and_strip_signature(JSContext* context, JSValue val, char* ou
JS_SetPropertyStr(context, reordered, "hash", JS_GetPropertyStr(context, val, "hash"));
JS_SetPropertyStr(context, reordered, "content", JS_GetPropertyStr(context, val, "content"));
JS_SetPropertyStr(context, reordered, "signature", JS_GetPropertyStr(context, val, "signature"));
bool result = _tf_ssb_verify_and_strip_signature_internal(context, reordered, out_id, out_id_size, out_signature, out_signature_size);
bool result = _tf_ssb_verify_and_strip_signature_internal(context, reordered, verify_flags, out_id, out_id_size, out_signature, out_signature_size);
JS_FreeValue(context, reordered);
if (result)
@ -1189,7 +1203,7 @@ bool tf_ssb_verify_and_strip_signature(JSContext* context, JSValue val, char* ou
JS_SetPropertyStr(context, reordered, "hash", JS_GetPropertyStr(context, val, "hash"));
JS_SetPropertyStr(context, reordered, "content", JS_GetPropertyStr(context, val, "content"));
JS_SetPropertyStr(context, reordered, "signature", JS_GetPropertyStr(context, val, "signature"));
result = _tf_ssb_verify_and_strip_signature_internal(context, reordered, out_id, out_id_size, out_signature, out_signature_size);
result = _tf_ssb_verify_and_strip_signature_internal(context, reordered, verify_flags, out_id, out_id_size, out_signature, out_signature_size);
JS_FreeValue(context, reordered);
if (result)
{
@ -2080,6 +2094,11 @@ static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const ch
JS_SetOpaque(object, NULL);
JS_FreeValue(ssb->context, object);
}
if (connection->read_back_pressure >= k_read_back_pressure_threshold && connection->tunnel_connection)
{
tf_ssb_connection_adjust_read_backpressure(connection->tunnel_connection, -1);
connection->read_back_pressure = 0;
}
if (connection->async.data && !uv_is_closing((uv_handle_t*)&connection->async))
{
uv_close((uv_handle_t*)&connection->async, _tf_ssb_connection_on_close);
@ -4155,7 +4174,7 @@ void tf_ssb_verify_strip_and_store_message(tf_ssb_t* ssb, JSValue value, tf_ssb_
};
char signature[crypto_sign_BYTES + 128] = { 0 };
int flags = 0;
if (tf_ssb_verify_and_strip_signature(context, value, async->id, sizeof(async->id), signature, sizeof(signature), &flags))
if (tf_ssb_verify_and_strip_signature(context, value, 0, async->id, sizeof(async->id), signature, sizeof(signature), &flags))
{
async->verified = true;
tf_ssb_db_store_message(ssb, context, async->id, value, signature, flags, _tf_ssb_verify_strip_and_store_callback, async);
@ -4555,19 +4574,32 @@ JSValue tf_ssb_connection_requests_to_object(tf_ssb_connection_t* connection)
void tf_ssb_connection_adjust_read_backpressure(tf_ssb_connection_t* connection, int delta)
{
const int k_threshold = 256;
int old_pressure = connection->read_back_pressure;
connection->read_back_pressure += delta;
if (!connection->is_closing)
{
uv_async_send(&connection->scheduled_async);
if (old_pressure < k_threshold && connection->read_back_pressure >= k_threshold)
if (old_pressure < k_read_back_pressure_threshold && connection->read_back_pressure >= k_read_back_pressure_threshold)
{
_tf_ssb_connection_read_stop(connection);
if (connection->tunnel_connection)
{
tf_ssb_connection_adjust_read_backpressure(connection->tunnel_connection, 1);
}
else
{
_tf_ssb_connection_read_stop(connection);
}
}
else if (old_pressure >= k_threshold && connection->read_back_pressure < k_threshold)
else if (old_pressure >= k_read_back_pressure_threshold && connection->read_back_pressure < k_read_back_pressure_threshold)
{
_tf_ssb_connection_read_start(connection);
if (connection->tunnel_connection)
{
tf_ssb_connection_adjust_read_backpressure(connection->tunnel_connection, -1);
}
else
{
_tf_ssb_connection_read_start(connection);
}
}
}
connection->ref_count += delta;

View File

@ -132,6 +132,11 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
tf_printf("Rebuilding messages_stats.\n");
_tf_ssb_db_exec(db, "DROP TABLE messages_stats");
}
else if (!_tf_ssb_db_has_rows(db, "SELECT name FROM pragma_table_info('messages_stats') WHERE name = 'size'"))
{
tf_printf("Rebuilding messages_stats.\n");
_tf_ssb_db_exec(db, "DROP TABLE messages_stats");
}
}
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_stats')"))
{
@ -140,19 +145,28 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
"CREATE TABLE IF NOT EXISTS messages_stats ("
" author TEXT PRIMARY KEY,"
" max_sequence INTEGER NOT NULL,"
" max_timestamp REAL NOT NULL"
" max_timestamp REAL NOT NULL,"
" size INTEGER NOT NULL DEFAULT 0"
")");
_tf_ssb_db_exec(
db, "INSERT OR REPLACE INTO messages_stats (author, max_sequence, max_timestamp) SELECT author, MAX(sequence), MAX(timestamp) FROM messages GROUP BY author");
_tf_ssb_db_exec(db,
"INSERT OR REPLACE INTO messages_stats (author, max_sequence, max_timestamp, size) SELECT author, MAX(sequence), MAX(timestamp), SUM(length(json(content))) FROM "
"messages GROUP BY author");
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
}
_tf_ssb_db_exec(db,
"CREATE TRIGGER IF NOT EXISTS messages_ai_stats AFTER INSERT ON messages BEGIN INSERT INTO messages_stats(author, max_sequence, max_timestamp) VALUES (new.author, "
"new.sequence, new.timestamp) ON CONFLICT DO UPDATE SET max_sequence = MAX(max_sequence, new.sequence), max_timestamp = MAX(max_timestamp, "
"new.timestamp); END");
"CREATE TRIGGER IF NOT EXISTS messages_ai_stats AFTER INSERT ON messages BEGIN INSERT INTO messages_stats(author, max_sequence, max_timestamp, size) VALUES (new.author, "
"new.sequence, new.timestamp, length(json(new.content))) ON CONFLICT DO UPDATE SET max_sequence = MAX(max_sequence, new.sequence), max_timestamp = MAX(max_timestamp, "
"new.timestamp), size = size + length(json(new.content)); END");
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad_stats");
_tf_ssb_db_exec(db,
"CREATE TRIGGER IF NOT EXISTS messages_ad_stats AFTER DELETE ON messages BEGIN UPDATE messages_stats SET max_sequence = (SELECT MAX(messages.sequence) FROM messages WHERE "
"messages.author = old.author), max_timestamp = (SELECT MAX(messages.timestamp) FROM messages WHERE messages.author = old.author); END");
"CREATE TRIGGER IF NOT EXISTS messages_ad_stats AFTER DELETE ON messages BEGIN "
"UPDATE messages_stats SET max_sequence = updated.sequence, max_timestamp = updated.timestamp, size = size - length(json(old.content)) "
"FROM ("
" SELECT COALESCE(MAX(messages.sequence), 0) AS sequence, COALESCE(MAX(messages.timestamp), 0) AS timestamp "
" FROM messages WHERE messages.author = old.author) AS updated "
"WHERE messages_stats.author = old.author; "
"DELETE FROM messages_stats WHERE messages_stats.author = old.author AND messages_stats.max_sequence = 0; "
"END");
if (_tf_ssb_db_has_rows(db, "SELECT name FROM pragma_table_info('messages') WHERE name = 'content' AND type == 'TEXT'"))
{
@ -176,13 +190,21 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
_tf_ssb_db_exec(db, "ALTER TABLE messages RENAME COLUMN sequence_before_author TO flags");
}
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_id_index ON messages (author, id)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_id_author_index ON messages (id, author)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_sequence_index ON messages (author, sequence)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_timestamp_index ON messages (author, timestamp)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_timestamp_index ON messages (timestamp)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_type_timestamp_index ON messages (content ->> 'type', timestamp)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_size_by_author_index ON messages (author, length(content))");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_type_author_channel_index ON messages (content ->> 'type', author, content ->> 'channel');");
_tf_ssb_db_exec(db,
"CREATE INDEX IF NOT EXISTS messages_type_author_channel_root_timestamp_index ON messages (author, timestamp, content ->> 'type', content ->> 'channel', content ->> "
"'root')");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_type_author_channel_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_author_id_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_by_author_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_timestamp_author_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_id_index");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_type_author_channel_root_rowid_index");
_tf_ssb_db_exec(db,
"CREATE TABLE IF NOT EXISTS blobs ("
" id TEXT PRIMARY KEY,"
@ -286,8 +308,10 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad_refs");
_tf_ssb_db_exec(db, "CREATE TRIGGER IF NOT EXISTS messages_ad_refs AFTER DELETE ON messages BEGIN DELETE FROM messages_refs WHERE messages_refs.message = old.id; END");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_refs_message_idx ON messages_refs (message)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_refs_ref_idx ON messages_refs (ref)");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_refs_message_idx");
_tf_ssb_db_exec(db, "DROP INDEX IF EXISTS messages_refs_ref_idx");
_tf_ssb_db_exec(db, "CREATE UNIQUE INDEX IF NOT EXISTS messages_refs_message_ref_idx ON messages_refs (message, ref)");
_tf_ssb_db_exec(db, "CREATE UNIQUE INDEX IF NOT EXISTS messages_refs_ref_message_idx ON messages_refs (ref, message)");
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('blobs_refs')"))
{
@ -922,7 +946,12 @@ bool tf_ssb_db_blob_store(tf_ssb_t* ssb, const uint8_t* blob, size_t size, char*
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_blob(statement, 2, blob, size, NULL) == SQLITE_OK)
{
result = sqlite3_step(statement) == SQLITE_DONE;
int r = sqlite3_step(statement);
result = r == SQLITE_DONE;
if (!result)
{
tf_printf("Blob store failed: %s.\n", sqlite3_errmsg(db));
}
rows = sqlite3_changes(db);
}
else
@ -2119,7 +2148,25 @@ void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callb
tf_ssb_run_work(ssb, _tf_ssb_db_resolve_index_work, _tf_ssb_db_resolve_index_after_work, request);
}
bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id)
static void _tf_ssb_db_set_flags(tf_ssb_t* ssb, const char* message_id, int flags)
{
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "UPDATE messages SET flags = ? WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_int(statement, 1, flags) == SQLITE_OK && sqlite3_bind_text(statement, 2, message_id, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) != SQLITE_DONE)
{
tf_printf("Setting flags of %s to %d failed: %s.\n", message_id, flags, sqlite3_errmsg(db));
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id, int64_t debug_sequence, bool fix)
{
JSContext* context = tf_ssb_get_context(ssb);
bool verified = true;
@ -2142,7 +2189,8 @@ bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id)
char calculated_id[k_id_base64_len];
char extracted_signature[256];
int calculated_flags = 0;
if (!tf_ssb_verify_and_strip_signature(context, message, calculated_id, sizeof(calculated_id), extracted_signature, sizeof(extracted_signature), &calculated_flags))
if (!tf_ssb_verify_and_strip_signature(context, message, i == debug_sequence ? k_tf_ssb_verify_flag_debug : 0, calculated_id, sizeof(calculated_id),
extracted_signature, sizeof(extracted_signature), &calculated_flags))
{
tf_printf("author=%s sequence=%" PRId64 " verify failed.\n", id, i);
verified = false;
@ -2150,7 +2198,14 @@ bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id)
if (calculated_flags != flags)
{
tf_printf("author=%s sequence=%" PRId64 " flag mismatch %d => %d.\n", id, i, flags, calculated_flags);
verified = false;
if (fix)
{
_tf_ssb_db_set_flags(ssb, message_id, calculated_flags);
}
else
{
verified = false;
}
}
if (strcmp(message_id, calculated_id))
{

View File

@ -444,9 +444,11 @@ void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callb
** Verify an author's feed.
** @param ssb The SSB instance.
** @param id The author'd identity.
** @param debug_sequence Message sequence number to debug if non-zero.
** @param fix Fix invalid messages when possible.
** @return true If the feed verified successfully.
*/
bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id);
bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id, int64_t debug_sequence, bool fix);
/**
** Check if a user has a specific permission.

View File

@ -34,6 +34,14 @@ enum
k_max_private_message_recipients = 8,
};
/**
** Flags affecting signature verification.
*/
typedef enum _tf_ssb_verify_flags_t
{
k_tf_ssb_verify_flag_debug = 1,
} tf_ssb_verify_flags_t;
/**
** The type of change to a set of connections.
*/
@ -446,6 +454,7 @@ bool tf_ssb_id_bin_to_str(char* str, size_t str_size, const uint8_t* bin);
** Verify a message's signature and remove the signature if successful.
** @param context A JS context.
** @param val The message.
** @param verify_flags Verification options of type tf_ssb_verify_flags_t.
** @param[out] out_id A buffer to receive the message's identity.
** @param out_id_size The size of out_id.
** @param[out] out_signature A buffer to receive the message's signature.
@ -453,7 +462,8 @@ bool tf_ssb_id_bin_to_str(char* str, size_t str_size, const uint8_t* bin);
** @param[out] out_flags tf_ssb_message_flags_t describing the message.
** @return True if the signature is valid and was successfully extracted.
*/
bool tf_ssb_verify_and_strip_signature(JSContext* context, JSValue val, char* out_id, size_t out_id_size, char* out_signature, size_t out_signature_size, int* out_flags);
bool tf_ssb_verify_and_strip_signature(
JSContext* context, JSValue val, int verify_flags, char* out_id, size_t out_id_size, char* out_signature, size_t out_signature_size, int* out_flags);
/**
** Determine the message identifier.

View File

@ -2199,6 +2199,7 @@ typedef struct _set_user_permission_t
const char* package_name;
const char* permission;
bool allow;
bool reset;
bool result;
JSValue promise[2];
} set_user_permission_t;
@ -2239,22 +2240,31 @@ static void _tf_ssb_set_user_permission_work(tf_ssb_t* ssb, void* user_data)
if (JS_IsUndefined(package_name))
{
package_name = JS_NewObject(context);
JS_SetPropertyStr(context, package_owner, work->package_name, package_name);
JS_SetPropertyStr(context, package_owner, work->package_name, JS_DupValue(context, package_name));
}
JSValue permission = JS_GetPropertyStr(context, package_name, work->permission);
if (JS_ToBool(context, permission) != work->allow)
if (work->reset)
{
JS_SetPropertyStr(context, package_name, work->permission, JS_NewBool(context, work->allow));
JSValue settings_json = JS_JSONStringify(context, settings_value, JS_NULL, JS_NULL);
const char* settings_string = JS_ToCString(context, settings_json);
work->result = tf_ssb_db_set_property(ssb, "core", "settings", settings_string);
JS_FreeCString(context, settings_string);
JS_FreeValue(context, settings_json);
JSAtom atom = JS_NewAtom(context, work->permission);
JS_DeleteProperty(context, package_name, atom, 0);
JS_FreeAtom(context, atom);
}
else
{
work->result = true;
JS_SetPropertyStr(context, package_name, work->permission, JS_NewBool(context, work->allow));
}
JSValue settings_json = JS_JSONStringify(context, settings_value, JS_NULL, JS_NULL);
const char* settings_string = JS_ToCString(context, settings_json);
work->result = tf_ssb_db_set_property(ssb, "core", "settings", settings_string);
JS_FreeCString(context, settings_string);
JS_FreeValue(context, settings_json);
JS_FreeValue(context, permission);
JS_FreeValue(context, package_owner);
JS_FreeValue(context, package_name);
JS_FreeValue(context, user);
JS_FreeValue(context, user_permissions);
JS_FreeValue(context, settings_value);
tf_free((void*)settings);
}
JS_FreeContext(context);
@ -2288,6 +2298,7 @@ static JSValue _tf_ssb_set_user_permission(JSContext* context, JSValueConst this
.package_name = JS_ToCString(context, argv[2]),
.permission = JS_ToCString(context, argv[3]),
.allow = JS_ToBool(context, argv[4]),
.reset = JS_IsUndefined(argv[4]),
};
JSValue result = JS_NewPromiseCapability(context, set->promise);
tf_ssb_run_work(set->ssb, _tf_ssb_set_user_permission_work, _tf_ssb_set_user_permission_after_work, set);

View File

@ -1594,27 +1594,20 @@ static void _tf_ssb_rpc_delete_feeds_work(tf_ssb_t* ssb, void* user_data)
db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db,
"DELETE FROM messages WHERE author IN ("
" SELECT author FROM messages WHERE author NOT IN (SELECT value FROM json_each(?)) GROUP BY author LIMIT 1"
") RETURNING author",
"DELETE FROM messages WHERE id IN ("
" SELECT id FROM messages WHERE author NOT IN (SELECT value FROM json_each(?)) ORDER BY rowid DESC LIMIT 1024"
")",
-1, &statement, NULL) == SQLITE_OK)
{
int status = SQLITE_OK;
bool printed = false;
if (sqlite3_bind_text(statement, 1, arg, -1, NULL) == SQLITE_OK)
{
while ((status = sqlite3_step(statement)) == SQLITE_ROW)
if (sqlite3_step(statement) != SQLITE_DONE)
{
if (!printed)
{
tf_printf("deleting %s\n", sqlite3_column_text(statement, 0));
printed = true;
delete->deleted++;
}
tf_printf("deleting messages: %s\n", sqlite3_errmsg(db));
}
if (status != SQLITE_DONE)
else
{
tf_printf("deleting feeds: %s\n", sqlite3_errmsg(db));
delete->deleted += sqlite3_changes(db);
}
}
sqlite3_finalize(statement);
@ -1627,7 +1620,7 @@ static void _tf_ssb_rpc_delete_feeds_work(tf_ssb_t* ssb, void* user_data)
JS_FreeRuntime(runtime);
delete->duration_ms = (uv_hrtime() - start_ns) / 1000000LL;
tf_printf("Deleted %d feeds in %d ms.\n", delete->deleted, (int)delete->duration_ms);
tf_printf("Deleted %d message in %d ms.\n", delete->deleted, (int)delete->duration_ms);
_tf_ssb_rpc_checkpoint(ssb);
}

View File

@ -18,6 +18,8 @@
#include "sodium/crypto_sign.h"
#include "sqlite3.h"
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
@ -659,7 +661,7 @@ void tf_ssb_test_following(const tf_test_options_t* options)
void tf_ssb_test_bench(const tf_test_options_t* options)
{
tf_printf("Testing following.\n");
tf_printf("Testing bench.\n");
uv_loop_t loop = { 0 };
uv_loop_init(&loop);
@ -1393,4 +1395,87 @@ void tf_ssb_test_invite(const tf_test_options_t* options)
uv_loop_close(&loop);
}
void tf_ssb_test_triggers(const tf_test_options_t* options)
{
tf_printf("Testing triggers.\n");
uv_loop_t loop = { 0 };
uv_loop_init(&loop);
tf_trace_t* trace = tf_trace_create();
unlink("out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
tf_ssb_set_trace(ssb0, trace);
tf_ssb_generate_keys(ssb0);
char id0[k_id_base64_len] = { 0 };
tf_ssb_whoami(ssb0, id0, sizeof(id0));
uint8_t priv0[512];
tf_ssb_get_private_key(ssb0, priv0, sizeof(priv0));
struct timespec start_time = { 0 };
struct timespec end_time = { 0 };
clock_gettime(CLOCK_REALTIME, &start_time);
const int k_messages = 5;
JSValue obj = JS_NewObject(tf_ssb_get_context(ssb0));
JS_SetPropertyStr(tf_ssb_get_context(ssb0), obj, "type", JS_NewString(tf_ssb_get_context(ssb0), "post"));
JS_SetPropertyStr(tf_ssb_get_context(ssb0), obj, "text", JS_NewString(tf_ssb_get_context(ssb0), "Hello, world!"));
for (int i = 0; i < k_messages; i++)
{
bool stored = false;
JSValue signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj, NULL, 0);
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
JS_FreeValue(tf_ssb_get_context(ssb0), signed_message);
_wait_stored(ssb0, &stored);
}
JS_FreeValue(tf_ssb_get_context(ssb0), obj);
clock_gettime(CLOCK_REALTIME, &end_time);
tf_printf("insert = %f seconds\n", (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_nsec - start_time.tv_nsec) / 1e9);
int64_t max_sequence = 0;
tf_ssb_db_get_latest_message_by_author(ssb0, id0, &max_sequence, NULL, 0);
tf_printf("max_sequence=%" PRId64 "\n", max_sequence);
assert(max_sequence == 5);
sqlite3* db = tf_ssb_acquire_db_writer(ssb0);
sqlite3_exec(db, "DELETE FROM messages WHERE sequence = 5", NULL, NULL, NULL);
tf_ssb_release_db_writer(ssb0, db);
max_sequence = 0;
tf_ssb_db_get_latest_message_by_author(ssb0, id0, &max_sequence, NULL, 0);
tf_printf("max_sequence=%" PRId64 "\n", max_sequence);
assert(max_sequence == 4);
tf_ssb_acquire_db_writer(ssb0);
sqlite3_exec(db, "DELETE FROM messages", NULL, NULL, NULL);
tf_ssb_release_db_writer(ssb0, db);
max_sequence = 0;
tf_ssb_db_get_latest_message_by_author(ssb0, id0, &max_sequence, NULL, 0);
tf_printf("max_sequence=%" PRId64 "\n", max_sequence);
assert(max_sequence == 0);
uv_run(&loop, UV_RUN_DEFAULT);
char* trace_data = tf_trace_export(trace);
if (trace_data)
{
FILE* file = fopen("out/trace.json", "wb");
if (file)
{
fwrite(trace_data, 1, strlen(trace_data), file);
fclose(file);
}
tf_free(trace_data);
}
tf_ssb_destroy(ssb0);
tf_trace_destroy(trace);
uv_loop_close(&loop);
}
#endif

View File

@ -83,4 +83,10 @@ void tf_ssb_test_connect_str(const tf_test_options_t* options);
*/
void tf_ssb_test_invite(const tf_test_options_t* options);
/**
** Test triggers.
** @param options The test options.
*/
void tf_ssb_test_triggers(const tf_test_options_t* options);
/** @} */

View File

@ -128,6 +128,7 @@ typedef struct _tf_task_t
sqlite3* _db;
tf_ssb_t* _ssb;
tf_http_t* _http;
tf_trace_t* _trace;
@ -1756,6 +1757,7 @@ void tf_task_activate(tf_task_t* task)
JS_SetPropertyStr(context, global, "getStats", JS_NewCFunction(context, _tf_task_getStats, "getStats", 0));
tf_httpd_register(context);
task->_http = tf_httpd_create(context);
}
else
{
@ -1868,13 +1870,10 @@ void tf_task_destroy(tf_task_t* task)
{
tf_ssb_destroy(task->_ssb);
}
/* This ensures the HTTP handlers get cleaned up. */
if (task->_trusted)
if (task->_http)
{
JSValue global = JS_GetGlobalObject(task->_context);
JS_SetPropertyStr(task->_context, global, "httpd", JS_UNDEFINED);
JS_FreeValue(task->_context, global);
tf_httpd_destroy(task->_http);
task->_http = NULL;
}
JS_FreeContext(task->_context);

View File

@ -1078,6 +1078,7 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "replicate", tf_ssb_test_replicate, false);
_tf_test_run(options, "connect_str", tf_ssb_test_connect_str, false);
_tf_test_run(options, "invite", tf_ssb_test_invite, false);
_tf_test_run(options, "triggers", tf_ssb_test_triggers, false);
tf_printf("Tests completed.\n");
#endif
}

View File

@ -1,2 +1,2 @@
#define VERSION_NUMBER "0.0.29"
#define VERSION_NUMBER "0.0.30-wip"
#define VERSION_NAME "This program kills fascists."

View File

@ -174,7 +174,26 @@ try:
select(driver, ['#document', 'frame', '=ssb'], ('click',))
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#edit'], ('send_keys', 'Hello, world!'))
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
select(driver, ['//label[text()="Remember this decision."]'], ('click',))
select(driver, ['//button[text()="❌ Deny"]'], ('click',))
driver.switch_to.alert.accept()
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
driver.switch_to.alert.accept()
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
select(driver, ['tf-navigation', 'shadow_root', '=🎛️'], ('click',))
select(driver, ['tf-navigation', 'shadow_root', '#permission_reset:ssb_append'], ('click',))
select(driver, ['tf-navigation', 'shadow_root', '#permissions_close'], ('click',))
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
select(driver, ['//button[text()="❌ Deny"]'], ('click',))
driver.switch_to.alert.accept()
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#edit'], ('send_keys', 'Hello, world 2!'))
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
select(driver, ['//label[text()="Remember this decision."]'], ('click',))
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#edit'], ('send_keys', 'Hello, world 3!'))
select(driver, ['#document', 'frame', 'tf-app', 'shadow_root', '#tf-tab-news', 'shadow_root', '#tf-compose', 'shadow_root', '#submit'], ('click',))
select(driver, ['tf-navigation', 'shadow_root', '#identity'], ('click',))
select(driver, ['tf-navigation', 'shadow_root', '#logout'], ('click',))

View File

@ -17,6 +17,7 @@ k_feeds = {
'habitat': 'https://tildegit.org/jeremylist/habitat.rss',
'manyverse': 'https://gitlab.com/staltz/manyverse/-/commits/master?format=atom',
'ahau': 'https://gitlab.com/ahau/ahau/-/commits/master.atom',
'patchfox': 'https://github.com/soapdog/patchfox/commits.atom',
}
def fix_title(entry):

View File

@ -1,4 +1,4 @@
VERSION=3.2.1
VERSION=3.3.0
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js -O deps/lit/lit-all.min.js
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js.map -O deps/lit/lit-all.min.js.map
cp -fv deps/lit/* apps/blog/