Compare commits

...

33 Commits

Author SHA1 Message Date
7a79534ca8 docs: Add some words about upgrading. 2025-03-02 21:21:04 -05:00
a74a9fc821 ssb: Clean up some muxrpc-related warnings. 2025-03-02 19:43:50 -05:00
e2c388b9db cleanup: Stray file again. 2025-03-02 19:29:27 -05:00
0f573ce09e ssb: Move identity info gathering to db. 2025-03-01 20:44:52 -05:00
bc70e41b7c ssb: prettier. 2025-02-27 20:17:24 -05:00
f500e14aa3 ssb: Make the connections tab a bit less scary. 2025-02-27 20:04:38 -05:00
8b47938238 perf: Make promise stack trace collection opt-in. 2025-02-27 19:41:21 -05:00
8912212d8e build: Fix 32-bit. 2025-02-27 19:10:48 -05:00
6a346bf940 http: Move websocket send to http, obvs. 2025-02-27 19:07:01 -05:00
b5bdae4611 js: prettier 2025-02-27 15:00:37 -05:00
6590da5793 js: Just delete some placeholder comments around code that I am trying to delete. 2025-02-27 15:00:02 -05:00
eb2b426ec7 js: Clean up some oddness in old code as I struggle to replace it. 2025-02-27 14:28:07 -05:00
4864a0411f js: This RPC info does not need to be global. 2025-02-27 13:52:24 -05:00
68590cae33 http: Avoid a potential null dereference. 2025-02-27 11:25:38 -05:00
abf2bbaec2 js: Very minor trimming. 2025-02-27 10:01:59 -05:00
0da7e2722f js: Add a place to start moving imports to C. 2025-02-27 09:58:15 -05:00
60d4b06057 http: Prevent reentering tf_http_destroy. 2025-02-27 08:52:42 -05:00
4c3df34950 build: Let's start work on 0.0.29. 2025-02-26 20:04:17 -05:00
7737e60b52 build: ios => CFBundleVersion=10. 2025-02-26 19:44:00 -05:00
71e816bc13 build: nix => 0.0.28. 2025-02-26 19:43:20 -05:00
c74f90ef04 core: Fix stock apps not being loaded/updated. 2025-02-26 18:54:00 -05:00
26cb7e5a17 ios: Redid icon stuff. 2025-02-26 18:27:54 -05:00
2bad6672d8 ios: Generate Assets.car out of an overabundance of caution that F-Droid is going to complain about this as a binary file. 2025-02-26 12:43:42 -05:00
71c4011526 build: Let's prepare a 0.0.28 release. 2025-02-26 12:07:54 -05:00
5e81078f59 update: CodeMirror. 2025-02-26 12:05:07 -05:00
31b78e74df ssb: Following calculation fixes. It was not handled appropriately if an account was encountered multiple times with decreasing depths. 2025-02-25 21:52:15 -05:00
2ff689aab0 buttfeed: De-duplicate updates by link. 2025-02-24 12:16:31 -05:00
0a6f0ed3f7 ssb: Audit timer use that might cause unnecessary delays during shutdown. 2025-02-23 13:07:36 -05:00
bfec46673d ssb: Test an experiment that blobs are getting priority over messages during initial sync. 2025-02-22 11:57:57 -05:00
3a16614c72 docs: Remember to update the latest_release tag. 2025-02-22 11:23:36 -05:00
9c9efb845c ssb: Schedule the clock for reevaluation any time new messages are added. I think this will improve initial replication. 2025-02-21 22:30:14 -05:00
6192f1b94d room: Fix the room app port number multiple ways. 2025-02-21 22:05:28 -05:00
ce451b2449 cli: Fix the return value for get_profile. 2025-02-21 19:58:01 -05:00
99 changed files with 553 additions and 409 deletions

View File

