Compare commits

..

2 Commits

Author SHA1 Message Date
d67e47ae4b
nix(module): module boilerplate 2025-02-20 10:27:18 +01:00
b43b8da9ab
nix (test): fix build 2025-02-20 10:17:14 +01:00
100 changed files with 430 additions and 552 deletions

View File

@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
## LD := Linker.
## ANDROID_SDK := Path to the Android SDK.
VERSION_CODE := 34
VERSION_CODE_IOS := 11
VERSION_NUMBER := 0.0.29-wip
VERSION_CODE := 33
VERSION_CODE_IOS := 8
VERSION_NUMBER := 0.0.28-wip
VERSION_NAME := This program kills fascists.
IPHONEOS_VERSION_MIN=14.0
@ -1419,7 +1419,7 @@ dist-ios: iosrelease-app
mkdir -p out/Payload/tildefriends.app
cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/
cp src/ios/tildefriends.png 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/icons/Assets.car out/Payload/tildefriends.app/
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
cd out; zip -r tildefriends.ipa Payload; cd ..

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,8 +31,8 @@ pkgs.stdenv.mkDerivation rec {
domain = "dev.tildefriends.net";
owner = "cory";
repo = "tildefriends";
rev = "v${version}";
hash = "sha256-vcLJCXgIrjC37t9oavK2QMRcMJljghuzKDYxQ4nyTcE=";
rev = "f02423d0846fefd5ab21fa4542fb77ce5714547c";
hash = "sha256-QyM7wmViXJc4r8uTu4oE/HO3Z9tzNbFIX2+AOTQz9ZY=";
fetchSubmodules = true;
};

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -1,18 +0,0 @@
# 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

@ -35,5 +35,27 @@
graphviz
];
};
nixosModules.default = {
config,
lib,
...
}: let
# Shorter name to access final settings a
# user of hello.nix module HAS ACTUALLY SET.
# cfg is a typical convention.
cfg = config.services.tildefriends;
in {
options.services.tildefriends = {
enable = lib.mkEnableOption "Enable Tilde Friends";
};
config = lib.mkIf cfg.enable {
systemd.services.tildefriends = {
wantedBy = ["multi-user.target"];
serviceConfig.ExecStart = "${pkgs.tildefriends}/bin/tildefriends";
};
};
};
});
}

View File

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

6
package-lock.json generated
View File

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

View File

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

View File

@ -1,19 +0,0 @@
#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);
}

View File

