Compare commits

..

12 Commits

Author SHA1 Message Date
580688381e prettier 2024-05-22 20:52:10 -04:00
e63d69a440 Missing generated semicolon. Sigh. 2024-05-22 20:44:28 -04:00
be64fe04fb Auto-update all the versions. 2024-05-22 20:35:48 -04:00
801ab20723 Merge pull request 'Add Nix support' (#62) from tasiaiso/tildefriends:tasiaiso-nix into main
Reviewed-on: cory/tildefriends#62
2024-05-22 20:31:11 -04:00
d974a5e044 An experiment in controlling memory usage when syncing. uv_read_stop when we have too active message/blob writes to the database and uv_read_start when we're back under control. #64 2024-05-22 19:53:33 -04:00
1be94ae0be Removed ssb.addEventListener and ssb.removeEventListener from the public API. Can do the same thing with core.register. 2024-05-22 18:51:21 -04:00
b883e6a485 Fix username/id extending off the screen in the welcome line. 2024-05-22 12:33:18 -04:00
a0210379ae Avoid confusing log output when responding with a method not found error. 2024-05-20 12:39:21 -04:00
e56dc207d1 Fix some shutdown issues in connection tracker code. 2024-05-16 12:41:48 -04:00
523c9c9ad2 Move mime type shenanigans from JS => C. 2024-05-15 19:25:48 -04:00
74bb2151c1 Fix shutdown issues with in-flight SSB connection attempts. 2024-05-15 12:37:13 -04:00
f79d7b35a4 Disallow creating accounts as a guest. #52 2024-05-14 12:41:17 -04:00
19 changed files with 364 additions and 261 deletions

View File

@@ -616,7 +616,7 @@ $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
unix: debug release unix: debug release
win: windebug winrelease win: windebug winrelease
all: $(BUILD_TYPES) all: $(BUILD_TYPES) default.nix
.PHONY: all win unix .PHONY: all win unix
ALL_APP_OBJS := \ ALL_APP_OBJS := \
@@ -673,6 +673,10 @@ src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
-e 's/android:targetSdkVersion="[[:digit:]]*"/android:targetSdkVersion="$(ANDROID_TARGET_SDK_VERSION)"/' \ -e 's/android:targetSdkVersion="[[:digit:]]*"/android:targetSdkVersion="$(ANDROID_TARGET_SDK_VERSION)"/' \
$@ $@
default.nix : $(firstword $(MAKEFILE_LIST))
@echo "[version] $@"
@sed -i -e 's/version = ".*";/version = "$(VERSION_NUMBER)";/' $@
# Android support. # Android support.
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@@ -858,7 +862,7 @@ clean:
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)
.PHONY: clean .PHONY: clean
dist: release-apk iosrelease-ipa $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe) dist: release-apk iosrelease-ipa $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe) default.nix
@echo [archive] dist/tildefriends-$(VERSION_NUMBER).tar.xz @echo [archive] dist/tildefriends-$(VERSION_NUMBER).tar.xz
@rm -rf out/tildefriends-$(VERSION_NUMBER) @rm -rf out/tildefriends-$(VERSION_NUMBER)
@mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER) @mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER)

View File

