Compare commits
19 Commits
fdroid-0.2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cb7947454 | |||
| 6a2f8cfb9b | |||
| 7962d6222e | |||
| 0feca811f0 | |||
| 08d124d5a0 | |||
| 801668f23f | |||
| 28723ee1d4 | |||
| c173f06c5b | |||
| c3bd805efc | |||
| 79b3df3e64 | |||
| 51e562b8db | |||
| 876abb169a | |||
| 4cbc08eb35 | |||
| f785e7dceb | |||
| f57643d7bc | |||
| c7c62c72e4 | |||
| 4ba4acbd41 | |||
| f7dcd63ae9 | |||
| 45d317c716 |
2
Doxyfile
2
Doxyfile
@@ -1066,7 +1066,7 @@ FILTER_SOURCE_PATTERNS =
|
||||
# (index.html). This can be useful if you have a project on for instance GitHub
|
||||
# and want to reuse the introduction page also for the doxygen output.
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE = README.md
|
||||
USE_MDFILE_AS_MAINPAGE = docs/index.md
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to source browsing
|
||||
|
||||
24
GNUmakefile
24
GNUmakefile
@@ -16,8 +16,8 @@ MAKEFLAGS += --no-builtin-rules
|
||||
## LD := Linker.
|
||||
## ANDROID_SDK := Path to the Android SDK.
|
||||
|
||||
VERSION_CODE := 50
|
||||
VERSION_NUMBER := 0.2025.12.1
|
||||
VERSION_CODE := 51
|
||||
VERSION_NUMBER := 0.2026.1-wip
|
||||
VERSION_NAME := This program kills fascists.
|
||||
|
||||
IPHONEOS_VERSION_MIN=14.5
|
||||
@@ -89,6 +89,7 @@ CFLAGS += \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-cast-function-type-mismatch \
|
||||
-Wno-format-truncation \
|
||||
-Wno-unknown-warning-option \
|
||||
-Wno-unused-parameter \
|
||||
-MMD \
|
||||
@@ -308,14 +309,14 @@ ifeq ($(UNAME_S),Linux)
|
||||
all: appimage out/release/tildefriends.standalone
|
||||
endif
|
||||
ifneq ($(UNAME_S),Haiku)
|
||||
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
out/debug/tildefriends: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
out/debug/tildefriends out/debug/sqlite3: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
out/debug/tildefriends out/debug/sqlite3: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_M),aarch64)
|
||||
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
out/debug/tildefriends: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
out/debug/tildefriends out/debug/sqlite3: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
|
||||
out/debug/tildefriends out/debug/sqlite3: LDFLAGS += -fsanitize=address -fsanitize=undefined
|
||||
endif
|
||||
|
||||
get_objs = \
|
||||
@@ -673,9 +674,10 @@ $(filter-out $(BUILD_DIR)/win%,$(SODIUM_OBJS)): CFLAGS += \
|
||||
-DHAVE_ALLOCA_H
|
||||
endif
|
||||
|
||||
SQLITE_ALL_SOURCES := deps/sqlite/sqlite3.c deps/sqlite/shell.c
|
||||
SQLITE_SOURCES := deps/sqlite/sqlite3.c
|
||||
SQLITE_OBJS := $(call get_objs,SQLITE_SOURCES)
|
||||
$(SQLITE_OBJS): CFLAGS += \
|
||||
$(call get_objs,SQLITE_ALL_SOURCES): CFLAGS += \
|
||||
-DSQLITE_DBCONFIG_DEFAULT_DEFENSIVE \
|
||||
-DSQLITE_DEFAULT_MEMSTATUS=0 \
|
||||
-DSQLITE_DQS=0 \
|
||||
@@ -822,7 +824,7 @@ $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
##
|
||||
## Common targets:
|
||||
##
|
||||
all: $(BUILD_TYPES) ## Build all targets that appear possible to build on this machine.
|
||||
all: $(BUILD_TYPES) out/debug/sqlite3 out/release/sqlite3 ## Build all targets that appear possible to build on this machine.
|
||||
debug: ## Build a debug executable for the current host platform.
|
||||
release: ## Build a release executable for the current host platform.
|
||||
armdebug: ## Cross-compile aarch64 debug on Linux.
|
||||
@@ -870,6 +872,10 @@ $(BUILD_DIR)/$(1)/%.o: %.S
|
||||
@$$(AS) -c $$< -o $$@
|
||||
endef
|
||||
|
||||
out/%/sqlite3 : out/%/deps/sqlite/sqlite3.o out/%/deps/sqlite/shell.o
|
||||
@echo "[link] $@"
|
||||
@$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
$(foreach build_type,$(BUILD_TYPES),$(eval $(call build_rules,$(build_type))))
|
||||
|
||||
src/version.h : $(firstword $(MAKEFILE_LIST))
|
||||
@@ -1387,7 +1393,7 @@ help: ## Display this help message.
|
||||
|
||||
docs: debug
|
||||
docs: ## Build HTML docs.
|
||||
@echo '# CLI Usage\n' > docs/usage.md
|
||||
@echo '@page usage CLI Usage\n' > docs/usage.md
|
||||
@echo "## tildefriends -h" >> docs/usage.md
|
||||
@echo '\n```' >> docs/usage.md
|
||||
@out/debug/tildefriends -h >> docs/usage.md
|
||||
|
||||
@@ -68,3 +68,5 @@ Docs live here: <https://docs.tildefriends.net/>.
|
||||
|
||||
All code unless otherwise noted in is provided under the
|
||||
[MIT](https://opensource.org/licenses/MIT) license.
|
||||
|
||||
<!-- @page readme README -->
|
||||
|
||||
2
apps/blog/commonmark.min.js
vendored
2
apps/blog/commonmark.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -8,11 +8,13 @@ async function main() {
|
||||
let message = await blog.get_blog_message(id);
|
||||
if (message) {
|
||||
respond({
|
||||
status_code: 200,
|
||||
data: await blog.render_blog_post_html(message),
|
||||
content_type: 'text/html; charset=utf-8',
|
||||
});
|
||||
} else {
|
||||
respond({
|
||||
status_code: 404,
|
||||
data: `Message ${id} not found.`,
|
||||
content_type: 'text/html; charset=utf-8',
|
||||
});
|
||||
@@ -20,6 +22,7 @@ async function main() {
|
||||
} else if (request.path == 'atom') {
|
||||
let blogs = await blog.get_posts();
|
||||
respond({
|
||||
status_code: 200,
|
||||
data: blog.render_atom(blogs),
|
||||
content_type: 'application/atom+xml',
|
||||
});
|
||||
@@ -29,6 +32,7 @@ async function main() {
|
||||
let title = (blog_post.title || '').replaceAll(/\W/g, '_').toLowerCase();
|
||||
if (request.path === title) {
|
||||
respond({
|
||||
status_code: 200,
|
||||
data: await blog.render_blog_post_html(blog_post),
|
||||
content_type: 'text/html; charset=utf-8',
|
||||
});
|
||||
@@ -36,6 +40,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
respond({
|
||||
status_code: 200,
|
||||
data: blog.render_html(blogs),
|
||||
content_type: 'text/html; charset=utf-8',
|
||||
});
|
||||
@@ -44,6 +49,7 @@ async function main() {
|
||||
|
||||
main().catch(function (error) {
|
||||
respond({
|
||||
status_code: 500,
|
||||
data: `<!DOCTYPE html>
|
||||
<pre style="color: #f00">${error.message}\n${error.stack}</pre>`,
|
||||
content_type: 'text/html',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🦀",
|
||||
"previous": "&PrgfYYKpL2tedApJXokIIk+h9/yRYo0aUliNF3QsnZ4=.sha256"
|
||||
"previous": "&S3M7rCH63nICYyuXtMz1WeabausZwmVktM85MroB8QU=.sha256"
|
||||
}
|
||||
|
||||
@@ -69,6 +69,27 @@ class TfComposeElement extends LitElement {
|
||||
: name;
|
||||
updated = true;
|
||||
}
|
||||
let stale = Object.fromEntries(
|
||||
Object.entries(draft.mentions ?? {}).filter(([k, v]) => k.startsWith('#'))
|
||||
);
|
||||
for (let match of text.matchAll(/(?:(?<!\w)#[\w-]+)/g)) {
|
||||
if (!draft.mentions) {
|
||||
draft.mentions = {};
|
||||
}
|
||||
let tag = match[0];
|
||||
delete stale[tag];
|
||||
if (!draft.mentions[tag]) {
|
||||
draft.mentions[tag] = {
|
||||
link: tag,
|
||||
};
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
for (let tag of Object.keys(stale)) {
|
||||
delete draft.mentions[tag];
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
setTimeout(() => this.notify(draft), 0);
|
||||
}
|
||||
@@ -77,12 +98,16 @@ class TfComposeElement extends LitElement {
|
||||
|
||||
input(event) {
|
||||
let edit = this.renderRoot.getElementById('edit');
|
||||
let preview = this.renderRoot.getElementById('preview');
|
||||
preview.innerHTML = this.process_text(edit.innerText);
|
||||
let edit_title = this.renderRoot.getElementById('edit_title');
|
||||
let edit_summary = this.renderRoot.getElementById('edit_summary');
|
||||
let content_warning = this.renderRoot.getElementById('content_warning');
|
||||
let draft = this.get_draft();
|
||||
draft.text = edit.innerText;
|
||||
draft.content_warning = content_warning?.value;
|
||||
if (draft.type == 'blog') {
|
||||
draft.title = edit_title.value;
|
||||
draft.summary = edit_summary.value;
|
||||
}
|
||||
setTimeout(() => this.notify(draft), 0);
|
||||
}
|
||||
|
||||
@@ -130,32 +155,36 @@ class TfComposeElement extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
async file_to_blob_id(file) {
|
||||
let self = this;
|
||||
let buffer = await file.arrayBuffer();
|
||||
let type = file.type;
|
||||
if (type.startsWith('image/')) {
|
||||
let best_buffer;
|
||||
let best_type;
|
||||
for (let format of ['image/png', 'image/jpeg', 'image/webp']) {
|
||||
let test_buffer = await self.convert_to_format(
|
||||
buffer,
|
||||
file.type,
|
||||
format
|
||||
);
|
||||
if (!best_buffer || test_buffer.length < best_buffer.length) {
|
||||
best_buffer = test_buffer;
|
||||
best_type = format;
|
||||
}
|
||||
}
|
||||
buffer = best_buffer;
|
||||
type = best_type;
|
||||
} else {
|
||||
buffer = Array.from(new Uint8Array(buffer));
|
||||
}
|
||||
return {id: await tfrpc.rpc.store_blob(buffer), type: type, buffer, buffer};
|
||||
}
|
||||
|
||||
async add_file(file) {
|
||||
try {
|
||||
let draft = this.get_draft();
|
||||
let self = this;
|
||||
let buffer = await file.arrayBuffer();
|
||||
let type = file.type;
|
||||
if (type.startsWith('image/')) {
|
||||
let best_buffer;
|
||||
let best_type;
|
||||
for (let format of ['image/png', 'image/jpeg', 'image/webp']) {
|
||||
let test_buffer = await self.convert_to_format(
|
||||
buffer,
|
||||
file.type,
|
||||
format
|
||||
);
|
||||
if (!best_buffer || test_buffer.length < best_buffer.length) {
|
||||
best_buffer = test_buffer;
|
||||
best_type = format;
|
||||
}
|
||||
}
|
||||
buffer = best_buffer;
|
||||
type = best_type;
|
||||
} else {
|
||||
buffer = Array.from(new Uint8Array(buffer));
|
||||
}
|
||||
let id = await tfrpc.rpc.store_blob(buffer);
|
||||
let {id, type, buffer} = await this.file_to_blob_id(file);
|
||||
let name = type.split('/')[0] + ':' + file.name;
|
||||
if (!draft.mentions) {
|
||||
draft.mentions = {};
|
||||
@@ -166,11 +195,10 @@ class TfComposeElement extends LitElement {
|
||||
type: type,
|
||||
size: buffer.length ?? buffer.byteLength,
|
||||
};
|
||||
let edit = self.renderRoot.getElementById('edit');
|
||||
edit.innerText += `\n`;
|
||||
self.input();
|
||||
draft.text = (draft.text ?? '') + `\n`;
|
||||
this.notify(draft);
|
||||
} catch (e) {
|
||||
alert(e?.message);
|
||||
alert(e?.message + e?.stack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,55 +226,74 @@ class TfComposeElement extends LitElement {
|
||||
async submit() {
|
||||
let self = this;
|
||||
let draft = this.get_draft();
|
||||
let edit = this.renderRoot.getElementById('edit');
|
||||
let message = {
|
||||
type: 'post',
|
||||
text: edit.innerText,
|
||||
channel: this.channel,
|
||||
};
|
||||
if (this.root || this.branch) {
|
||||
message.root = this.new_thread ? (this.branch ?? this.root) : this.root;
|
||||
message.branch = this.branch;
|
||||
}
|
||||
let reply = Object.fromEntries(
|
||||
(
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.id, messages.author FROM messages
|
||||
JOIN json_each(?) AS refs ON messages.id = refs.value
|
||||
`,
|
||||
[JSON.stringify([this.root, this.branch])]
|
||||
)
|
||||
).map((row) => [row.id, row.author])
|
||||
);
|
||||
if (Object.keys(reply).length) {
|
||||
message.reply = reply;
|
||||
}
|
||||
if (Object.values(draft.mentions || {}).length) {
|
||||
message.mentions = Object.values(draft.mentions);
|
||||
}
|
||||
if (draft.content_warning !== undefined) {
|
||||
message.contentWarning = draft.content_warning;
|
||||
}
|
||||
console.log('Would post:', message);
|
||||
if (draft.encrypt_to) {
|
||||
let to = new Set(draft.encrypt_to);
|
||||
to.add(this.whoami);
|
||||
to = [...to];
|
||||
message.recps = to;
|
||||
console.log('message is now', message);
|
||||
message = await tfrpc.rpc.encrypt(
|
||||
this.whoami,
|
||||
to,
|
||||
JSON.stringify(message)
|
||||
if (draft.type == 'blog') {
|
||||
let message = {
|
||||
type: 'blog',
|
||||
title: draft.title,
|
||||
summary: draft.summary,
|
||||
thumbnail: draft.thumbnail,
|
||||
blog: await tfrpc.rpc.store_blob(draft.text),
|
||||
};
|
||||
if (Object.values(draft.mentions || {}).length) {
|
||||
message.mentions = Object.values(draft.mentions);
|
||||
}
|
||||
console.log('Would post:', message);
|
||||
try {
|
||||
await tfrpc.rpc.appendMessage(this.whoami, message);
|
||||
self.notify(undefined);
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
}
|
||||
} else {
|
||||
let message = {
|
||||
type: 'post',
|
||||
text: draft.text,
|
||||
channel: this.channel,
|
||||
};
|
||||
if (this.root || this.branch) {
|
||||
message.root = this.new_thread ? (this.branch ?? this.root) : this.root;
|
||||
message.branch = this.branch;
|
||||
}
|
||||
let reply = Object.fromEntries(
|
||||
(
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.id, messages.author FROM messages
|
||||
JOIN json_each(?) AS refs ON messages.id = refs.value
|
||||
`,
|
||||
[JSON.stringify([this.root, this.branch])]
|
||||
)
|
||||
).map((row) => [row.id, row.author])
|
||||
);
|
||||
console.log('encrypted as', message);
|
||||
}
|
||||
try {
|
||||
await tfrpc.rpc.appendMessage(this.whoami, message);
|
||||
self.notify(undefined);
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
if (Object.keys(reply).length) {
|
||||
message.reply = reply;
|
||||
}
|
||||
if (Object.values(draft.mentions || {}).length) {
|
||||
message.mentions = Object.values(draft.mentions);
|
||||
}
|
||||
if (draft.content_warning !== undefined) {
|
||||
message.contentWarning = draft.content_warning;
|
||||
}
|
||||
console.log('Would post:', message);
|
||||
if (draft.encrypt_to) {
|
||||
let to = new Set(draft.encrypt_to);
|
||||
to.add(this.whoami);
|
||||
to = [...to];
|
||||
message.recps = to;
|
||||
console.log('message is now', message);
|
||||
message = await tfrpc.rpc.encrypt(
|
||||
this.whoami,
|
||||
to,
|
||||
JSON.stringify(message)
|
||||
);
|
||||
console.log('encrypted as', message);
|
||||
}
|
||||
try {
|
||||
await tfrpc.rpc.appendMessage(this.whoami, message);
|
||||
self.notify(undefined);
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,8 +391,7 @@ class TfComposeElement extends LitElement {
|
||||
super.updated();
|
||||
let edit = this.renderRoot.getElementById('edit');
|
||||
if (this.last_updated_text !== edit.innerText) {
|
||||
let preview = this.renderRoot.getElementById('preview');
|
||||
preview.innerHTML = this.process_text(edit.innerText);
|
||||
this.process_text(edit.innerText);
|
||||
this.last_updated_text = edit.innerText;
|
||||
}
|
||||
this._tribute.collection[0].values = this.get_values();
|
||||
@@ -584,6 +630,100 @@ class TfComposeElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
can_be_blog() {
|
||||
let draft = this.get_draft();
|
||||
return (
|
||||
this.root === undefined &&
|
||||
this.branch === undefined &&
|
||||
draft.encrypt_to === undefined
|
||||
);
|
||||
}
|
||||
|
||||
set_type(type) {
|
||||
let draft = this.get_draft();
|
||||
draft.type = type;
|
||||
this.notify(draft);
|
||||
}
|
||||
|
||||
render_post_form(draft) {
|
||||
return html`
|
||||
<style>
|
||||
.w3-input:empty::before {
|
||||
content: attr(placeholder);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.w3-input:empty:focus::before {
|
||||
content: '';
|
||||
}
|
||||
</style>
|
||||
<span
|
||||
class="w3-input w3-theme-d1 w3-border"
|
||||
style="resize: vertical; width: 100%; white-space: pre-wrap"
|
||||
placeholder="Write a post here."
|
||||
id="edit"
|
||||
@input=${this.input}
|
||||
@paste=${this.paste}
|
||||
contenteditable="plaintext-only"
|
||||
.innerText=${live(draft.text ?? '')}
|
||||
></span>
|
||||
`;
|
||||
}
|
||||
|
||||
blog_set_image() {
|
||||
let self = this;
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.addEventListener('change', async function (event) {
|
||||
input.parentNode.removeChild(input);
|
||||
let file = event.target.files[0];
|
||||
let {id} = await self.file_to_blob_id(file);
|
||||
let draft = self.get_draft();
|
||||
draft.thumbnail = id;
|
||||
self.notify(draft);
|
||||
});
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
}
|
||||
|
||||
blog_clear_image() {
|
||||
let draft = this.get_draft();
|
||||
delete draft.thumbnail;
|
||||
this.notify(draft);
|
||||
}
|
||||
|
||||
render_blog_form(draft) {
|
||||
return html`
|
||||
<style>
|
||||
.w3-input:empty::before {
|
||||
content: attr(placeholder);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.w3-input:empty:focus::before {
|
||||
content: '';
|
||||
}
|
||||
</style>
|
||||
<label for="edit_title">Title</label>
|
||||
<input class="w3-input w3-theme-d1" type="text" placeholder="Enter blog post title." id="edit_title" @input=${this.input} value=${live(draft.title)}></input>
|
||||
<label for="edit_summary">Summary</label>
|
||||
<textarea class="w3-input w3-theme-d1" placeholder="Enter blog post summary." id="edit_summary" @input=${this.input}>${draft.summary}</textarea>
|
||||
<label for="edit">Post</label>
|
||||
<span
|
||||
class="w3-input w3-theme-d1"
|
||||
style="resize: vertical; width: 100%; white-space: pre-wrap"
|
||||
placeholder="Write blog post here."
|
||||
id="edit"
|
||||
@input=${this.input}
|
||||
@paste=${this.paste}
|
||||
contenteditable="plaintext-only"
|
||||
.innerText=${live(draft.text ?? '')}
|
||||
></span>
|
||||
<div class="w3-margin-top">
|
||||
<button class="w3-button w3-theme-d1" @click=${this.blog_set_image}>Set Banner Image</button>
|
||||
${draft.thumbnail ? html`<button class="w3-button w3-theme-d1" @click=${this.blog_clear_image}>Remove Banner Image</button>` : undefined}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
let draft = self.get_draft();
|
||||
@@ -606,14 +746,6 @@ class TfComposeElement extends LitElement {
|
||||
<style>
|
||||
${generate_theme()}
|
||||
</style>
|
||||
<style>
|
||||
.w3-input:empty::before {
|
||||
content: attr(placeholder);
|
||||
}
|
||||
.w3-input:empty:focus::before {
|
||||
content: '';
|
||||
}
|
||||
</style>
|
||||
<div
|
||||
class="w3-card-4 w3-theme-d4 w3-padding w3-margin-top w3-margin-bottom"
|
||||
style="box-sizing: border-box"
|
||||
@@ -626,20 +758,26 @@ class TfComposeElement extends LitElement {
|
||||
</header>
|
||||
<div class="w3-container" style="padding: 0 0 16px 0">
|
||||
<div class="w3-half">
|
||||
<span
|
||||
class="w3-input w3-theme-d1 w3-border"
|
||||
style="resize: vertical; width: 100%; white-space: pre-wrap"
|
||||
placeholder="Write a post here."
|
||||
id="edit"
|
||||
@input=${this.input}
|
||||
@paste=${this.paste}
|
||||
contenteditable="plaintext-only"
|
||||
.innerText=${live(draft.text ?? '')}
|
||||
></span>
|
||||
${draft.type === undefined || draft.type === 'post'
|
||||
? this.render_post_form(draft)
|
||||
: this.render_blog_form(draft)}
|
||||
</div>
|
||||
<div class="w3-half w3-container">
|
||||
${content_warning}
|
||||
<p id="preview"></p>
|
||||
${draft.type === 'blog' && draft.title
|
||||
? html`<h3>${unsafeHTML(tfutils.markdown(draft.title))}</h3>`
|
||||
: undefined}
|
||||
${draft.type === 'blog' && draft.summary
|
||||
? html`<div class="w3-panel w3-theme-d1">
|
||||
${unsafeHTML(tfutils.markdown(draft.summary))}
|
||||
</div>`
|
||||
: undefined}
|
||||
${draft.type === 'blog' && draft.thumbnail
|
||||
? html`<img src=${'/' + draft.thumbnail + '/view'} />`
|
||||
: undefined}
|
||||
<p id="preview">
|
||||
${unsafeHTML(tfutils.markdown(draft.text ?? ''))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
${Object.values(draft.mentions || {}).map((x) =>
|
||||
@@ -683,6 +821,23 @@ class TfComposeElement extends LitElement {
|
||||
>
|
||||
Attach
|
||||
</button>
|
||||
${(draft.type === undefined || draft.type === 'post') &&
|
||||
this.can_be_blog()
|
||||
? html` <button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => this.set_type('blog')}
|
||||
>
|
||||
Make Blog Post
|
||||
</button>`
|
||||
: undefined}
|
||||
${draft.type === 'blog'
|
||||
? html` <button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
@click=${() => this.set_type('post')}
|
||||
>
|
||||
Make Post
|
||||
</button>`
|
||||
: undefined}
|
||||
${this.render_attach_app_button()} ${encrypt}
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-theme-d1"
|
||||
|
||||
@@ -1008,9 +1008,10 @@ class TfMessageElement extends LitElement {
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
</div>
|
||||
${this.render_menu()} ${this.render_votes()}
|
||||
${this.render_refs()} ${this.render_actions()}
|
||||
${this.render_menu()}
|
||||
</div>
|
||||
${this.render_votes()} ${this.render_refs()}
|
||||
${this.render_actions()}
|
||||
`);
|
||||
break;
|
||||
case 'raw':
|
||||
|
||||
@@ -22,34 +22,38 @@ async function main() {
|
||||
let path = request.path.replaceAll(/(%[0-9a-fA-F]{2})/g, (x) =>
|
||||
String.fromCharCode(parseInt(x.substring(1), 16))
|
||||
);
|
||||
let match = path.match(/^(%.{44}\.sha256)(?:\/)?(.*)$/);
|
||||
|
||||
let content_type = guess_content_type(request.path);
|
||||
let root = await query(
|
||||
`
|
||||
SELECT root.content ->> 'root' AS root
|
||||
FROM messages site
|
||||
JOIN messages root
|
||||
ON site.id = ? AND root.author = site.author AND root.content ->> 'site' = site.id
|
||||
ORDER BY root.sequence DESC LIMIT 1
|
||||
`,
|
||||
[match[1]]
|
||||
);
|
||||
let root_id = root[0]['root'];
|
||||
let last_id = root_id;
|
||||
let blob = await ssb.blobGet(root_id);
|
||||
try {
|
||||
for (let part of match[2]?.split('/')) {
|
||||
let dir = JSON.parse(utf8Decode(blob));
|
||||
last_id = dir?.links[part];
|
||||
blob = await ssb.blobGet(dir?.links[part]);
|
||||
content_type = guess_content_type(part);
|
||||
}
|
||||
} catch {}
|
||||
let blob;
|
||||
let last_id;
|
||||
|
||||
let match = path.match(/^(%.{44}\.sha256)(?:\/)?(.*)$/);
|
||||
if (match) {
|
||||
let root = await query(
|
||||
`
|
||||
SELECT root.content ->> 'root' AS root
|
||||
FROM messages site
|
||||
JOIN messages root
|
||||
ON site.id = ? AND root.author = site.author AND root.content ->> 'site' = site.id
|
||||
ORDER BY root.sequence DESC LIMIT 1
|
||||
`,
|
||||
[match[1]]
|
||||
);
|
||||
let root_id = root[0]?.root;
|
||||
last_id = root_id;
|
||||
blob = await ssb.blobGet(root_id);
|
||||
try {
|
||||
for (let part of match[2]?.split('/')) {
|
||||
let dir = JSON.parse(utf8Decode(blob));
|
||||
last_id = dir?.links[part];
|
||||
blob = await ssb.blobGet(dir?.links[part]);
|
||||
content_type = guess_content_type(part);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
respond({
|
||||
status_code: 200,
|
||||
data: blob ? utf8Decode(blob) : `${last_id} not found`,
|
||||
status_code: blob ? 200 : 404,
|
||||
data: blob ? utf8Decode(blob) : `${last_id ?? request.path} not found`,
|
||||
content_type: content_type,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "👋",
|
||||
"previous": "&sVSmI40DUgnS4TUa2AiKrlNj+qN3WDeXII3364OSMIo=.sha256"
|
||||
"previous": "&8rKGBsPducu7WpnOVm9b5A3OZk5hFVI9RsxeFS9c0lk=.sha256"
|
||||
}
|
||||
|
||||
@@ -143,17 +143,24 @@
|
||||
<h3>Desktop</h3>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-black w3-padding-large"
|
||||
class="w3-button w3-round-large w3-black w3-padding-large w3-margin-top"
|
||||
href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
||||
><i class="fa fa-download"></i> Download</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray"
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
|
||||
>
|
||||
<img src="appimage.svg" style="height: 2em; margin: 0" />
|
||||
Get Linux x86_64 AppImage
|
||||
</a>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
href="https://dev.tildefriends.net/releases/tildefriends-aarch64.AppImage"
|
||||
>
|
||||
<img src="appimage.svg" style="height: 2em; margin: 0" />
|
||||
Get Linux aarch64 AppImage
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
Tilde Friends is distributed as a single executable file (or
|
||||
|
||||
@@ -560,7 +560,7 @@ exports.callAppHandler = async function callAppHandler(
|
||||
});
|
||||
let process;
|
||||
try {
|
||||
process = await getProcessBlob(
|
||||
process = await exports.getProcessBlob(
|
||||
app_blob_id,
|
||||
'handler_' + g_handler_index++,
|
||||
{
|
||||
@@ -598,7 +598,7 @@ exports.callAppHandler = async function callAppHandler(
|
||||
if (typeof answer?.data == 'string') {
|
||||
answer.data = utf8Encode(answer.data);
|
||||
}
|
||||
response.writeHead(answer?.status_code, {
|
||||
response.writeHead(answer?.status_code ?? 500, {
|
||||
'Content-Type': answer?.content_type,
|
||||
'Content-Length': answer?.data?.length,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
}:
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "tildefriends";
|
||||
version = "0.2025.11";
|
||||
version = "0.2025.12";
|
||||
|
||||
src = pkgs.fetchFromGitea {
|
||||
domain = "dev.tildefriends.net";
|
||||
owner = "cory";
|
||||
repo = "tildefriends";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-z4v4ghKOBTMv+agTUKg+HU8zfE4imluXFsozQCT4qX8=";
|
||||
hash = "sha256-SSHsZLo3BVoK34cd+fN3ANEXAlj8cXLGm6EnjdMSFAo=";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
|
||||
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
6
deps/codemirror_src/package-lock.json
generated
vendored
6
deps/codemirror_src/package-lock.json
generated
vendored
@@ -186,9 +186,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.39.7",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.7.tgz",
|
||||
"integrity": "sha512-3Vif9hnNHJnl2YgOtkR/wzGzhYcQ8gy3LGdUhkLUU8xSBbgsTxrE8he/CMTpeINm5TgxLe2FmzvF6IYQL/BSAg==",
|
||||
"version": "6.39.8",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.8.tgz",
|
||||
"integrity": "sha512-1rASYd9Z/mE3tkbC9wInRlCNyCkSn+nLsiQKZhEDUUJiUfs/5FHDpCUDaQpoTIaNGeDc6/bhaEAyLmeEucEFPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
@page app_development App Development
|
||||
|
||||
- @subpage app_development_cheat_sheet
|
||||
- @subpage app_development_guide
|
||||
@@ -1,5 +0,0 @@
|
||||
@page howto How To
|
||||
|
||||
- @subpage upgrading
|
||||
- @subpage transfer_account
|
||||
- @subpage connecting_manyverse
|
||||
21
docs/index.md
Normal file
21
docs/index.md
Normal file
@@ -0,0 +1,21 @@
|
||||
@mainpage Index
|
||||
|
||||
## Overview
|
||||
|
||||
- @subpage readme
|
||||
- @subpage usage
|
||||
- @subpage inspiration
|
||||
- @subpage model
|
||||
- @subpage vision
|
||||
|
||||
## App Development
|
||||
|
||||
- @subpage app_development_cheat_sheet
|
||||
- @subpage app_development_guide
|
||||
|
||||
## HOWTO
|
||||
|
||||
- @subpage upgrading
|
||||
- @subpage transfer_account
|
||||
- @subpage connecting_manyverse
|
||||
- @subpage release_checklist
|
||||
@@ -1,5 +0,0 @@
|
||||
@page overview Overview
|
||||
|
||||
- @subpage inspiration
|
||||
- @subpage model
|
||||
- @subpage vision
|
||||
@@ -1,4 +1,4 @@
|
||||
# Release Checklist
|
||||
@page release_checklist Release Checklist
|
||||
|
||||
- make sure CI is passing
|
||||
- run the tests
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# CLI Usage
|
||||
@page usage CLI Usage
|
||||
|
||||
## tildefriends -h
|
||||
|
||||
|
||||
@@ -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="50"
|
||||
android:versionName="0.2025.12.1">
|
||||
android:versionCode="51"
|
||||
android:versionName="0.2026.1-wip">
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
|
||||
@@ -63,7 +63,8 @@ static void _file_read_read_callback(uv_fs_t* req)
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to read %s: %s", req->path, uv_strerror(req->result)));
|
||||
char buffer[256];
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to read %s: %s", req->path, uv_strerror_r(req->result, buffer, sizeof(buffer))));
|
||||
}
|
||||
uv_fs_req_cleanup(req);
|
||||
int result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
|
||||
@@ -89,7 +90,8 @@ static void _file_read_open_callback(uv_fs_t* req)
|
||||
int result = uv_fs_read(req->loop, req, fsreq->file, &buf, 1, 0, _file_read_read_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to read %s: %s", path, uv_strerror(result)));
|
||||
char buffer[256];
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to read %s: %s", path, uv_strerror_r(result, buffer, sizeof(buffer))));
|
||||
result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
@@ -100,7 +102,8 @@ static void _file_read_open_callback(uv_fs_t* req)
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for read: %s", path, uv_strerror(req->result)));
|
||||
char buffer[256];
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for read: %s", path, uv_strerror_r(req->result, buffer, sizeof(buffer))));
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_free(req);
|
||||
}
|
||||
@@ -128,7 +131,8 @@ static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int ar
|
||||
int result = uv_fs_open(tf_task_get_loop(task), &req->fs, actual, UV_FS_O_RDONLY, 0, _file_read_open_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for read: %s", file_name, uv_strerror(result)));
|
||||
char buffer[256];
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for read: %s", file_name, uv_strerror_r(result, buffer, sizeof(buffer))));
|
||||
uv_fs_req_cleanup(&req->fs);
|
||||
tf_free(req);
|
||||
}
|
||||
@@ -243,7 +247,8 @@ static JSValue _file_read_file_zip(JSContext* context, JSValueConst this_val, in
|
||||
int r = uv_queue_work(tf_task_get_loop(task), &work->request, _file_read_file_zip_work, _file_read_file_zip_after_work);
|
||||
if (r)
|
||||
{
|
||||
tf_task_reject_promise(task, work->promise, JS_ThrowInternalError(context, "Failed to create read work for %s: %s", file_name, uv_strerror(r)));
|
||||
char buffer[256];
|
||||
tf_task_reject_promise(task, work->promise, JS_ThrowInternalError(context, "Failed to create read work for %s: %s", file_name, uv_strerror_r(r, buffer, sizeof(buffer))));
|
||||
tf_free((void*)work->file_path);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
36
src/http.c
36
src/http.c
@@ -585,7 +585,8 @@ static void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t
|
||||
}
|
||||
else if (read_size < 0)
|
||||
{
|
||||
_http_connection_destroy(connection, uv_strerror(read_size));
|
||||
char buffer[256];
|
||||
_http_connection_destroy(connection, uv_strerror_r(read_size, buffer, sizeof(buffer)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,12 +602,14 @@ static void _http_timer_reset(tf_http_connection_t* connection)
|
||||
int r = uv_timer_stop(&connection->timeout);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_timer_stop: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_timer_stop: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
r = uv_timer_start(&connection->timeout, _http_timer_callback, k_timeout_ms, 0);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_timer_start: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_timer_start: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -626,7 +629,8 @@ static void _http_on_connection(uv_stream_t* stream, int status)
|
||||
int r = uv_tcp_init(connection->http->loop, &connection->tcp);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_tcp_init: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_tcp_init: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
_http_connection_destroy(connection, "uv_tcp_init");
|
||||
return;
|
||||
}
|
||||
@@ -635,14 +639,16 @@ static void _http_on_connection(uv_stream_t* stream, int status)
|
||||
connection->timeout.data = connection;
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_timer_init: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_timer_init: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
_http_connection_destroy(connection, "uv_timer_init");
|
||||
return;
|
||||
}
|
||||
r = uv_timer_start(&connection->timeout, _http_timer_callback, k_timeout_ms, 0);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_timer_start: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_timer_start: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
_http_connection_destroy(connection, "uv_timer_start");
|
||||
return;
|
||||
}
|
||||
@@ -650,7 +656,8 @@ static void _http_on_connection(uv_stream_t* stream, int status)
|
||||
r = uv_accept(stream, (uv_stream_t*)&connection->tcp);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_accept: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_accept: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
_http_connection_destroy(connection, "uv_accept");
|
||||
return;
|
||||
}
|
||||
@@ -658,7 +665,8 @@ static void _http_on_connection(uv_stream_t* stream, int status)
|
||||
r = uv_read_start((uv_stream_t*)&connection->tcp, _http_allocate_buffer, _http_on_read);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_read_start: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_read_start: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
_http_connection_destroy(connection, "uv_read_start");
|
||||
return;
|
||||
}
|
||||
@@ -679,7 +687,8 @@ int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_http_cleanup_t
|
||||
int r = uv_tcp_init(http->loop, &listener->tcp);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_tcp_init: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_tcp_init: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
|
||||
if (r == 0)
|
||||
@@ -709,7 +718,8 @@ int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_http_cleanup_t
|
||||
r = uv_tcp_bind(&listener->tcp, addr, 0);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("%s:%d: uv_tcp_bind: %s\n", __FILE__, __LINE__, uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("%s:%d: uv_tcp_bind: %s\n", __FILE__, __LINE__, uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -727,7 +737,8 @@ int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_http_cleanup_t
|
||||
r = uv_listen((uv_stream_t*)&listener->tcp, 16, _http_on_connection);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_listen: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_listen: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,7 +917,8 @@ static void _http_write_internal(tf_http_connection_t* connection, const void* d
|
||||
int r = uv_write(write, (uv_stream_t*)&connection->tcp, &(uv_buf_t) { .base = (void*)(write + 1), .len = size }, 1, _http_on_write);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("uv_write: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_write: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
134
src/httpd.js.c
134
src/httpd.js.c
@@ -117,146 +117,12 @@ static JSValue _httpd_response_end(JSContext* context, JSValueConst this_val, in
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue _httpd_response_send(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
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);
|
||||
JS_FreeCString(context, message);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static void _httpd_websocket_close_callback(tf_http_request_t* request)
|
||||
{
|
||||
JSContext* context = request->context;
|
||||
JSValue response_object = JS_MKPTR(JS_TAG_OBJECT, request->user_data);
|
||||
JSValue on_close = JS_GetPropertyStr(context, response_object, "onClose");
|
||||
JSValue response = JS_Call(context, on_close, JS_UNDEFINED, 0, NULL);
|
||||
tf_util_report_error(context, response);
|
||||
JS_FreeValue(context, response);
|
||||
JS_FreeValue(context, on_close);
|
||||
JS_SetPropertyStr(context, response_object, "onMessage", JS_UNDEFINED);
|
||||
JS_SetPropertyStr(context, response_object, "onClose", JS_UNDEFINED);
|
||||
JS_FreeValue(context, response_object);
|
||||
}
|
||||
|
||||
static void _httpd_message_callback(tf_http_request_t* request, int op_code, const void* data, size_t size)
|
||||
{
|
||||
JSContext* context = request->context;
|
||||
JSValue response_object = JS_MKPTR(JS_TAG_OBJECT, request->user_data);
|
||||
JSValue on_message = JS_GetPropertyStr(context, response_object, "onMessage");
|
||||
JSValue event = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, event, "opCode", JS_NewInt32(context, op_code));
|
||||
JS_SetPropertyStr(context, event, "data", JS_NewStringLen(context, data, size));
|
||||
JSValue response = JS_Call(context, on_message, JS_UNDEFINED, 1, &event);
|
||||
tf_util_report_error(context, response);
|
||||
JS_FreeValue(context, response);
|
||||
JS_FreeValue(context, event);
|
||||
JS_FreeValue(context, on_message);
|
||||
}
|
||||
|
||||
static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id);
|
||||
tf_http_request_ref(request);
|
||||
const char* header_connection = tf_http_request_get_header(request, "connection");
|
||||
const char* header_upgrade = tf_http_request_get_header(request, "upgrade");
|
||||
const char* header_sec_websocket_key = tf_http_request_get_header(request, "sec-websocket-key");
|
||||
if (header_connection && header_upgrade && header_sec_websocket_key && strstr(header_connection, "Upgrade") && strcasecmp(header_upgrade, "websocket") == 0)
|
||||
{
|
||||
static const char* k_magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
size_t key_length = strlen(header_sec_websocket_key);
|
||||
size_t size = key_length + 36;
|
||||
uint8_t* key_magic = alloca(size);
|
||||
memcpy(key_magic, header_sec_websocket_key, key_length);
|
||||
memcpy(key_magic + key_length, k_magic, 36);
|
||||
|
||||
uint8_t digest[20];
|
||||
SHA1_CTX sha1 = { 0 };
|
||||
SHA1Init(&sha1);
|
||||
SHA1Update(&sha1, key_magic, size);
|
||||
SHA1Final(digest, &sha1);
|
||||
|
||||
char key[41] = { 0 };
|
||||
tf_base64_encode(digest, sizeof(digest), key, sizeof(key));
|
||||
|
||||
const char* headers[64] = { 0 };
|
||||
int headers_count = 0;
|
||||
|
||||
headers[headers_count * 2 + 0] = "Upgrade";
|
||||
headers[headers_count * 2 + 1] = "websocket";
|
||||
headers_count++;
|
||||
|
||||
headers[headers_count * 2 + 0] = "Connection";
|
||||
headers[headers_count * 2 + 1] = "Upgrade";
|
||||
headers_count++;
|
||||
|
||||
headers[headers_count * 2 + 0] = "Sec-WebSocket-Accept";
|
||||
headers[headers_count * 2 + 1] = key;
|
||||
headers_count++;
|
||||
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(tf_task_get(context));
|
||||
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
|
||||
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
|
||||
tf_free((void*)session);
|
||||
JSValue name = !JS_IsUndefined(jwt) ? JS_GetPropertyStr(context, jwt, "name") : JS_UNDEFINED;
|
||||
const char* name_string = !JS_IsUndefined(name) ? JS_ToCString(context, name) : NULL;
|
||||
const char* session_token = tf_httpd_make_session_jwt(tf_ssb_get_context(ssb), ssb, name_string);
|
||||
const char* cookie = tf_httpd_make_set_session_cookie_header(request, session_token);
|
||||
tf_free((void*)session_token);
|
||||
JS_FreeCString(context, name_string);
|
||||
JS_FreeValue(context, name);
|
||||
JS_FreeValue(context, jwt);
|
||||
headers[headers_count * 2 + 0] = "Set-Cookie";
|
||||
headers[headers_count * 2 + 1] = cookie ? cookie : "";
|
||||
headers_count++;
|
||||
|
||||
bool send_version = !tf_http_request_get_header(request, "sec-websocket-version") || strcmp(tf_http_request_get_header(request, "sec-websocket-version"), "13") != 0;
|
||||
if (send_version)
|
||||
{
|
||||
headers[headers_count * 2 + 0] = "Sec-WebSocket-Accept";
|
||||
headers[headers_count * 2 + 1] = key;
|
||||
headers_count++;
|
||||
}
|
||||
int js_headers_count = _object_to_headers(context, argv[1], headers + headers_count * 2, tf_countof(headers) - headers_count * 2);
|
||||
headers_count += js_headers_count;
|
||||
|
||||
tf_http_request_websocket_upgrade(request);
|
||||
tf_http_respond(request, 101, headers, headers_count, NULL, 0);
|
||||
|
||||
for (int i = headers_count - js_headers_count; i < headers_count * 2; i++)
|
||||
{
|
||||
JS_FreeCString(context, headers[i * 2 + 0]);
|
||||
JS_FreeCString(context, headers[i * 2 + 1]);
|
||||
}
|
||||
|
||||
tf_free((void*)cookie);
|
||||
|
||||
request->on_message = _httpd_message_callback;
|
||||
request->on_close = _httpd_websocket_close_callback;
|
||||
request->context = context;
|
||||
request->user_data = JS_VALUE_GET_PTR(JS_DupValue(context, this_val));
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_http_respond(request, 400, NULL, 0, NULL, 0);
|
||||
}
|
||||
tf_http_request_unref(request);
|
||||
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue tf_httpd_make_response_object(JSContext* context, tf_http_request_t* request)
|
||||
{
|
||||
JSValue response_object = JS_NewObjectClass(context, _httpd_request_class_id);
|
||||
JS_SetOpaque(response_object, request);
|
||||
JS_SetPropertyStr(context, response_object, "writeHead", JS_NewCFunction(context, _httpd_response_write_head, "writeHead", 2));
|
||||
JS_SetPropertyStr(context, response_object, "end", JS_NewCFunction(context, _httpd_response_end, "end", 1));
|
||||
JS_SetPropertyStr(context, response_object, "send", JS_NewCFunction(context, _httpd_response_send, "send", 2));
|
||||
JS_SetPropertyStr(context, response_object, "upgrade", JS_NewCFunction(context, _httpd_websocket_upgrade, "upgrade", 2));
|
||||
return response_object;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.2025.12.1</string>
|
||||
<string>0.2026.1</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>50</string>
|
||||
<string>51</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -1940,7 +1940,8 @@ static jint _tf_server_main(JNIEnv* env, jobject this_object, jstring files_dir,
|
||||
int result = uv_chdir(files);
|
||||
if (result)
|
||||
{
|
||||
tf_printf("uv_chdir: %s\n", uv_strerror(result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_chdir: %s\n", uv_strerror_r(result, buffer, sizeof(buffer)));
|
||||
}
|
||||
|
||||
size_t args_length = strlen(out_port_file) + strlen("--args=http_port=0,out_http_port_file=") + 1;
|
||||
|
||||
@@ -133,7 +133,8 @@ void tf_packetstream_start(tf_packetstream_t* stream)
|
||||
int result = uv_read_start((uv_stream_t*)&stream->stream, _packetstream_allocate, _packetstream_on_read);
|
||||
if (result)
|
||||
{
|
||||
tf_printf("uv_read_start: %s\n", uv_strerror(result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_read_start: %s\n", uv_strerror_r(result, buffer, sizeof(buffer)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +163,8 @@ void tf_packetstream_send(tf_packetstream_t* stream, int packet_type, const char
|
||||
int result = uv_write(request, (uv_stream_t*)&stream->stream, &write_buffer, 1, _packetstream_on_write);
|
||||
if (result)
|
||||
{
|
||||
tf_printf("tf_packetstream_send: uv_write: %s\n", uv_strerror(result));
|
||||
char buffer[256];
|
||||
tf_printf("tf_packetstream_send: uv_write: %s\n", uv_strerror_r(result, buffer, sizeof(buffer)));
|
||||
tf_free(request);
|
||||
}
|
||||
}
|
||||
|
||||
65
src/ssb.c
65
src/ssb.c
@@ -473,7 +473,7 @@ static void _tf_ssb_connection_on_write(uv_write_t* req, int status)
|
||||
if (status)
|
||||
{
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "write failed asynchronously: %s", uv_strerror(status));
|
||||
tf_util_uv_error_buf(buffer, sizeof(buffer), "write failed asynchronously: ", status);
|
||||
tf_ssb_connection_close(connection, buffer);
|
||||
}
|
||||
tf_free(req);
|
||||
@@ -492,7 +492,7 @@ static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t si
|
||||
{
|
||||
tf_ssb_connection_adjust_write_count(connection, -1);
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer), "write failed: %s", uv_strerror(result));
|
||||
tf_util_uv_error_buf(buffer, sizeof(buffer), "write failed: ", result);
|
||||
tf_ssb_connection_close(connection, buffer);
|
||||
tf_free(write);
|
||||
}
|
||||
@@ -2233,7 +2233,8 @@ static void _tf_ssb_connection_on_tcp_recv_internal(tf_ssb_connection_t* connect
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_ssb_connection_close(connection, uv_strerror(nread));
|
||||
char buffer[256];
|
||||
tf_ssb_connection_close(connection, tf_util_uv_error_buf(buffer, sizeof(buffer), "read: ", nread));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2275,7 +2276,7 @@ static bool _tf_ssb_connection_read_start(tf_ssb_connection_t* connection)
|
||||
if (result && result != UV_EALREADY)
|
||||
{
|
||||
char reason[1024];
|
||||
snprintf(reason, sizeof(reason), "uv_read_start failed: %s", uv_strerror(result));
|
||||
tf_util_uv_error_buf(reason, sizeof(reason), "uv_read_start failed: ", result);
|
||||
tf_ssb_connection_close(connection, reason);
|
||||
return false;
|
||||
}
|
||||
@@ -2290,7 +2291,7 @@ static bool _tf_ssb_connection_read_stop(tf_ssb_connection_t* connection)
|
||||
if (result && result != UV_EALREADY)
|
||||
{
|
||||
char reason[1024];
|
||||
snprintf(reason, sizeof(reason), "uv_read_stop failed: %s", uv_strerror(result));
|
||||
tf_util_uv_error_buf(reason, sizeof(reason), "uv_read_stop failed: ", result);
|
||||
tf_ssb_connection_close(connection, reason);
|
||||
return false;
|
||||
}
|
||||
@@ -2312,7 +2313,7 @@ static void _tf_ssb_connection_on_connect(uv_connect_t* connect, int status)
|
||||
else
|
||||
{
|
||||
char reason[1024];
|
||||
snprintf(reason, sizeof(reason), "uv_tcp_connect failed: %s", uv_strerror(status));
|
||||
tf_util_uv_error_buf(reason, sizeof(reason), "uv_tcp_connect failed: ", status);
|
||||
tf_ssb_connection_close(connection, reason);
|
||||
}
|
||||
}
|
||||
@@ -2825,7 +2826,8 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
|
||||
int r = uv_loop_close(ssb->loop);
|
||||
if (r != 0 && !ssb->quiet)
|
||||
{
|
||||
tf_printf("uv_loop_close: %s\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("uv_loop_close: %s\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
}
|
||||
if (!ssb->quiet)
|
||||
@@ -3031,8 +3033,10 @@ static tf_ssb_connection_t* _tf_ssb_connection_create(
|
||||
int result = uv_tcp_connect(&connection->connect, &connection->tcp, (const struct sockaddr*)addr, _tf_ssb_connection_on_connect);
|
||||
if (result)
|
||||
{
|
||||
char prefix[256];
|
||||
snprintf(prefix, sizeof(prefix), "uv_tcp_connect(%s): ", host);
|
||||
char reason[1024];
|
||||
snprintf(reason, sizeof(reason), "uv_tcp_connect(%s) => %s", host, uv_strerror(result));
|
||||
tf_util_uv_error_buf(reason, sizeof(reason), prefix, result);
|
||||
connection->connect.data = NULL;
|
||||
_tf_ssb_connection_destroy(connection, reason);
|
||||
}
|
||||
@@ -3138,8 +3142,10 @@ static void _tf_on_connect_getaddrinfo(uv_getaddrinfo_t* addrinfo, int result, s
|
||||
}
|
||||
else if (connect->callback)
|
||||
{
|
||||
char prefix[256];
|
||||
snprintf(prefix, sizeof(prefix), "uv_getaddrinfo(%s): ", connect->host);
|
||||
char reason[1024];
|
||||
snprintf(reason, sizeof(reason), "uv_getaddrinfo(%s) => %s", connect->host, uv_strerror(result));
|
||||
tf_util_uv_error_buf(reason, sizeof(reason), prefix, result);
|
||||
connect->callback(NULL, reason, connect->user_data);
|
||||
}
|
||||
}
|
||||
@@ -3183,13 +3189,15 @@ void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* ke
|
||||
int r = uv_getaddrinfo(ssb->loop, &connect->req, _tf_on_connect_getaddrinfo, host, NULL, &(struct addrinfo) { .ai_family = AF_INET });
|
||||
if (r < 0)
|
||||
{
|
||||
char prefix[256];
|
||||
snprintf(prefix, sizeof(prefix), "uv_getaddrinfo(%s): ", connect->host);
|
||||
char reason[1024];
|
||||
tf_util_uv_error_buf(reason, sizeof(reason), prefix, r);
|
||||
if (callback)
|
||||
{
|
||||
char reason[1024];
|
||||
snprintf(reason, sizeof(reason), "uv_getaddr_info(%s): %s", host, uv_strerror(r));
|
||||
callback(NULL, reason, user_data);
|
||||
}
|
||||
tf_printf("uv_getaddrinfo(%s): %s\n", host, uv_strerror(r));
|
||||
tf_printf("%s\n", reason);
|
||||
tf_free(connect);
|
||||
tf_ssb_unref(ssb);
|
||||
}
|
||||
@@ -3233,13 +3241,15 @@ static void _tf_ssb_connect_with_invite(
|
||||
int r = uv_getaddrinfo(ssb->loop, &connect->req, _tf_on_connect_getaddrinfo, host, NULL, &(struct addrinfo) { .ai_family = AF_INET });
|
||||
if (r < 0)
|
||||
{
|
||||
char prefix[256];
|
||||
snprintf(prefix, sizeof(prefix), "uv_getaddrinfo(%s): ", connect->host);
|
||||
char reason[1024];
|
||||
tf_util_uv_error_buf(reason, sizeof(reason), prefix, r);
|
||||
if (callback)
|
||||
{
|
||||
char reason[1024];
|
||||
snprintf(reason, sizeof(reason), "uv_getaddr_info(%s): %s", host, uv_strerror(r));
|
||||
callback(NULL, reason, user_data);
|
||||
}
|
||||
tf_printf("uv_getaddrinfo(%s): %s\n", host, uv_strerror(r));
|
||||
tf_printf("%s\n", reason);
|
||||
tf_free(connect);
|
||||
tf_ssb_unref(ssb);
|
||||
}
|
||||
@@ -3250,7 +3260,8 @@ static void _tf_ssb_on_connection(uv_stream_t* stream, int status)
|
||||
tf_ssb_t* ssb = stream->data;
|
||||
if (status < 0)
|
||||
{
|
||||
tf_printf("uv_listen failed: %s\n", uv_strerror(status));
|
||||
char buffer[256];
|
||||
tf_printf("uv_listen failed: %s\n", uv_strerror_r(status, buffer, sizeof(buffer)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3267,7 +3278,7 @@ static void _tf_ssb_on_connection(uv_stream_t* stream, int status)
|
||||
{
|
||||
connection->tcp.data = NULL;
|
||||
char reason[1024];
|
||||
snprintf(reason, sizeof(reason), "uv_tcp_init() => %s", uv_strerror(result));
|
||||
tf_util_uv_error_buf(reason, sizeof(reason), "uv_tcp_init: ", result);
|
||||
_tf_ssb_connection_destroy(connection, reason);
|
||||
return;
|
||||
}
|
||||
@@ -3276,7 +3287,7 @@ static void _tf_ssb_on_connection(uv_stream_t* stream, int status)
|
||||
if (result != 0)
|
||||
{
|
||||
char reason[1024];
|
||||
snprintf(reason, sizeof(reason), "uv_accept() => %s", uv_strerror(result));
|
||||
tf_util_uv_error_buf(reason, sizeof(reason), "uv_accept: ", result);
|
||||
_tf_ssb_connection_destroy(connection, reason);
|
||||
return;
|
||||
}
|
||||
@@ -3307,7 +3318,8 @@ static void _tf_ssb_update_broadcast_result(tf_ssb_t* ssb, struct sockaddr* addr
|
||||
{
|
||||
char broadcast_str[256] = { 0 };
|
||||
uv_ip4_name((struct sockaddr_in*)address, broadcast_str, sizeof(broadcast_str));
|
||||
tf_printf("Unable to send broadcast for %s via %s (%d): %s.\n", address_str, broadcast_str, result, uv_strerror(result));
|
||||
char buffer[256];
|
||||
tf_printf("Unable to send broadcast for %s via %s (%d): %s.\n", address_str, broadcast_str, result, uv_strerror_r(result, buffer, sizeof(buffer)));
|
||||
}
|
||||
ssb->broadcast_results[i].result = result;
|
||||
}
|
||||
@@ -3334,7 +3346,8 @@ static void _tf_ssb_send_broadcast(tf_ssb_t* ssb, struct sockaddr_in* address, s
|
||||
int r = uv_tcp_getsockname(&ssb->server, &server_addr, &len);
|
||||
if (r != 0)
|
||||
{
|
||||
tf_printf("Unable to get server's address: %s.\n", uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("Unable to get server's address: %s.\n", uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
else if (server_addr.sa_family != AF_INET)
|
||||
{
|
||||
@@ -3472,14 +3485,16 @@ int tf_ssb_server_open(tf_ssb_t* ssb, int port)
|
||||
int status = uv_tcp_bind(&ssb->server, (struct sockaddr*)&addr, 0);
|
||||
if (status != 0)
|
||||
{
|
||||
tf_printf("%s:%d: uv_tcp_bind failed: %s\n", __FILE__, __LINE__, uv_strerror(status));
|
||||
char buffer[256];
|
||||
tf_printf("%s:%d: uv_tcp_bind failed: %s\n", __FILE__, __LINE__, uv_strerror_r(status, buffer, sizeof(buffer)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
status = uv_listen((uv_stream_t*)&ssb->server, SOMAXCONN, _tf_ssb_on_connection);
|
||||
if (status != 0)
|
||||
{
|
||||
tf_printf("uv_listen failed: %s\n", uv_strerror(status));
|
||||
char buffer[256];
|
||||
tf_printf("uv_listen failed: %s\n", uv_strerror_r(status, buffer, sizeof(buffer)));
|
||||
/* TODO: cleanup */
|
||||
return 0;
|
||||
}
|
||||
@@ -3739,13 +3754,15 @@ void tf_ssb_broadcast_listener_start(tf_ssb_t* ssb, bool linger)
|
||||
int result = uv_udp_bind(&ssb->broadcast_listener, (const struct sockaddr*)&addr, UV_UDP_REUSEADDR);
|
||||
if (result != 0)
|
||||
{
|
||||
tf_printf("bind: %s\n", uv_strerror(result));
|
||||
char buffer[256];
|
||||
tf_printf("bind: %s\n", uv_strerror_r(result, buffer, sizeof(buffer)));
|
||||
}
|
||||
|
||||
result = uv_udp_recv_start(&ssb->broadcast_listener, _tf_ssb_on_broadcast_listener_alloc, _tf_ssb_on_broadcast_listener_recv);
|
||||
if (result != 0)
|
||||
{
|
||||
tf_printf("uv_udp_recv_start: %s\n", uv_strerror(result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_udp_recv_start: %s\n", uv_strerror_r(result, buffer, sizeof(buffer)));
|
||||
}
|
||||
|
||||
if (!linger)
|
||||
|
||||
@@ -970,6 +970,11 @@ bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_
|
||||
|
||||
void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id)
|
||||
{
|
||||
if (!id || *id != '&' || strlen(id) != 52 || strcmp(id + 52 - strlen(".sha256"), ".sha256") != 0)
|
||||
{
|
||||
tf_printf("Dropping blob wants request: %s.\n", id);
|
||||
return;
|
||||
}
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO blob_wants_cache (id, timestamp) VALUES (?, unixepoch() * 1000) ON CONFLICT DO UPDATE SET timestamp = excluded.timestamp",
|
||||
-1, &statement, NULL) == SQLITE_OK)
|
||||
@@ -1055,7 +1060,8 @@ static void _tf_ssb_db_blob_store_after_work(tf_ssb_t* ssb, int status, void* us
|
||||
}
|
||||
if (status != 0)
|
||||
{
|
||||
tf_printf("tf_ssb_db_blob_store_async -> uv_queue_work failed asynchronously: %s\n", uv_strerror(status));
|
||||
char buffer[256];
|
||||
tf_printf("tf_ssb_db_blob_store_async -> uv_queue_work failed asynchronously: %s\n", uv_strerror_r(status, buffer, sizeof(buffer)));
|
||||
}
|
||||
if (blob_work->callback)
|
||||
{
|
||||
|
||||
@@ -69,7 +69,8 @@ static void _tf_ssb_export_scandir(uv_fs_t* req)
|
||||
int r = uv_fs_unlink(tf_ssb_get_loop(export->ssb), &req, path, NULL);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("Failed to unlink %s: %s.\n", path, uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("Failed to unlink %s: %s.\n", path, uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
uv_fs_req_cleanup(&req);
|
||||
tf_free(path);
|
||||
@@ -197,7 +198,8 @@ void tf_ssb_export(tf_ssb_t* ssb, const char* key)
|
||||
int r = uv_fs_scandir(tf_ssb_get_loop(ssb), &export.req, file_path, 0, _tf_ssb_export_scandir);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("Failed to scan directory %s: %s.\n", file_path, uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("Failed to scan directory %s: %s.\n", file_path, uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
while (!export.done)
|
||||
{
|
||||
|
||||
@@ -100,7 +100,8 @@ static char* _tf_ssb_import_read_file(uv_loop_t* loop, const char* path, size_t*
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("Failed to read %s: %s.\n", path, uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("Failed to read %s: %s.\n", path, uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
uv_fs_req_cleanup(&read_req);
|
||||
|
||||
@@ -108,12 +109,14 @@ static char* _tf_ssb_import_read_file(uv_loop_t* loop, const char* path, size_t*
|
||||
r = uv_fs_close(loop, &close_req, handle, NULL);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("Failed to close %s: %s.\n", path, uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("Failed to close %s: %s.\n", path, uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("Failed to open %s: %s.\n", path, uv_strerror(handle));
|
||||
char buffer[256];
|
||||
tf_printf("Failed to open %s: %s.\n", path, uv_strerror_r(handle, buffer, sizeof(buffer)));
|
||||
}
|
||||
uv_fs_req_cleanup(&req);
|
||||
return data;
|
||||
@@ -163,7 +166,8 @@ static void _tf_ssb_import_recursive_add_files(tf_ssb_t* ssb, uv_loop_t* loop, J
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("Failed to scan directory %s: %s.\n", path, uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("Failed to scan directory %s: %s.\n", path, uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
uv_fs_req_cleanup(&req);
|
||||
}
|
||||
@@ -234,7 +238,8 @@ static void _tf_ssb_import_app_json(tf_ssb_t* ssb, uv_loop_t* loop, JSContext* c
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("Failed to open %s: %s.\n", path, uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("Failed to open %s: %s.\n", path, uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
uv_fs_req_cleanup(&req);
|
||||
}
|
||||
@@ -270,7 +275,8 @@ void tf_ssb_import(tf_ssb_t* ssb, const char* user, const char* path)
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("Failed to scan directory %s: %s.\n", path, uv_strerror(r));
|
||||
char buffer[256];
|
||||
tf_printf("Failed to scan directory %s: %s.\n", path, uv_strerror_r(r, buffer, sizeof(buffer)));
|
||||
}
|
||||
uv_fs_req_cleanup(&req);
|
||||
}
|
||||
|
||||
@@ -182,7 +182,8 @@ static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int a
|
||||
int pipe_result = uv_socketpair(SOCK_STREAM, 0, fds, 0, 0);
|
||||
if (pipe_result)
|
||||
{
|
||||
tf_printf("uv_socketpair failed: %s\n", uv_strerror(pipe_result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_socketpair failed: %s\n", uv_strerror_r(pipe_result, buffer, sizeof(buffer)));
|
||||
}
|
||||
|
||||
uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream);
|
||||
@@ -190,12 +191,14 @@ static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int a
|
||||
pipe_result = uv_pipe_init(tf_task_get_loop(parent), pipe, 1);
|
||||
if (pipe_result != 0)
|
||||
{
|
||||
tf_printf("uv_pipe_init failed: %s\n", uv_strerror(pipe_result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_pipe_init failed: %s\n", uv_strerror_r(pipe_result, buffer, sizeof(buffer)));
|
||||
}
|
||||
pipe_result = uv_pipe_open(pipe, fds[0]);
|
||||
if (pipe_result != 0)
|
||||
{
|
||||
tf_printf("uv_pipe_open failed: %s\n", uv_strerror(pipe_result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_pipe_open failed: %s\n", uv_strerror_r(pipe_result, buffer, sizeof(buffer)));
|
||||
}
|
||||
|
||||
if (start_service)
|
||||
@@ -248,7 +251,8 @@ static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int a
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("uv_spawn failed: %s\n", uv_strerror(spawn_result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_spawn failed: %s\n", uv_strerror_r(spawn_result, buffer, sizeof(buffer)));
|
||||
JS_FreeValue(context, taskObject);
|
||||
}
|
||||
}
|
||||
@@ -323,7 +327,8 @@ tf_taskstub_t* tf_taskstub_create_parent(tf_task_t* task, uv_file file)
|
||||
int result = uv_pipe_open(tf_packetstream_get_pipe(parentStub->_stream), file);
|
||||
if (result != 0)
|
||||
{
|
||||
tf_printf("uv_pipe_open failed: %s\n", uv_strerror(result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_pipe_open failed: %s\n", uv_strerror_r(result, buffer, sizeof(buffer)));
|
||||
}
|
||||
tf_packetstream_start(parentStub->_stream);
|
||||
return parentStub;
|
||||
|
||||
@@ -817,6 +817,9 @@ static void _test_httpd(const tf_test_options_t* options)
|
||||
_http_check_status_code("http://localhost:8080/~core/test/nonexistent.txt", 404);
|
||||
_http_check_body_contains("http://localhost:8080/&MV9b23bQeMQ7isAGTkoBZGErH853yGk0W/yUx1iU7dM=.sha256/view", "Hello, world!");
|
||||
|
||||
_http_check_body_contains("http://localhost:8080/~core/web/hello.txt", "hello.txt not found");
|
||||
_http_check_status_code("http://localhost:8080/~core/web/hello.txt", 404);
|
||||
|
||||
#if !defined(__HAIKU__)
|
||||
char url[1024];
|
||||
snprintf(url, sizeof(url), "http://localhost:8080/%s/", app_id);
|
||||
@@ -889,7 +892,8 @@ static void _test_auto(const tf_test_options_t* options)
|
||||
int spawn_result = uv_spawn(&loop, &process, &process_options);
|
||||
if (spawn_result)
|
||||
{
|
||||
tf_printf("uv_spawn: %s\n", uv_strerror(spawn_result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_spawn: %s\n", uv_strerror_r(spawn_result, buffer, sizeof(buffer)));
|
||||
abort();
|
||||
}
|
||||
|
||||
@@ -902,7 +906,8 @@ static void _test_auto(const tf_test_options_t* options)
|
||||
spawn_result = uv_spawn(&loop, &selenium, &process_options);
|
||||
if (spawn_result)
|
||||
{
|
||||
tf_printf("uv_spawn: %s\n", uv_strerror(spawn_result));
|
||||
char buffer[256];
|
||||
tf_printf("uv_spawn: %s\n", uv_strerror_r(spawn_result, buffer, sizeof(buffer)));
|
||||
abort();
|
||||
}
|
||||
|
||||
|
||||
@@ -684,3 +684,11 @@ size_t tf_string_set(char* buffer, size_t size, const char* string)
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
const char* tf_util_uv_error_buf(char* buffer, size_t size, const char* prefix, int error)
|
||||
{
|
||||
char message[256];
|
||||
uv_strerror_r(error, message, sizeof(message));
|
||||
snprintf(buffer, size, "%s%s", prefix, message);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -249,4 +249,14 @@ bool tf_util_is_mobile();
|
||||
*/
|
||||
size_t tf_string_set(char* buffer, size_t size, const char* string);
|
||||
|
||||
/**
|
||||
** Format a string representation of a libuv error into a string buffer.
|
||||
** @param buffer The string buffer to be populated.
|
||||
** @param size The size of the buffer.
|
||||
** @param prefix Prefix to the error message.
|
||||
** @param error The libuv error number.
|
||||
** @return The formatted string.
|
||||
*/
|
||||
const char* tf_util_uv_error_buf(char* buffer, size_t size, const char* prefix, int error);
|
||||
|
||||
/** @} */
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#define VERSION_NUMBER "0.2025.12.1"
|
||||
#define VERSION_NUMBER "0.2026.1-wip"
|
||||
#define VERSION_NAME "This program kills fascists."
|
||||
|
||||
Reference in New Issue
Block a user