@ -1,18 +0,0 @@
#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,7 +84,6 @@ typedef struct _tf_http_listener_t
typedef struct _tf_http_t
{
bool is_shutting_down;
bool is_in_destroy;
tf_http_listener_t** listeners;
int listeners_count;
@ -396,7 +395,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
if (fin)
{
if (connection->request && connection->request->on_message)
if (connection->request->on_message)
{
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,
@ -787,13 +786,7 @@ static void _http_free_listener_on_close(uv_handle_t* handle)
void tf_http_destroy(tf_http_t* http)
{
if (http->is_in_destroy)
{
return;
}
http->is_shutting_down = true;
http->is_in_destroy = true;
for (int i = 0; i < http->connections_count; i++)
{
@ -852,10 +845,6 @@ void tf_http_destroy(tf_http_t* http)
tf_free(http);
}
else
{
http->is_in_destroy = false;
}
}
const char* tf_http_status_text(int status)
@ -974,42 +963,9 @@ static void _http_write(tf_http_connection_t* connection, const void* data, size
}
}
void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size)
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t 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);
_http_write(request->connection, data, size);
}
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,11 +209,10 @@ const char* tf_http_get_cookie(const char* cookie_header, const char* name);
** Send a websocket message.
** @param request The HTTP request which was previously updated to a websocket
** session with tf_http_request_websocket_upgrade().
** @param op_code Websocket op code.
** @param data The message data.
** @param size The size of data.
*/
void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size);
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size);
/**
** Upgrade an HTTP request to a websocket session.

View File

@ -152,9 +152,44 @@ static JSValue _httpd_response_send(JSContext* context, JSValueConst this_val, i
tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id);
int opcode = 0x1;
JS_ToInt32(context, &opcode, argv[1]);
size_t length = 0;
const char* message = JS_ToCStringLen(context, &length, argv[0]);
tf_http_request_websocket_send(request, opcode, message, length);
uint64_t length = 0;
size_t length_size = 0;
const char* message = JS_ToCStringLen(context, &length_size, argv[0]);
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);
return JS_UNDEFINED;
}

View File

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

BIN
src/ios/icons/Assets.car Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 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: 13 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: 974 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -1 +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"}]}
{"images":[{"idiom":"ios-marketing","scale":"1x","size":"1024x1024","filename":"icon-ios-marketing-1-1024-1024.png"}],"info":{"author":"xcode","version":1}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 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: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

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

Binary file not shown.

After

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);
tf_free((void*)profile);
tf_free((void*)default_db_path);
return profile != NULL ? EXIT_SUCCESS : EXIT_FAILURE;
return profile != NULL;
}
static int _tf_command_get_contacts(const char* file, int argc, char* argv[])
@ -1299,12 +1299,10 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
int64_t http_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, "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);
if (http_port || https_port || *out_http_port_file)
if (http_port || https_port)
{
if (args->zip)
{

View File

@ -22,6 +22,8 @@ static int64_t s_tls_malloc_size;
static int64_t s_js_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)
{
return (size + 7) & ~7;
@ -154,7 +156,7 @@ static void _tf_mem_summarize(void* ptr, size_t size, int frames_count, void* co
{
summary_t* summary = user_data;
tf_mem_allocation_t allocation = {
.stack_hash = tf_util_fnv32a(frames, sizeof(void*) * frames_count, 0),
.stack_hash = fnv32a(frames, sizeof(void*) * frames_count, 0),
.count = 1,
.size = size,
.frames_count = frames_count,

View File

@ -746,9 +746,7 @@ static bool _tf_ssb_connection_get_request_callback(
*out_name = request->name;
}
request->last_active = uv_now(connection->ssb->loop);
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);
}
@ -756,7 +754,6 @@ 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);
}
}
return true;
}
return false;
@ -802,8 +799,6 @@ void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t requ
connection->requests_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)
{
uv_timer_start(&connection->activity_timer, _tf_ssb_connection_activity_timer, k_activity_timeout_ms, 0);
@ -812,7 +807,6 @@ 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);
}
}
_tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_update, connection);
connection->last_notified_active = now_ms;
}
@ -2003,7 +1997,7 @@ static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const ch
if (!connection->is_closing)
{
connection->is_closing = true;
uv_timer_start(&connection->linger_timer, _tf_ssb_connection_linger_timer, ssb->shutting_down ? 0 : 5000, 0);
uv_timer_start(&connection->linger_timer, _tf_ssb_connection_linger_timer, 5000, 0);
_tf_ssb_notify_connections_changed(ssb, k_tf_ssb_change_update, connection);
}
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)
{
return count ? bsearch(id, list, count, sizeof(following_t*), _following_compare) != NULL : false;
return count ? bsearch(id, list, count, sizeof(following_t*), _following_compare) != 0 : false;
}
static bool _add_following_entry(following_t*** list, int* count, following_t* add)
@ -1432,26 +1432,25 @@ static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, foll
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)
{
int old_depth = entry->depth;
entry->depth = tf_min(depth, entry->depth);
if (depth < max_depth && depth < old_depth)
{
if (!_is_blocked_by_active_blocks(entry->id, active_blocks))
{
if (!entry->populated)
if (depth < max_depth && !entry->populated && !_is_blocked_by_active_blocks(entry->id, active_blocks))
{
entry->populated = true;
_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 };
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);
}
}
}
}
}
tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth, bool include_blocks)
{
@ -2331,120 +2330,3 @@ bool tf_ssb_db_has_invite(sqlite3* db, const char* id)
}
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,29 +564,4 @@ 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);
/**
** 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,14 +613,87 @@ typedef struct _identity_info_work_t
const char* name;
const char* package_owner;
const char* package_name;
tf_ssb_identity_info_t* info;
int count;
char** identities;
char** names;
int result;
char active_identity[k_id_base64_len];
JSValue promise[2];
} 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)
{
identity_info_work_t* request = user_data;
request->info = tf_ssb_db_get_identity_info(ssb, request->name, request->package_owner, request->package_name);
char id[k_id_base64_len] = "";
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)
@ -630,20 +703,20 @@ static void _tf_ssb_getIdentityInfo_after_work(tf_ssb_t* ssb, int status, void*
JSValue result = JS_NewObject(context);
JSValue identities = JS_NewArray(context);
for (int i = 0; i < request->info->count; i++)
for (int i = 0; i < request->count; i++)
{
JS_SetPropertyUint32(context, identities, i, JS_NewString(context, request->info->identity[i]));
JS_SetPropertyUint32(context, identities, i, JS_NewString(context, request->identities[i]));
}
JS_SetPropertyStr(context, result, "identities", identities);
JSValue names = JS_NewObject(context);
for (int i = 0; i < request->info->count; i++)
for (int i = 0; i < request->count; 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, names, request->identities[i], JS_NewString(context, request->names[i] ? request->names[i] : request->identities[i]));
}
JS_SetPropertyStr(context, result, "names", names);
JS_SetPropertyStr(context, result, "identity", JS_NewString(context, request->info->active_identity));
JS_SetPropertyStr(context, result, "identity", JS_NewString(context, request->active_identity));
JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
@ -652,10 +725,16 @@ 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[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->package_owner);
tf_free((void*)request->package_name);
tf_free(request->info);
tf_free(request);
}

View File

@ -83,19 +83,6 @@ static void _tf_ssb_rpc_blobs_get_after_work(tf_ssb_connection_t* connection, in
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)
{
if (flags & k_ssb_rpc_flag_end_error)
@ -135,7 +122,7 @@ static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags
.request_number = request_number,
};
snprintf(work->id, sizeof(work->id), "%s", id);
tf_ssb_connection_schedule_idle(connection, id, _tf_ssb_blobs_get_callback, work);
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_blobs_get_work, _tf_ssb_rpc_blobs_get_after_work, work);
JS_FreeCString(context, id);
JS_FreeValue(context, arg);
@ -593,7 +580,6 @@ static void _tf_ssb_rpc_connection_blobs_get_callback(
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,
(const uint8_t*)(stored ? "true" : "false"), strlen(stored ? "true" : "false"), NULL, NULL, NULL);
tf_ssb_connection_remove_request(connection, -request_number);
}
}
@ -1115,23 +1101,6 @@ 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)
{
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
@ -1139,7 +1108,6 @@ static void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t f
if (_is_error(context, args))
{
/* TODO: Send createHistoryStream. */
tf_ssb_connection_set_ebt_request_number(connection, 0);
tf_ssb_connection_remove_request(connection, -request_number);
return;
}
@ -1172,7 +1140,18 @@ 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))
{
_tf_ssb_rpc_ebt_schedule_send_clock(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 = 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, author);
@ -1876,25 +1855,10 @@ 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);
}
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)
{
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_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, "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 */

View File

@ -1,6 +1,5 @@
#include "task.h"
#include "api.js.h"
#include "bcrypt.js.h"
#include "database.js.h"
#include "file.js.h"
@ -164,7 +163,6 @@ typedef struct _tf_task_t
promise_stack_t* _promise_stacks;
int _promise_stack_count;
bool _promise_stack_debug;
timeout_t* timeouts;
@ -1252,8 +1250,6 @@ 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)
{
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);
if (found)
@ -1261,7 +1257,6 @@ static void _remove_promise_stack(tf_task_t* task, uint32_t hash)
found->count--;
}
}
}
static void _tf_task_free_promise(tf_task_t* task, promiseid_t id)
{
@ -1275,26 +1270,33 @@ static void _tf_task_free_promise(tf_task_t* task, promiseid_t id)
}
}
JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise)
uint32_t fnv32a(const void* buffer, int length, uint32_t start)
{
uint32_t stack_hash = 0;
if (task->_promise_stack_debug)
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 error = JS_ThrowInternalError(task->_context, "promise callstack");
JSValue exception = JS_GetException(task->_context);
JSValue stack_value = JS_GetPropertyStr(task->_context, exception, "stack");
size_t length = 0;
const char* stack = JS_ToCStringLen(task->_context, &length, stack_value);
stack_hash = tf_util_fnv32a((const void*)stack, (int)length, 0);
uint32_t stack_hash = fnv32a((const void*)stack, (int)length, 0);
void* buffer[32];
int count = tf_util_backtrace(buffer, sizeof(buffer) / sizeof(*buffer));
stack_hash = tf_util_fnv32a((const void*)buffer, sizeof(void*) * count, stack_hash);
stack_hash = fnv32a((const void*)buffer, sizeof(void*) * count, stack_hash);
_add_promise_stack(task, stack_hash, stack, buffer, count);
JS_FreeCString(task->_context, stack);
JS_FreeValue(task->_context, stack_value);
JS_FreeValue(task->_context, exception);
JS_FreeValue(task->_context, error);
}
promiseid_t promiseId;
do
@ -1589,10 +1591,6 @@ tf_task_t* tf_task_create()
*task = (tf_task_t) { 0 };
++_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 };
tf_get_js_malloc_functions(&funcs);
task->_runtime = JS_NewRuntime2(&funcs, NULL);
@ -1699,7 +1697,6 @@ void tf_task_activate(tf_task_t* task)
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_register(context, task->_ssb);
tf_api_register(context);
tf_ssb_set_hitch_callback(task->_ssb, _tf_task_record_hitch, task);
if (task->_args)

View File

@ -683,14 +683,3 @@ bool tf_util_is_mobile()
{
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,13 +224,4 @@ void tf_util_document_settings(const char* line_prefix);
*/
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.29-wip"
#define VERSION_NUMBER "0.0.28-wip"
#define VERSION_NAME "This program kills fascists."

View File

@ -25,7 +25,6 @@ def fix_title(entry):
return entry.title.split('\n')[0]
def get_entries():
seen = set()
results = []
for name, url in k_feeds.items():
feed = feedparser.parse(url)
@ -42,12 +41,8 @@ def get_entries():
continue
if entry.summary.startswith('<a href='):
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]))
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.sort()
results.reverse()