@@ -78,7 +78,7 @@ async function main() {
alert('Successfully created: ' + id); alert('Successfully created: ' + id);
await tfrpc.rpc.reload(); await tfrpc.rpc.reload();
} catch (e) { } catch (e) {
alert('Error creating identity: ' + e); alert('Error creating identity: ' + e.message);
} }
} }
handler.hide_id = function hide_id(event, element) { handler.hide_id = function hide_id(event, element) {

View File

@@ -67,9 +67,6 @@ tfrpc.register(function getHash(id, message) {
tfrpc.register(function setHash(hash) { tfrpc.register(function setHash(hash) {
return app.setHash(hash); return app.setHash(hash);
}); });
ssb.addEventListener('message', async function (id) {
await tfrpc.rpc.notifyNewMessage(id);
});
tfrpc.register(async function store_blob(blob) { tfrpc.register(async function store_blob(blob) {
if (Array.isArray(blob)) { if (Array.isArray(blob)) {
blob = Uint8Array.from(blob); blob = Uint8Array.from(blob);
@@ -91,10 +88,12 @@ tfrpc.register(function getActiveIdentity() {
tfrpc.register(async function try_decrypt(id, content) { tfrpc.register(async function try_decrypt(id, content) {
return await ssb.privateMessageDecrypt(id, content); return await ssb.privateMessageDecrypt(id, content);
}); });
ssb.addEventListener('broadcasts', async function () { core.register('onMessage', async function (id) {
await tfrpc.rpc.notifyNewMessage(id);
});
core.register('onBroadcastsChanged', async function () {
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts()); await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
}); });
core.register('onConnectionsChanged', async function () { core.register('onConnectionsChanged', async function () {
await tfrpc.rpc.set('connections', await ssb.connections()); await tfrpc.rpc.set('connections', await ssb.connections());
}); });

View File

@@ -55,7 +55,7 @@ function new_message() {
return g_new_message_promise; return g_new_message_promise;
} }
ssb.addEventListener('message', function (id) { core.register('onMessage', function (id) {
let resolve = g_new_message_resolve; let resolve = g_new_message_resolve;
g_new_message_promise = new Promise(function (resolve, reject) { g_new_message_promise = new Promise(function (resolve, reject) {
g_new_message_resolve = resolve; g_new_message_resolve = resolve;

View File

@@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🐌", "emoji": "🐌",
"previous": "&wA6sLaDxtYeFdVCCu8jyhPsGYtGZEjbWQHeGOn0Yifg=.sha256" "previous": "&h0sTvkhc3zEJw/sH612fy5i554Gr1AKzCBbLkm0KH28=.sha256"
} }

View File

@@ -76,7 +76,7 @@ tfrpc.register(function getHash(id, message) {
tfrpc.register(function setHash(hash) { tfrpc.register(function setHash(hash) {
return app.setHash(hash); return app.setHash(hash);
}); });
ssb.addEventListener('message', async function (id) { core.register('onMessage', async function (id) {
await tfrpc.rpc.notifyNewMessage(id); await tfrpc.rpc.notifyNewMessage(id);
}); });
tfrpc.register(async function store_blob(blob) { tfrpc.register(async function store_blob(blob) {
@@ -103,7 +103,7 @@ tfrpc.register(async function encrypt(id, recipients, content) {
tfrpc.register(async function getActiveIdentity() { tfrpc.register(async function getActiveIdentity() {
return await ssb.getActiveIdentity(); return await ssb.getActiveIdentity();
}); });
ssb.addEventListener('broadcasts', async function () { core.register('onBroadcastsChanged', async function () {
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts()); await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
}); });

View File

@@ -136,7 +136,7 @@ class TfTabNewsElement extends LitElement {
${this.new_messages_text()} ${this.new_messages_text()}
</button> </button>
</p> </p>
<div> <div class="w3-bar">
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>! Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
${edit_profile} ${edit_profile}
</div> </div>

View File

@@ -22,7 +22,8 @@ class TfUserElement extends LitElement {
let image = html`<span let image = html`<span
class="w3-theme-light w3-circle" class="w3-theme-light w3-circle"
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em" style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
>?</span>`; >?</span
>`;
let name = this.users?.[this.id]?.name; let name = this.users?.[this.id]?.name;
name = name =
name !== undefined name !== undefined
@@ -31,7 +32,8 @@ class TfUserElement extends LitElement {
if (this.users[this.id]) { if (this.users[this.id]) {
let image_link = this.users[this.id].image; let image_link = this.users[this.id].image;
image_link = typeof image_link == 'string' ? image_link : image_link?.link; image_link =
typeof image_link == 'string' ? image_link : image_link?.link;
if (image_link !== undefined) { if (image_link !== undefined) {
image = html`<img image = html`<img
class="w3-circle" class="w3-circle"
@@ -41,8 +43,7 @@ class TfUserElement extends LitElement {
} }
} }
return html` <div style="display: inline-block; font-weight: bold"> return html` <div style="display: inline-block; font-weight: bold">
${image} ${image} ${name}
${name}
</div>`; </div>`;
} }
} }

View File

@@ -50,7 +50,7 @@ function new_message() {
return g_new_message_promise; return g_new_message_promise;
} }
ssb.addEventListener('message', function (id) { core.register('onMessage', function (id) {
let resolve = g_new_message_resolve; let resolve = g_new_message_resolve;
g_new_message_promise = new Promise(function (resolve, reject) { g_new_message_promise = new Promise(function (resolve, reject) {
g_new_message_resolve = resolve; g_new_message_resolve = resolve;

View File

@@ -208,7 +208,10 @@ class TfNavigationElement extends LitElement {
</div> </div>
</div> </div>
`; `;
} else { } else if (
this.credentials?.session?.name &&
this.credentials.session.name !== 'guest'
) {
return html` return html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" /> <link type="text/css" rel="stylesheet" href="/static/w3.css" />
<button <button

View File

@@ -8,116 +8,6 @@ let gStatsTimer = false;
const k_content_security_policy = const k_content_security_policy =
'sandbox allow-downloads allow-top-navigation-by-user-activation'; 'sandbox allow-downloads allow-top-navigation-by-user-activation';
const k_mime_types = {
css: 'text/css',
html: 'text/html',
js: 'text/javascript',
json: 'text/json',
map: 'application/json',
svg: 'image/svg+xml',
};
const k_magic_bytes = [
{bytes: [0xff, 0xd8, 0xff, 0xdb], type: 'image/jpeg'},
{
bytes: [
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
],
type: 'image/jpeg',
},
{bytes: [0xff, 0xd8, 0xff, 0xee], type: 'image/jpeg'},
{
bytes: [
0xff,
0xd8,
0xff,
0xe1,
null,
null,
0x45,
0x78,
0x69,
0x66,
0x00,
0x00,
],
type: 'image/jpeg',
},
{bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], type: 'image/png'},
{bytes: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], type: 'image/gif'},
{bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], type: 'image/gif'},
{
bytes: [
0x52,
0x49,
0x46,
0x46,
null,
null,
null,
null,
0x57,
0x45,
0x42,
0x50,
],
type: 'image/webp',
},
{bytes: [0x3c, 0x73, 0x76, 0x67], type: 'image/svg+xml'},
{
bytes: [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
],
type: 'audio/mpeg',
},
{
bytes: [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x69,
0x73,
0x6f,
0x6d,
],
type: 'video/mp4',
},
{
bytes: [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
],
type: 'video/mp4',
},
{bytes: [0x4d, 0x54, 0x68, 0x64], type: 'audio/midi'},
];
let k_static_files = [ let k_static_files = [
{uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'}, {uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'},
]; ];
@@ -577,7 +467,8 @@ async function getProcessBlob(blobId, key, options) {
if ( if (
process.credentials && process.credentials &&
process.credentials.session && process.credentials.session &&
process.credentials.session.name process.credentials.session.name &&
process.credentials.session.name !== 'guest'
) { ) {
let id = ssb.createIdentity(process.credentials.session.name); let id = ssb.createIdentity(process.credentials.session.name);
await process.sendIdentities(); await process.sendIdentities();
@@ -595,6 +486,8 @@ async function getProcessBlob(blobId, key, options) {
] ]
); );
return id; return id;
} else {
throw new Error('Must be signed-in to create an account.');
} }
}; };
if (process.credentials?.permissions?.administration) { if (process.credentials?.permissions?.administration) {
@@ -785,6 +678,8 @@ async function getProcessBlob(blobId, key, options) {
); );
} }
}; };
imports.ssb.addEventListener = undefined;
imports.ssb.removeEventListener = undefined;
imports.ssb.getIdentityInfo = undefined; imports.ssb.getIdentityInfo = undefined;
imports.fetch = function (url, options) { imports.fetch = function (url, options) {
return http.fetch(url, options, gGlobalSettings.fetch_hosts); return http.fetch(url, options, gGlobalSettings.fetch_hosts);
@@ -938,29 +833,6 @@ function startsWithBytes(data, bytes) {
} }
} }
/**
* TODOC
* @param {*} path
* @returns
*/
function guessTypeFromName(path) {
let extension = path.split('.').pop();
return k_mime_types[extension];
}
/**
* TODOC
* @param {*} data
* @returns
*/
function guessTypeFromMagicBytes(data) {
for (let magic of k_magic_bytes) {
if (startsWithBytes(data, magic.bytes)) {
return magic.type;
}
}
}
/** /**
* TODOC * TODOC
* @param {*} response * @param {*} response
@@ -976,7 +848,9 @@ function sendData(response, data, type, headers, status_code) {
Object.assign( Object.assign(
{ {
'Content-Type': 'Content-Type':
type || guessTypeFromMagicBytes(data) || 'application/binary', type ||
httpd.mime_type_from_magic_bytes(data) ||
'application/binary',
'Content-Length': data.byteLength, 'Content-Length': data.byteLength,
}, },
headers || {} headers || {}
@@ -1345,7 +1219,9 @@ async function blobHandler(request, response, blobId, uri) {
'Content-Security-Policy': k_content_security_policy, 'Content-Security-Policy': k_content_security_policy,
}; };
data = await getBlobOrContent(id); data = await getBlobOrContent(id);
let type = guessTypeFromName(uri) || guessTypeFromMagicBytes(data); let type =
httpd.mime_type_from_extension(uri) ||
httpd.mime_type_from_magic_bytes(data);
sendData(response, data, type, headers); sendData(response, data, type, headers);
} }
} else { } else {
@@ -1354,6 +1230,10 @@ async function blobHandler(request, response, blobId, uri) {
} }
} }
ssb.addEventListener('message', function () {
broadcastEvent('onMessage', [...arguments]);
});
ssb.addEventListener('broadcasts', function () { ssb.addEventListener('broadcasts', function () {
broadcastEvent('onBroadcastsChanged', []); broadcastEvent('onBroadcastsChanged', []);
}); });

View File

@@ -24,7 +24,7 @@
}: }:
pkgs.stdenv.mkDerivation rec { pkgs.stdenv.mkDerivation rec {
pname = "tildefriends"; pname = "tildefriends";
version = "0.0.19"; version = "0.0.19-wip";
src = pkgs.fetchFromGitea { src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net"; domain = "dev.tildefriends.net";

118
flake.lock generated
View File

@@ -1,61 +1,61 @@
{ {
"nodes": { "nodes": {
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1710146030, "lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"type": "github" "type": "github"
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1715395895, "lastModified": 1715395895,
"narHash": "sha256-DreMqi6+qa21ffLQqhMQL2XRUkAGt3N7iVB5FhJKie4=", "narHash": "sha256-DreMqi6+qa21ffLQqhMQL2XRUkAGt3N7iVB5FhJKie4=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "71bae31b7dbc335528ca7e96f479ec93462323ff", "rev": "71bae31b7dbc335528ca7e96f479ec93462323ff",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-23.11", "ref": "nixos-23.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }
}, },
"systems": { "systems": {
"locked": { "locked": {
"lastModified": 1681028828, "lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems", "owner": "nix-systems",
"repo": "default", "repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-systems", "owner": "nix-systems",
"repo": "default", "repo": "default",
"type": "github" "type": "github"
} }
} }
}, },
"root": "root", "root": "root",
"version": 7 "version": 7
} }

View File

@@ -498,6 +498,151 @@ static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int
return result; return result;
} }
typedef struct _magic_bytes_t
{
const char* type;
uint8_t bytes[12];
uint8_t ignore[12];
} magic_bytes_t;
static bool _magic_bytes_match(const magic_bytes_t* magic, const uint8_t* actual, size_t size)
{
if (size < sizeof(magic->bytes))
{
return false;
}
int length = (int)tf_min(sizeof(magic->bytes), size);
for (int i = 0; i < length; i++)
{
if ((magic->bytes[i] & ~magic->ignore[i]) != (actual[i] & ~magic->ignore[i]))
{
return false;
}
}
return true;
}
static JSValue _httpd_mime_type_from_magic_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
size_t size = 0;
uint8_t* bytes = tf_util_try_get_array_buffer(context, &size, argv[0]);
if (bytes)
{
const magic_bytes_t k_magic_bytes[] = {
{
.type = "image/jpeg",
.bytes = { 0xff, 0xd8, 0xff, 0xdb },
},
{
.type = "image/jpeg",
.bytes = { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01 },
},
{
.type = "image/jpeg",
.bytes = { 0xff, 0xd8, 0xff, 0xee },
},
{
.type = "image/jpeg",
.bytes = { 0xff, 0xd8, 0xff, 0xe1, 0x00, 0x00, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 },
.ignore = { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "image/png",
.bytes = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a },
},
{
.type = "image/gif",
.bytes = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 },
},
{
.type = "image/gif",
.bytes = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 },
},
{
.type = "image/webp",
.bytes = { 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50 },
.ignore = { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "image/svg+xml",
.bytes = { 0x3c, 0x73, 0x76, 0x67 },
},
{
.type = "audio/mpeg",
.bytes = { 0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32 },
.ignore = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "video/mp4",
.bytes = { 0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d },
.ignore = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "video/mp4",
.bytes = { 0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32 },
.ignore = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "audio/midi",
.bytes = { 0x4d, 0x54, 0x68, 0x64 },
},
};
for (int i = 0; i < tf_countof(k_magic_bytes); i++)
{
if (_magic_bytes_match(&k_magic_bytes[i], bytes, size))
{
result = JS_NewString(context, k_magic_bytes[i].type);
break;
}
}
}
return result;
}
static const char* _ext_to_content_type(const char* ext, bool use_fallback)
{
if (ext)
{
typedef struct _ext_type_t
{
const char* ext;
const char* type;
} ext_type_t;
const ext_type_t k_types[] = {
{ .ext = ".html", .type = "text/html; charset=UTF-8" },
{ .ext = ".js", .type = "text/javascript; charset=UTF-8" },
{ .ext = ".mjs", .type = "text/javascript; charset=UTF-8" },
{ .ext = ".css", .type = "text/css; charset=UTF-8" },
{ .ext = ".png", .type = "image/png" },
{ .ext = ".json", .type = "application/json" },
{ .ext = ".map", .type = "application/json" },
{ .ext = ".svg", .type = "image/svg+xml" },
};
for (int i = 0; i < tf_countof(k_types); i++)
{
if (strcmp(ext, k_types[i].ext) == 0)
{
return k_types[i].type;
}
}
}
return use_fallback ? "application/binary" : NULL;
}
static JSValue _httpd_mime_type_from_extension(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
const char* name = JS_ToCString(context, argv[0]);
const char* type = _ext_to_content_type(strrchr(name, '.'), false);
JS_FreeCString(context, name);
return type ? JS_NewString(context, type) : JS_UNDEFINED;
}
static void _httpd_finalizer(JSRuntime* runtime, JSValue value) static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
{ {
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id); tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
@@ -627,30 +772,6 @@ typedef struct _http_file_t
char etag[512]; char etag[512];
} http_file_t; } http_file_t;
static const char* _ext_to_content_type(const char* ext)
{
if (ext)
{
if (strcmp(ext, ".html") == 0)
{
return "text/html; charset=UTF-8";
}
else if (strcmp(ext, ".js") == 0 || strcmp(ext, ".mjs") == 0)
{
return "text/javascript; charset=UTF-8";
}
else if (strcmp(ext, ".css") == 0)
{
return "text/css; charset=UTF-8";
}
else if (strcmp(ext, ".png") == 0)
{
return "image/png";
}
}
return "application/binary";
}
static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data) static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
{ {
http_file_t* file = user_data; http_file_t* file = user_data;
@@ -659,7 +780,7 @@ static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int r
{ {
if (strcmp(path, "core/tfrpc.js") == 0) if (strcmp(path, "core/tfrpc.js") == 0)
{ {
const char* content_type = _ext_to_content_type(strrchr(path, '.')); const char* content_type = _ext_to_content_type(strrchr(path, '.'), true);
const char* headers[] = { const char* headers[] = {
"Content-Type", "Content-Type",
content_type, content_type,
@@ -672,7 +793,7 @@ static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int r
} }
else else
{ {
const char* content_type = _ext_to_content_type(strrchr(path, '.')); const char* content_type = _ext_to_content_type(strrchr(path, '.'), true);
const char* headers[] = { const char* headers[] = {
"Content-Type", "Content-Type",
content_type, content_type,
@@ -1519,6 +1640,8 @@ void tf_httpd_register(JSContext* context)
JS_SetPropertyStr(context, httpd, "start", JS_NewCFunction(context, _httpd_endpoint_start, "start", 2)); JS_SetPropertyStr(context, httpd, "start", JS_NewCFunction(context, _httpd_endpoint_start, "start", 2));
JS_SetPropertyStr(context, httpd, "set_http_redirect", JS_NewCFunction(context, _httpd_set_http_redirect, "set_http_redirect", 1)); JS_SetPropertyStr(context, httpd, "set_http_redirect", JS_NewCFunction(context, _httpd_set_http_redirect, "set_http_redirect", 1));
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1)); JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
JS_SetPropertyStr(context, httpd, "mime_type_from_magic_bytes", JS_NewCFunction(context, _httpd_mime_type_from_magic_bytes, "mime_type_from_magic_bytes", 1));
JS_SetPropertyStr(context, httpd, "mime_type_from_extension", JS_NewCFunction(context, _httpd_mime_type_from_extension, "mime_type_from_extension", 1));
JS_SetPropertyStr(context, global, "httpd", httpd); JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global); JS_FreeValue(context, global);
} }

View File

@@ -342,6 +342,8 @@ typedef struct _tf_ssb_connection_t
tf_ssb_debug_message_t* debug_messages[k_debug_close_message_count]; tf_ssb_debug_message_t* debug_messages[k_debug_close_message_count];
int ref_count; int ref_count;
int read_back_pressure;
} tf_ssb_connection_t; } tf_ssb_connection_t;
static JSClassID _connection_class_id; static JSClassID _connection_class_id;
@@ -1618,6 +1620,7 @@ static void _tf_ssb_connection_rpc_recv(tf_ssb_connection_t* connection, uint8_t
} }
if (!found && !_tf_ssb_name_equals(context, val, (const char*[]) { "Error", NULL })) if (!found && !_tf_ssb_name_equals(context, val, (const char*[]) { "Error", NULL }))
{ {
tf_ssb_connection_add_request(connection, -request_number, namebuf, NULL, NULL, NULL, NULL);
char buffer[256]; char buffer[256];
_tf_ssb_name_to_string(context, val, buffer, sizeof(buffer)); _tf_ssb_name_to_string(context, val, buffer, sizeof(buffer));
tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, buffer); tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, buffer);
@@ -2061,6 +2064,30 @@ static void _tf_ssb_connection_client_send_hello(tf_ssb_connection_t* connection
connection->state = k_tf_ssb_state_sent_hello; connection->state = k_tf_ssb_state_sent_hello;
} }
static bool _tf_ssb_connection_read_start(tf_ssb_connection_t* connection)
{
int result = uv_read_start((uv_stream_t*)&connection->tcp, _tf_ssb_connection_on_tcp_alloc, _tf_ssb_connection_on_tcp_recv);
if (result && result != UV_EALREADY)
{
tf_printf("uv_read_start => %s\n", uv_strerror(result));
_tf_ssb_connection_close(connection, "uv_read_start failed");
return false;
}
return true;
}
static bool _tf_ssb_connection_read_stop(tf_ssb_connection_t* connection)
{
int result = uv_read_stop((uv_stream_t*)&connection->tcp);
if (result && result != UV_EALREADY)
{
tf_printf("uv_read_stop => %s\n", uv_strerror(result));
_tf_ssb_connection_close(connection, "uv_read_stop failed");
return false;
}
return true;
}
static void _tf_ssb_connection_on_connect(uv_connect_t* connect, int status) static void _tf_ssb_connection_on_connect(uv_connect_t* connect, int status)
{ {
tf_ssb_connection_t* connection = connect->data; tf_ssb_connection_t* connection = connect->data;
@@ -2068,13 +2095,7 @@ static void _tf_ssb_connection_on_connect(uv_connect_t* connect, int status)
if (status == 0) if (status == 0)
{ {
connection->state = k_tf_ssb_state_connected; connection->state = k_tf_ssb_state_connected;
int result = uv_read_start(connect->handle, _tf_ssb_connection_on_tcp_alloc, _tf_ssb_connection_on_tcp_recv); if (_tf_ssb_connection_read_start(connection))
if (result)
{
tf_printf("uv_read_start => %s\n", uv_strerror(status));
_tf_ssb_connection_close(connection, "uv_read_start failed");
}
else
{ {
_tf_ssb_connection_client_send_hello(connection); _tf_ssb_connection_client_send_hello(connection);
} }
@@ -2565,6 +2586,11 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
} }
} }
bool tf_ssb_is_shutting_down(tf_ssb_t* ssb)
{
return ssb->shutting_down;
}
void tf_ssb_run(tf_ssb_t* ssb) void tf_ssb_run(tf_ssb_t* ssb)
{ {
uv_run(ssb->loop, UV_RUN_DEFAULT); uv_run(ssb->loop, UV_RUN_DEFAULT);
@@ -2722,22 +2748,30 @@ typedef struct _connect_t
static void _tf_on_connect_getaddrinfo(uv_getaddrinfo_t* addrinfo, int result, struct addrinfo* info) static void _tf_on_connect_getaddrinfo(uv_getaddrinfo_t* addrinfo, int result, struct addrinfo* info)
{ {
connect_t* connect = addrinfo->data; connect_t* connect = addrinfo->data;
if (result == 0 && info) if (!connect->ssb->shutting_down)
{ {
struct sockaddr_in addr = *(struct sockaddr_in*)info->ai_addr; if (result == 0 && info)
addr.sin_port = htons(connect->port); {
tf_ssb_connection_create(connect->ssb, connect->host, &addr, connect->key); struct sockaddr_in addr = *(struct sockaddr_in*)info->ai_addr;
addr.sin_port = htons(connect->port);
tf_ssb_connection_create(connect->ssb, connect->host, &addr, connect->key);
}
else
{
tf_printf("getaddrinfo(%s) => %s\n", connect->host, uv_strerror(result));
}
} }
else
{
tf_printf("getaddrinfo(%s) => %s\n", connect->host, uv_strerror(result));
}
tf_free(connect);
uv_freeaddrinfo(info); uv_freeaddrinfo(info);
tf_ssb_unref(connect->ssb);
tf_free(connect);
} }
void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* key) void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* key)
{ {
if (ssb->shutting_down)
{
return;
}
connect_t* connect = tf_malloc(sizeof(connect_t)); connect_t* connect = tf_malloc(sizeof(connect_t));
*connect = (connect_t) { *connect = (connect_t) {
.ssb = ssb, .ssb = ssb,
@@ -2749,11 +2783,13 @@ void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* ke
tf_ssb_connections_store(ssb->connections_tracker, host, port, id); tf_ssb_connections_store(ssb->connections_tracker, host, port, id);
snprintf(connect->host, sizeof(connect->host), "%s", host); snprintf(connect->host, sizeof(connect->host), "%s", host);
memcpy(connect->key, key, k_id_bin_len); memcpy(connect->key, key, k_id_bin_len);
tf_ssb_ref(ssb);
int r = uv_getaddrinfo(ssb->loop, &connect->req, _tf_on_connect_getaddrinfo, host, NULL, &(struct addrinfo) { .ai_family = AF_INET }); int r = uv_getaddrinfo(ssb->loop, &connect->req, _tf_on_connect_getaddrinfo, host, NULL, &(struct addrinfo) { .ai_family = AF_INET });
if (r < 0) if (r < 0)
{ {
tf_printf("uv_getaddrinfo: %s\n", uv_strerror(r)); tf_printf("uv_getaddrinfo: %s\n", uv_strerror(r));
tf_free(connect); tf_free(connect);
tf_ssb_unref(ssb);
} }
} }
@@ -2810,7 +2846,7 @@ static void _tf_ssb_on_connection(uv_stream_t* stream, int status)
_tf_ssb_notify_connections_changed(ssb, k_tf_ssb_change_create, connection); _tf_ssb_notify_connections_changed(ssb, k_tf_ssb_change_create, connection);
connection->state = k_tf_ssb_state_server_wait_hello; connection->state = k_tf_ssb_state_server_wait_hello;
uv_read_start((uv_stream_t*)&connection->tcp, _tf_ssb_connection_on_tcp_alloc, _tf_ssb_connection_on_tcp_recv); _tf_ssb_connection_read_start(connection);
} }
static void _tf_ssb_send_broadcast(tf_ssb_t* ssb, struct sockaddr_in* address, struct sockaddr_in* netmask) static void _tf_ssb_send_broadcast(tf_ssb_t* ssb, struct sockaddr_in* address, struct sockaddr_in* netmask)
@@ -4049,3 +4085,26 @@ JSValue tf_ssb_connection_requests_to_object(tf_ssb_connection_t* connection)
} }
return object; return object;
} }
void tf_ssb_connection_adjust_read_backpressure(tf_ssb_connection_t* connection, int delta)
{
const int k_threshold = 256;
int old_pressure = connection->read_back_pressure;
connection->read_back_pressure += delta;
if (!connection->closing)
{
if (old_pressure < k_threshold && connection->read_back_pressure >= k_threshold)
{
_tf_ssb_connection_read_stop(connection);
}
else if (old_pressure >= k_threshold && connection->read_back_pressure < k_threshold)
{
_tf_ssb_connection_read_start(connection);
}
}
connection->ref_count += delta;
if (connection->ref_count == 0 && connection->closing)
{
_tf_ssb_connection_destroy(connection, "backpressure released");
}
}

View File

@@ -86,6 +86,10 @@ typedef struct _tf_ssb_connections_get_next_t
static void _tf_ssb_connections_get_next_work(tf_ssb_t* ssb, void* user_data) static void _tf_ssb_connections_get_next_work(tf_ssb_t* ssb, void* user_data)
{ {
tf_ssb_connections_get_next_t* next = user_data; tf_ssb_connections_get_next_t* next = user_data;
if (tf_ssb_is_shutting_down(ssb))
{
return;
}
next->ready = _tf_ssb_connections_get_next_connection(next->connections, next->host, sizeof(next->host), &next->port, next->key, sizeof(next->key)); next->ready = _tf_ssb_connections_get_next_connection(next->connections, next->host, sizeof(next->host), &next->port, next->key, sizeof(next->key));
} }
@@ -159,6 +163,10 @@ typedef struct _tf_ssb_connections_update_t
static void _tf_ssb_connections_update_work(tf_ssb_t* ssb, void* user_data) static void _tf_ssb_connections_update_work(tf_ssb_t* ssb, void* user_data)
{ {
tf_ssb_connections_update_t* update = user_data; tf_ssb_connections_update_t* update = user_data;
if (tf_ssb_is_shutting_down(ssb))
{
return;
}
sqlite3_stmt* statement; sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (update->attempted) if (update->attempted)

View File

@@ -144,6 +144,13 @@ tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path
*/ */
void tf_ssb_destroy(tf_ssb_t* ssb); void tf_ssb_destroy(tf_ssb_t* ssb);
/**
** Checking if the SSB instance is in the process of shutting down.
** @param ssb The SSB instance.
** @return true If the SSB instance is shutting down.
*/
bool tf_ssb_is_shutting_down(tf_ssb_t* ssb);
/** /**
** Start optional periodic work. ** Start optional periodic work.
** @param ssb The SSB instance. ** @param ssb The SSB instance.
@@ -989,4 +996,13 @@ void tf_ssb_schedule_work(tf_ssb_t* ssb, int delay_ms, void (*callback)(tf_ssb_t
*/ */
bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_t payload_length, const char* signature, bool signature_is_urlb64); bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_t payload_length, const char* signature, bool signature_is_urlb64);
/**
** Adjust read backpressure. If it gets too high, TCP receive will be paused
** until it lowers.
** @param connection The connection on which to affect backpressure.
** @param delta The change in backpressure. Higher will eventually pause
** receive. Lower will resume it.
*/
void tf_ssb_connection_adjust_read_backpressure(tf_ssb_connection_t* connection, int delta);
/** @} */ /** @} */

View File

@@ -1889,7 +1889,7 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
JS_SetPropertyStr(context, object, "storeMessage", JS_NewCFunction(context, _tf_ssb_storeMessage, "storeMessage", 1)); JS_SetPropertyStr(context, object, "storeMessage", JS_NewCFunction(context, _tf_ssb_storeMessage, "storeMessage", 1));
JS_SetPropertyStr(context, object, "blobStore", JS_NewCFunction(context, _tf_ssb_blobStore, "blobStore", 1)); JS_SetPropertyStr(context, object, "blobStore", JS_NewCFunction(context, _tf_ssb_blobStore, "blobStore", 1));
/* Should be trusted only. */ /* Trusted only. */
JS_SetPropertyStr(context, object, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2)); JS_SetPropertyStr(context, object, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2));
JS_SetPropertyStr(context, object, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2)); JS_SetPropertyStr(context, object, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2));

View File

@@ -404,6 +404,7 @@ typedef struct _blobs_get_t
bool done; bool done;
bool storing; bool storing;
tf_ssb_t* ssb; tf_ssb_t* ssb;
tf_ssb_connection_t* connection;
uint8_t buffer[]; uint8_t buffer[];
} blobs_get_t; } blobs_get_t;
@@ -411,6 +412,7 @@ static void _tf_ssb_rpc_blob_store_callback(const char* id, bool is_new, void* u
{ {
blobs_get_t* get = user_data; blobs_get_t* get = user_data;
get->storing = false; get->storing = false;
tf_ssb_connection_adjust_read_backpressure(get->connection, -1);
if (get->done) if (get->done)
{ {
tf_free(get); tf_free(get);
@@ -433,6 +435,7 @@ static void _tf_ssb_rpc_connection_blobs_get_callback(
if (JS_ToBool(context, args)) if (JS_ToBool(context, args))
{ {
get->storing = true; get->storing = true;
tf_ssb_connection_adjust_read_backpressure(connection, 1);
tf_ssb_db_blob_store_async(ssb, get->buffer, get->received, _tf_ssb_rpc_blob_store_callback, get); tf_ssb_db_blob_store_async(ssb, get->buffer, get->received, _tf_ssb_rpc_blob_store_callback, get);
} }
/* TODO: Should we send the response in the callback? */ /* TODO: Should we send the response in the callback? */
@@ -455,7 +458,7 @@ static void _tf_ssb_rpc_connection_blobs_get_cleanup(tf_ssb_t* ssb, void* user_d
static void _tf_ssb_rpc_connection_blobs_get(tf_ssb_connection_t* connection, const char* blob_id, size_t size) static void _tf_ssb_rpc_connection_blobs_get(tf_ssb_connection_t* connection, const char* blob_id, size_t size)
{ {
blobs_get_t* get = tf_malloc(sizeof(blobs_get_t) + size); blobs_get_t* get = tf_malloc(sizeof(blobs_get_t) + size);
*get = (blobs_get_t) { .ssb = tf_ssb_connection_get_ssb(connection), .expected_size = size }; *get = (blobs_get_t) { .ssb = tf_ssb_connection_get_ssb(connection), .connection = connection, .expected_size = size };
snprintf(get->id, sizeof(get->id), "%s", blob_id); snprintf(get->id, sizeof(get->id), "%s", blob_id);
memset(get->buffer, 0, size); memset(get->buffer, 0, size);
@@ -1000,6 +1003,12 @@ static void _tf_ssb_rpc_ebt_replicate_send_messages(tf_ssb_connection_t* connect
} }
} }
static void _tf_ssb_rpc_ebt_replicate_store_callback(const char* id, bool verified, bool is_new, void* user_data)
{
tf_ssb_connection_t* connection = user_data;
tf_ssb_connection_adjust_read_backpressure(connection, -1);
}
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);
@@ -1022,7 +1031,8 @@ static void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t f
if (!JS_IsUndefined(author)) if (!JS_IsUndefined(author))
{ {
/* Looks like a message. */ /* Looks like a message. */
tf_ssb_verify_strip_and_store_message(ssb, args, NULL, NULL); tf_ssb_connection_adjust_read_backpressure(connection, 1);
tf_ssb_verify_strip_and_store_message(ssb, args, _tf_ssb_rpc_ebt_replicate_store_callback, connection);
} }
else else
{ {