From 24530e11589cdd31bf71aa634768fe1d8806f65f Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 31 Jul 2022 19:01:08 +0000 Subject: [PATCH] First glimpse of multiple SSB identities per Tilde Friends user. git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3941 ed5197a5-7fde-0310-b194-c3ffbd925b24 --- apps/cory/ssb.json | 2 +- apps/cory/ssb/app.js | 56 +++++++++++++++++++-------------- apps/cory/ssb/index.html | 8 ++++- apps/cory/ssb/tf.js | 16 ++++++++-- core/core.js | 7 +++++ core/ssb.js | 16 +++++----- src/ssb.db.c | 14 +++++++++ src/ssb.db.h | 1 + src/ssb.js.c | 68 ++++++++++++++-------------------------- 9 files changed, 108 insertions(+), 80 deletions(-) diff --git a/apps/cory/ssb.json b/apps/cory/ssb.json index b8f756ef..6d0cca0f 100644 --- a/apps/cory/ssb.json +++ b/apps/cory/ssb.json @@ -1 +1 @@ -{"type":"tildefriends-app","files":{"app.js":"&TOlRdlgclkUtDVAOmfSoAQCoRzzMN4wAvKmF0cRvOHk=.sha256","index.html":"&TGAqOmpnOigx4XQbFix82ugZEOfR1Gdc2zMWUQMSvwI=.sha256","vue-material.js":"&K5cdLqXYCENPak/TCINHQhyJhpS4G9DlZHGwoh/LF2g=.sha256","tf-user.js":"&smJOHGgIsKwQXeEJD3VbHX4A+vhr1Se54WlSHUP/Km8=.sha256","tf-message.js":"&7M+IUC+xHyc306xRrJcx3TnYpONYLlgCsF80m2M2ws4=.sha256","tf.js":"&0+ub/dNZzf74GUWyVIzGZPb6pKJDx6y7I4Stsosh9Y0=.sha256","commonmark.min.js":"&EP0OeR9zyLwZannz+0ga4s9AGES2RLvvIIQYHqqV6+k=.sha256","vue.js":"&g1wvA+yHl1sVC+eufTsg9If7ZeVyMTBU+h0tks7ZNzE=.sha256","vue-material-theme-default-dark.css":"&RP2nr+2CR18BpHHw5ST9a5GJUCOG9n0G2kuGkcQioWE=.sha256","vue-material.min.css":"&kGbUM2QgFSyHZRzqQb0b+0S3EVIlZ0AXpdiAVjIhou8=.sha256","roboto.css":"&jJv43Om673mQO5JK0jj7714s5E+5Yrf82H6LcDx7wUs=.sha256","material-icons.css":"&a28PdcVvgq/DxyIvJAx/e+ZOEtOuHnr3kjLWKyzH11M=.sha256","tf-shared.js":"&MPINm55jkpz2rrNbwsYl09PKGvbgL3nwgBy6CMQkSnw=.sha256","style.css":"&qegBNCrVUihxffRUxGFuG/6u+0Y6d18zHtfNHBZtZ04=.sha256"}} \ No newline at end of file +{"type":"tildefriends-app","files":{"app.js":"&acYxT0G3CMw7yeOLrSZEyUMddadjz3Tj3s922ufQBcQ=.sha256","index.html":"&zWXxIB5f290SW1LGSasi41hNMJUwWzFTnSGeO/lh5zI=.sha256","vue-material.js":"&K5cdLqXYCENPak/TCINHQhyJhpS4G9DlZHGwoh/LF2g=.sha256","tf-user.js":"&smJOHGgIsKwQXeEJD3VbHX4A+vhr1Se54WlSHUP/Km8=.sha256","tf-message.js":"&7M+IUC+xHyc306xRrJcx3TnYpONYLlgCsF80m2M2ws4=.sha256","tf.js":"&FD0n9f4JMrvTFTxNOpMKDPv0e2Cex49G+oWdZOUcd/I=.sha256","commonmark.min.js":"&EP0OeR9zyLwZannz+0ga4s9AGES2RLvvIIQYHqqV6+k=.sha256","vue.js":"&g1wvA+yHl1sVC+eufTsg9If7ZeVyMTBU+h0tks7ZNzE=.sha256","vue-material-theme-default-dark.css":"&RP2nr+2CR18BpHHw5ST9a5GJUCOG9n0G2kuGkcQioWE=.sha256","vue-material.min.css":"&kGbUM2QgFSyHZRzqQb0b+0S3EVIlZ0AXpdiAVjIhou8=.sha256","roboto.css":"&jJv43Om673mQO5JK0jj7714s5E+5Yrf82H6LcDx7wUs=.sha256","material-icons.css":"&a28PdcVvgq/DxyIvJAx/e+ZOEtOuHnr3kjLWKyzH11M=.sha256","tf-shared.js":"&MPINm55jkpz2rrNbwsYl09PKGvbgL3nwgBy6CMQkSnw=.sha256","style.css":"&qegBNCrVUihxffRUxGFuG/6u+0Y6d18zHtfNHBZtZ04=.sha256"}} \ No newline at end of file diff --git a/apps/cory/ssb/app.js b/apps/cory/ssb/app.js index 09fe1faf..07111028 100644 --- a/apps/cory/ssb/app.js +++ b/apps/cory/ssb/app.js @@ -5,6 +5,8 @@ const k_votes_max = 20; var g_ready = false; var g_selected = null; +let g_whoami = null; +let g_hash = null; var g_blocking_cache = {}; var g_following_cache = {}; @@ -313,7 +315,6 @@ async function getRelatedPostIds(db, message, ids, limit) { id = JSON.parse(message.content).root || id; } catch { } - print("id = ", id); for (var i = 0; i < ids.length; i += k_batch_max) { var ids_batch = ids.slice(i, Math.min(i + k_batch_max, ids.length)); await ssb.sqlStream( @@ -395,8 +396,9 @@ async function getPosts(db, ids) { } tfrpc.register(async function ready() { + let identities = await ssb.getIdentities(); + await tfrpc.rpc.set_identities(identities); g_ready = true; - return refresh(g_selected); }); tfrpc.register(async function store_blob(blob) { @@ -439,13 +441,24 @@ async function updateSequences(db) { return changes; } -async function refresh(selected) { +let g_refresh_limit = 5; + +async function refresh_internal(whoami, selected) { + if (typeof(whoami) !== 'string') { + return; + } + if (whoami !== g_whoami || selected !== g_selected) { + g_whoami = whoami; + g_selected = selected; + } else { + return; + } var timing = []; timing.push({name: 'start', time: new Date()}); g_following_cache = {}; g_following_deep_cache = {}; await tfrpc.rpc.clear(); - var whoami = await ssb.whoami(); + await tfrpc.rpc.set_identities(await ssb.getIdentities()); var db = await database("ssb"); g_sequence = {}; try { @@ -458,14 +471,15 @@ async function refresh(selected) { timing.push({name: 'blocked', time: new Date()}); var all_followed = await followingDeep(db, [whoami], 2, blocked); timing.push({name: 'all_followed', time: new Date()}); - if (selected) { - g_selected = selected; - } else { - g_selected = all_followed; + let actual_selected = selected ? selected : all_followed; + let hash = selected && selected.length == 1 ? selected[0] : null; + if (hash !== g_hash) { + g_hash = hash; + print(g_hash, '=>', hash); + //await tfrpc.rpc.set_hash(hash); } await Promise.all([ - tfrpc.rpc.set('whoami', whoami),, - tfrpc.rpc.set_hash(selected && selected.length == 1 ? selected[0] : null), + tfrpc.rpc.set('whoami', whoami), tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts()), tfrpc.rpc.set('connections', await ssb.connections()), tfrpc.rpc.set('apps', await core.apps()), @@ -477,7 +491,7 @@ async function refresh(selected) { m = m.length ? m[0] : {id: selected[0]}; ids = await getRelatedPostIds(db, m, all_followed, k_posts_max); } else { - ids = await getRecentPostIds2(db, whoami, g_selected, (new Date()).valueOf() - (24 * 60 * 60 * 1000)); + ids = await getRecentPostIds2(db, whoami, actual_selected, (new Date()).valueOf() - (24 * 60 * 60 * 1000)); } timing.push({name: 'get_post_ids', time: new Date()}); var posts = await getPosts(db, ids); @@ -544,7 +558,7 @@ ssb.addEventListener('message', async function(id) { var db = await database("ssb"); var posts = await getPosts(db, [id]); for (let post of posts) { - if (post.author == await ssb.whoami() || + if (post.author == g_whoami || JSON.parse(post.content).type != 'post') { await tfrpc.rpc.push_posts([post]); } else { @@ -553,6 +567,10 @@ ssb.addEventListener('message', async function(id) { } }); +tfrpc.register(async function refresh(whoami, selected) { + return refresh_internal(whoami, selected); +}); + async function addAppSources(message) { if (message.mentions) { for (let mention of message.mentions) { @@ -576,19 +594,11 @@ core.register('message', async function(m) { await ssb.connect(m.message.connect); } else if (m.message.appendMessage) { await addAppSources(m.message.appendMessage); - await ssb.appendMessage(m.message.appendMessage); - } else if (m.message.refresh) { - await refresh(g_selected); + await ssb.appendMessageWithIdentity(g_whoami, m.message.appendMessage); } } else if (m.event == 'hashChange') { - if (m.hash.length > 1) { - g_selected = [m.hash.substring(1)]; - } else { - g_selected = null; - } - if (g_ready) { - await refresh(g_selected); - } + let selected = m.hash.length > 1 ? [m.hash.substring(1)] : null; + await refresh_internal(g_whoami, selected); } else if (m.event == 'focus' || m.event == 'blur') { /* Shh. */ } else { diff --git a/apps/cory/ssb/index.html b/apps/cory/ssb/index.html index 963cd0df..167c3561 100644 --- a/apps/cory/ssb/index.html +++ b/apps/cory/ssb/index.html @@ -43,7 +43,13 @@ -
Welcome, .
+
Welcome, + + + {{identity}} + + +
diff --git a/apps/cory/ssb/tf.js b/apps/cory/ssb/tf.js index b3a265cb..c74e682f 100644 --- a/apps/cory/ssb/tf.js +++ b/apps/cory/ssb/tf.js @@ -3,6 +3,7 @@ import * as tfshared from './tf-shared.js'; export var g_data = { whoami: null, + identities: [], connections: [], messages: [], messages_by_id: {}, @@ -48,7 +49,9 @@ tfrpc.register(function clear() { g_load_start = new Date(); g_data.loading = true; Object.keys(g_data_initial).forEach(function(key) { - Vue.set(g_data, key, JSON.parse(JSON.stringify(g_data_initial[key]))); + if (key != 'identities' && key != 'whoami') { + Vue.set(g_data, key, JSON.parse(JSON.stringify(g_data_initial[key]))); + } }); }); @@ -56,6 +59,15 @@ tfrpc.register(function set(key, value) { g_data[key] = value; }); +tfrpc.register(function set_identities(value) { + if (JSON.stringify(g_data.identities) != JSON.stringify(value)) { + g_data.identities = value.map(x => x); + } + if (!g_data.whoami && value.length) { + g_data.whoami = value[0]; + } +}); + tfrpc.register(function push_users(users) { for (let [id, user] of Object.entries(users)) { Vue.set(g_data.users, id, Object.assign({}, g_data.users[id] || {}, user)); @@ -207,7 +219,7 @@ window.addEventListener('load', function() { }, markdown: tfshared.markdown, refresh: function() { - window.parent.postMessage({refresh: true}, '*'); + tfrpc.rpc.refresh(g_data.whoami); }, add_app_to_mentions: function(app) { Vue.set(g_data.mentions, g_data.apps[app], { diff --git a/core/core.js b/core/core.js index 8261aa13..5f1e8e17 100644 --- a/core/core.js +++ b/core/core.js @@ -249,6 +249,13 @@ async function getProcessBlob(blobId, key, options) { return ssb.getIdentities(process.credentials.session.name); } }; + imports.ssb.appendMessageWithIdentity = function(id, message) { + if (process.credentials && + process.credentials.session && + process.credentials.session.name) { + return ssb.appendMessageWithIdentity(process.credentials.session.name, id, message); + } + }; if (process.credentials && process.credentials.session && process.credentials.session.name) { diff --git a/core/ssb.js b/core/ssb.js index 753364eb..dc3a7b8b 100644 --- a/core/ssb.js +++ b/core/ssb.js @@ -77,10 +77,10 @@ ssb.addEventListener('connections', function(change, connection) { var sequence = get_latest_sequence_for_author(connection.id); if (k_use_create_history_stream) { connection.send_json({'name': ['createHistoryStream'], 'type': 'source', 'args': [{'id': connection.id, 'seq': sequence, 'live': true, 'keys': false}]}, storeMessage); - var me = ssb.whoami(); - followingDeep(g_database, [me], 2).then(function(ids) { + var identities = ssb.getAllIdentities(); + followingDeep(g_database, identities, 2).then(function(ids) { for (let id of ids) { - if (id == me) { + if (identities.indexOf(id) != -1) { continue; } var sequence = get_latest_sequence_for_author(id); @@ -201,10 +201,10 @@ ssb.addRpc(['tunnel', 'isRoom'], function(request) { }); function ebtReplicateSendClock(request, have) { - var me = ssb.whoami(); + var identities = ssb.getAllIdentities(); var message = {}; var last_sent = request.connection.sent_clock || {}; - var ids = followingDeep(g_database, [me], 2).concat([request.connection.id]); + var ids = followingDeep(g_database, identities, 2).concat([request.connection.id]); if (!Object.keys(last_sent).length) { for (let id of ids) { message[id] = get_latest_sequence_for_author(id); @@ -261,11 +261,10 @@ function formatMessage(row) { } function ebtReplicateRegisterMessageCallback(request) { - var me = ssb.whoami(); ssb.addEventListener('message', function(message_id) { ssb.sqlStream( - 'SELECT previous, author, id, sequence, timestamp, hash, content, signature FROM messages WHERE id = ?1 AND author = ?2', - [message_id, me], + 'SELECT previous, author, id, sequence, timestamp, hash, content, signature FROM messages WHERE id = ?1', + [message_id], function (row) { request.send_json(formatMessage(row)); }); @@ -273,7 +272,6 @@ function ebtReplicateRegisterMessageCallback(request) { } function ebtReplicateCommon(request) { - var me = ssb.whoami(); if (request.message.author) { storeMessage(request); } else { diff --git a/src/ssb.db.c b/src/ssb.db.c index 6827ffad..a0617f54 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -759,6 +759,20 @@ void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)( } } +void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* identity, void* user_data), void* user_data) +{ + sqlite3* db = tf_ssb_get_db(ssb); + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, "SELECT public_key FROM identities ORDER BY public_key", -1, &statement, NULL) == SQLITE_OK) + { + while (sqlite3_step(statement) == SQLITE_ROW) + { + callback((const char*)sqlite3_column_text(statement, 0), user_data); + } + sqlite3_finalize(statement); + } +} + bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size) { bool success = false; diff --git a/src/ssb.db.h b/src/ssb.db.h index 62e2e373..17f269bc 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -21,4 +21,5 @@ bool tf_ssb_db_check(sqlite3* db, const char* author); int tf_ssb_db_identity_get_count_for_user(tf_ssb_t* ssb, const char* user); bool tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_key, const char* private_key); void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(const char* identity, void* user_data), void* user_data); +void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* identity, void* user_data), void* user_data); bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size); diff --git a/src/ssb.js.c b/src/ssb.js.c index 725aa004..34538ef6 100644 --- a/src/ssb.js.c +++ b/src/ssb.js.c @@ -18,20 +18,6 @@ static JSClassID _tf_ssb_classId; void _tf_ssb_on_rpc(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data); -static JSValue _tf_ssb_whoami(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) -{ - tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); - if (ssb) - { - char id[512]; - if (tf_ssb_whoami(ssb, id, sizeof(id))) - { - return JS_NewString(context, id); - } - } - return JS_NULL; -} - static JSValue _tf_ssb_createIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); @@ -69,7 +55,9 @@ typedef struct _identities_visit_t static void _tf_ssb_getIdentities_visit(const char* identity, void* data) { identities_visit_t* state = data; - JS_SetPropertyUint32(state->context, state->array, state->count++, JS_NewString(state->context, identity)); + char id[k_id_base64_len]; + snprintf(id, sizeof(id), "@%s", identity); + JS_SetPropertyUint32(state->context, state->array, state->count++, JS_NewString(state->context, id)); } static JSValue _tf_ssb_getIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) @@ -90,6 +78,22 @@ static JSValue _tf_ssb_getIdentities(JSContext* context, JSValueConst this_val, return result; } +static JSValue _tf_ssb_getAllIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + JSValue result = JS_NewArray(context); + tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); + if (ssb) + { + identities_visit_t state = + { + .context = context, + .array = result, + }; + tf_ssb_db_identity_visit_all(ssb, _tf_ssb_getIdentities_visit, &state); + } + return result; +} + static JSValue _tf_ssb_appendMessageWithIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue result = JS_UNDEFINED; @@ -277,31 +281,6 @@ static JSValue _tf_ssb_sqlStream(JSContext* context, JSValueConst this_val, int return result; } -static JSValue _tf_ssb_post(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) -{ - tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); - if (ssb) - { - const char* post_text = JS_ToCString(context, argv[0]); - if (post_text) - { - tf_ssb_append_post(ssb, post_text); - JS_FreeCString(context, post_text); - } - } - return JS_NULL; -} - -static JSValue _tf_ssb_appendMessage(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) -{ - tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); - if (ssb) - { - tf_ssb_append_message(ssb, argv[0]); - } - return JS_NULL; -} - static JSValue _tf_ssb_storeMessage(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); @@ -846,25 +825,26 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) JSValue object = JS_NewObjectClass(context, _tf_ssb_classId); JS_SetPropertyStr(context, global, "ssb", object); JS_SetOpaque(object, ssb); + + /* Requires an identity. */ JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1)); JS_SetPropertyStr(context, object, "getIdentities", JS_NewCFunction(context, _tf_ssb_getIdentities, "getIdentities", 1)); JS_SetPropertyStr(context, object, "appendMessageWithIdentity", JS_NewCFunction(context, _tf_ssb_appendMessageWithIdentity, "appendMessageWithIdentity", 3)); - JS_SetPropertyStr(context, object, "whoami", JS_NewCFunction(context, _tf_ssb_whoami, "whoami", 0)); + /* Does not require an identity. */ + JS_SetPropertyStr(context, object, "getAllIdentities", JS_NewCFunction(context, _tf_ssb_getAllIdentities, "getAllIdentities", 0)); JS_SetPropertyStr(context, object, "getMessage", JS_NewCFunction(context, _tf_ssb_getMessage, "getMessage", 2)); JS_SetPropertyStr(context, object, "blobGet", JS_NewCFunction(context, _tf_ssb_blobGet, "blobGet", 1)); JS_SetPropertyStr(context, object, "blobStore", JS_NewCFunction(context, _tf_ssb_blobStore, "blobStore", 2)); JS_SetPropertyStr(context, object, "messageContentGet", JS_NewCFunction(context, _tf_ssb_messageContentGet, "messageContentGet", 1)); JS_SetPropertyStr(context, object, "connections", JS_NewCFunction(context, _tf_ssb_connections, "connections", 0)); JS_SetPropertyStr(context, object, "sqlStream", JS_NewCFunction(context, _tf_ssb_sqlStream, "sqlStream", 3)); - JS_SetPropertyStr(context, object, "post", JS_NewCFunction(context, _tf_ssb_post, "post", 1)); - JS_SetPropertyStr(context, object, "appendMessage", JS_NewCFunction(context, _tf_ssb_appendMessage, "appendMessage", 1)); JS_SetPropertyStr(context, object, "storeMessage", JS_NewCFunction(context, _tf_ssb_storeMessage, "storeMessage", 1)); JS_SetPropertyStr(context, object, "getBroadcasts", JS_NewCFunction(context, _tf_ssb_getBroadcasts, "getBroadcasts", 0)); JS_SetPropertyStr(context, object, "connect", JS_NewCFunction(context, _tf_ssb_connect, "connect", 1)); + /* Should be trusted only. */ JS_SetPropertyStr(context, object, "addRpc", JS_NewCFunction(context, _tf_ssb_add_rpc, "addRpc", 2)); - JS_SetPropertyStr(context, object, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2)); JS_SetPropertyStr(context, object, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2));