4 Commits

10 changed files with 223 additions and 12 deletions

View File

@@ -253,7 +253,10 @@ $(ANDROID_TARGETS): CFLAGS += \
-fno-asynchronous-unwind-tables \
-funwind-tables \
-Wno-unknown-warning-option
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
$(ANDROID_TARGETS): LDFLAGS += \
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
-Wl,-z,max-page-size=16384 \
-fPIC
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
$(DEBUG_TARGETS): LDFLAGS += -Og
$(RELEASE_TARGETS): CFLAGS += \

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦀",
"previous": "&TTGzyovmfKozjELCGPBFLLEXQcpfaArOMmqzemvz9J8=.sha256"
"previous": "&5T+xPy3LhgmU2ape4dlJLRhYhmE5J1SQkI+wFm6Fss4=.sha256"
}

View File

@@ -22,6 +22,7 @@ class TfElement extends LitElement {
guest: {type: Boolean},
url: {type: String},
private_messages: {type: Array},
grouped_private_messages: {type: Object},
recent_reactions: {type: Array},
is_administrator: {type: Boolean},
stay_connected: {type: Boolean},
@@ -140,7 +141,9 @@ class TfElement extends LitElement {
'',
'@',
'👍',
'🔐',
...Object.keys(this.grouped_private_messages)
.sort()
.map((x) => '🔐' + JSON.parse(x).join(',')),
...this.channels.map((x) => '#' + x),
];
let index = channel_names.indexOf(this.hash.substring(1));
@@ -366,6 +369,32 @@ class TfElement extends LitElement {
return result;
}
async group_private_messages(messages) {
let groups = {};
let result = await this.decrypt(
await tfrpc.rpc.query(
`
SELECT messages.id, author, timestamp, json(content) AS content
FROM messages
JOIN json_each(?) AS ids
WHERE messages.id = ids.value
ORDER BY timestamp DESC
`,
[JSON.stringify(messages)]
)
);
for (let message of result) {
let key = JSON.stringify(
message?.decrypted?.recps?.filter((x) => x != this.whoami)?.sort()
);
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(message);
}
return groups;
}
async load_channels_latest(following) {
let start_time = new Date();
let latest_private = this.get_latest_private(following);
@@ -438,12 +467,15 @@ class TfElement extends LitElement {
console.log('channels took', (new Date() - start_time) / 1000.0);
let self = this;
start_time = new Date();
latest_private.then(function (latest) {
latest_private.then(async function (latest) {
self.channels_latest = Object.assign({}, self.channels_latest, {
'🔐': latest[0],
});
console.log('private took', (new Date() - start_time) / 1000.0);
self.private_messages = latest[1];
self.grouped_private_messages = await self.group_private_messages(
latest[1]
);
});
}
@@ -630,6 +662,7 @@ class TfElement extends LitElement {
@loadmessages=${this.reset_progress}
.connections=${this.connections}
.private_messages=${this.private_messages}
.grouped_private_messages=${this.grouped_private_messages}
.recent_reactions=${this.recent_reactions}
?is_administrator=${this.is_administrator}
?stay_connected=${this.stay_connected}

View File

@@ -18,6 +18,7 @@ class TfTabNewsFeedElement extends LitElement {
time_range: {type: Array},
time_loading: {type: Array},
private_messages: {type: Array},
grouped_private_messages: {type: Object},
recent_reactions: {type: Array},
};
}
@@ -227,6 +228,31 @@ class TfTabNewsFeedElement extends LitElement {
]
);
result = (await this.decrypt(result)).filter((x) => x.decrypted);
} else if (this.hash.startsWith('#🔐')) {
let ids = this.hash.substring('#🔐'.length).split(',');
console.log(this.grouped_private_messages);
result = await tfrpc.rpc.query(
`
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
JOIN json_each(?1) AS private_messages ON messages.id = private_messages.value
WHERE
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
json(messages.content) LIKE '"%'
ORDER BY messages.rowid DESC LIMIT ?4
`,
[
JSON.stringify(
this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
(x) => x.id
) ?? []
),
start_time,
end_time,
k_max_results,
]
);
result = (await this.decrypt(result)).filter((x) => x.decrypted);
} else if (this.hash == '#👍') {
result = await tfrpc.rpc.query(
`

View File

@@ -24,6 +24,7 @@ class TfTabNewsElement extends LitElement {
channels_latest: {type: Object},
connections: {type: Array},
private_messages: {type: Array},
grouped_private_messages: {type: Object},
recent_reactions: {type: Array},
peer_exchange: {type: Boolean},
is_administrator: {type: Boolean},
@@ -257,12 +258,28 @@ class TfTabNewsElement extends LitElement {
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
>${this.unread_status('👍')}👍votes</a
>
<a
href="#🔐"
class="w3-bar-item w3-button"
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined}
>${this.unread_status('🔐')}🔐private</a
>
${Object.keys(this?.grouped_private_messages ?? [])
?.sort()
?.map(
(key) => html`
<a
href=${'#🔐' + JSON.parse(key).join(',')}
class="w3-bar-item w3-button"
style=${this.hash == '#🔐' + JSON.parse(key).join(',')
? 'font-weight: bold'
: undefined}
>${(key != '[]' ? JSON.parse(key) : [this.whoami]).map(
(id) => html`
<tf-user
id=${id}
nolink="true"
.users=${this.users}
></tf-user>
`
)}</a
>
`
)}
${Object.keys(this.drafts)
.sort()
.map(
@@ -434,6 +451,7 @@ class TfTabNewsElement extends LitElement {
.channels_unread=${this.channels_unread}
.channels_latest=${this.channels_latest}
.private_messages=${this.private_messages}
.grouped_private_messages=${this.grouped_private_messages}
.recent_reactions=${this.recent_reactions}
></tf-tab-news-feed>
</div>

View File

@@ -9,6 +9,7 @@ class TfUserElement extends LitElement {
fallback_name: {type: String},
icon_only: {type: Boolean},
users: {type: Object},
nolink: {type: Boolean},
};
}
@@ -37,7 +38,9 @@ class TfUserElement extends LitElement {
let name_string = name ?? this.fallback_name ?? this.id;
name = this.icon_only
? undefined
: html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`;
: !this.nolink
? html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`
: html`<span>${name_string}</span>`;
if (user) {
let image_link = user.image;
@@ -56,7 +59,8 @@ class TfUserElement extends LitElement {
}
}
return html` <div
style="display: inline-block; vertical-align: middle; font-weight: bold; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis"
style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
(this.nolink ? '' : '; font-weight: bold')}
>
${image} ${name}
</div>`;

View File

@@ -133,6 +133,15 @@ typedef struct _tf_ssb_message_added_callback_node_t
tf_ssb_message_added_callback_node_t* next;
} tf_ssb_message_added_callback_node_t;
typedef struct _tf_ssb_blob_stored_callback_node_t tf_ssb_blob_stored_callback_node_t;
typedef struct _tf_ssb_blob_stored_callback_node_t
{
tf_ssb_blob_stored_callback_t* callback;
tf_ssb_callback_cleanup_t* cleanup;
void* user_data;
tf_ssb_blob_stored_callback_node_t* next;
} tf_ssb_blob_stored_callback_node_t;
typedef struct _tf_ssb_blob_want_added_callback_node_t tf_ssb_blob_want_added_callback_node_t;
typedef struct _tf_ssb_blob_want_added_callback_node_t
{
@@ -235,6 +244,9 @@ typedef struct _tf_ssb_t
tf_ssb_message_added_callback_node_t* message_added;
int message_added_count;
tf_ssb_blob_stored_callback_node_t* blob_stored;
int blob_stored_count;
tf_ssb_blob_want_added_callback_node_t* blob_want_added;
int blob_want_added_count;
@@ -2741,6 +2753,17 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
}
tf_free(node);
}
while (ssb->blob_stored)
{
tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored;
ssb->blob_stored = node->next;
ssb->blob_stored_count--;
if (node->cleanup)
{
node->cleanup(ssb, node->user_data);
}
tf_free(node);
}
while (ssb->blob_want_added)
{
tf_ssb_blob_want_added_callback_node_t* node = ssb->blob_want_added;
@@ -3960,9 +3983,53 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca
}
}
void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void (*cleanup)(tf_ssb_t* ssb, void* user_data), void* user_data)
{
tf_ssb_blob_stored_callback_node_t* node = tf_malloc(sizeof(tf_ssb_blob_stored_callback_node_t));
*node = (tf_ssb_blob_stored_callback_node_t) {
.callback = callback,
.cleanup = cleanup,
.user_data = user_data,
.next = ssb->blob_stored,
};
ssb->blob_stored = node;
ssb->blob_stored_count++;
}
void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data)
{
tf_ssb_blob_stored_callback_node_t** it = &ssb->blob_stored;
while (*it)
{
if ((*it)->callback == callback && (*it)->user_data == user_data)
{
tf_ssb_blob_stored_callback_node_t* node = *it;
*it = node->next;
ssb->blob_stored_count--;
if (node->cleanup)
{
node->cleanup(ssb, node->user_data);
}
tf_free(node);
}
else
{
it = &(*it)->next;
}
}
}
void tf_ssb_notify_blob_stored(tf_ssb_t* ssb, const char* id)
{
tf_ssb_blob_stored_callback_node_t* next = NULL;
ssb->blobs_stored++;
for (tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored; node; node = next)
{
next = node->next;
tf_trace_begin(ssb->trace, "blob stored callback");
node->callback(ssb, id, node->user_data);
tf_trace_end(ssb->trace);
}
}
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_keys)

View File

@@ -680,6 +680,31 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca
*/
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_with_keys);
/**
** A callback called when a blob is added to the database.
** @param ssb The SSB instance.
** @param id The blob identifier.
** @param user_data The user data.
*/
typedef void(tf_ssb_blob_stored_callback_t)(tf_ssb_t* ssb, const char* id, void* user_data);
/**
** Register a callback called when a blob is added to the database.
** @param ssb The SSB instance.
** @param callback The callback function.
** @param cleanup A function to call when the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Remove a callback registered for when a blob is added to the database.
** @param ssb The SSB instance.
** @param callback The callback function.
** @param user_data User data registered with the callback.
*/
void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data);
/**
** Record that a new blob was stored.
** @param ssb The SSB instance.

View File

@@ -1627,6 +1627,20 @@ static void _tf_ssb_on_message_added_callback(tf_ssb_t* ssb, const char* author,
JS_FreeValue(context, string);
}
static void _tf_ssb_on_blob_stored_callback(tf_ssb_t* ssb, const char* id, void* user_data)
{
JSContext* context = tf_ssb_get_context(ssb);
JSValue callback = JS_MKPTR(JS_TAG_OBJECT, user_data);
JSValue string = JS_NewString(context, id);
JSValue response = JS_Call(context, callback, JS_UNDEFINED, 1, &string);
if (tf_util_report_error(context, response))
{
tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, user_data);
}
JS_FreeValue(context, response);
JS_FreeValue(context, string);
}
static void _tf_ssb_on_blob_want_added_callback(tf_ssb_t* ssb, const char* id, void* user_data)
{
JSContext* context = tf_ssb_get_context(ssb);
@@ -1747,6 +1761,11 @@ static JSValue _tf_ssb_add_event_listener(JSContext* context, JSValueConst this_
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
tf_ssb_add_message_added_callback(ssb, _tf_ssb_on_message_added_callback, _tf_ssb_cleanup_value, ptr);
}
else if (strcmp(event_name, "blob") == 0)
{
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
tf_ssb_add_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, _tf_ssb_cleanup_value, ptr);
}
else if (strcmp(event_name, "blob_want_added") == 0)
{
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
@@ -1790,6 +1809,11 @@ static JSValue _tf_ssb_remove_event_listener(JSContext* context, JSValueConst th
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
tf_ssb_remove_message_added_callback(ssb, _tf_ssb_on_message_added_callback, ptr);
}
else if (strcmp(event_name, "blob") == 0)
{
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, ptr);
}
else if (strcmp(event_name, "blob_want_added") == 0)
{
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));

View File

@@ -152,6 +152,12 @@ static void _wait_stored(tf_ssb_t* ssb, bool* stored)
}
}
static void _blob_stored(tf_ssb_t* ssb, const char* id, void* user_data)
{
tf_printf("blob stored %s\n", id);
*(bool*)user_data = true;
}
void tf_ssb_test_ssb(const tf_test_options_t* options)
{
tf_printf("Testing SSB.\n");
@@ -224,8 +230,13 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
char blob_id[k_id_base64_len] = { 0 };
const char* k_blob = "Hello, blob!";
bool blob_stored = false;
tf_ssb_add_blob_stored_callback(ssb0, _blob_stored, NULL, &blob_stored);
b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL);
tf_ssb_notify_blob_stored(ssb0, blob_id);
tf_ssb_remove_blob_stored_callback(ssb0, _blob_stored, &blob_stored);
assert(b);
assert(blob_stored);
JSContext* context0 = tf_ssb_get_context(ssb0);
JSValue obj = JS_NewObject(context0);