@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker. ## LD := Linker.
## ANDROID_SDK := Path to the Android SDK. ## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 33 VERSION_CODE := 34
VERSION_CODE_IOS := 8 VERSION_CODE_IOS := 11
VERSION_NUMBER := 0.0.28-wip VERSION_NUMBER := 0.0.29-wip
VERSION_NAME := This program kills fascists. VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0 IPHONEOS_VERSION_MIN=14.0
@ -1419,7 +1419,7 @@ dist-ios: iosrelease-app
mkdir -p out/Payload/tildefriends.app mkdir -p out/Payload/tildefriends.app
cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/ cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/
cp src/ios/tildefriends.png out/Payload/tildefriends.app/ cp src/ios/tildefriends.png out/Payload/tildefriends.app/
cp src/ios/icons/Assets.car out/Payload/tildefriends.app/ xcrun -sdk iphoneos actool --compile out/Payload/tildefriends.app/ --platform iphoneos --minimum-deployment-target $(IPHONEOS_VERSION_MIN) --app-icon AppIcon src/ios/icons/Assets.xcassets src/ios/icons/*.png --output-partial-info-plist out/actool.plist
cp src/ios/distribution.mobileprovision out/Payload/tildefriends.app/embedded.mobileprovision cp src/ios/distribution.mobileprovision out/Payload/tildefriends.app/embedded.mobileprovision
xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/Payload/tildefriends.app xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/Payload/tildefriends.app
cd out; zip -r tildefriends.ipa Payload; cd .. cd out; zip -r tildefriends.ipa Payload; cd ..

View File

@ -1,7 +1,9 @@
async function main() { async function main() {
let host = core.url.match(/.*\/\/(.*?)\//)[1]; print(core.url);
let host = core.url.match(/.*?\/\/([^:/]*)/)[1];
let port = await ssb.port();
let id = (await ssb.getServerIdentity()).substring(1); let id = (await ssb.getServerIdentity()).substring(1);
let room = `net:${host}:${ssb.port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`; let room = `net:${host}:${port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
await app.setDocument(` await app.setDocument(`
<body style="color: #fff"> <body style="color: #fff">
<h1>Server</h1> <h1>Server</h1>

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🦀", "emoji": "🦀",
"previous": "&jAAzd36Nmpw0sRA1Dx9wLiIwGX+q//+S/Han+RLlEOw=.sha256" "previous": "&Qae0CxJGEH7OspuapuXX/GurmV+VBtQiMhHRmCBKCwU=.sha256"
} }

View File

@ -206,6 +206,21 @@ class TfTabConnectionsElement extends LitElement {
}); });
} }
toggle_accordian(id) {
let element = this.renderRoot.getElementById(id);
element.classList.toggle('w3-hide');
}
valid_connections() {
return this.connections.filter((x) => x.tunnel === undefined);
}
valid_broadcasts() {
return this.broadcasts
.filter((x) => x.address)
.filter((x) => this.connections.map((c) => c.id).indexOf(x.pubkey) == -1);
}
render() { render() {
let self = this; let self = this;
return html` return html`
@ -220,27 +235,33 @@ class TfTabConnectionsElement extends LitElement {
> >
Connect Connect
</button> </button>
<h2>Broadcasts</h2> <h2
<ul class="w3-ul w3-border"> class="w3-button w3-block w3-theme-d1"
${this.broadcasts @click=${() => self.toggle_accordian('connections')}
.filter((x) => x.address) >
.filter( Connections (${this.valid_connections().length})
(x) => self.connections.map((c) => c.id).indexOf(x.pubkey) == -1 </h2>
) <ul class="w3-ul w3-border" id="connections">
.map((x) => self.render_broadcast(x))} ${this.valid_connections().map(
</ul> (x) => html` <li class="w3-bar">${this.render_connection(x)}</li> `
<h2>Connections</h2>
<ul class="w3-ul w3-border">
${this.connections
.filter((x) => x.tunnel === undefined)
.map(
(x) => html`
<li class="w3-bar">${this.render_connection(x)}</li>
`
)} )}
</ul> </ul>
<h2>Stored Connections</h2> <h2
<ul class="w3-ul w3-border"> class="w3-button w3-block w3-theme-d1"
@click=${() => self.toggle_accordian('broadcasts')}
>
Broadcasts (${this.valid_broadcasts().length})
</h2>
<ul class="w3-ul w3-border w3-hide" id="broadcasts">
${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
</ul>
<h2
class="w3-button w3-block w3-theme-d1"
@click=${() => self.toggle_accordian('stored_connections')}
>
Stored Connections (${this.stored_connections.length})
</h2>
<ul class="w3-ul w3-border w3-hide" id="stored_connections">
${this.stored_connections.map( ${this.stored_connections.map(
(x) => html` (x) => html`
<li> <li>
@ -267,8 +288,13 @@ class TfTabConnectionsElement extends LitElement {
` `
)} )}
</ul> </ul>
<h2>Local Accounts</h2> <h2
<div class="w3-container"> class="w3-button w3-block w3-theme-d1"
@click=${() => self.toggle_accordian('local_accounts')}
>
Local Accounts (${this.identities.length})
</h2>
<div class="w3-container w3-hide" id="local_accounts">
${this.identities.map( ${this.identities.map(
(x) => (x) =>
html`<div html`<div

View File

@ -1,53 +1,26 @@
import * as core from './core.js'; import * as core from './core.js';
let g_next_id = 1;
let g_calls = {};
let gSessionIndex = 0; let gSessionIndex = 0;
/**
* TODOC
* @returns
*/
function makeSessionId() {
return 'session_' + (gSessionIndex++).toString();
}
/**
* TODOC
* @returns
*/
function App() { function App() {
this._on_output = null;
this._send_queue = []; this._send_queue = [];
this.calls = {};
this._next_call_id = 1;
return this; return this;
} }
/**
* TODOC
* @param {*} callback
*/
App.prototype.readOutput = function (callback) {
this._on_output = callback;
};
/**
* TODOC
* @param {*} api
* @returns
*/
App.prototype.makeFunction = function (api) { App.prototype.makeFunction = function (api) {
let self = this; let self = this;
let result = function () { let result = function () {
let id = g_next_id++; let id = self._next_call_id++;
while (!id || g_calls[id]) { while (!id || self.calls[id]) {
id = g_next_id++; id = self._next_call_id++;
} }
let promise = new Promise(function (resolve, reject) { let promise = new Promise(function (resolve, reject) {
g_calls[id] = {resolve: resolve, reject: reject}; self.calls[id] = {resolve: resolve, reject: reject};
}); });
let message = { let message = {
message: 'tfrpc', action: 'tfrpc',
method: api[0], method: api[0],
params: [...arguments], params: [...arguments],
id: id, id: id,
@ -59,10 +32,6 @@ App.prototype.makeFunction = function (api) {
return result; return result;
}; };
/**
* TODOC
* @param {*} message
*/
App.prototype.send = function (message) { App.prototype.send = function (message) {
if (this._send_queue) { if (this._send_queue) {
if (this._on_output) { if (this._on_output) {
@ -77,11 +46,6 @@ App.prototype.send = function (message) {
} }
}; };
/**
* TODOC
* @param {*} request
* @param {*} response
*/
exports.app_socket = async function socket(request, response) { exports.app_socket = async function socket(request, response) {
let process; let process;
let options = {}; let options = {};
@ -102,10 +66,16 @@ exports.app_socket = async function socket(request, response) {
try { try {
message = JSON.parse(event.data); message = JSON.parse(event.data);
} catch (error) { } catch (error) {
print('ERROR', error, event.data, event.data.length, event.opCode); print(
'WebSocket error:',
error,
event.data,
event.data.length,
event.opCode
);
return; return;
} }
if (message.action == 'hello') { if (!process && message.action == 'hello') {
let packageOwner; let packageOwner;
let packageName; let packageName;
let blobId; let blobId;
@ -122,7 +92,7 @@ exports.app_socket = async function socket(request, response) {
if (!blobId) { if (!blobId) {
response.send( response.send(
JSON.stringify({ JSON.stringify({
message: 'tfrpc', action: 'tfrpc',
method: 'error', method: 'error',
params: [message.path + ' not found'], params: [message.path + ' not found'],
id: -1, id: -1,
@ -163,7 +133,7 @@ exports.app_socket = async function socket(request, response) {
options.packageOwner = packageOwner; options.packageOwner = packageOwner;
options.packageName = packageName; options.packageName = packageName;
options.url = message.url; options.url = message.url;
let sessionId = makeSessionId(); let sessionId = 'session_' + (gSessionIndex++).toString();
if (blobId) { if (blobId) {
if (message.edit_only) { if (message.edit_only) {
response.send( response.send(
@ -175,9 +145,24 @@ exports.app_socket = async function socket(request, response) {
} }
} }
if (process) { if (process) {
process.app.readOutput(function (message) { process.client_api.tfrpc = function (message) {
if (message.id) {
let calls = process?.app?.calls;
if (calls) {
let call = calls[message.id];
if (call) {
if (message.error !== undefined) {
call.reject(message.error);
} else {
call.resolve(message.result);
}
delete calls[message.id];
}
}
}
};
process.app._on_output = (message) =>
response.send(JSON.stringify(message), 0x1); response.send(JSON.stringify(message), 0x1);
});
process.app.send(); process.app.send();
} }
@ -206,28 +191,15 @@ exports.app_socket = async function socket(request, response) {
if (process && process.timeout > 0) { if (process && process.timeout > 0) {
setTimeout(ping, process.timeout); setTimeout(ping, process.timeout);
} }
} else if (message.action == 'resetPermission') { } else {
if (process) { if (process) {
process.resetPermission(message.permission); if (process.client_api[message.action]) {
} process.client_api[message.action](message);
} else if (message.action == 'setActiveIdentity') { } else if (process.eventHandlers['message']) {
process.setActiveIdentity(message.identity);
} else if (message.action == 'createIdentity') {
await process.createIdentity();
} else if (message.message == 'tfrpc') {
if (message.id && g_calls[message.id]) {
if (message.error !== undefined) {
g_calls[message.id].reject(message.error);
} else {
g_calls[message.id].resolve(message.result);
}
delete g_calls[message.id];
}
} else {
if (process && process.eventHandlers['message']) {
await core.invoke(process.eventHandlers['message'], [message]); await core.invoke(process.eventHandlers['message'], [message]);
} }
} }
}
} else if (event.opCode == 0x8) { } else if (event.opCode == 0x8) {
// Close. // Close.
if (process && process.task) { if (process && process.task) {

View File

@ -1325,7 +1325,7 @@ function _receive_websocket_message(message) {
line.append(key, message.stats[key]); line.append(key, message.stats[key]);
} }
} }
} else if (message && message.message === 'tfrpc' && message.method) { } else if (message && message.action === 'tfrpc' && message.method) {
let api = k_api[message.method]; let api = k_api[message.method];
let id = message.id; let id = message.id;
let params = message.params; let params = message.params;
@ -1333,14 +1333,14 @@ function _receive_websocket_message(message) {
Promise.resolve(api.func(...params)) Promise.resolve(api.func(...params))
.then(function (result) { .then(function (result) {
send({ send({
message: 'tfrpc', action: 'tfrpc',
id: id, id: id,
result: result, result: result,
}); });
}) })
.catch(function (error) { .catch(function (error) {
send({ send({
message: 'tfrpc', action: 'tfrpc',
id: id, id: id,
error: error, error: error,
}); });

View File

@ -3,31 +3,22 @@ import * as http from './http.js';
let gProcesses = {}; let gProcesses = {};
let gStatsTimer = false; let gStatsTimer = false;
let kPingInterval = 60 * 1000; let g_handler_index = 0;
/** const k_ping_interval = 60 * 1000;
* TODOC
* @param {*} out function printError(error) {
* @param {*} error
*/
function printError(out, error) {
if (error.stackTrace) { if (error.stackTrace) {
out.print(error.fileName + ':' + error.lineNumber + ': ' + error.message); print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
out.print(error.stackTrace); print(error.stackTrace);
} else { } else {
for (let [k, v] of Object.entries(error)) { for (let [k, v] of Object.entries(error)) {
out.print(k, v); print(k, v);
} }
out.print(error.toString()); print(error.toString());
} }
} }
/**
* TODOC
* @param {*} handlers
* @param {*} argv
* @returns
*/
function invoke(handlers, argv) { function invoke(handlers, argv) {
let promises = []; let promises = [];
if (handlers) { if (handlers) {
@ -48,12 +39,6 @@ function invoke(handlers, argv) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {*} eventName
* @param {*} argv
* @returns
*/
function broadcastEvent(eventName, argv) { function broadcastEvent(eventName, argv) {
let promises = []; let promises = [];
for (let process of Object.values(gProcesses)) { for (let process of Object.values(gProcesses)) {
@ -64,11 +49,6 @@ function broadcastEvent(eventName, argv) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {*} message
* @returns
*/
function broadcast(message) { function broadcast(message) {
let sender = this; let sender = this;
let promises = []; let promises = [];
@ -85,12 +65,6 @@ function broadcast(message) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {String} eventName
* @param {*} argv
* @returns
*/
function broadcastAppEventToUser( function broadcastAppEventToUser(
user, user,
packageOwner, packageOwner,
@ -113,12 +87,6 @@ function broadcastAppEventToUser(
return Promise.all(promises); return Promise.all(promises);
} }
/**
* TODOC
* @param {*} caller
* @param {*} process
* @returns
*/
function getUser(caller, process) { function getUser(caller, process) {
return { return {
key: process.key, key: process.key,
@ -129,12 +97,6 @@ function getUser(caller, process) {
}; };
} }
/**
* TODOC
* @param {*} user
* @param {*} process
* @returns
*/
async function getApps(user, process) { async function getApps(user, process) {
if ( if (
process.credentials && process.credentials &&
@ -161,28 +123,13 @@ async function getApps(user, process) {
return {}; return {};
} }
/**
* TODOC
* @param {*} from
* @param {*} to
* @param {*} message
* @returns
*/
function postMessageInternal(from, to, message) { function postMessageInternal(from, to, message) {
if (to.eventHandlers['message']) { if (to.eventHandlers['message']) {
return invoke(to.eventHandlers['message'], [getUser(from, from), message]); return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
} }
} }
/**
* TODOC
* @param {*} blobId
* @param {*} key
* @param {*} options
* @returns
*/
async function getProcessBlob(blobId, key, options) { async function getProcessBlob(blobId, key, options) {
// TODO(tasiaiso): break this down ?
let process = gProcesses[key]; let process = gProcesses[key];
if (!process && !(options && 'create' in options && !options.create)) { if (!process && !(options && 'create' in options && !options.create)) {
let resolveReady; let resolveReady;
@ -201,7 +148,7 @@ async function getProcessBlob(blobId, key, options) {
} }
process.lastActive = Date.now(); process.lastActive = Date.now();
process.lastPing = null; process.lastPing = null;
process.timeout = kPingInterval; process.timeout = k_ping_interval;
process.ready = new Promise(function (resolve, reject) { process.ready = new Promise(function (resolve, reject) {
resolveReady = resolve; resolveReady = resolve;
rejectReady = reject; rejectReady = reject;
@ -461,10 +408,10 @@ async function getProcessBlob(blobId, key, options) {
if (process.app) { if (process.app) {
process.app.makeFunction(['error'])(error); process.app.makeFunction(['error'])(error);
} else { } else {
printError({print: print}, error); printError(error);
} }
} catch (e) { } catch (e) {
printError({print: print}, error); printError(error);
} }
}; };
imports.ssb = Object.fromEntries( imports.ssb = Object.fromEntries(
@ -649,17 +596,26 @@ async function getProcessBlob(blobId, key, options) {
permissions: await imports.core.permissionsGranted(), permissions: await imports.core.permissionsGranted(),
}); });
}; };
process.resetPermission = async function resetPermission(permission) { process.client_api = {
createIdentity: function () {
return process.createIdentity();
},
resetPermission: async function resetPermission(message) {
let user = process?.credentials?.session?.name; let user = process?.credentials?.session?.name;
await ssb.setUserPermission( await ssb.setUserPermission(
user, user,
options?.packageOwner, options?.packageOwner,
options?.packageName, options?.packageName,
permission, message.permission,
undefined undefined
); );
return process.sendPermissions(); return process.sendPermissions();
},
setActiveIdentity: function setActiveIdentity(message) {
return process.setActiveIdentity(message.identity);
},
}; };
ssb.registerImports(imports, process);
process.task.setImports(imports); process.task.setImports(imports);
process.task.activate(); process.task.activate();
let source = await ssb.blobGet(blobId); let source = await ssb.blobGet(blobId);
@ -686,7 +642,7 @@ async function getProcessBlob(blobId, key, options) {
); );
} }
} catch (e) { } catch (e) {
printError({print: print}, e); printError(e);
} }
broadcastEvent('onSessionBegin', [getUser(process, process)]); broadcastEvent('onSessionBegin', [getUser(process, process)]);
if (process.app) { if (process.app) {
@ -700,14 +656,10 @@ async function getProcessBlob(blobId, key, options) {
sendStats(); sendStats();
} }
} catch (error) { } catch (error) {
if (process.app) { if (process?.app && process?.task?.onError) {
if (process?.task?.onError) {
process.task.onError(error); process.task.onError(error);
} else { } else {
printError({print: print}, error); printError(error);
}
} else {
printError({print: print}, error);
} }
rejectReady(error); rejectReady(error);
} }
@ -727,9 +679,6 @@ ssb.addEventListener('connections', function () {
broadcastEvent('onConnectionsChanged', []); broadcastEvent('onConnectionsChanged', []);
}); });
/**
* TODOC
*/
async function loadSettings() { async function loadSettings() {
let data = {}; let data = {};
try { try {
@ -748,9 +697,6 @@ async function loadSettings() {
return data; return data;
} }
/**
* TODOC
*/
function sendStats() { function sendStats() {
let apps = Object.values(gProcesses) let apps = Object.values(gProcesses)
.filter((process) => process.app) .filter((process) => process.app)
@ -766,8 +712,6 @@ function sendStats() {
} }
} }
let g_handler_index = 0;
exports.callAppHandler = async function callAppHandler( exports.callAppHandler = async function callAppHandler(
response, response,
app_blob_id, app_blob_id,

View File

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

File diff suppressed because one or more lines are too long

View File

@ -115,9 +115,9 @@
} }
}, },
"node_modules/@codemirror/search": { "node_modules/@codemirror/search": {
"version": "6.5.9", "version": "6.5.10",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.9.tgz", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz",
"integrity": "sha512-7DdQ9aaZMMxuWB1u6IIFWWuK9NocVZwvo4nG8QjJTS6oZGvteoLSiXw3EbVZVlO08Ri2ltO89JVInMpfcJxhtg==", "integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",

View File

@ -4,7 +4,8 @@
- run the tests - run the tests
- format + prettier - format + prettier
- update metadata/en-US/changelogs - update metadata/en-US/changelogs
- git tag - git tag v1.2.3
- git tag -f latest_release
- push - push
- make a release on gitea - make a release on gitea
- upload the artifacts - upload the artifacts

18
docs/upgrading.md Normal file
View File

@ -0,0 +1,18 @@
# Upgrading
Tilde Friends can be upgraded simply by running a new executable against an
existing database.
Tilde Friends writes all data to a `db.sqlite` file, either in
`~/.local/share/tildefriends/` or in the working directory where it is run,
depending on the platform and whether each one already exists. Run with
`tildefriends run -d DB_PATH` to specify the path to the database explicitly.
This file can be copied and moved across machines as needed like any [sqlite3
database](https://www.sqlite.org/onefile.html).
Schema changes and compatibility breaks have been rare, by design. In general,
upgrading is not expected to require any manual intervention and likely does
not involve any automatic migration, either. Downgrading is not well-supported
but will probably just work excepting rare changes that will be called out in
the changelog.

View File

@ -1,3 +1,5 @@
* Allow specifying all global settings from the command-line (CLI usage changed).
* Replication improvements.
* An iOS build is on TestFlight. * An iOS build is on TestFlight.
* macOS targets are debug and release like everywhere else. * macOS targets are debug and release like everywhere else.
* Running from a subdirectory is fine. * Running from a subdirectory is fine.
@ -5,10 +7,9 @@
* Invite fixes. * Invite fixes.
* Follow/block UI fixes. * Follow/block UI fixes.
* Mobile automatically logs in. * Mobile automatically logs in.
* Allow specifying all global settings from the command-line. * Updates:
* UpdateS:
* CodeMirror * CodeMirror
* OpenSSL 3.4.1 * OpenSSL 3.4.1
* libbacktrace * libbacktrace
* sqlite 3.49.1
* speedscope 1.22.2 * speedscope 1.22.2
* sqlite 3.49.1

6
package-lock.json generated
View File

@ -11,9 +11,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.5.1", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz",
"integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==",
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },

View File

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

19
src/api.js.c Normal file
View File

@ -0,0 +1,19 @@
#include "api.js.h"
#include "log.h"
#include <quickjs.h>
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
return JS_UNDEFINED;
}
void tf_api_register(JSContext* context)
{
JSValue global = JS_GetGlobalObject(context);
JSValue ssb = JS_GetPropertyStr(context, global, "ssb");
JS_SetPropertyStr(context, ssb, "registerImports", JS_NewCFunction(context, _tf_api_register_imports, "registerImports", 2));
JS_FreeValue(context, ssb);
JS_FreeValue(context, global);
}

18
src/api.js.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
/**
** \defgroup api_js JS API
** Functions that are ultimately exposed to apps.
** @{
*/
/** A JS context. */
typedef struct JSContext JSContext;
/**
** Register JS API functions.
** @param context The JS context.
*/
void tf_api_register(JSContext* context);
/** @} */

View File

@ -84,6 +84,7 @@ typedef struct _tf_http_listener_t
typedef struct _tf_http_t typedef struct _tf_http_t
{ {
bool is_shutting_down; bool is_shutting_down;
bool is_in_destroy;
tf_http_listener_t** listeners; tf_http_listener_t** listeners;
int listeners_count; int listeners_count;
@ -395,7 +396,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
if (fin) if (fin)
{ {
if (connection->request->on_message) if (connection->request && connection->request->on_message)
{ {
tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "websocket"); tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "websocket");
connection->request->on_message(connection->request, connection->fragment_length ? connection->fragment_op_code : op_code, connection->request->on_message(connection->request, connection->fragment_length ? connection->fragment_op_code : op_code,
@ -786,7 +787,13 @@ static void _http_free_listener_on_close(uv_handle_t* handle)
void tf_http_destroy(tf_http_t* http) void tf_http_destroy(tf_http_t* http)
{ {
if (http->is_in_destroy)
{
return;
}
http->is_shutting_down = true; http->is_shutting_down = true;
http->is_in_destroy = true;
for (int i = 0; i < http->connections_count; i++) for (int i = 0; i < http->connections_count; i++)
{ {
@ -845,6 +852,10 @@ void tf_http_destroy(tf_http_t* http)
tf_free(http); tf_free(http);
} }
else
{
http->is_in_destroy = false;
}
} }
const char* tf_http_status_text(int status) const char* tf_http_status_text(int status)
@ -963,9 +974,42 @@ static void _http_write(tf_http_connection_t* connection, const void* data, size
} }
} }
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size) void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size)
{ {
_http_write(request->connection, data, size); uint8_t* copy = tf_malloc(size + 16);
bool fin = true;
size_t header = 1;
copy[0] = (fin ? (1 << 7) : 0) | (op_code & 0xf);
if (size < 126)
{
copy[1] = size;
header += 1;
}
else if (size < (1 << 16))
{
copy[1] = 126;
copy[2] = (size >> 8) & 0xff;
copy[3] = (size >> 0) & 0xff;
header += 3;
}
else
{
uint32_t high = ((uint64_t)size >> 32) & 0xffffffff;
uint32_t low = (size >> 0) & 0xffffffff;
copy[1] = 127;
copy[2] = (high >> 24) & 0xff;
copy[3] = (high >> 16) & 0xff;
copy[4] = (high >> 8) & 0xff;
copy[5] = (high >> 0) & 0xff;
copy[6] = (low >> 24) & 0xff;
copy[7] = (low >> 16) & 0xff;
copy[8] = (low >> 8) & 0xff;
copy[9] = (low >> 0) & 0xff;
header += 9;
}
memcpy(copy + header, data, size);
_http_write(request->connection, copy, header + size);
tf_free(copy);
} }
void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length) void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length)

View File

@ -209,10 +209,11 @@ const char* tf_http_get_cookie(const char* cookie_header, const char* name);
** Send a websocket message. ** Send a websocket message.
** @param request The HTTP request which was previously updated to a websocket ** @param request The HTTP request which was previously updated to a websocket
** session with tf_http_request_websocket_upgrade(). ** session with tf_http_request_websocket_upgrade().
** @param op_code Websocket op code.
** @param data The message data. ** @param data The message data.
** @param size The size of data. ** @param size The size of data.
*/ */
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size); void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size);
/** /**
** Upgrade an HTTP request to a websocket session. ** Upgrade an HTTP request to a websocket session.

View File

@ -152,44 +152,9 @@ static JSValue _httpd_response_send(JSContext* context, JSValueConst this_val, i
tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id); tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id);
int opcode = 0x1; int opcode = 0x1;
JS_ToInt32(context, &opcode, argv[1]); JS_ToInt32(context, &opcode, argv[1]);
uint64_t length = 0; size_t length = 0;
size_t length_size = 0; const char* message = JS_ToCStringLen(context, &length, argv[0]);
const char* message = JS_ToCStringLen(context, &length_size, argv[0]); tf_http_request_websocket_send(request, opcode, message, length);
length = length_size;
uint8_t* copy = tf_malloc(length + 16);
bool fin = true;
size_t header = 1;
copy[0] = (fin ? (1 << 7) : 0) | (opcode & 0xf);
if (length < 126)
{
copy[1] = length;
header += 1;
}
else if (length < (1 << 16))
{
copy[1] = 126;
copy[2] = (length >> 8) & 0xff;
copy[3] = (length >> 0) & 0xff;
header += 3;
}
else
{
uint32_t high = (length >> 32) & 0xffffffff;
uint32_t low = (length >> 0) & 0xffffffff;
copy[1] = 127;
copy[2] = (high >> 24) & 0xff;
copy[3] = (high >> 16) & 0xff;
copy[4] = (high >> 8) & 0xff;
copy[5] = (high >> 0) & 0xff;
copy[6] = (low >> 24) & 0xff;
copy[7] = (low >> 16) & 0xff;
copy[8] = (low >> 8) & 0xff;
copy[9] = (low >> 0) & 0xff;
header += 9;
}
memcpy(copy + header, message, length);
tf_http_request_send(request, copy, header + length);
tf_free(copy);
JS_FreeCString(context, message); JS_FreeCString(context, message);
return JS_UNDEFINED; return JS_UNDEFINED;
} }

View File

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -1 +1 @@
{"images":[{"idiom":"ios-marketing","scale":"1x","size":"1024x1024","filename":"icon-ios-marketing-1-1024-1024.png"}],"info":{"author":"xcode","version":1}} {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View File

@ -1 +0,0 @@
{"info": {"version": 1, "author": "xcode"}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
src/ios/icons/appstore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

BIN
src/ios/icons/playstore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

View File

@ -1057,7 +1057,7 @@ static int _tf_command_get_profile(const char* file, int argc, char* argv[])
sqlite3_close(db); sqlite3_close(db);
tf_free((void*)profile); tf_free((void*)profile);
tf_free((void*)default_db_path); tf_free((void*)default_db_path);
return profile != NULL; return profile != NULL ? EXIT_SUCCESS : EXIT_FAILURE;
} }
static int _tf_command_get_contacts(const char* file, int argc, char* argv[]) static int _tf_command_get_contacts(const char* file, int argc, char* argv[])
@ -1299,10 +1299,12 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
sqlite3* db = tf_ssb_acquire_db_reader(ssb); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
int64_t http_port = 0; int64_t http_port = 0;
int64_t https_port = 0; int64_t https_port = 0;
char out_http_port_file[512] = "";
tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port); tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port);
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port); tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port);
tf_ssb_db_get_global_setting_string(db, "out_http_port_file", out_http_port_file, sizeof(out_http_port_file));
tf_ssb_release_db_reader(ssb, db); tf_ssb_release_db_reader(ssb, db);
if (http_port || https_port) if (http_port || https_port || *out_http_port_file)
{ {
if (args->zip) if (args->zip)
{ {

View File

@ -22,8 +22,6 @@ static int64_t s_tls_malloc_size;
static int64_t s_js_malloc_size; static int64_t s_js_malloc_size;
static int64_t s_sqlite_malloc_size; static int64_t s_sqlite_malloc_size;
extern uint32_t fnv32a(const void* buffer, int length, uint32_t start);
static size_t _tf_mem_round_up(size_t size) static size_t _tf_mem_round_up(size_t size)
{ {
return (size + 7) & ~7; return (size + 7) & ~7;
@ -156,7 +154,7 @@ static void _tf_mem_summarize(void* ptr, size_t size, int frames_count, void* co
{ {
summary_t* summary = user_data; summary_t* summary = user_data;
tf_mem_allocation_t allocation = { tf_mem_allocation_t allocation = {
.stack_hash = fnv32a(frames, sizeof(void*) * frames_count, 0), .stack_hash = tf_util_fnv32a(frames, sizeof(void*) * frames_count, 0),
.count = 1, .count = 1,
.size = size, .size = size,
.frames_count = frames_count, .frames_count = frames_count,

View File

@ -746,7 +746,9 @@ static bool _tf_ssb_connection_get_request_callback(
*out_name = request->name; *out_name = request->name;
} }
request->last_active = uv_now(connection->ssb->loop); request->last_active = uv_now(connection->ssb->loop);
if (connection->flags & k_tf_ssb_connect_flag_one_shot) if (tf_ssb_connection_is_connected(connection) && !tf_ssb_connection_is_closing(connection) && !tf_ssb_is_shutting_down(tf_ssb_connection_get_ssb(connection)))
{
if ((connection->flags & k_tf_ssb_connect_flag_one_shot))
{ {
uv_timer_start(&connection->activity_timer, _tf_ssb_connection_activity_timer, k_activity_timeout_ms, 0); uv_timer_start(&connection->activity_timer, _tf_ssb_connection_activity_timer, k_activity_timeout_ms, 0);
} }
@ -754,6 +756,7 @@ static bool _tf_ssb_connection_get_request_callback(
{ {
uv_timer_start(&connection->ssb->request_activity_timer, _tf_ssb_request_activity_timer, k_rpc_active_ms, 0); uv_timer_start(&connection->ssb->request_activity_timer, _tf_ssb_request_activity_timer, k_rpc_active_ms, 0);
} }
}
return true; return true;
} }
return false; return false;
@ -799,6 +802,8 @@ void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t requ
connection->requests_count++; connection->requests_count++;
connection->ssb->request_count++; connection->ssb->request_count++;
} }
if (tf_ssb_connection_is_connected(connection) && !tf_ssb_connection_is_closing(connection) && !tf_ssb_is_shutting_down(tf_ssb_connection_get_ssb(connection)))
{
if (connection->flags & k_tf_ssb_connect_flag_one_shot) if (connection->flags & k_tf_ssb_connect_flag_one_shot)
{ {
uv_timer_start(&connection->activity_timer, _tf_ssb_connection_activity_timer, k_activity_timeout_ms, 0); uv_timer_start(&connection->activity_timer, _tf_ssb_connection_activity_timer, k_activity_timeout_ms, 0);
@ -807,6 +812,7 @@ void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t requ
{ {
uv_timer_start(&connection->ssb->request_activity_timer, _tf_ssb_request_activity_timer, k_rpc_active_ms, 0); uv_timer_start(&connection->ssb->request_activity_timer, _tf_ssb_request_activity_timer, k_rpc_active_ms, 0);
} }
}
_tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_update, connection); _tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_update, connection);
connection->last_notified_active = now_ms; connection->last_notified_active = now_ms;
} }
@ -1997,7 +2003,7 @@ static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const ch
if (!connection->is_closing) if (!connection->is_closing)
{ {
connection->is_closing = true; connection->is_closing = true;
uv_timer_start(&connection->linger_timer, _tf_ssb_connection_linger_timer, 5000, 0); uv_timer_start(&connection->linger_timer, _tf_ssb_connection_linger_timer, ssb->shutting_down ? 0 : 5000, 0);
_tf_ssb_notify_connections_changed(ssb, k_tf_ssb_change_update, connection); _tf_ssb_notify_connections_changed(ssb, k_tf_ssb_change_update, connection);
} }
if (connection->connect_callback) if (connection->connect_callback)

View File

@ -1277,7 +1277,7 @@ static int _following_compare(const void* a, const void* b)
static bool _has_following_entry(const char* id, following_t** list, int count) static bool _has_following_entry(const char* id, following_t** list, int count)
{ {
return count ? bsearch(id, list, count, sizeof(following_t*), _following_compare) != 0 : false; return count ? bsearch(id, list, count, sizeof(following_t*), _following_compare) != NULL : false;
} }
static bool _add_following_entry(following_t*** list, int* count, following_t* add) static bool _add_following_entry(following_t*** list, int* count, following_t* add)
@ -1432,24 +1432,25 @@ static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, foll
static void _get_following( static void _get_following(
tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, int depth, int max_depth, block_node_t* active_blocks, bool include_blocks) tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, int depth, int max_depth, block_node_t* active_blocks, bool include_blocks)
{ {
int old_depth = entry->depth;
entry->depth = tf_min(depth, entry->depth); entry->depth = tf_min(depth, entry->depth);
if (depth < max_depth && !entry->populated && !_is_blocked_by_active_blocks(entry->id, active_blocks)) if (depth < max_depth && depth < old_depth)
{
if (!_is_blocked_by_active_blocks(entry->id, active_blocks))
{
if (!entry->populated)
{ {
entry->populated = true; entry->populated = true;
_populate_follows_and_blocks(ssb, entry, following, following_count, active_blocks, include_blocks); _populate_follows_and_blocks(ssb, entry, following, following_count, active_blocks, include_blocks);
}
if (depth < max_depth)
{
block_node_t blocks = { .entry = entry, .parent = active_blocks }; block_node_t blocks = { .entry = entry, .parent = active_blocks };
for (int i = 0; i < entry->following_count; i++) for (int i = 0; i < entry->following_count; i++)
{
if (!_has_following_entry(entry->following[i]->id, entry->blocking, entry->blocking_count))
{ {
_get_following(ssb, entry->following[i], following, following_count, depth + 1, max_depth, &blocks, include_blocks); _get_following(ssb, entry->following[i], following, following_count, depth + 1, max_depth, &blocks, include_blocks);
} }
} }
} }
}
} }
tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth, bool include_blocks) tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth, bool include_blocks)
@ -2330,3 +2331,120 @@ bool tf_ssb_db_has_invite(sqlite3* db, const char* id)
} }
return has; return has;
} }
static void _tf_ssb_db_get_identity_info_visit(const char* identity, void* user_data)
{
tf_ssb_identity_info_t* info = user_data;
info->identity = tf_resize_vec(info->identity, (info->count + 1) * sizeof(char*));
info->name = tf_resize_vec(info->name, (info->count + 1) * sizeof(char*));
char buffer[k_id_base64_len];
snprintf(buffer, sizeof(buffer), "@%s", identity);
info->identity[info->count] = tf_strdup(buffer);
info->name[info->count] = NULL;
info->count++;
}
tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* user, const char* package_owner, const char* package_name)
{
tf_ssb_identity_info_t* info = tf_malloc(sizeof(tf_ssb_identity_info_t));
*info = (tf_ssb_identity_info_t) { 0 };
char id[k_id_base64_len] = "";
if (tf_ssb_db_user_has_permission(ssb, NULL, user, "administration"))
{
if (tf_ssb_whoami(ssb, id, sizeof(id)))
{
_tf_ssb_db_get_identity_info_visit(*id == '@' ? id + 1 : id, info);
}
}
tf_ssb_db_identity_visit(ssb, user, _tf_ssb_db_get_identity_info_visit, info);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
int result = sqlite3_prepare(db,
"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 identities ON messages.author = ('@' || identities.public_key) "
" WHERE "
" (identities.user = ? OR identities.public_key = ?) AND "
" json_extract(messages.content, '$.type') = 'about' AND "
" content ->> 'about' = messages.author AND name IS NOT NULL) "
"WHERE author_rank = 1 ",
-1, &statement, NULL);
if (result == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, *id == '@' ? id + 1 : id, -1, NULL) == SQLITE_OK)
{
int r = SQLITE_OK;
while ((r = sqlite3_step(statement)) == SQLITE_ROW)
{
const char* identity = (const char*)sqlite3_column_text(statement, 0);
const char* name = (const char*)sqlite3_column_text(statement, 1);
for (int i = 0; i < info->count; i++)
{
if (!info->name[i] && strcmp(info->identity[i], identity) == 0)
{
info->name[i] = tf_strdup(name);
break;
}
}
}
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s.\n", sqlite3_errmsg(db));
}
tf_ssb_db_identity_get_active(db, user, package_owner, package_name, info->active_identity, sizeof(info->active_identity));
if (!*info->active_identity && info->count)
{
snprintf(info->active_identity, sizeof(info->active_identity), "%s", info->identity[0]);
}
tf_ssb_release_db_reader(ssb, db);
size_t size = sizeof(tf_ssb_identity_info_t) + sizeof(char*) * info->count * 2;
for (int i = 0; i < info->count; i++)
{
size += strlen(info->identity[i]) + 1;
size += info->name[i] ? strlen(info->name[i]) + 1 : 0;
}
tf_ssb_identity_info_t* copy = tf_malloc(size);
*copy = *info;
copy->identity = (const char**)(copy + 1);
copy->name = (const char**)(copy + 1) + copy->count;
char* p = (char*)((const char**)(copy + 1) + copy->count * 2);
for (int i = 0; i < info->count; i++)
{
size_t length = strlen(info->identity[i]);
memcpy(p, info->identity[i], length + 1);
copy->identity[i] = p;
p += length + 1;
tf_free((void*)info->identity[i]);
if (info->name[i])
{
length = strlen(info->name[i]);
memcpy(p, info->name[i], length + 1);
copy->name[i] = p;
p += length + 1;
tf_free((void*)info->name[i]);
}
else
{
copy->name[i] = NULL;
}
}
tf_free(info->name);
tf_free(info->identity);
tf_free(info);
return copy;
}

View File

@ -564,4 +564,29 @@ int tf_ssb_sqlite_authorizer(void* user_data, int action_code, const char* arg0,
*/ */
bool tf_ssb_db_has_invite(sqlite3* db, const char* id); bool tf_ssb_db_has_invite(sqlite3* db, const char* id);
/**
** Identity information
*/
typedef struct _tf_ssb_identity_info_t
{
/** An array of identities. */
const char** identity;
/** A array of identities, one for each identity. */
const char** name;
/** The number of elements in both the identity and name arrays. */
int count;
/** The active identity. */
char active_identity[k_id_base64_len];
} tf_ssb_identity_info_t;
/**
** Get available identities, names, and the active identity for a user.
** @param ssb The SSB instance.
** @param user The user name.
** @param package_owner The owner of the package for which identity info is being fetched.
** @param package_name The name of the package for which identity info is being fetched.
** @return A struct of identities, names, and the active identity. Free with tf_free().
*/
tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* user, const char* package_owner, const char* package_name);
/** @} */ /** @} */

View File

@ -613,87 +613,14 @@ typedef struct _identity_info_work_t
const char* name; const char* name;
const char* package_owner; const char* package_owner;
const char* package_name; const char* package_name;
int count; tf_ssb_identity_info_t* info;
char** identities;
char** names;
int result;
char active_identity[k_id_base64_len];
JSValue promise[2]; JSValue promise[2];
} identity_info_work_t; } identity_info_work_t;
static void _tf_ssb_getIdentityInfo_visit(const char* identity, void* data)
{
identity_info_work_t* request = data;
request->identities = tf_resize_vec(request->identities, (request->count + 1) * sizeof(char*));
request->names = tf_resize_vec(request->names, (request->count + 1) * sizeof(char*));
char buffer[k_id_base64_len];
snprintf(buffer, sizeof(buffer), "@%s", identity);
request->identities[request->count] = tf_strdup(buffer);
request->names[request->count] = NULL;
request->count++;
}
static void _tf_ssb_getIdentityInfo_work(tf_ssb_t* ssb, void* user_data) static void _tf_ssb_getIdentityInfo_work(tf_ssb_t* ssb, void* user_data)
{ {
identity_info_work_t* request = user_data; identity_info_work_t* request = user_data;
char id[k_id_base64_len] = ""; request->info = tf_ssb_db_get_identity_info(ssb, request->name, request->package_owner, request->package_name);
if (tf_ssb_db_user_has_permission(ssb, NULL, request->name, "administration"))
{
if (tf_ssb_whoami(ssb, id, sizeof(id)))
{
_tf_ssb_getIdentityInfo_visit(*id == '@' ? id + 1 : id, request);
}
}
tf_ssb_db_identity_visit(ssb, request->name, _tf_ssb_getIdentityInfo_visit, request);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
request->result = sqlite3_prepare(db,
"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 identities ON messages.author = ('@' || identities.public_key) "
" WHERE "
" (identities.user = ? OR identities.public_key = ?) AND "
" json_extract(messages.content, '$.type') = 'about' AND "
" content ->> 'about' = messages.author AND name IS NOT NULL) "
"WHERE author_rank = 1 ",
-1, &statement, NULL);
if (request->result == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, request->name, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, *id == '@' ? id + 1 : id, -1, NULL) == SQLITE_OK)
{
int r = SQLITE_OK;
while ((r = sqlite3_step(statement)) == SQLITE_ROW)
{
const char* identity = (const char*)sqlite3_column_text(statement, 0);
const char* name = (const char*)sqlite3_column_text(statement, 1);
for (int i = 0; i < request->count; i++)
{
if (!request->names[i] && strcmp(request->identities[i], identity) == 0)
{
request->names[i] = tf_strdup(name);
break;
}
}
}
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s.\n", sqlite3_errmsg(db));
}
tf_ssb_db_identity_get_active(db, request->name, request->package_owner, request->package_name, request->active_identity, sizeof(request->active_identity));
if (!*request->active_identity && request->count)
{
snprintf(request->active_identity, sizeof(request->active_identity), "%s", request->identities[0]);
}
tf_ssb_release_db_reader(ssb, db);
} }
static void _tf_ssb_getIdentityInfo_after_work(tf_ssb_t* ssb, int status, void* user_data) static void _tf_ssb_getIdentityInfo_after_work(tf_ssb_t* ssb, int status, void* user_data)
@ -703,20 +630,20 @@ static void _tf_ssb_getIdentityInfo_after_work(tf_ssb_t* ssb, int status, void*
JSValue result = JS_NewObject(context); JSValue result = JS_NewObject(context);
JSValue identities = JS_NewArray(context); JSValue identities = JS_NewArray(context);
for (int i = 0; i < request->count; i++) for (int i = 0; i < request->info->count; i++)
{ {
JS_SetPropertyUint32(context, identities, i, JS_NewString(context, request->identities[i])); JS_SetPropertyUint32(context, identities, i, JS_NewString(context, request->info->identity[i]));
} }
JS_SetPropertyStr(context, result, "identities", identities); JS_SetPropertyStr(context, result, "identities", identities);
JSValue names = JS_NewObject(context); JSValue names = JS_NewObject(context);
for (int i = 0; i < request->count; i++) for (int i = 0; i < request->info->count; i++)
{ {
JS_SetPropertyStr(context, names, request->identities[i], JS_NewString(context, request->names[i] ? request->names[i] : request->identities[i])); JS_SetPropertyStr(context, names, request->info->identity[i], JS_NewString(context, request->info->name[i] ? request->info->name[i] : request->info->identity[i]));
} }
JS_SetPropertyStr(context, result, "names", names); JS_SetPropertyStr(context, result, "names", names);
JS_SetPropertyStr(context, result, "identity", JS_NewString(context, request->active_identity)); JS_SetPropertyStr(context, result, "identity", JS_NewString(context, request->info->active_identity));
JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 1, &result); JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error); tf_util_report_error(context, error);
@ -725,16 +652,10 @@ static void _tf_ssb_getIdentityInfo_after_work(tf_ssb_t* ssb, int status, void*
JS_FreeValue(context, request->promise[0]); JS_FreeValue(context, request->promise[0]);
JS_FreeValue(context, request->promise[1]); JS_FreeValue(context, request->promise[1]);
for (int i = 0; i < request->count; i++)
{
tf_free(request->identities[i]);
tf_free(request->names[i]);
}
tf_free(request->identities);
tf_free(request->names);
tf_free((void*)request->name); tf_free((void*)request->name);
tf_free((void*)request->package_owner); tf_free((void*)request->package_owner);
tf_free((void*)request->package_name); tf_free((void*)request->package_name);
tf_free(request->info);
tf_free(request); tf_free(request);
} }

View File

@ -83,6 +83,19 @@ static void _tf_ssb_rpc_blobs_get_after_work(tf_ssb_connection_t* connection, in
tf_free(work); tf_free(work);
} }
static void _tf_ssb_blobs_get_callback(tf_ssb_connection_t* connection, bool skip, void* user_data)
{
blobs_get_work_t* work = user_data;
if (!skip)
{
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_blobs_get_work, _tf_ssb_rpc_blobs_get_after_work, work);
}
else
{
_tf_ssb_rpc_blobs_get_after_work(connection, -1, work);
}
}
static void _tf_ssb_rpc_blobs_get(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 void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{ {
if (flags & k_ssb_rpc_flag_end_error) if (flags & k_ssb_rpc_flag_end_error)
@ -122,7 +135,7 @@ static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags
.request_number = request_number, .request_number = request_number,
}; };
snprintf(work->id, sizeof(work->id), "%s", id); snprintf(work->id, sizeof(work->id), "%s", id);
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_blobs_get_work, _tf_ssb_rpc_blobs_get_after_work, work); tf_ssb_connection_schedule_idle(connection, id, _tf_ssb_blobs_get_callback, work);
JS_FreeCString(context, id); JS_FreeCString(context, id);
JS_FreeValue(context, arg); JS_FreeValue(context, arg);
@ -580,6 +593,7 @@ static void _tf_ssb_rpc_connection_blobs_get_callback(
bool stored = true; bool stored = true;
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream | k_ssb_rpc_flag_end_error, -request_number, NULL, tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream | k_ssb_rpc_flag_end_error, -request_number, NULL,
(const uint8_t*)(stored ? "true" : "false"), strlen(stored ? "true" : "false"), NULL, NULL, NULL); (const uint8_t*)(stored ? "true" : "false"), strlen(stored ? "true" : "false"), NULL, NULL, NULL);
tf_ssb_connection_remove_request(connection, -request_number);
} }
} }
@ -1101,6 +1115,23 @@ static void _tf_ssb_rpc_ebt_replicate_resend_clock(tf_ssb_connection_t* connecti
} }
} }
static void _tf_ssb_rpc_ebt_schedule_send_clock(tf_ssb_connection_t* connection)
{
tf_ssb_ebt_t* ebt = tf_ssb_connection_get_ebt(connection);
int pending = tf_ssb_ebt_get_send_clock_pending(ebt) + 1;
tf_ssb_ebt_set_send_clock_pending(ebt, pending);
if (pending == 1)
{
resend_clock_t* resend = tf_malloc(sizeof(resend_clock_t));
*resend = (resend_clock_t) {
.connection = connection,
.request_number = -tf_ssb_connection_get_ebt_request_number(connection),
.pending = pending,
};
tf_ssb_connection_schedule_idle(connection, "ebt.clock", _tf_ssb_rpc_ebt_replicate_resend_clock, resend);
}
}
static void _tf_ssb_rpc_ebt_replicate(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 void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{ {
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
@ -1108,6 +1139,7 @@ static void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t f
if (_is_error(context, args)) if (_is_error(context, args))
{ {
/* TODO: Send createHistoryStream. */ /* TODO: Send createHistoryStream. */
tf_ssb_connection_set_ebt_request_number(connection, 0);
tf_ssb_connection_remove_request(connection, -request_number); tf_ssb_connection_remove_request(connection, -request_number);
return; return;
} }
@ -1140,18 +1172,7 @@ static void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t f
if (resend_clock && tf_ssb_connection_is_connected(connection) && !tf_ssb_is_shutting_down(tf_ssb_connection_get_ssb(connection)) && !tf_ssb_connection_is_closing(connection)) if (resend_clock && tf_ssb_connection_is_connected(connection) && !tf_ssb_is_shutting_down(tf_ssb_connection_get_ssb(connection)) && !tf_ssb_connection_is_closing(connection))
{ {
int pending = tf_ssb_ebt_get_send_clock_pending(ebt) + 1; _tf_ssb_rpc_ebt_schedule_send_clock(connection);
tf_ssb_ebt_set_send_clock_pending(ebt, pending);
if (pending == 1)
{
resend_clock_t* resend = tf_malloc(sizeof(resend_clock_t));
*resend = (resend_clock_t) {
.connection = connection,
.request_number = request_number,
.pending = pending,
};
tf_ssb_connection_schedule_idle(connection, "ebt.clock", _tf_ssb_rpc_ebt_replicate_resend_clock, resend);
}
} }
JS_FreeValue(context, name); JS_FreeValue(context, name);
JS_FreeValue(context, author); JS_FreeValue(context, author);
@ -1855,10 +1876,25 @@ static void _tf_ssb_rpc_invite_use(tf_ssb_connection_t* connection, uint8_t flag
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_invite_use_work, _tf_ssb_rpc_invite_use_after_work, work); tf_ssb_connection_run_work(connection, _tf_ssb_rpc_invite_use_work, _tf_ssb_rpc_invite_use_after_work, work);
} }
static void _tf_ssb_rpc_message_added_callback(tf_ssb_t* ssb, const char* id, void* user_data)
{
tf_ssb_connection_t* connections[256];
int count = tf_ssb_get_connections(ssb, connections, tf_countof(connections));
for (int i = 0; i < count; i++)
{
tf_ssb_connection_t* connection = connections[i];
if (tf_ssb_connection_is_connected(connection) && !tf_ssb_is_shutting_down(tf_ssb_connection_get_ssb(connection)) && !tf_ssb_connection_is_closing(connection))
{
_tf_ssb_rpc_ebt_schedule_send_clock(connections[i]);
}
}
}
void tf_ssb_rpc_register(tf_ssb_t* ssb) void tf_ssb_rpc_register(tf_ssb_t* ssb)
{ {
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_rpc_connections_changed_callback, NULL, NULL); tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_rpc_connections_changed_callback, NULL, NULL);
tf_ssb_add_broadcasts_changed_callback(ssb, _tf_ssb_rpc_broadcasts_changed_callback, NULL, NULL); tf_ssb_add_broadcasts_changed_callback(ssb, _tf_ssb_rpc_broadcasts_changed_callback, NULL, NULL);
tf_ssb_add_message_added_callback(ssb, _tf_ssb_rpc_message_added_callback, NULL, NULL);
tf_ssb_add_rpc_callback(ssb, "gossip.ping", _tf_ssb_rpc_gossip_ping, NULL, NULL); /* DUPLEX */ tf_ssb_add_rpc_callback(ssb, "gossip.ping", _tf_ssb_rpc_gossip_ping, NULL, NULL); /* DUPLEX */
tf_ssb_add_rpc_callback(ssb, "blobs.get", _tf_ssb_rpc_blobs_get, NULL, NULL); /* SOURCE */ tf_ssb_add_rpc_callback(ssb, "blobs.get", _tf_ssb_rpc_blobs_get, NULL, NULL); /* SOURCE */
tf_ssb_add_rpc_callback(ssb, "blobs.has", _tf_ssb_rpc_blobs_has, NULL, NULL); /* ASYNC */ tf_ssb_add_rpc_callback(ssb, "blobs.has", _tf_ssb_rpc_blobs_has, NULL, NULL); /* ASYNC */

View File

@ -1,5 +1,6 @@
#include "task.h" #include "task.h"
#include "api.js.h"
#include "bcrypt.js.h" #include "bcrypt.js.h"
#include "database.js.h" #include "database.js.h"
#include "file.js.h" #include "file.js.h"
@ -163,6 +164,7 @@ typedef struct _tf_task_t
promise_stack_t* _promise_stacks; promise_stack_t* _promise_stacks;
int _promise_stack_count; int _promise_stack_count;
bool _promise_stack_debug;
timeout_t* timeouts; timeout_t* timeouts;
@ -1251,11 +1253,14 @@ static void _add_promise_stack(tf_task_t* task, uint32_t hash, const char* stack
static void _remove_promise_stack(tf_task_t* task, uint32_t hash) static void _remove_promise_stack(tf_task_t* task, uint32_t hash)
{ {
if (task->_promise_stack_debug)
{
promise_stack_t* found = bsearch(&hash, task->_promise_stacks, task->_promise_stack_count, sizeof(promise_stack_t), _promise_stack_compare); promise_stack_t* found = bsearch(&hash, task->_promise_stacks, task->_promise_stack_count, sizeof(promise_stack_t), _promise_stack_compare);
if (found) if (found)
{ {
found->count--; found->count--;
} }
}
} }
static void _tf_task_free_promise(tf_task_t* task, promiseid_t id) static void _tf_task_free_promise(tf_task_t* task, promiseid_t id)
@ -1270,33 +1275,26 @@ static void _tf_task_free_promise(tf_task_t* task, promiseid_t id)
} }
} }
uint32_t fnv32a(const void* buffer, int length, uint32_t start)
{
uint32_t result = 0x811c9dc5;
for (int i = 0; i < length; i++)
{
result ^= ((const uint8_t*)buffer)[i];
result += (result << 1) + (result << 4) + (result << 7) + (result << 8) + (result << 24);
}
return result;
}
JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise) JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise)
{ {
uint32_t stack_hash = 0;
if (task->_promise_stack_debug)
{
JSValue error = JS_ThrowInternalError(task->_context, "promise callstack"); JSValue error = JS_ThrowInternalError(task->_context, "promise callstack");
JSValue exception = JS_GetException(task->_context); JSValue exception = JS_GetException(task->_context);
JSValue stack_value = JS_GetPropertyStr(task->_context, exception, "stack"); JSValue stack_value = JS_GetPropertyStr(task->_context, exception, "stack");
size_t length = 0; size_t length = 0;
const char* stack = JS_ToCStringLen(task->_context, &length, stack_value); const char* stack = JS_ToCStringLen(task->_context, &length, stack_value);
uint32_t stack_hash = fnv32a((const void*)stack, (int)length, 0); stack_hash = tf_util_fnv32a((const void*)stack, (int)length, 0);
void* buffer[32]; void* buffer[32];
int count = tf_util_backtrace(buffer, sizeof(buffer) / sizeof(*buffer)); int count = tf_util_backtrace(buffer, sizeof(buffer) / sizeof(*buffer));
stack_hash = fnv32a((const void*)buffer, sizeof(void*) * count, stack_hash); stack_hash = tf_util_fnv32a((const void*)buffer, sizeof(void*) * count, stack_hash);
_add_promise_stack(task, stack_hash, stack, buffer, count); _add_promise_stack(task, stack_hash, stack, buffer, count);
JS_FreeCString(task->_context, stack); JS_FreeCString(task->_context, stack);
JS_FreeValue(task->_context, stack_value); JS_FreeValue(task->_context, stack_value);
JS_FreeValue(task->_context, exception); JS_FreeValue(task->_context, exception);
JS_FreeValue(task->_context, error); JS_FreeValue(task->_context, error);
}
promiseid_t promiseId; promiseid_t promiseId;
do do
@ -1591,6 +1589,10 @@ tf_task_t* tf_task_create()
*task = (tf_task_t) { 0 }; *task = (tf_task_t) { 0 };
++_count; ++_count;
char buffer[8] = { 0 };
size_t buffer_size = sizeof(buffer);
task->_promise_stack_debug = uv_os_getenv("TF_PROMISE_DEBUG", buffer, &buffer_size) == 0 && strcmp(buffer, "1") == 0;
JSMallocFunctions funcs = { 0 }; JSMallocFunctions funcs = { 0 };
tf_get_js_malloc_functions(&funcs); tf_get_js_malloc_functions(&funcs);
task->_runtime = JS_NewRuntime2(&funcs, NULL); task->_runtime = JS_NewRuntime2(&funcs, NULL);
@ -1697,6 +1699,7 @@ void tf_task_activate(tf_task_t* task)
task->_ssb = tf_ssb_create(&task->_loop, task->_context, task->_db_path, task->_network_key); task->_ssb = tf_ssb_create(&task->_loop, task->_context, task->_db_path, task->_network_key);
tf_ssb_set_trace(task->_ssb, task->_trace); tf_ssb_set_trace(task->_ssb, task->_trace);
tf_ssb_register(context, task->_ssb); tf_ssb_register(context, task->_ssb);
tf_api_register(context);
tf_ssb_set_hitch_callback(task->_ssb, _tf_task_record_hitch, task); tf_ssb_set_hitch_callback(task->_ssb, _tf_task_record_hitch, task);
if (task->_args) if (task->_args)

View File

@ -683,3 +683,14 @@ bool tf_util_is_mobile()
{ {
return TF_IS_MOBILE != 0; return TF_IS_MOBILE != 0;
} }
uint32_t tf_util_fnv32a(const void* buffer, int length, uint32_t start)
{
uint32_t result = 0x811c9dc5;
for (int i = 0; i < length; i++)
{
result ^= ((const uint8_t*)buffer)[i];
result += (result << 1) + (result << 4) + (result << 7) + (result << 8) + (result << 24);
}
return result;
}

View File

@ -224,4 +224,13 @@ void tf_util_document_settings(const char* line_prefix);
*/ */
bool tf_util_is_mobile(); bool tf_util_is_mobile();
/**
** Compute a 32-bit hash of a buffer.
** @param buffer The data.
** @param length The size of the buffer in bytes.
** @param start The hash seed.
** @return The computed hash.
*/
uint32_t tf_util_fnv32a(const void* buffer, int length, uint32_t start);
/** @} */ /** @} */

View File

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

View File

@ -25,6 +25,7 @@ def fix_title(entry):
return entry.title.split('\n')[0] return entry.title.split('\n')[0]
def get_entries(): def get_entries():
seen = set()
results = [] results = []
for name, url in k_feeds.items(): for name, url in k_feeds.items():
feed = feedparser.parse(url) feed = feedparser.parse(url)
@ -41,8 +42,12 @@ def get_entries():
continue continue
if entry.summary.startswith('<a href='): if entry.summary.startswith('<a href='):
for m in re.findall(r'<a href="(.*?)">.*?</a>$\s*^([^\n]+)$', entry.summary, re.S | re.M): for m in re.findall(r'<a href="(.*?)">.*?</a>$\s*^([^\n]+)$', entry.summary, re.S | re.M):
if not m[0] in seen:
seen.add(m[0])
results.append((time.mktime(entry.get('updated_parsed')), name, m[0], m[1])) results.append((time.mktime(entry.get('updated_parsed')), name, m[0], m[1]))
else: else:
if not entry.link in seen:
seen.add(entry.link)
results.append((time.mktime(entry.get('updated_parsed')), name, entry.link, entry.title.split('\n')[0])) results.append((time.mktime(entry.get('updated_parsed')), name, entry.link, entry.title.split('\n')[0]))
results.sort() results.sort()
results.reverse() results.reverse()