ssb: Proof of concept to try to stay connected to a handful of peers. #130
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 37m27s

This commit is contained in:
2025-06-28 13:47:58 -04:00
parent 8f84ff2611
commit 052663efbe
7 changed files with 70 additions and 14 deletions

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🦀", "emoji": "🦀",
"previous": "&fHj5cygP9tDGnJRTFZes1kywnjetkTH9z74qFy8nhXg=.sha256" "previous": "&lOCyez82jwCUs8R/Ynx2nBK598+qDZ7RhGnWOaNQO9U=.sha256"
} }

View File

@ -23,6 +23,8 @@ class TfElement extends LitElement {
url: {type: String}, url: {type: String},
private_messages: {type: Array}, private_messages: {type: Array},
recent_reactions: {type: Array}, recent_reactions: {type: Array},
is_administrator: {type: Boolean},
stay_connected: {type: Boolean},
}; };
} }
@ -73,6 +75,10 @@ class TfElement extends LitElement {
async initial_load() { async initial_load() {
let whoami = await tfrpc.rpc.getActiveIdentity(); let whoami = await tfrpc.rpc.getActiveIdentity();
let ids = (await tfrpc.rpc.getIdentities()) || []; let ids = (await tfrpc.rpc.getIdentities()) || [];
this.is_administrator = await tfrpc.rpc.isAdministrator();
this.stay_connected =
this.is_administrator &&
(await tfrpc.rpc.globalSettingsGet('stay_connected'));
this.url = await tfrpc.rpc.url(); this.url = await tfrpc.rpc.url();
this.whoami = whoami ?? (ids.length ? ids[0] : undefined); this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
this.guest = !this.whoami?.length; this.guest = !this.whoami?.length;
@ -658,6 +664,18 @@ class TfElement extends LitElement {
tfrpc.rpc.sync(); tfrpc.rpc.sync();
} }
async toggle_stay_connected() {
let stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
let new_stay_connected = !this.stay_connected;
try {
if (new_stay_connected != stay_connected) {
await tfrpc.rpc.globalSettingsSet('stay_connected', new_stay_connected);
}
} finally {
this.stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
}
}
render() { render() {
let self = this; let self = this;
@ -680,14 +698,26 @@ class TfElement extends LitElement {
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"
> >
${this.is_administrator
? html`
<button <button
class=${'w3-bar-item w3-button w3-circle w3-ripple' + class=${'w3-bar-item w3-button w3-circle w3-ripple' +
(this.connections?.some((x) => x.flags.one_shot) ? ' w3-spin' : '')} (this.connections?.some((x) => x.flags.one_shot)
? ' w3-spin'
: '')}
style="width: 1.5em; height: 1.5em; padding: 8px" style="width: 1.5em; height: 1.5em; padding: 8px"
@click=${this.refresh} @click=${this.refresh}
> >
</button> </button>
<button
class="w3-bar-item w3-button w3-ripple"
@click=${this.toggle_stay_connected}
>
${this.stay_connected ? '🔗' : '⛓️‍💥'}
</button>
`
: undefined}
${Object.entries(k_tabs).map( ${Object.entries(k_tabs).map(
([k, v]) => html` ([k, v]) => html`
<button <button

View File

@ -686,7 +686,11 @@ class TfMessageElement extends LitElement {
${x.action} ${x.action}
${x.users.map( ${x.users.map(
(y) => html` (y) => html`
<tf-user id=${y} .users=${this.users} icon_only=true></tf-user> <tf-user
id=${y}
.users=${this.users}
icon_only="true"
></tf-user>
` `
)} )}
</div> </div>

View File

@ -76,7 +76,8 @@
<h2>First-time user checklist:</h2> <h2>First-time user checklist:</h2>
<ol type="1" style="text-align: left"> <ol type="1" style="text-align: left">
<li> <li>
<a href="https://dev.tildefriends.net/cory/tildefriends/releases/latest" <a
href="https://dev.tildefriends.net/cory/tildefriends/releases/latest"
>Download</a >Download</a
> >
Tilde Friends or use Tilde Friends or use

View File

@ -61,6 +61,7 @@ options:
autologin (default: false): Whether mobile autologin is supported. autologin (default: false): Whether mobile autologin is supported.
broadcast (default: true): Send network discovery broadcasts. broadcast (default: true): Send network discovery broadcasts.
discovery (default: true): Receive network discovery broadcasts. discovery (default: true): Receive network discovery broadcasts.
stay_connected (default: false): Whether to attempt to keep several peer connections open.
-o, --one-proc Run everything in one process (unsafely!). -o, --one-proc Run everything in one process (unsafely!).
-z, --zip path Zip archive from which to load files. -z, --zip path Zip archive from which to load files.
-v, --verbose Log raw messages. -v, --verbose Log raw messages.

View File

@ -2,6 +2,7 @@
#include "log.h" #include "log.h"
#include "mem.h" #include "mem.h"
#include "ssb.db.h"
#include "ssb.h" #include "ssb.h"
#include "util.js.h" #include "util.js.h"
@ -51,15 +52,19 @@ static void _tf_ssb_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t
} }
} }
static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connections, char* host, size_t host_size, int* port, char* key, size_t key_size) static bool _tf_ssb_connections_get_next_connection(
tf_ssb_connections_t* connections, char* host, size_t host_size, int* port, char* key, size_t key_size, bool* out_stay_connected)
{ {
bool result = false; bool result = false;
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(connections->ssb); sqlite3* db = tf_ssb_acquire_db_reader(connections->ssb);
tf_ssb_db_get_global_setting_bool(db, "stay_connected", out_stay_connected);
if (sqlite3_prepare_v2(db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > ?1) ORDER BY last_attempt LIMIT 1", if (sqlite3_prepare_v2(db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > ?1) ORDER BY last_attempt LIMIT 1",
-1, &statement, NULL) == SQLITE_OK) -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_int(statement, 1, 60000) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_int(statement, 1, *out_stay_connected ? 15 : 60000) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
tf_string_set(host, host_size, (const char*)sqlite3_column_text(statement, 0)); tf_string_set(host, host_size, (const char*)sqlite3_column_text(statement, 0));
*port = sqlite3_column_int(statement, 1); *port = sqlite3_column_int(statement, 1);
@ -80,6 +85,8 @@ typedef struct _tf_ssb_connections_get_next_t
{ {
tf_ssb_connections_t* connections; tf_ssb_connections_t* connections;
bool ready; bool ready;
bool stay_connected;
bool full;
char host[256]; char host[256];
int port; int port;
char key[k_id_base64_len]; char key[k_id_base64_len];
@ -92,7 +99,7 @@ static void _tf_ssb_connections_get_next_work(tf_ssb_t* ssb, void* user_data)
{ {
return; return;
} }
next->ready = _tf_ssb_connections_get_next_connection(next->connections, next->host, sizeof(next->host), &next->port, next->key, sizeof(next->key)); next->ready = _tf_ssb_connections_get_next_connection(next->connections, next->host, sizeof(next->host), &next->port, next->key, sizeof(next->key), &next->stay_connected);
} }
static void _tf_ssb_connections_get_next_after_work(tf_ssb_t* ssb, int status, void* user_data) static void _tf_ssb_connections_get_next_after_work(tf_ssb_t* ssb, int status, void* user_data)
@ -100,12 +107,20 @@ static void _tf_ssb_connections_get_next_after_work(tf_ssb_t* ssb, int status, v
tf_ssb_connections_get_next_t* next = user_data; tf_ssb_connections_get_next_t* next = user_data;
if (next->ready) if (next->ready)
{ {
/*
** Might be a duplicate connection or otherwise discarded by
** tf_ssb_connect() before we otherwise set attempted, so do it
** here.
*/
tf_ssb_connections_set_attempted(next->connections, next->host, next->port, next->key);
uint8_t key_bin[k_id_bin_len]; uint8_t key_bin[k_id_bin_len];
if (tf_ssb_id_str_to_bin(key_bin, next->key)) if (tf_ssb_id_str_to_bin(key_bin, next->key))
{ {
tf_ssb_connect(ssb, next->host, next->port, key_bin, k_tf_ssb_connect_flag_do_not_store, NULL, NULL); tf_ssb_connect(ssb, next->host, next->port, key_bin, k_tf_ssb_connect_flag_do_not_store, NULL, NULL);
} }
} }
uv_timer_set_repeat(&next->connections->timer, next->stay_connected ? (next->full ? 2000 : 200) : (next->full ? 10000 : 2000));
tf_free(next); tf_free(next);
} }
@ -124,6 +139,7 @@ static void _tf_ssb_connections_timer(uv_timer_t* timer)
tf_ssb_connections_get_next_t* next = tf_malloc(sizeof(tf_ssb_connections_get_next_t)); tf_ssb_connections_get_next_t* next = tf_malloc(sizeof(tf_ssb_connections_get_next_t));
*next = (tf_ssb_connections_get_next_t) { *next = (tf_ssb_connections_get_next_t) {
.connections = connections, .connections = connections,
.full = count + 1 == tf_countof(active),
}; };
tf_ssb_run_work(connections->ssb, _tf_ssb_connections_get_next_work, _tf_ssb_connections_get_next_after_work, next); tf_ssb_run_work(connections->ssb, _tf_ssb_connections_get_next_work, _tf_ssb_connections_get_next_after_work, next);
} }

View File

@ -400,6 +400,10 @@ static const setting_t k_settings[] = {
{ .name = "autologin", .type = "boolean", .description = "Whether mobile autologin is supported.", .default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE != 0 } }, { .name = "autologin", .type = "boolean", .description = "Whether mobile autologin is supported.", .default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE != 0 } },
{ .name = "broadcast", .type = "boolean", .description = "Send network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } }, { .name = "broadcast", .type = "boolean", .description = "Send network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
{ .name = "discovery", .type = "boolean", .description = "Receive network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } }, { .name = "discovery", .type = "boolean", .description = "Receive network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
{ .name = "stay_connected",
.type = "boolean",
.description = "Whether to attempt to keep several peer connections open.",
.default_value = { .kind = k_kind_bool, .bool_value = false } },
}; };
static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind) static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind)