30 Commits

Author SHA1 Message Date
5647196924 ssb: Add a manual theme color picker.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 18m1s
2025-10-22 19:39:20 -04:00
49b1834bc6 docs: Prep release notes.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 19m14s
2025-10-22 18:40:29 -04:00
d111647ea8 ssb: Merge the query tab into the search tab. Search for something starting with sql: to search for arbitrary SQL.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-10-22 18:21:22 -04:00
d7580dab9b update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 19m29s
2025-10-22 12:22:09 -04:00
28db8a8d5f update: speedscope 1.24.0.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-10-22 12:20:35 -04:00
e64d5617e7 ssb: Fix contact groups expanding/collapsing together.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 19m23s
2025-10-19 08:39:46 -04:00
acd114650a ssb: Slightly improved following/blocking messages. following=true blocking=false is a common situation that should be described as following.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 18m46s
2025-10-16 12:48:49 -04:00
7a47ffaa61 welcome: -OpenSSL.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-10-15 20:19:03 -04:00
42f7f66f35 cleanup: Some OpenSSL cruft.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 19m37s
2025-10-15 20:10:15 -04:00
b2b4ffeeae cleanup: Remove OpenSSL and consequently https support. Run behind a reverse proxy if you need https.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-10-15 20:02:59 -04:00
26de1f7daa update: CodeMirror.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-10-15 19:22:57 -04:00
07605933dc core: core.globalSettingsDescriptions JS => C.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m48s
2025-10-15 19:09:41 -04:00
4bdc7ec616 core: core.globalSettingsGet JS => C. 2025-10-15 18:24:38 -04:00
8ca64550e5 test: Messages added callbacks are not a reliable way to count messages. Fix -t=invite sometimes stalling.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m38s
2025-10-09 12:50:25 -04:00
25dbac804c core: Minor cleanup and style. 2025-10-09 12:45:38 -04:00
6ab3fd168b update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m43s
2025-10-08 19:17:08 -04:00
94858e2371 core: ssb.getIdentities() + ssb.getOwnerIdentities() JS => C.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-10-08 19:13:54 -04:00
6d13502e94 core: ssb.getActiveIdentity JS => C. 2025-10-08 18:30:38 -04:00
77001e595c ssb: Faster queries through lots of trial and error.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m5s
2025-10-07 21:15:09 -04:00
6fad20ffa3 ssb: Recover some of the load time lost in filtering out subscribed channels.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m20s
2025-10-06 12:45:40 -04:00
00fb6c9839 update: CoreMirror. 2025-10-06 06:56:08 -04:00
97fcf72d63 core: Move some simple properties JS => C. 2025-10-06 06:53:41 -04:00
5d8d02515d ssb: This query is slightly faster but not fast enough.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 31m48s
2025-10-02 12:47:57 -04:00
859fe1feb0 ssb: Exclude followed mentions from the general channel, too.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m18s
2025-10-01 20:02:37 -04:00
8f61d83f41 ssb: Exclude subscribed channels from general posts. 2025-10-01 19:52:02 -04:00
6423b3e479 ssb: Consolidate the connection sidebar options. Three copies on-screen is too many.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 32m47s
2025-10-01 19:12:31 -04:00
2bc8cec8a2 core: Move users()+permissionsForUser() from JS to C. 2025-10-01 18:36:50 -04:00
b49a6cd685 ssb: Place the message collapse better with the messages it collapses.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 33m8s
2025-10-01 18:06:45 -04:00
2885380f40 core: Remove some unused events.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2025-10-01 17:55:32 -04:00
2ec3b6a249 http: Add some logging to try to diagnose an intermittent shutdown issue. 2025-10-01 17:48:23 -04:00
53 changed files with 1261 additions and 1886 deletions

4
.gitmodules vendored
View File

@@ -19,10 +19,6 @@
[submodule "deps/picohttpparser"]
path = deps/picohttpparser
url = https://github.com/h2o/picohttpparser.git
[submodule "deps/openssl_src"]
path = deps/openssl_src
url = https://github.com/openssl/openssl.git
shallow = true
[submodule "deps/c-ares"]
path = deps/c-ares
url = https://github.com/c-ares/c-ares.git

View File

@@ -4,7 +4,6 @@ RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc \
libc6-dev \
perl \
make
COPY . /app

View File

@@ -38,7 +38,6 @@ BUNDLETOOL = out/bundletool.jar
HAVE_WIN :=
HAVE_CROSS_AARCH64 :=
USE_SYSTEM_SSL :=
export SOURCE_DATE_EPOCH=1
export TZ=UTC
@@ -65,7 +64,6 @@ LDFLAGS += \
-lbsd \
-lnetwork \
-Wno-stringop-overflow
USE_SYSTEM_SSL := 1
HAVE_ANDROID = 0
HAVE_LINUX_IOS = 0
HAVE_LINUX_MACOS = 0
@@ -80,13 +78,12 @@ LDFLAGS += \
HAVE_ANDROID :=
HAVE_LINUX_IOS :=
HAVE_LINUX_MACOS :=
USE_SYSTEM_SSL := 1
else
$(error Unexpected host platform $(UNAME_S).)
endif
# Everything is set above.
$(info Building Tilde Friends $(VERSION_NUMBER) android=$(if $(HAVE_ANDROID),1,0) win=$(if $(HAVE_WIN),1,0) cross_aarch64=$(if $(HAVE_CROSS_AARCH64),1,0) cross_ios=$(if $(HAVE_LINUX_IOS),1,0) cross_macos=$(if $(HAVE_LINUX_MACOS),1,0) system_ssl=$(if $(USE_SYSTEM_SSL),1,0))
$(info Building Tilde Friends $(VERSION_NUMBER) android=$(if $(HAVE_ANDROID),1,0) win=$(if $(HAVE_WIN),1,0) cross_aarch64=$(if $(HAVE_CROSS_AARCH64),1,0) cross_ios=$(if $(HAVE_LINUX_IOS),1,0) cross_macos=$(if $(HAVE_LINUX_MACOS),1,0))
CFLAGS += \
-std=gnu11 \
@@ -270,16 +267,12 @@ $(WINDOWS_TARGETS): AS = $(CC)
$(WINDOWS_TARGETS): CFLAGS += \
-D_WIN32_WINNT=0x0A00 \
-DWINVER=0x0A00 \
-DNTDDI_VERSION=NTDDI_WIN10 \
-Iout/openssl/$(UNAME_S)/mingw64/usr/local/include
-DNTDDI_VERSION=NTDDI_WIN10
$(WINDOWS_TARGETS): LDFLAGS += \
-static \
-lm \
-Lout/openssl/$(UNAME_S)/mingw64/usr/local/lib
-lm
$(AARCH64_TARGETS): CC = aarch64-linux-gnu-gcc
$(AARCH64_TARGETS): AS = $(CC)
$(AARCH64_TARGETS): CFLAGS += -Iout/openssl/Linux/aarch64/usr/local/include
$(AARCH64_TARGETS): LDFLAGS += -Lout/openssl/Linux/aarch64/usr/local/lib
ifeq ($(UNAME_S),Darwin)
$(HOST_TARGETS): CC = xcrun clang
$(IOS_TARGETS): IOS_SYSROOT := $(shell xcrun --sdk iphoneos --show-sdk-path)
@@ -304,39 +297,12 @@ $(ANDROID_TARGETS): AS = $(CC)
$(ANDROID_TARGETS): CFLAGS += \
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
-Wno-unknown-warning-option
$(ANDROID_ARMV7A_TARGETS): CFLAGS += -Iout/openssl/android/armeabi-v7a/usr/local/include
$(ANDROID_ARMV7A_TARGETS): LDFLAGS += -Lout/openssl/android/armeabi-v7a/usr/local/lib
$(ANDROID_ARM64_TARGETS): CFLAGS += -Iout/openssl/android/arm64-v8a/usr/local/include
$(ANDROID_ARM64_TARGETS): LDFLAGS += -Lout/openssl/android/arm64-v8a/usr/local/lib
$(ANDROID_X86_TARGETS): CFLAGS += -Iout/openssl/android/x86/usr/local/include
$(ANDROID_X86_TARGETS): CFLAGS += -Wno-atomic-alignment
$(ANDROID_X86_TARGETS): LDFLAGS += -Lout/openssl/android/x86/usr/local/lib
$(ANDROID_X86_64_TARGETS): CFLAGS += -Iout/openssl/android/x86_64/usr/local/include
$(ANDROID_X86_64_TARGETS): LDFLAGS += -Lout/openssl/android/x86_64/usr/local/lib
$(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type
$(MACOS_TARGETS): LDFLAGS += -Wl,-dead_strip
$(NONMACOS_TARGETS): LDFLAGS += -Wl,--gc-sections -Wl,--as-needed
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
$(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
ifeq ($(UNAME_S),Darwin)
$(IOS_TARGETS): CFLAGS += -Iout/openssl/ios/ios64-xcrun/usr/local/include
$(IOS_TARGETS): LDFLAGS += -Lout/openssl/ios/ios64-xcrun/usr/local/lib
else
$(IOS_TARGETS): CFLAGS += -Iout/openssl/$(UNAME_S)/ios64-cross/usr/local/include
$(IOS_TARGETS): LDFLAGS += -Lout/openssl/$(UNAME_S)/ios64-cross/usr/local/lib
$(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-x86_64/usr/local/include
$(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-arm/usr/local/include
$(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib
$(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-arm/usr/local/lib
$(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-x86_64/usr/local/include
$(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-arm/usr/local/include
$(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib
$(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-arm/usr/local/lib
endif
$(IOSSIM_TARGETS): CFLAGS += -Iout/openssl/ios/iossimulator-xcrun/usr/local/include
$(IOSSIM_TARGETS): LDFLAGS += -Lout/openssl/ios/iossimulator-xcrun/usr/local/lib
$(HOST_TARGETS): CFLAGS += -Iout/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/include
$(HOST_TARGETS): LDFLAGS += -Lout/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib
ifeq ($(UNAME_M),x86_64)
ifeq ($(UNAME_S),Linux)
@@ -824,9 +790,6 @@ $(MINIUNZIP_OBJS): CFLAGS += \
LDFLAGS += \
-pthread \
-lm
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS) $(AARCH64_TARGETS) $(filter-out $(HOST_TARGETS),$(MACOS_TARGETS)): LDFLAGS += \
-lssl \
-lcrypto
ifneq ($(UNAME_S),Haiku)
ifneq ($(UNAME_S),OpenBSD)
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
@@ -834,8 +797,6 @@ $(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
endif
endif
$(WINDOWS_TARGETS): LDFLAGS += \
-lssl \
-lcrypto \
-lcrypt32 \
-ldbghelp \
-liphlpapi \
@@ -848,9 +809,7 @@ $(WINDOWS_TARGETS): LDFLAGS += \
$(ANDROID_TARGETS): LDFLAGS += \
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
-ldl \
-llog \
-lssl \
-lcrypto
-llog
$(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): CFLAGS += \
-Wno-unknown-warning-option
$(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
@@ -1225,94 +1184,6 @@ iossimdebuggo: out/tildefriends-iossimdebug.app/tildefriends ## Build, install,
xcrun simctl launch booted com.unprompted.tildefriends
.PHONY: iossimdebuggo
ANDROID_DEPS := out/openssl/android/arm64-v8a/usr/local/lib/libssl.a
$(ANDROID_DEPS):
+@export ANDROID_NDK_ROOT=$(ANDROID_NDK)
+@export BUILD_PLATFORM=android
+@export TOOLCHAIN=$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64
+@PATH="$$TOOLCHAIN/x86_64-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=x86_64 SSL_TARGET=android-x86_64 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
+@PATH="$$TOOLCHAIN/i686-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=x86 SSL_TARGET=android-x86 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
+@PATH="$$TOOLCHAIN/arm-linux-androideabi/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=armeabi-v7a SSL_TARGET=android-arm OPTIONS="--target=armv7a-linux-androideabi -Wl,--fix-cortex-a8 -D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
+@PATH="$$TOOLCHAIN/aarch64-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=arm64-v8a SSL_TARGET=android-arm64 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
$(filter $(BUILD_DIR)/android%,$(APP_OBJS)): | $(ANDROID_DEPS)
ifeq ($(UNAME_S),Linux)
ifneq ($(USE_SYSTEM_SSL),1)
LOCAL_DEPS := out/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a
$(LOCAL_DEPS):
+@tools/ssl-local
$(filter $(BUILD_DIR)/debug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/release/%,$(APP_OBJS)): | $(LOCAL_DEPS)
endif
ifeq ($(HAVE_CROSS_AARCH64),1)
LOCAL_DEPS := out/openssl/$(UNAME_S)/aarch64/usr/local/lib/libssl.a
$(LOCAL_DEPS):
+@OPTIONS="--cross-compile-prefix=aarch64-linux-gnu-" BUILD_TARGET=aarch64 SSL_TARGET=linux-aarch64 tools/ssl-local
$(filter $(BUILD_DIR)/armdebug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/armrelease/%,$(APP_OBJS)): | $(LOCAL_DEPS)
endif
ifeq ($(HAVE_LINUX_IOS),1)
LOCAL_DEPS := out/openssl/$(UNAME_S)/ios64-cross/usr/local/lib/libssl.a
$(LOCAL_DEPS):
+@PATH=deps/ios_toolchain/target/bin:$$PATH \
BUILD_TARGET=ios64-cross \
SSL_TARGET=ios64-cross \
CROSS_COMPILE=../../deps/ios_toolchain/target/bin/arm-apple-darwin11- \
CROSS_TOP=../../deps/ios_toolchain/target \
CROSS_SDK=iPhoneOS18.2.sdk \
CC=clang \
OPTIONS=-miphoneos-version-min=$(IPHONEOS_VERSION_MIN) \
tools/ssl-local
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(LOCAL_DEPS)
endif
ifeq ($(HAVE_LINUX_MACOS),1)
LOCAL_DEPS := out/openssl/$(UNAME_S)/macos-arm/usr/local/lib/libssl.a
$(LOCAL_DEPS):
+@PATH=../../deps/macos_toolchain/bin:$$PATH \
BUILD_TARGET=macos-arm \
SSL_TARGET=darwin64-arm64 \
CC=../../deps/macos_toolchain/bin/oa64-clang \
RANLIB=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ranlib \
AR=../../deps/macos_toolchain/bin/arm64-apple-darwin24-ar \
tools/ssl-local
$(filter $(BUILD_DIR)/macosrelease-arm/% $(BUILD_DIR)/macosdebug-arm/%,$(APP_OBJS)): | $(LOCAL_DEPS)
LOCAL_DEPS := out/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib/libssl.a
$(LOCAL_DEPS):
+@PATH=../../deps/macos_toolchain/bin:$$PATH \
BUILD_TARGET=macos-x86_64 \
SSL_TARGET=darwin64-x86_64 \
CC=../../deps/macos_toolchain/bin/o64-clang \
RANLIB=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ranlib \
AR=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ar \
tools/ssl-local
$(filter $(BUILD_DIR)/macosrelease-x86_64/% $(BUILD_DIR)/macosdebug-x86_64/%,$(APP_OBJS)): | $(LOCAL_DEPS)
endif
endif
ifeq ($(UNAME_S),Darwin)
LOCAL_DEPS := out/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a
$(LOCAL_DEPS):
+@tools/ssl-local
$(filter $(BUILD_DIR)/debug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/release/%,$(APP_OBJS)): | $(LOCAL_DEPS)
endif
ifeq ($(HAVE_WIN),1)
WINDOWS_DEPS := out/openssl/$(UNAME_S)/mingw64/usr/local/lib/libssl.a
$(WINDOWS_DEPS):
+@BUILD_TARGET=mingw64 SSL_TARGET=mingw64 OPTIONS="--cross-compile-prefix=x86_64-w64-mingw32-" tools/ssl-local
$(filter $(BUILD_DIR)/win%,$(APP_OBJS)): | $(WINDOWS_DEPS)
endif
ifeq ($(UNAME_S),Darwin)
IOS_DEPS := out/openssl/ios/ios64-xcrun/usr/local/lib/libssl.a
$(IOS_DEPS):
+@BUILD_PLATFORM=ios BUILD_TARGET=ios64-xcrun SSL_TARGET=ios64-xcrun OPTIONS="-fPIC -Wno-macro-redefined -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)" tools/ssl-local
+@BUILD_PLATFORM=ios BUILD_TARGET=iossimulator-xcrun SSL_TARGET=iossimulator-xcrun OPTIONS="-fPIC -Wno-macro-redefined" tools/ssl-local
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
endif
out/macos%/tildefriends: out/macos%-arm/tildefriends out/macos%-x86_64/tildefriends
@echo [lipo] $@
@mkdir -p $(@D)

View File

@@ -38,8 +38,6 @@ dependencies in the right places.
### Requirements
System OpenSSL libraries are assumed to be available on Haiku and OpenBSD.
On MacOS, Xcode's command-line tools are expected to be available.
### Build Commands

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦀",
"previous": "&ULwb8udTxSkqf+mcZ0NxJs6p/hGB03eQEEKzJvWO3fk=.sha256"
"previous": "&ceivZzkWd2Bvvdzdvdv68bZm2Y9oC0J352C78ZmMMU4=.sha256"
}

View File

@@ -1,6 +1,6 @@
import * as tfrpc from '/static/tfrpc.js';
import {html, render} from './lit-all.min.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
let g_emojis;
@@ -140,6 +140,9 @@ export async function picker(callback, anchor, author, recent) {
<style>
${styles}
</style>
<style>
${generate_theme()}
</style>
<div
class="w3-modal"
style="display: block; box-sizing: border-box; z-index: 10"

View File

@@ -12,12 +12,14 @@ import * as tf_tab_news from './tf-tab-news.js';
import * as tf_tab_news_feed from './tf-tab-news-feed.js';
import * as tf_tab_search from './tf-tab-search.js';
import * as tf_tab_connections from './tf-tab-connections.js';
import * as tf_tab_query from './tf-tab-query.js';
import * as tf_tag from './tf-tag.js';
import * as tf_styles from './tf-styles.js';
window.addEventListener('load', function () {
let style = document.createElement('style');
style.innerText = tf_styles.styles;
Promise.resolve(tf_styles.generate_theme()).then(function (x) {
style.innerText += x;
});
document.body.appendChild(style);
});

View File

@@ -1,6 +1,6 @@
import {LitElement, html, css, guard, until} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfElement extends LitElement {
static get properties() {
@@ -206,8 +206,6 @@ class TfElement extends LitElement {
this.tab = 'search';
} else if (this.hash === '#connections') {
this.tab = 'connections';
} else if (this.hash.startsWith('#sql=')) {
this.tab = 'query';
} else {
this.tab = 'news';
}
@@ -736,17 +734,6 @@ class TfElement extends LitElement {
: null}
></tf-tab-search>
`;
} else if (this.tab === 'query') {
return html`
<tf-tab-query
.following=${this.following}
whoami=${this.whoami}
.users=${this.users}
query=${this.hash?.startsWith('#sql=')
? this.hash.substring(5)
: null}
></tf-tab-query>
`;
}
}
@@ -757,8 +744,6 @@ class TfElement extends LitElement {
await tfrpc.rpc.setHash('#');
} else if (tab === 'connections') {
await tfrpc.rpc.setHash('#connections');
} else if (tab === 'query') {
await tfrpc.rpc.setHash('#sql=');
}
}
@@ -778,6 +763,17 @@ class TfElement extends LitElement {
}
}
async pick_color() {
let input = document.createElement('input');
input.type = 'color';
input.value = (await tfrpc.rpc.localStorageGet('color')) ?? '#ff0000';
input.addEventListener('change', async function () {
await tfrpc.rpc.localStorageSet('color', input.value);
window.location.reload();
});
input.click();
}
render() {
let self = this;
@@ -792,7 +788,6 @@ class TfElement extends LitElement {
'📰': 'news',
'📡': 'connections',
'🔍': 'search',
'👩‍💻': 'query',
};
let tabs = html`
@@ -835,6 +830,12 @@ class TfElement extends LitElement {
</button>
`
)}
<button
class="w3-bar-item w3-button w3-right"
@click=${this.pick_color}
>
🎨<span class="w3-hide-small">Color</span>
</button>
</div>
`;
let contents = this.guest
@@ -870,6 +871,9 @@ class TfElement extends LitElement {
`
: undefined;
return html`
<style>
${generate_theme()}
</style>
<div
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
class="w3-theme-dark"

View File

@@ -1,7 +1,7 @@
import {LitElement, html, unsafeHTML, live} from './lit-all.min.js';
import * as tfutils from './tf-utils.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
import Tribute from './tribute.esm.js';
class TfComposeElement extends LitElement {
@@ -603,6 +603,9 @@ class TfComposeElement extends LitElement {
🔐 Encrypt
</button>`;
let result = html`
<style>
${generate_theme()}
</style>
<style>
.w3-input:empty::before {
content: attr(placeholder);

View File

@@ -4,12 +4,14 @@ import {
html,
repeat,
render,
unsafeCSS,
unsafeHTML,
until,
} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js';
import * as emojis from './emojis.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfMessageElement extends LitElement {
static get properties() {
@@ -326,7 +328,9 @@ class TfMessageElement extends LitElement {
}
expanded_key() {
return this.message?.id || this.messages?.map((x) => x.id).join(':');
return (
this.message?.id || this.message?.messages?.map((x) => x.id).join(':')
);
}
set_expanded(expanded, tag) {
@@ -365,32 +369,34 @@ class TfMessageElement extends LitElement {
`;
} else {
return html` <ul class="w3-container w3-margin-bottom w3-ul w3-card-4">
${repeat(
this.message.child_messages || [],
(x) => x.id,
(x) =>
html`<li style="padding: 0">
<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
depth=${this.depth + 1}
></tf-message>
</li>`
)}
</ul>
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse
</button>`;
${repeat(
this.message.child_messages || [],
(x) => x.id,
(x) =>
html`<li style="padding: 0">
<tf-message
.message=${x}
whoami=${this.whoami}
.users=${this.users}
.drafts=${this.drafts}
.expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
.recent_reactions=${this.recent_reactions}
depth=${this.depth + 1}
></tf-message>
</li>`
)}
<li style="padding: 0" class="w3-margin-bottom">
<button
class="w3-button w3-theme-d1 w3-block w3-bar"
style="box-sizing: border-box"
@click=${() => self.set_expanded(false)}
>
Collapse
</button>
</li>
</ul>`;
}
} else {
return undefined;
@@ -620,15 +626,17 @@ class TfMessageElement extends LitElement {
let sorted = this.message.messages
.map((x) => [
x.author,
x.content.blocking !== undefined
? x.content.blocking
? 'is blocking'
: 'is no longer blocking'
: x.content.following !== undefined
? x.content.following
? 'is following'
: 'is no longer following'
: '',
x.content.following && x.content.blocking
? 'is following and blocking'
: x.content.following
? 'is following'
: x.content.blocking
? 'is blocking'
: x.content.blocking !== undefined
? 'is no longer blocking'
: x.content.following !== undefined
? 'is no longer following'
: '',
x.content.contact,
x,
])
@@ -695,7 +703,7 @@ class TfMessageElement extends LitElement {
: undefined;
}
render() {
_render() {
let content = this.message?.content;
if (this.message?.decrypted?.type == 'post') {
content = this.message.decrypted;
@@ -1089,6 +1097,15 @@ class TfMessageElement extends LitElement {
return this.render_small_frame(this.render_raw());
}
}
render() {
return html`
<style>
${generate_theme()}
</style>
${this._render()}
`;
}
}
customElements.define('tf-message', TfMessageElement);

View File

@@ -1,6 +1,6 @@
import {LitElement, html, unsafeHTML, repeat, until} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfNewsElement extends LitElement {
static get properties() {
@@ -231,6 +231,9 @@ class TfNewsElement extends LitElement {
}
}
return html`
<style>
${generate_theme()}
</style>
<div>
${repeat(
final_messages,

View File

@@ -1,7 +1,7 @@
import {LitElement, html, until, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfProfileElement extends LitElement {
static get properties() {
@@ -316,7 +316,9 @@ class TfProfileElement extends LitElement {
}
image = this.editing?.image ?? image;
let description = this.editing?.description ?? profile.description;
return html`<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
return html`
<style>${generate_theme()}</style>
<div class="w3-card-4 w3-container w3-theme-d3" style="box-sizing: border-box">
<header class="w3-container">
<p><tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)} in ${this.sequence} messages)</p>
</header>

View File

@@ -1,5 +1,5 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfReactionsModalElement extends LitElement {
static get properties() {
@@ -24,50 +24,57 @@ class TfReactionsModalElement extends LitElement {
render() {
let self = this;
return this.votes?.length
? html` <div
class="w3-modal w3-animate-opacity"
style="display: block; box-sizing: border-box; z-index: 10"
@click=${this.clear}
>
? html` <style>
${generate_theme()}
</style>
<div
class="w3-modal-content w3-card-4 w3-theme-d1"
onclick="event.stopPropagation()"
class="w3-modal w3-animate-opacity"
style="display: block; box-sizing: border-box; z-index: 10"
@click=${this.clear}
>
<div class="w3-container w3-padding">
<header class="w3-container">
<h2>Reactions</h2>
<span class="w3-button w3-display-topright" @click=${this.clear}
>&times;</span
>
</header>
<ul class="w3-theme-dark w3-container w3-ul">
${this.votes
.sort((x, y) => y.timestamp - x.timestamp)
.map(
(x) => html`
<li style="display: flex; flex-direction: row; gap: 4px">
<span style="flex-basis: 3em"
>${x?.content?.vote?.expression}</span
<div
class="w3-modal-content w3-card-4 w3-theme-d1"
onclick="event.stopPropagation()"
>
<div class="w3-container w3-padding">
<header class="w3-container">
<h2>Reactions</h2>
<span
class="w3-button w3-display-topright"
@click=${this.clear}
>&times;</span
>
</header>
<ul class="w3-theme-dark w3-container w3-ul">
${this.votes
.sort((x, y) => y.timestamp - x.timestamp)
.map(
(x) => html`
<li
style="display: flex; flex-direction: row; gap: 4px"
>
<tf-user
style="flex: 1 1"
id=${x.author}
.users=${this.users}
></tf-user>
<span
style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
>${new Date(x?.timestamp).toLocaleString()}</span
>
</li>
`
)}
</ul>
<footer class="w3-container w3-padding">
<button class="w3-button" @click=${this.clear}>Close</button>
</footer>
<span style="flex-basis: 3em"
>${x?.content?.vote?.expression}</span
>
<tf-user
style="flex: 1 1"
id=${x.author}
.users=${this.users}
></tf-user>
<span
style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis"
>${new Date(x?.timestamp).toLocaleString()}</span
>
</li>
`
)}
</ul>
<footer class="w3-container w3-padding">
<button class="w3-button" @click=${this.clear}>Close</button>
</footer>
</div>
</div>
</div>
</div>`
</div>`
: undefined;
}
}

View File

@@ -1,4 +1,5 @@
import {css, unsafeCSS} from './lit-all.min.js';
import {css, unsafeCSS, until} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
const tf = css`
img {
@@ -408,16 +409,8 @@ function is_dark(hex, value) {
return (r * 299 + g * 587 + b * 114) / 1000 < value;
}
function generated() {
let now = new Date();
let k_color = rgb_to_hex([
(now.getDay() * 128) / 6,
(now.getHours() * 128) / 23,
(now.getSeconds() * 128) / 59,
]);
//let k_color = '#034f84';
//let k_color = rgb_to_hex([Math.random() * 256, Math.random() * 256, Math.random() * 256]);
let [r, g, b] = hex_to_rgb(k_color);
export function generate(color) {
let [r, g, b] = hex_to_rgb(color);
let [h, s, l] = rgb_to_hsl(r, g, b);
let theme1 = {
@@ -461,4 +454,28 @@ function generated() {
return unsafeCSS(result);
}
export let styles = [tf, w3, generated()];
let g_theme;
export function generate_theme() {
return g_theme
? g_theme
: until(
tfrpc.rpc.localStorageGet('color').then(function (value) {
g_theme = generate(value ?? '#034f84');
return g_theme;
}),
generated_now()
);
}
function generated_now() {
let now = new Date();
return generate(
rgb_to_hex([
(now.getDay() * 128) / 6,
(now.getHours() * 128) / 23,
(now.getSeconds() * 128) / 59,
])
);
}
export let styles = [tf, w3];

View File

@@ -1,6 +1,6 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfTabConnectionsElement extends LitElement {
static get properties() {
@@ -251,6 +251,9 @@ class TfTabConnectionsElement extends LitElement {
render() {
let self = this;
return html`
<style>
${generate_theme()}
</style>
<div class="w3-container" style="box-sizing: border-box">
<h2>New Connection</h2>
<textarea class="w3-input w3-theme-d1" id="code"></textarea>

View File

@@ -1,6 +1,6 @@
import {LitElement, cache, html, unsafeHTML, until} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfTabNewsFeedElement extends LitElement {
static get properties() {
@@ -256,14 +256,24 @@ class TfTabNewsFeedElement extends LitElement {
let t0 = new Date();
let initial_messages = await tfrpc.rpc.query(
`
WITH
channels AS (SELECT '#' || value AS value FROM json_each(?5))
SELECT TRUE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
messages.content ->> 'type' != 'vote'
FROM messages
JOIN json_each(?1) AS following ON messages.author = following.value
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
messages.content ->> 'type' != 'vote' AND
(messages.content ->> 'channel' IS NULL OR ('#' || (messages.content ->> 'channel')) NOT IN (SELECT * FROM channels)) AND
NOT EXISTS (SELECT * FROM messages_refs JOIN channels ON messages_refs.message = messages.id AND messages_refs.ref = channels.value)
ORDER BY timestamp DESC LIMIT ?4
`,
[JSON.stringify(this.following), start_time, end_time, k_max_results]
[
JSON.stringify(this.following),
start_time,
end_time,
k_max_results,
JSON.stringify(Object.keys(this.channels_latest)),
]
);
let t1 = new Date();
result = await this._fetch_related_messages(initial_messages);
@@ -396,6 +406,9 @@ class TfTabNewsFeedElement extends LitElement {
this._private_messages =
JSON.stringify(this.private_messages) +
JSON.stringify(this.grouped_private_messages);
this._channels_latest = JSON.stringify(
Object.keys(this.channels_latest ?? {})
);
let now = new Date().valueOf();
let start_time = now - 24 * 60 * 60 * 1000;
this.start_time = start_time;
@@ -471,7 +484,9 @@ class TfTabNewsFeedElement extends LitElement {
this._messages_following !== JSON.stringify(this.following) ||
this._private_messages !==
JSON.stringify(this.private_messages) +
JSON.stringify(this.grouped_private_messages)
JSON.stringify(this.grouped_private_messages) ||
this._channels_latest !==
JSON.stringify(Object.keys(this.channels_latest))
) {
console.log(
`loading messages for ${this.whoami} (following ${this.following.length})`
@@ -523,6 +538,9 @@ class TfTabNewsFeedElement extends LitElement {
`;
}
return cache(html`
<style>
${generate_theme()}
</style>
${this.unread_allowed()
? html`<button
class="w3-button w3-theme-d1"

View File

@@ -7,7 +7,7 @@ import {
until,
} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfTabNewsElement extends LitElement {
static get properties() {
@@ -211,35 +211,6 @@ class TfTabNewsElement extends LitElement {
>
&times;
</div>
${this.is_administrator
? html`
<button
class="w3-bar-item w3-button"
@click=${() =>
this.dispatchEvent(
new Event('refresh', {bubbles: true, composed: true})
)}
>
<span style="display: inline-block; width: 1.8em">↻</span>
Sync now
</button>
<button
class="w3-bar-item w3-button w3-ripple"
@click=${() =>
this.dispatchEvent(
new Event('toggle_stay_connected', {
bubbles: true,
composed: true,
})
)}
>
<span style="display: inline-block; width: 1.8em"
>${this.stay_connected ? '🔗' : '⛓️‍💥'}</span
>
${this.stay_connected ? 'Online mode' : 'Passive mode'}
</button>
`
: undefined}
${this.hash.startsWith('##') &&
this.channels.indexOf(this.hash.substring(2)) == -1
? html`
@@ -334,6 +305,21 @@ class TfTabNewsElement extends LitElement {
>
↻ Sync now
</button>
<button
class="w3-bar-item w3-button w3-ripple"
@click=${() =>
this.dispatchEvent(
new Event('toggle_stay_connected', {
bubbles: true,
composed: true,
})
)}
>
<span style="display: inline-block; width: 1.8em"
>${this.stay_connected ? '🔗' : '⛓️‍💥'}</span
>
${this.stay_connected ? 'Online mode' : 'Passive mode'}
</button>
<button
class=${'w3-bar-item w3-button' +
(this.peer_exchange !== false ? ' w3-hide' : '')}
@@ -409,6 +395,9 @@ class TfTabNewsElement extends LitElement {
</div>`;
}
return cache(html`
<style>
${generate_theme()}
</style>
${this.render_sidebar()}
<div
style="margin-left: 2in; padding: 0px; top: 0; height: 100vh; max-height: 100%; overflow: auto; contain: layout"

View File

@@ -1,136 +0,0 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
class TfTabQueryElement extends LitElement {
static get properties() {
return {
whoami: {type: String},
users: {type: Object},
following: {type: Array},
query: {type: String},
expanded: {type: Object},
results: {type: Array},
error: {type: Object},
duration: {type: Number},
};
}
static styles = styles;
constructor() {
super();
let self = this;
this.whoami = null;
this.users = {};
this.following = [];
this.expanded = {};
this.duration = undefined;
}
async search(query) {
console.log('Searching...', this.whoami, query);
this.results = [];
this.error = undefined;
this.duration = undefined;
let search = this.renderRoot.getElementById('search');
if (search) {
search.value = query;
search.focus();
}
await tfrpc.rpc.setHash('#sql=' + encodeURIComponent(query));
let start_time = new Date();
try {
this.results = await tfrpc.rpc.query(query, []);
} catch (error) {
this.error = error;
}
let end_time = new Date();
this.duration = (end_time - start_time).valueOf();
console.log('Done.');
search = this.renderRoot.getElementById('search');
if (search) {
search.value = query;
search.focus();
}
}
search_keydown(event) {
if (event.keyCode == 13 && event.ctrlKey) {
this.query = this.renderRoot.getElementById('search').value;
event.preventDefault();
}
}
on_expand(event) {
if (event.detail.expanded) {
let expand = {};
expand[event.detail.id] = true;
this.expanded = Object.assign({}, this.expanded, expand);
} else {
delete this.expanded[event.detail.id];
this.expanded = Object.assign({}, this.expanded);
}
}
render_results() {
if (!this.results?.length) {
return html`<div>No results.</div>`;
} else {
let keys = Object.keys(this.results[0]).sort();
return html`<table style="width: 100%; max-width: 100%">
<tr>
${keys.map((key) => html`<th>${key}</th>`)}
</tr>
${this.results.map(
(row) =>
html`<tr>
${keys.map((key) => html`<td>${row[key]}</td>`)}
</tr>`
)}
</table>`;
}
}
render_error() {
if (this.error) {
return html`<h2 style="color: red">${this.error.message}</h2>
<pre style="color: red">${this.error.stack}</pre>`;
}
}
render() {
if (this.query !== this.last_query) {
this.last_query = this.query;
this.search(this.query);
}
let self = this;
return html`
<div style="display: flex; flex-direction: row; gap: 4px">
<textarea
id="search"
rows="8"
class="w3-input w3-theme-d1"
style="flex: 1; resize: vertical"
@keydown=${this.search_keydown}
>
${this.query}</textarea
>
<button
class="w3-button w3-theme-d1"
@click=${(event) =>
self.search(self.renderRoot.getElementById('search').value)}
>
Execute
</button>
</div>
<div ?hidden=${this.duration === undefined}>
Took ${this.duration / 1000.0} seconds.
</div>
<div ?hidden=${this.duration !== undefined}>Executing...</div>
${this.render_error()} ${this.render_results()}
`;
}
}
customElements.define('tf-tab-query', TfTabQueryElement);

View File

@@ -1,6 +1,6 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfTabSearchElement extends LitElement {
static get properties() {
@@ -11,6 +11,9 @@ class TfTabSearchElement extends LitElement {
following: {type: Array},
query: {type: String},
expanded: {type: Object},
messages: {type: Array},
results: {type: Array},
error: {type: Object},
};
}
@@ -38,24 +41,40 @@ class TfTabSearchElement extends LitElement {
search.select();
}
await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query));
let results = await tfrpc.rpc.query(
`
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value
ORDER BY timestamp DESC limit 100
`,
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
);
console.log('Done.');
search = this.renderRoot.getElementById('search');
if (search) {
search.value = query;
search.focus();
search.select();
this.error = undefined;
this.results = [];
this.messages = [];
if (query.startsWith('sql:')) {
this.messages = [];
try {
this.results = await tfrpc.rpc.query(
query.substring('sql:'.length),
[]
);
} catch (e) {
this.results = [];
this.error = e;
}
} else {
let results = await tfrpc.rpc.query(
`
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value
ORDER BY timestamp DESC limit 100
`,
['"' + query.replace('"', '""') + '"', JSON.stringify(this.following)]
);
console.log('Done.');
search = this.renderRoot.getElementById('search');
if (search) {
search.value = query;
search.focus();
search.select();
}
this.messages = results;
}
this.renderRoot.getElementById('news').messages = results;
}
search_keydown(event) {
@@ -87,6 +106,39 @@ class TfTabSearchElement extends LitElement {
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
}
render_results() {
if (this.error) {
return html`<h2 style="color: red">${this.error.message}</h2>
<pre style="color: red">${this.error.stack}</pre>`;
} else if (this.messages?.length) {
return html`<tf-news
id="news"
whoami=${this.whoami}
.messages=${this.messages}
.users=${this.users}
.expanded=${this.expanded}
.drafts=${this.drafts}
@tf-expand=${this.on_expand}
@tf-draft=${this.draft}
></tf-news>`;
} else if (this.results?.length) {
let keys = Object.keys(this.results[0]).sort();
return html`<table style="width: 100%; max-width: 100%">
<tr>
${keys.map((key) => html`<th>${key}</th>`)}
</tr>
${this.results.map(
(row) =>
html`<tr>
${keys.map((key) => html`<td>${row[key]}</td>`)}
</tr>`
)}
</table>`;
} else {
return html`<div>No results.</div>`;
}
}
render() {
if (this.query !== this.last_query) {
this.last_query = this.query;
@@ -94,11 +146,14 @@ class TfTabSearchElement extends LitElement {
}
let self = this;
return html`
<div style="display: flex; flex-direction: row; gap: 4px">
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
<style>${generate_theme()}</style>
<div class="w3-padding">
<div style="display: flex; flex-direction: row; gap: 4px">
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input>
<button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
</div>
${this.render_results()}
</div>
<tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} .drafts=${this.drafts} @tf-expand=${this.on_expand} @tf-draft=${this.draft}></tf-news>
`;
}
}

View File

@@ -1,5 +1,5 @@
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfTagElement extends LitElement {
static get properties() {
@@ -17,11 +17,15 @@ class TfTagElement extends LitElement {
render() {
let number = this.count ? html` (${this.count})` : undefined;
return html`<a
href=${'#' + encodeURIComponent(this.tag)}
class="w3-tag w3-theme-d1 w3-round-4 w3-button"
>${this.tag}${number}</a
> `;
return html`
<style>
${generate_theme()}</style
><a
href=${'#' + encodeURIComponent(this.tag)}
class="w3-tag w3-theme-d1 w3-round-4 w3-button"
>${this.tag}${number}</a
>
`;
}
}

View File

@@ -1,6 +1,6 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
import {styles, generate_theme} from './tf-styles.js';
class TfUserElement extends LitElement {
static get properties() {
@@ -58,12 +58,15 @@ class TfUserElement extends LitElement {
/>`;
}
}
return html` <div
style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
(this.nolink ? '' : '; font-weight: bold')}
>
${image} ${name}
</div>`;
return html` <style>
${generate_theme()}
</style>
<div
style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
(this.nolink ? '' : '; font-weight: bold')}
>
${image} ${name}
</div>`;
}
}

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "👋",
"previous": "&ijyL/pyTwguBd9njagU7Vpc/1EyRermZuzrlq1mnzbY=.sha256"
"previous": "&n1QkPkB5JoduFSx8UKOY3IlZqS2GwLiTUZv4ZrEOthQ=.sha256"
}

View File

@@ -340,10 +340,6 @@
<i class="fa fa-lock w3-text-purple w3-jumbo"></i>
<p>libsodium</p>
</a>
<a href="https://github.com/openssl/openssl/releases" class="w3-col s3">
<i class="fa fa-shield-halved w3-text-green w3-jumbo"></i>
<p>OpenSSL</p>
</a>
<a
href="https://github.com/ianlancetaylor/libbacktrace"
class="w3-col s3"
@@ -351,13 +347,13 @@
<i class="fa fa-burst w3-text-pink w3-jumbo"></i>
<p>libbacktrace</p>
</a>
</div>
<div class="w3-row" style="margin-top: 64px">
<a href="https://codemirror.net/docs/changelog/" class="w3-col s3">
<i class="fa fa-keyboard w3-text-indigo w3-jumbo"></i>
<p>CodeMirror</p>
</a>
</div>
<div class="w3-row" style="margin-top: 64px">
<a href="https://github.com/jlfwong/speedscope/" class="w3-col s3">
<i class="fa fa-microscope w3-text-orange w3-jumbo"></i>
<p>Speedscope</p>
@@ -370,9 +366,6 @@
<i class="fa fa-book-atlas w3-text-purple w3-jumbo"></i>
<p>c-ares</p>
</a>
</div>
<div class="w3-row" style="margin-top: 64px">
<a href="https://www.gnu.org/software/make/" class="w3-col s3">
<i class="fa fa-hammer w3-text-teal w3-jumbo"></i>
<p>GNU Make</p>

View File

@@ -9,18 +9,17 @@
import {LitElement, html, css, svg} from '/lit/lit-all.min.js';
let cm6;
let gSocket;
let gCurrentFile;
let gFiles = {};
let gApp = {files: {}, emoji: '📦'};
let gEditor;
let gOriginalInput;
let gUnloading;
let g_socket;
let g_current_file;
let g_files = {};
let g_app = {files: {}, emoji: '📦'};
let g_editor;
let g_unloading;
let kErrorColor = '#dc322f';
let kDisconnectColor = '#f00';
let kStatusColor = '#fff';
let k_color_error = '#dc322f';
let k_color_disconnect = '#f00';
let k_color_status = '#fff';
/** \endcond */
/** Functions that server-side app code can call through the app object. */
@@ -410,7 +409,7 @@ class TfNavigationElement extends LitElement {
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<div
class="w3-bar-item"
style="color: ${this.status.color ?? kStatusColor}"
style="color: ${this.status.color ?? k_color_status}"
>
${this.status.message}
</div>
@@ -429,7 +428,7 @@ class TfNavigationElement extends LitElement {
<div class="w3-model w3-animate-top" style="position: absolute; left: 50%; transform: translate(-50%); z-index: 1">
<dijv class="w3-modal-content w3-card-4" style="display: block; padding: 1em">
<span id="close_error" @click=${self.clear_error} class="w3-button w3-display-topright">&times;</span>
<div style="color: ${this.status.color ?? kErrorColor}"><b>ERROR:</b><p id="error" style="white-space: pre">${this.status.message}</p></div>
<div style="color: ${this.status.color ?? k_color_error}"><b>ERROR:</b><p id="error" style="white-space: pre">${this.status.message}</p></div>
</div>
</div>
`
@@ -521,7 +520,7 @@ class TfFilesElement extends LitElement {
for (let file of event.dataTransfer.files) {
let buffer = await file.arrayBuffer();
let text = new TextDecoder('latin1').decode(buffer);
gFiles[file.name] = {
g_files[file.name] = {
doc: cm6.EditorState.create({
doc: text,
extensions: cm6.extensions,
@@ -529,9 +528,9 @@ class TfFilesElement extends LitElement {
buffer: buffer,
isNew: true,
};
gCurrentFile = file.name;
g_current_file = file.name;
}
openFile(gCurrentFile);
openFile(g_current_file);
updateFiles();
}
@@ -895,11 +894,11 @@ async function edit() {
: 'flex';
try {
if (!gEditor) {
if (!g_editor) {
cm6 = await import('/codemirror/cm6.js');
gEditor = cm6.TildeFriendsEditorView(document.getElementById('editor'));
g_editor = cm6.TildeFriendsEditorView(document.getElementById('editor'));
}
gEditor.onDocChange = updateFiles;
g_editor.onDocChange = updateFiles;
await load();
} catch (error) {
alert(`${error.message}\n\n${error.stack}`);
@@ -932,13 +931,13 @@ function loadFile(name, id) {
return response.text();
})
.then(function (text) {
gFiles[name].doc = cm6.EditorState.create({
g_files[name].doc = cm6.EditorState.create({
doc: text,
extensions: cm6.extensions,
});
gFiles[name].original = gFiles[name].doc.doc.toString();
if (!Object.values(gFiles).some((x) => !x.doc)) {
openFile(Object.keys(gFiles).sort()[0]);
g_files[name].original = g_files[name].doc.doc.toString();
if (!Object.values(g_files).some((x) => !x.doc)) {
openFile(Object.keys(g_files).sort()[0]);
}
});
}
@@ -956,31 +955,31 @@ async function load(path) {
} else if (response.status != 404) {
throw new Error(response.status + ' ' + response.statusText);
}
gFiles = {};
g_files = {};
let isApp = false;
let promises = [];
if (json && json['type'] == 'tildefriends-app') {
isApp = true;
Object.keys(json['files']).forEach(function (name) {
gFiles[name] = {};
g_files[name] = {};
promises.push(loadFile(name, json['files'][name]));
});
if (Object.keys(json['files']).length == 0) {
document.getElementById('editPane').style.display = 'flex';
}
gApp = json;
gApp.emoji = gApp.emoji || '📦';
document.getElementById('icon').innerHTML = gApp.emoji;
g_app = json;
g_app.emoji = g_app.emoji || '📦';
document.getElementById('icon').innerHTML = g_app.emoji;
}
if (!isApp) {
document.getElementById('editPane').style.display = 'flex';
let text = '// New script.\n';
gCurrentFile = 'app.js';
gFiles[gCurrentFile] = {
g_current_file = 'app.js';
g_files[g_current_file] = {
doc: cm6.EditorState.create({doc: text, extensions: cm6.extensions}),
};
openFile(gCurrentFile);
openFile(g_current_file);
}
return Promise.all(promises);
}
@@ -1001,13 +1000,14 @@ function closeEditor() {
*/
function save(save_to) {
document.getElementById('save').disabled = true;
if (gCurrentFile) {
gFiles[gCurrentFile].doc = gEditor.state;
if (g_current_file) {
g_files[g_current_file].doc = g_editor.state;
if (
!gFiles[gCurrentFile].isNew &&
!gFiles[gCurrentFile].doc.doc.toString() == gFiles[gCurrentFile].original
!g_files[g_current_file].isNew &&
!g_files[g_current_file].doc.doc.toString() ==
g_files[g_current_file].original
) {
delete gFiles[gCurrentFile].buffer;
delete g_files[g_current_file].buffer;
}
}
@@ -1022,8 +1022,8 @@ function save(save_to) {
}
let promises = [];
for (let name of Object.keys(gFiles)) {
let file = gFiles[name];
for (let name of Object.keys(g_files)) {
let file = g_files[name];
if (!file.isNew && file.doc.doc.toString() == file.original) {
continue;
}
@@ -1065,14 +1065,14 @@ function save(save_to) {
let app = {
type: 'tildefriends-app',
files: Object.fromEntries(
Object.keys(gFiles).map((x) => [x, gFiles[x].id || gApp.files[x]])
Object.keys(g_files).map((x) => [x, g_files[x].id || g_app.files[x]])
),
emoji: gApp.emoji || '📦',
emoji: g_app.emoji || '📦',
};
Object.values(gFiles).forEach(function (file) {
Object.values(g_files).forEach(function (file) {
delete file.id;
});
gApp = JSON.parse(JSON.stringify(app));
g_app = JSON.parse(JSON.stringify(app));
return fetch(save_path + 'save', {
method: 'POST',
@@ -1087,7 +1087,7 @@ function save(save_to) {
if (save_path != window.location.pathname) {
alert('Saved to ' + save_path + '.');
} else if (!gFiles['app.js']) {
} else if (!g_files['app.js']) {
window.location.reload();
} else {
reconnect(save_path);
@@ -1099,7 +1099,7 @@ function save(save_to) {
})
.finally(function () {
document.getElementById('save').disabled = false;
Object.values(gFiles).forEach(function (file) {
Object.values(g_files).forEach(function (file) {
file.original = file.doc.doc.toString();
});
updateFiles();
@@ -1112,8 +1112,8 @@ function save(save_to) {
function changeIcon() {
let value = prompt('Enter a new app icon emoji:');
if (value !== undefined) {
gApp.emoji = value || '📦';
document.getElementById('icon').innerHTML = gApp.emoji;
g_app.emoji = value || '📦';
document.getElementById('icon').innerHTML = g_app.emoji;
}
}
@@ -1190,9 +1190,12 @@ function api_postMessage(message) {
function api_error(error) {
if (error) {
if (typeof error == 'string') {
setStatusMessage('⚠️ ' + error, kErrorColor);
setStatusMessage('⚠️ ' + error, k_color_error);
} else {
setStatusMessage('⚠️ ' + error.message + '\n' + error.stack, kErrorColor);
setStatusMessage(
'⚠️ ' + error.message + '\n' + error.stack,
k_color_error
);
}
}
console.log('error', error);
@@ -1309,7 +1312,7 @@ function api_setHash(hash) {
*/
function _receive_websocket_message(message) {
if (message && message.action == 'session') {
setStatusMessage('🟢 Executing...', kStatusColor);
setStatusMessage('🟢 Executing...', k_color_status);
let navigation = document.getElementsByTagName('tf-navigation')[0];
navigation.credentials = message.credentials;
navigation.identities = message.identities;
@@ -1409,7 +1412,7 @@ function setStatusMessage(message, color) {
document.getElementsByTagName('tf-navigation')[0].status = {
message: message,
color: color,
is_error: color == kErrorColor,
is_error: color == k_color_error,
};
}
@@ -1419,11 +1422,11 @@ function setStatusMessage(message, color) {
*/
function send(value) {
try {
if (gSocket && gSocket.readyState == gSocket.OPEN) {
gSocket.send(JSON.stringify(value));
if (g_socket && g_socket.readyState == g_socket.OPEN) {
g_socket.send(JSON.stringify(value));
}
} catch (error) {
setStatusMessage('🤷 Send failed: ' + error.toString(), kErrorColor);
setStatusMessage('🤷 Send failed: ' + error.toString(), k_color_error);
}
}
@@ -1438,7 +1441,7 @@ function hashChange() {
* Make sure the app is connected on window focus, and notify the app.
*/
function focus() {
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
if (g_socket && g_socket.readyState == g_socket.CLOSED) {
connectSocket();
} else {
send({event: 'focus'});
@@ -1449,9 +1452,7 @@ function focus() {
* Notify the app of lost focus.
*/
function blur() {
if (gSocket && gSocket.readyState == gSocket.OPEN) {
send({event: 'blur'});
}
send({event: 'blur'});
}
/**
@@ -1508,8 +1509,8 @@ function message(event) {
* @param path The path to which the WebSocket should be connected.
*/
function reconnect(path) {
let oldSocket = gSocket;
gSocket = null;
let oldSocket = g_socket;
g_socket = null;
if (oldSocket) {
oldSocket.onopen = null;
oldSocket.onclose = null;
@@ -1524,24 +1525,24 @@ function reconnect(path) {
* @param path The path to which to connect.
*/
function connectSocket(path) {
if (!gSocket || gSocket.readyState != gSocket.OPEN) {
if (gSocket) {
gSocket.onopen = null;
gSocket.onclose = null;
gSocket.onmessage = null;
gSocket.close();
if (!g_socket || g_socket.readyState != g_socket.OPEN) {
if (g_socket) {
g_socket.onopen = null;
g_socket.onclose = null;
g_socket.onmessage = null;
g_socket.close();
}
setStatusMessage('⚪ Connecting...', kStatusColor);
gSocket = new WebSocket(
setStatusMessage('⚪ Connecting...', k_color_status);
g_socket = new WebSocket(
(window.location.protocol == 'https:' ? 'wss://' : 'ws://') +
window.location.hostname +
(window.location.port.length ? ':' + window.location.port : '') +
'/app/socket'
);
gSocket.onopen = function () {
setStatusMessage('🟡 Authenticating...', kStatusColor);
g_socket.onopen = function () {
setStatusMessage('🟡 Authenticating...', k_color_status);
let connect_path = path ?? window.location.pathname;
gSocket.send(
g_socket.send(
JSON.stringify({
action: 'hello',
path: connect_path,
@@ -1553,12 +1554,12 @@ function connectSocket(path) {
})
);
};
gSocket.onmessage = function (event) {
g_socket.onmessage = function (event) {
_receive_websocket_message(JSON.parse(event.data));
};
gSocket.onclose = function (event) {
if (gUnloading) {
setStatusMessage('⚪ Closing...', kStatusColor);
g_socket.onclose = function (event) {
if (g_unloading) {
setStatusMessage('⚪ Closing...', k_color_status);
} else {
const k_codes = {
1000: 'Normal closure',
@@ -1579,7 +1580,7 @@ function connectSocket(path) {
};
setStatusMessage(
'🔴 Closed: ' + (k_codes[event.code] || event.code),
kDisconnectColor
k_color_disconnect
);
}
};
@@ -1592,24 +1593,24 @@ function connectSocket(path) {
*/
function openFile(name) {
let newDoc =
name && gFiles[name]
? gFiles[name].doc
name && g_files[name]
? g_files[name].doc
: cm6.EditorState.create({doc: '', extensions: cm6.extensions});
let oldDoc = gEditor.state;
gEditor.setState(newDoc);
let oldDoc = g_editor.state;
g_editor.setState(newDoc);
if (gFiles[gCurrentFile]) {
gFiles[gCurrentFile].doc = oldDoc;
if (g_files[g_current_file]) {
g_files[g_current_file].doc = oldDoc;
if (
!gFiles[gCurrentFile].isNew &&
gFiles[gCurrentFile].doc.doc.toString() == oldDoc.doc.toString()
!g_files[g_current_file].isNew &&
g_files[g_current_file].doc.doc.toString() == oldDoc.doc.toString()
) {
delete gFiles[gCurrentFile].buffer;
delete g_files[g_current_file].buffer;
}
}
gCurrentFile = name;
g_current_file = name;
updateFiles();
gEditor.focus();
g_editor.focus();
}
/**
@@ -1619,19 +1620,19 @@ function updateFiles() {
let files = document.getElementsByTagName('tf-files-pane')[0];
if (files) {
files.files = Object.fromEntries(
Object.keys(gFiles).map((file) => [
Object.keys(g_files).map((file) => [
file,
{
clean:
(file == gCurrentFile
? gEditor.state.doc.toString()
: gFiles[file].doc.doc.toString()) == gFiles[file].original,
(file == g_current_file
? g_editor.state.doc.toString()
: g_files[file].doc.doc.toString()) == g_files[file].original,
},
])
);
files.current = gCurrentFile;
files.current = g_current_file;
}
gEditor.focus();
g_editor.focus();
}
/**
@@ -1639,7 +1640,7 @@ function updateFiles() {
* @param name The file's name.
*/
function makeNewFile(name) {
gFiles[name] = {
g_files[name] = {
doc: cm6.EditorState.create({extensions: cm6.extensions}),
};
openFile(name);
@@ -1650,7 +1651,7 @@ function makeNewFile(name) {
*/
function newFile() {
let name = prompt('Name of new file:', 'file.js');
if (name && !gFiles[name]) {
if (name && !g_files[name]) {
makeNewFile(name);
}
}
@@ -1659,9 +1660,9 @@ function newFile() {
* Prompt to remove a file.
*/
function removeFile() {
if (confirm('Remove ' + gCurrentFile + '?')) {
delete gFiles[gCurrentFile];
openFile(Object.keys(gFiles)[0]);
if (confirm('Remove ' + g_current_file + '?')) {
delete g_files[g_current_file];
openFile(Object.keys(g_files)[0]);
}
}
@@ -1677,13 +1678,13 @@ async function appExport() {
`${name}.json`,
JSON.stringify({
type: 'tildefriends-app',
emoji: gApp.emoji || '📦',
emoji: g_app.emoji || '📦',
})
);
for (let file of Object.keys(gFiles)) {
for (let file of Object.keys(g_files)) {
zip.file(
`${name}/${file}`,
gFiles[file].buffer ?? gFiles[file].doc.doc.toString()
g_files[file].buffer ?? g_files[file].doc.doc.toString()
);
}
let content = await zip.generateAsync({
@@ -1802,9 +1803,9 @@ async function sourcePretty() {
let babel = (await import('/prettier/babel.mjs')).default;
let estree = (await import('/prettier/estree.mjs')).default;
let prettier_html = (await import('/prettier/html.mjs')).default;
let source = gEditor.state.doc.toString();
let source = g_editor.state.doc.toString();
let formatted = await prettier.format(source, {
parser: gCurrentFile?.toLowerCase()?.endsWith('.html') ? 'html' : 'babel',
parser: g_current_file?.toLowerCase()?.endsWith('.html') ? 'html' : 'babel',
plugins: [babel, estree, prettier_html],
trailingComma: 'es5',
useTabs: true,
@@ -1813,10 +1814,10 @@ async function sourcePretty() {
bracketSpacing: false,
});
if (source !== formatted) {
gEditor.dispatch({
g_editor.dispatch({
changes: {
from: 0,
to: gEditor.state.doc.length,
to: g_editor.state.doc.length,
insert: formatted,
},
});
@@ -1861,7 +1862,7 @@ window.addEventListener('load', function () {
window.addEventListener('message', message, false);
window.addEventListener('online', connectSocket);
window.addEventListener('beforeunload', function () {
gUnloading = true;
g_unloading = true;
});
document.getElementById('name').value = window.location.pathname;
document

View File

@@ -178,6 +178,7 @@ async function getProcessBlob(blobId, key, options) {
process.task = new Task();
process.packageOwner = options.packageOwner;
process.packageName = options.packageName;
process.url = options?.url;
process.eventHandlers = {};
if (!options?.script || options?.script === 'app.js') {
process.app = new app.App();
@@ -191,7 +192,6 @@ async function getProcessBlob(blobId, key, options) {
});
gProcesses[key] = process;
process.task.onExit = function (exitCode, terminationSignal) {
broadcastEvent('onSessionEnd', [getUser(process, process)]);
process.task = null;
delete gProcesses[key];
};
@@ -199,13 +199,6 @@ async function getProcessBlob(blobId, key, options) {
core: {
broadcast: broadcast.bind(process),
user: getUser(process, process),
users: async function () {
try {
return JSON.parse(await new Database('auth').get('users'));
} catch {
return [];
}
},
permissionsGranted: async function () {
let user = process?.credentials?.session?.name;
let settings = await loadSettings();
@@ -235,10 +228,6 @@ async function getProcessBlob(blobId, key, options) {
return settings.userPermissions[user];
}
},
permissionsForUser: async function (user) {
let settings = await loadSettings();
return settings?.permissions?.[user] ?? [];
},
permissionTest: async function (permission) {
let user = process?.credentials?.session?.name;
let settings = await loadSettings();
@@ -299,11 +288,6 @@ async function getProcessBlob(blobId, key, options) {
throw Error(`Permission denied: ${permission}.`);
}
},
app: {
owner: options?.packageOwner,
name: options?.packageName,
},
url: options?.url,
},
};
process.sendIdentities = async function () {
@@ -355,7 +339,7 @@ async function getProcessBlob(blobId, key, options) {
options.packageName,
'setActiveIdentity',
[
await ssb.getActiveIdentity(
await imports.ssb.getActiveIdentity(
process.credentials?.session?.name,
options.packageOwner,
options.packageName
@@ -368,19 +352,6 @@ async function getProcessBlob(blobId, key, options) {
}
};
if (process.credentials?.permissions?.administration) {
imports.core.globalSettingsDescriptions = async function () {
let settings = Object.assign({}, defaultGlobalSettings());
for (let [key, value] of Object.entries(await loadSettings())) {
if (settings[key]) {
settings[key].value = value;
}
}
return settings;
};
imports.core.globalSettingsGet = async function (key) {
let settings = await loadSettings();
return settings?.[key];
};
imports.core.globalSettingsSet = async function (key, value) {
await imports.core.permissionTest('set_global_setting');
print('Setting', key, value);
@@ -462,26 +433,6 @@ async function getProcessBlob(blobId, key, options) {
}
};
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
imports.ssb.getActiveIdentity = () =>
ssb.getActiveIdentity(
process.credentials?.session?.name,
options.packageOwner,
options.packageName
);
imports.ssb.getOwnerIdentities = function () {
if (options.packageOwner) {
return ssb.getIdentities(options.packageOwner);
}
};
imports.ssb.getIdentities = function () {
if (
process.credentials &&
process.credentials.session &&
process.credentials.session.name
) {
return ssb.getIdentities(process.credentials.session.name);
}
};
imports.ssb.getPrivateKey = function (id) {
if (
process.credentials &&
@@ -654,7 +605,6 @@ async function getProcessBlob(blobId, key, options) {
} catch (e) {
printError(e);
}
broadcastEvent('onSessionBegin', [getUser(process, process)]);
if (process.app) {
process.app.send({action: 'ready', version: version()});
await process.sendPermissions();

File diff suppressed because one or more lines are too long

236
deps/codemirror_src/package-lock.json generated vendored
View File

@@ -31,9 +31,9 @@
}
},
"node_modules/@codemirror/commands": {
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.9.0.tgz",
"integrity": "sha512-454TVgjhO6cMufsyyGN70rGIfJxJEjcqjBG2x2Y03Y/+Fm99d3O/Kv1QDYWuG6hvxsgmjXmBuATikIIYvERX+w==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@@ -56,9 +56,9 @@
}
},
"node_modules/@codemirror/lang-html": {
"version": "6.4.10",
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.10.tgz",
"integrity": "sha512-h/SceTVsN5r+WE+TVP2g3KDvNoSzbSrtZXCKo4vkKdbfT5t4otuVgngGdFukOO/rwRD2++pCxoh6xD4TEVMkQA==",
"version": "6.4.11",
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
@@ -69,7 +69,7 @@
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0",
"@lezer/css": "^1.1.0",
"@lezer/html": "^1.3.0"
"@lezer/html": "^1.3.12"
}
},
"node_modules/@codemirror/lang-javascript": {
@@ -112,9 +112,9 @@
}
},
"node_modules/@codemirror/lint": {
"version": "6.8.5",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.0.tgz",
"integrity": "sha512-wZxW+9XDytH3SKvS8cQzMyQCaaazH8XL1EMHleHe00wVzsv7NBQKVW2yzEHrRhmM7ZOhVdItPbvlRBvMp9ej7A==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
@@ -155,9 +155,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.38.4",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.4.tgz",
"integrity": "sha512-hduz0suCcUSC/kM8Fq3A9iLwInJDl8fD1xLpTIk+5xkNm8z/FT7UsIa9sOXrkpChh+XXc18RzswE8QqELsVl+g==",
"version": "6.38.6",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
@@ -217,9 +217,9 @@
}
},
"node_modules/@lezer/common": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.3.0.tgz",
"integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==",
"license": "MIT"
},
"node_modules/@lezer/css": {
@@ -234,12 +234,12 @@
}
},
"node_modules/@lezer/highlight": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.2.tgz",
"integrity": "sha512-z8TQwaBXXQIvG6i2g3e9cgMwUUXu9Ib7jo2qRRggdhwKpM56Dw3PM3wmexn+EGaaOZ7az0K7sjc3/gcGW7sz7A==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
"@lezer/common": "^1.3.0"
}
},
"node_modules/@lezer/html": {
@@ -360,9 +360,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
"integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
"integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
"cpu": [
"arm"
],
@@ -373,9 +373,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
"integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz",
"integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==",
"cpu": [
"arm64"
],
@@ -386,9 +386,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
"integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz",
"integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==",
"cpu": [
"arm64"
],
@@ -399,9 +399,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
"integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
"integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==",
"cpu": [
"x64"
],
@@ -412,9 +412,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
"integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz",
"integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==",
"cpu": [
"arm64"
],
@@ -425,9 +425,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
"integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz",
"integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==",
"cpu": [
"x64"
],
@@ -438,9 +438,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
"integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz",
"integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==",
"cpu": [
"arm"
],
@@ -451,9 +451,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
"integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz",
"integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==",
"cpu": [
"arm"
],
@@ -464,9 +464,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
"integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz",
"integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==",
"cpu": [
"arm64"
],
@@ -477,9 +477,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
"integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz",
"integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==",
"cpu": [
"arm64"
],
@@ -490,9 +490,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
"integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz",
"integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==",
"cpu": [
"loong64"
],
@@ -503,9 +503,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
"integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz",
"integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==",
"cpu": [
"ppc64"
],
@@ -516,9 +516,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
"integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz",
"integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==",
"cpu": [
"riscv64"
],
@@ -529,9 +529,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
"integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz",
"integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==",
"cpu": [
"riscv64"
],
@@ -542,9 +542,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
"integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz",
"integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==",
"cpu": [
"s390x"
],
@@ -555,9 +555,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz",
"integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==",
"cpu": [
"x64"
],
@@ -568,9 +568,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz",
"integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==",
"cpu": [
"x64"
],
@@ -581,9 +581,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
"integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz",
"integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==",
"cpu": [
"arm64"
],
@@ -594,9 +594,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
"integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz",
"integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==",
"cpu": [
"arm64"
],
@@ -607,9 +607,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
"integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz",
"integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==",
"cpu": [
"ia32"
],
@@ -620,9 +620,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
"integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz",
"integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==",
"cpu": [
"x64"
],
@@ -633,9 +633,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz",
"integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz",
"integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==",
"cpu": [
"x64"
],
@@ -805,12 +805,12 @@
}
},
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.0",
"is-core-module": "^2.16.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
@@ -825,9 +825,9 @@
}
},
"node_modules/rollup": {
"version": "4.52.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz",
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
"version": "4.52.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -840,28 +840,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.3",
"@rollup/rollup-android-arm64": "4.52.3",
"@rollup/rollup-darwin-arm64": "4.52.3",
"@rollup/rollup-darwin-x64": "4.52.3",
"@rollup/rollup-freebsd-arm64": "4.52.3",
"@rollup/rollup-freebsd-x64": "4.52.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
"@rollup/rollup-linux-arm-musleabihf": "4.52.3",
"@rollup/rollup-linux-arm64-gnu": "4.52.3",
"@rollup/rollup-linux-arm64-musl": "4.52.3",
"@rollup/rollup-linux-loong64-gnu": "4.52.3",
"@rollup/rollup-linux-ppc64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-gnu": "4.52.3",
"@rollup/rollup-linux-riscv64-musl": "4.52.3",
"@rollup/rollup-linux-s390x-gnu": "4.52.3",
"@rollup/rollup-linux-x64-gnu": "4.52.3",
"@rollup/rollup-linux-x64-musl": "4.52.3",
"@rollup/rollup-openharmony-arm64": "4.52.3",
"@rollup/rollup-win32-arm64-msvc": "4.52.3",
"@rollup/rollup-win32-ia32-msvc": "4.52.3",
"@rollup/rollup-win32-x64-gnu": "4.52.3",
"@rollup/rollup-win32-x64-msvc": "4.52.3",
"@rollup/rollup-android-arm-eabi": "4.52.5",
"@rollup/rollup-android-arm64": "4.52.5",
"@rollup/rollup-darwin-arm64": "4.52.5",
"@rollup/rollup-darwin-x64": "4.52.5",
"@rollup/rollup-freebsd-arm64": "4.52.5",
"@rollup/rollup-freebsd-x64": "4.52.5",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.5",
"@rollup/rollup-linux-arm-musleabihf": "4.52.5",
"@rollup/rollup-linux-arm64-gnu": "4.52.5",
"@rollup/rollup-linux-arm64-musl": "4.52.5",
"@rollup/rollup-linux-loong64-gnu": "4.52.5",
"@rollup/rollup-linux-ppc64-gnu": "4.52.5",
"@rollup/rollup-linux-riscv64-gnu": "4.52.5",
"@rollup/rollup-linux-riscv64-musl": "4.52.5",
"@rollup/rollup-linux-s390x-gnu": "4.52.5",
"@rollup/rollup-linux-x64-gnu": "4.52.5",
"@rollup/rollup-linux-x64-musl": "4.52.5",
"@rollup/rollup-openharmony-arm64": "4.52.5",
"@rollup/rollup-win32-arm64-msvc": "4.52.5",
"@rollup/rollup-win32-ia32-msvc": "4.52.5",
"@rollup/rollup-win32-x64-gnu": "4.52.5",
"@rollup/rollup-win32-x64-msvc": "4.52.5",
"fsevents": "~2.3.2"
}
},
@@ -925,9 +925,9 @@
}
},
"node_modules/style-mod": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
"license": "MIT"
},
"node_modules/supports-preserve-symlinks-flag": {

1
deps/openssl_src vendored

Submodule deps/openssl_src deleted from 7b371d80d9

View File

@@ -11,7 +11,7 @@
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
</head>
<body>
<script src="speedscope-HCR63FMT.js"></script>
<script src="speedscope-432XE7GS.js"></script>

View File

@@ -1,3 +1,3 @@
speedscope@1.23.1
Mon Aug 11 11:43:09 PDT 2025
0cec0f82c334aed6cf19d43cabeadcda0d95e0fc
speedscope@1.24.0
Mon Oct 20 18:11:29 PDT 2025
fc76932551754a442cd5c4f0afdba28032d14d8a

File diff suppressed because one or more lines are too long

View File

@@ -41,7 +41,6 @@ options:
ssb_port (default: 8008): Port on which to listen for SSB secure handshake connections.
http_local_only (default: false): Whether to bind http(s) to the loopback address. Otherwise any.
http_port (default: 12345): Port on which to listen for HTTP connections.
https_port (default: 0): Port on which to listen for secure HTTP connections.
out_http_port_file (default: ""): File to which to write bound HTTP port.
blob_fetch_age_seconds (default: -1): Only blobs mentioned more recently than this age will be automatically fetched.
blob_expire_age_seconds (default: -1): Blobs older than this will be automatically deleted.

View File

@@ -0,0 +1,8 @@
* Remove OpenSSL.
* Exclude messages for subscribed channels from the general feed.
* Remove the query tab in favor of searching for "sql:SELECT ...".
* Faster load times.
* Fix contact groups expanding/collapsing.
* Give channel subscribe/unsubscribe similar grouping treatment to follows/blocks.
* Slightly improved following/blocking message display.
* Updates: CodeMirror, libbacktrace, and speedscope 1.24.0.

View File

@@ -147,6 +147,55 @@ static JSValue _tf_api_core_apps(JSContext* context, JSValueConst this_val, int
return result;
}
typedef struct _users_t
{
const char* users;
JSContext* context;
JSValue promise[2];
} users_t;
static void _tf_api_core_users_work(tf_ssb_t* ssb, void* user_data)
{
users_t* work = user_data;
work->users = tf_ssb_db_get_property(ssb, "auth", "users");
}
static void _tf_api_core_users_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
users_t* work = user_data;
JSContext* context = work->context;
JSValue result = JS_UNDEFINED;
if (work->users)
{
result = JS_ParseJSON(context, work->users, strlen(work->users), NULL);
tf_free((void*)work->users);
}
if (JS_IsUndefined(result))
{
result = JS_NewArray(context);
}
JSValue error = JS_Call(context, JS_IsArray(context, result) ? work->promise[0] : work->promise[1], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static JSValue _tf_api_core_users(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
users_t* work = tf_malloc(sizeof(users_t));
*work = (users_t) {
.context = context,
};
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_api_core_users_work, _tf_api_core_users_after_work, work);
return result;
}
static JSValue _tf_api_core_register(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSValue event_name = argv[0];
@@ -210,6 +259,459 @@ static JSValue _tf_api_core_unregister(JSContext* context, JSValueConst this_val
return JS_UNDEFINED;
}
typedef struct _permissions_for_user_t
{
const char* user;
const char* settings;
JSContext* context;
JSValue promise[2];
} permissions_for_user_t;
static void _tf_api_core_permissions_for_user_work(tf_ssb_t* ssb, void* user_data)
{
permissions_for_user_t* work = user_data;
work->settings = tf_ssb_db_get_property(ssb, "core", "settings");
}
static void _tf_api_core_permissions_for_user_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
permissions_for_user_t* work = user_data;
JSContext* context = work->context;
JSValue result = JS_UNDEFINED;
if (work->settings)
{
JSValue json = JS_ParseJSON(context, work->settings, strlen(work->settings), NULL);
if (JS_IsObject(json))
{
JSValue permissions = JS_GetPropertyStr(context, json, "permissions");
if (JS_IsObject(permissions))
{
result = JS_GetPropertyStr(context, permissions, work->user);
}
JS_FreeValue(context, permissions);
}
JS_FreeValue(context, json);
tf_free((void*)work->settings);
}
if (JS_IsUndefined(result))
{
result = JS_NewArray(context);
}
JSValue error = JS_Call(context, JS_IsArray(context, result) ? work->promise[0] : work->promise[1], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
JS_FreeCString(context, work->user);
tf_free(work);
}
static JSValue _tf_api_core_permissionsForUser(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
permissions_for_user_t* work = tf_malloc(sizeof(permissions_for_user_t));
*work = (permissions_for_user_t) {
.context = context,
.user = JS_ToCString(context, argv[0]),
};
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_api_core_permissions_for_user_work, _tf_api_core_permissions_for_user_after_work, work);
return result;
}
typedef struct _active_identity_work_t
{
JSContext* context;
const char* name;
const char* package_owner;
const char* package_name;
char identity[k_id_base64_len];
int result;
JSValue promise[2];
} active_identity_work_t;
static void _tf_ssb_getActiveIdentity_visit(const char* identity, void* user_data)
{
active_identity_work_t* request = user_data;
if (!*request->identity)
{
snprintf(request->identity, sizeof(request->identity), "@%s", identity);
}
}
static void _tf_ssb_getActiveIdentity_work(tf_ssb_t* ssb, void* user_data)
{
active_identity_work_t* request = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_identity_get_active(db, request->name, request->package_owner, request->package_name, request->identity, sizeof(request->identity));
tf_ssb_release_db_reader(ssb, db);
if (!*request->identity)
{
tf_ssb_db_identity_visit(ssb, request->name, _tf_ssb_getActiveIdentity_visit, request);
}
if (!*request->identity && tf_ssb_db_user_has_permission(ssb, NULL, request->name, "administration"))
{
tf_ssb_whoami(ssb, request->identity, sizeof(request->identity));
}
}
static void _tf_ssb_getActiveIdentity_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
active_identity_work_t* request = user_data;
JSContext* context = request->context;
if (request->result == 0)
{
JSValue identity = JS_NewString(context, request->identity);
JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 1, &identity);
JS_FreeValue(context, identity);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
}
else
{
JSValue error = JS_Call(context, request->promise[1], JS_UNDEFINED, 0, NULL);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
}
JS_FreeValue(context, request->promise[0]);
JS_FreeValue(context, request->promise[1]);
tf_free((void*)request->name);
tf_free((void*)request->package_owner);
tf_free((void*)request->package_name);
tf_free(request);
}
static const char* _tf_ssb_get_process_credentials_session_name(JSContext* context, JSValue process)
{
JSValue credentials = JS_IsObject(process) ? JS_GetPropertyStr(context, process, "credentials") : JS_UNDEFINED;
JSValue session = JS_IsObject(credentials) ? JS_GetPropertyStr(context, credentials, "session") : JS_UNDEFINED;
JSValue name_value = JS_IsObject(session) ? JS_GetPropertyStr(context, session, "name") : JS_UNDEFINED;
const char* name = JS_ToCString(context, name_value);
const char* result = tf_strdup(name);
JS_FreeCString(context, name);
JS_FreeValue(context, name_value);
JS_FreeValue(context, session);
JS_FreeValue(context, credentials);
return result;
}
static JSValue _tf_ssb_getActiveIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
JSValue process = data[0];
JSValue package_owner_value = JS_GetPropertyStr(context, process, "packageOwner");
JSValue package_name_value = JS_GetPropertyStr(context, process, "packageName");
const char* name = _tf_ssb_get_process_credentials_session_name(context, process);
const char* package_owner = JS_ToCString(context, package_owner_value);
const char* package_name = JS_ToCString(context, package_name_value);
active_identity_work_t* work = tf_malloc(sizeof(active_identity_work_t));
*work = (active_identity_work_t) {
.context = context,
.name = tf_strdup(name),
.package_owner = tf_strdup(package_owner),
.package_name = tf_strdup(package_name),
};
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_free((void*)name);
JS_FreeCString(context, package_owner);
JS_FreeCString(context, package_name);
JS_FreeValue(context, package_owner_value);
JS_FreeValue(context, package_name_value);
tf_ssb_run_work(ssb, _tf_ssb_getActiveIdentity_work, _tf_ssb_getActiveIdentity_after_work, work);
return result;
}
typedef struct _identities_visit_t
{
JSContext* context;
JSValue promise[2];
const char** identities;
int count;
char user[];
} identities_visit_t;
static void _tf_ssb_getIdentities_visit(const char* identity, void* user_data)
{
identities_visit_t* work = user_data;
work->identities = tf_resize_vec(work->identities, (work->count + 1) * sizeof(const char*));
char id[k_id_base64_len];
snprintf(id, sizeof(id), "@%s", identity);
work->identities[work->count++] = tf_strdup(id);
}
static void _tf_ssb_get_all_identities_work(tf_ssb_t* ssb, void* user_data)
{
tf_ssb_db_identity_visit_all(ssb, _tf_ssb_getIdentities_visit, user_data);
}
static void _tf_ssb_get_identities_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
identities_visit_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_NewArray(context);
for (int i = 0; i < work->count; i++)
{
JS_SetPropertyUint32(context, result, i, JS_NewString(context, work->identities[i]));
tf_free((void*)work->identities[i]);
}
tf_free(work->identities);
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static JSValue _tf_ssb_getAllIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
if (ssb)
{
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t));
*work = (identities_visit_t) {
.context = context,
};
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_get_all_identities_work, _tf_ssb_get_identities_after_work, work);
}
return result;
}
static void _tf_ssb_get_identities_work(tf_ssb_t* ssb, void* user_data)
{
identities_visit_t* work = user_data;
if (tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
{
char id[k_id_base64_len] = "";
if (tf_ssb_whoami(ssb, id, sizeof(id)))
{
_tf_ssb_getIdentities_visit(*id == '@' ? id + 1 : id, work);
}
}
tf_ssb_db_identity_visit(ssb, work->user, _tf_ssb_getIdentities_visit, user_data);
}
static JSValue _tf_ssb_getIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSValue result = JS_UNDEFINED;
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
JSValue process = data[0];
if (ssb)
{
const char* user = _tf_ssb_get_process_credentials_session_name(context, process);
if (user)
{
size_t user_length = user ? strlen(user) : 0;
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t) + user_length + 1);
*work = (identities_visit_t) {
.context = context,
};
memcpy(work->user, user, user_length + 1);
tf_free((void*)user);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_get_identities_work, _tf_ssb_get_identities_after_work, work);
}
}
return result;
}
static JSValue _tf_ssb_getOwnerIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSValue result = JS_UNDEFINED;
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
JSValue process = data[0];
if (ssb)
{
JSValue value = JS_GetPropertyStr(context, process, "packageOwner");
const char* user = JS_ToCString(context, value);
size_t user_length = user ? strlen(user) : 0;
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t) + user_length + 1);
*work = (identities_visit_t) {
.context = context,
};
memcpy(work->user, user, user_length + 1);
JS_FreeCString(context, user);
JS_FreeValue(context, value);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_get_identities_work, _tf_ssb_get_identities_after_work, work);
}
return result;
}
typedef struct _settings_descriptions_get_t
{
const char* settings;
JSContext* context;
JSValue promise[2];
} settings_descriptions_get_t;
static void _tf_ssb_get_settings_descriptions_work(tf_ssb_t* ssb, void* user_data)
{
settings_descriptions_get_t* work = user_data;
work->settings = tf_ssb_db_get_property(ssb, "core", "settings");
}
static void _tf_ssb_get_settings_descriptions_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
settings_descriptions_get_t* work = user_data;
JSContext* context = work->context;
JSValue result = JS_NewObject(context);
JSValue settings = JS_ParseJSON(context, work->settings ? work->settings : "", work->settings ? strlen(work->settings) : 0, NULL);
const char* name;
const char* type;
tf_setting_kind_t kind;
const char* description;
for (int i = 0; tf_util_get_global_setting_by_index(i, &name, &type, &kind, &description); i++)
{
JSValue entry = JS_NewObject(context);
JS_SetPropertyStr(context, entry, "type", JS_NewString(context, type));
JS_SetPropertyStr(context, entry, "description", JS_NewString(context, description));
switch (kind)
{
case k_kind_unknown:
break;
case k_kind_bool:
JS_SetPropertyStr(context, entry, "default_value", JS_NewBool(context, tf_util_get_default_global_setting_bool(name)));
break;
case k_kind_int:
JS_SetPropertyStr(context, entry, "default_value", JS_NewInt32(context, tf_util_get_default_global_setting_int(name)));
break;
case k_kind_string:
JS_SetPropertyStr(context, entry, "default_value", JS_NewString(context, tf_util_get_default_global_setting_string(name)));
break;
}
if (JS_IsObject(settings))
{
JS_SetPropertyStr(context, entry, "value", JS_GetPropertyStr(context, settings, name));
}
JS_SetPropertyStr(context, result, name, entry);
}
JS_FreeValue(context, settings);
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free((void*)work->settings);
tf_free(work);
}
static JSValue _tf_ssb_globalSettingsDescriptions(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
settings_descriptions_get_t* work = tf_malloc(sizeof(settings_descriptions_get_t));
*work = (settings_descriptions_get_t) { .context = context };
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_get_settings_descriptions_work, _tf_ssb_get_settings_descriptions_after_work, work);
return result;
}
typedef struct _settings_get_t
{
const char* key;
tf_setting_kind_t kind;
void* value;
JSContext* context;
JSValue promise[2];
} settings_get_t;
static void _tf_ssb_settings_get_work(tf_ssb_t* ssb, void* user_data)
{
settings_get_t* work = user_data;
work->kind = tf_util_get_global_setting_kind(work->key);
switch (work->kind)
{
case k_kind_unknown:
break;
case k_kind_bool:
{
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
bool value = false;
tf_ssb_db_get_global_setting_bool(db, work->key, &value);
work->value = (void*)(intptr_t)value;
tf_ssb_release_db_reader(ssb, db);
}
break;
case k_kind_int:
{
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
int64_t value = 0;
tf_ssb_db_get_global_setting_int64(db, work->key, &value);
work->value = (void*)(intptr_t)value;
tf_ssb_release_db_reader(ssb, db);
}
break;
case k_kind_string:
{
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
work->value = (void*)tf_ssb_db_get_global_setting_string_alloc(db, work->key);
tf_ssb_release_db_reader(ssb, db);
}
break;
}
}
static void _tf_ssb_settings_get_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
settings_get_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_UNDEFINED;
switch (work->kind)
{
case k_kind_unknown:
break;
case k_kind_bool:
result = work->value ? JS_TRUE : JS_FALSE;
break;
case k_kind_int:
result = JS_NewInt64(context, (int64_t)(intptr_t)work->value);
break;
case k_kind_string:
result = JS_NewString(context, (const char*)work->value);
tf_free(work->value);
break;
}
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
JS_FreeCString(context, work->key);
tf_free(work);
}
static JSValue _tf_ssb_globalSettingsGet(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
settings_get_t* work = tf_malloc(sizeof(settings_get_t));
*work = (settings_get_t) { .context = context, .key = JS_ToCString(context, argv[0]) };
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_settings_get_work, _tf_ssb_settings_get_after_work, work);
return result;
}
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue imports = argv[0];
@@ -218,6 +720,37 @@ static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_va
JS_SetPropertyStr(context, core, "apps", JS_NewCFunctionData(context, _tf_api_core_apps, 1, 0, 1, &process));
JS_SetPropertyStr(context, core, "register", JS_NewCFunctionData(context, _tf_api_core_register, 2, 0, 1, &process));
JS_SetPropertyStr(context, core, "unregister", JS_NewCFunctionData(context, _tf_api_core_unregister, 2, 0, 1, &process));
JS_SetPropertyStr(context, core, "users", JS_NewCFunctionData(context, _tf_api_core_users, 0, 0, 1, &process));
JS_SetPropertyStr(context, core, "permissionsForUser", JS_NewCFunctionData(context, _tf_api_core_permissionsForUser, 1, 0, 1, &process));
JSValue app = JS_NewObject(context);
JS_SetPropertyStr(context, app, "owner", JS_GetPropertyStr(context, process, "packageOwner"));
JS_SetPropertyStr(context, app, "name", JS_GetPropertyStr(context, process, "packageName"));
JS_SetPropertyStr(context, core, "app", app);
JS_SetPropertyStr(context, core, "url", JS_GetPropertyStr(context, process, "url"));
JSValue ssb = JS_GetPropertyStr(context, imports, "ssb");
JS_SetPropertyStr(context, ssb, "getAllIdentities", JS_NewCFunction(context, _tf_ssb_getAllIdentities, "getAllIdentities", 0));
JS_SetPropertyStr(context, ssb, "getActiveIdentity", JS_NewCFunctionData(context, _tf_ssb_getActiveIdentity, 0, 0, 1, &process));
JS_SetPropertyStr(context, ssb, "getIdentities", JS_NewCFunctionData(context, _tf_ssb_getIdentities, 0, 0, 1, &process));
JS_SetPropertyStr(context, ssb, "getOwnerIdentities", JS_NewCFunctionData(context, _tf_ssb_getOwnerIdentities, 0, 0, 1, &process));
JS_FreeValue(context, ssb);
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");
JSValue permissions = JS_IsObject(credentials) ? JS_GetPropertyStr(context, credentials, "permissions") : JS_UNDEFINED;
JSValue administration = JS_IsObject(permissions) ? JS_GetPropertyStr(context, permissions, "administration") : JS_UNDEFINED;
if (JS_ToBool(context, administration) > 0)
{
JS_SetPropertyStr(context, core, "globalSettingsDescriptions", JS_NewCFunction(context, _tf_ssb_globalSettingsDescriptions, "globalSettingsDescriptions", 0));
JS_SetPropertyStr(context, core, "globalSettingsGet", JS_NewCFunction(context, _tf_ssb_globalSettingsGet, "globalSettingsGet", 1));
}
JS_FreeValue(context, administration);
JS_FreeValue(context, permissions);
JS_FreeValue(context, credentials);
JS_FreeValue(context, core);
return JS_UNDEFINED;
}

View File

@@ -2,7 +2,6 @@
#include "log.h"
#include "mem.h"
#include "tls.h"
#include "trace.h"
#include "util.js.h"
@@ -20,10 +19,12 @@
static const int k_timeout_ms = 60000;
static tf_http_t** s_http_instances;
int s_http_instance_count;
typedef struct _tf_http_connection_t
{
tf_http_t* http;
tf_tls_session_t* tls;
uv_tcp_t tcp;
uv_shutdown_t shutdown;
uv_timer_t timeout;
@@ -75,7 +76,6 @@ typedef struct _tf_http_handler_t
typedef struct _tf_http_listener_t
{
tf_http_t* http;
tf_tls_context_t* tls;
uv_tcp_t tcp;
tf_http_cleanup_t* cleanup;
void* user_data;
@@ -106,7 +106,6 @@ typedef struct _tf_http_t
static const char* _http_connection_get_header(const tf_http_connection_t* connection, const char* name);
static void _http_connection_destroy(tf_http_connection_t* connection, const char* reason);
static void _http_timer_reset(tf_http_connection_t* connection);
static void _http_tls_update(tf_http_connection_t* connection);
static void _http_builtin_404_handler(tf_http_request_t* request);
tf_http_t* tf_http_create(uv_loop_t* loop)
@@ -115,6 +114,8 @@ tf_http_t* tf_http_create(uv_loop_t* loop)
*http = (tf_http_t) {
.loop = loop,
};
s_http_instances = tf_resize_vec(s_http_instances, sizeof(tf_http_t*) * (s_http_instance_count + 1));
s_http_instances[s_http_instance_count++] = http;
return http;
}
@@ -255,11 +256,6 @@ static void _http_connection_destroy(tf_http_connection_t* connection, const cha
{
uv_close((uv_handle_t*)&connection->timeout, _http_connection_on_close);
}
if (connection->tls)
{
tf_tls_session_destroy(connection->tls);
connection->tls = NULL;
}
if (connection->ref_count == 0 && !connection->tcp.data && !connection->shutdown.data && !connection->timeout.data)
{
@@ -441,7 +437,6 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
*request = (tf_http_request_t) {
.http = connection->http,
.connection = connection,
.is_tls = connection->tls != NULL,
.method = connection->method,
.path = connection->path,
.query = connection->query,
@@ -582,21 +577,7 @@ static void _http_on_read(uv_stream_t* stream, ssize_t read_size, const uv_buf_t
_http_timer_reset(connection);
if (read_size > 0)
{
if (connection->tls)
{
if (tf_tls_session_write_encrypted(connection->tls, buffer->base, read_size) < 0)
{
_http_connection_destroy(connection, "tf_tls_session_write_encrypted");
}
else
{
_http_tls_update(connection);
}
}
else
{
_http_on_read_plain(connection, buffer->base, read_size);
}
_http_on_read_plain(connection, buffer->base, read_size);
}
else if (read_size < 0)
{
@@ -638,17 +619,6 @@ static void _http_on_connection(uv_stream_t* stream, int status)
tf_http_connection_t* connection = tf_malloc(sizeof(tf_http_connection_t));
*connection = (tf_http_connection_t) { .http = http, .tcp = { .data = connection }, .is_receiving_headers = true };
if (listener->tls)
{
connection->tls = tf_tls_context_create_session(listener->tls);
if (!connection->tls)
{
_http_connection_destroy(connection, "tf_tls_context_create_session");
return;
}
tf_tls_session_start_accept(connection->tls);
connection->is_handshaking = true;
}
int r = uv_tcp_init(connection->http->loop, &connection->tcp);
if (r)
{
@@ -689,21 +659,15 @@ static void _http_on_connection(uv_stream_t* stream, int status)
return;
}
if (connection->tls)
{
_http_tls_update(connection);
}
http->connections = tf_resize_vec(http->connections, sizeof(tf_http_connection_t*) * (http->connections_count + 1));
http->connections[http->connections_count++] = connection;
}
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data)
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_http_cleanup_t* cleanup, void* user_data)
{
tf_http_listener_t* listener = tf_malloc(sizeof(tf_http_listener_t));
*listener = (tf_http_listener_t) {
.http = http,
.tls = tls,
.tcp = { .data = listener },
.cleanup = cleanup,
.user_data = user_data,
@@ -872,6 +836,21 @@ void tf_http_destroy(tf_http_t* http)
http->handlers_count = 0;
tf_free(http);
for (int i = 0; i < s_http_instance_count; i++)
{
if (s_http_instances[i] == http)
{
s_http_instances[i] = s_http_instances[--s_http_instance_count];
break;
}
}
if (s_http_instance_count == 0)
{
tf_free(s_http_instances);
s_http_instances = NULL;
}
tf_printf("http %p destroyed\n", http);
}
else
{
@@ -928,71 +907,10 @@ static void _http_write_internal(tf_http_connection_t* connection, const void* d
}
}
static void _http_tls_update(tf_http_connection_t* connection)
{
bool again = true;
while (again)
{
again = false;
if (connection->is_handshaking && connection->tls)
{
switch (tf_tls_session_handshake(connection->tls))
{
case k_tls_handshake_done:
connection->is_handshaking = false;
break;
case k_tls_handshake_more:
break;
case k_tls_handshake_failed:
_http_connection_destroy(connection, "tf_tls_session_handshake");
return;
}
}
/* Maybe we became disconnected and cleaned up our TLS session. */
if (connection->tls)
{
char buffer[8192];
int r = tf_tls_session_read_encrypted(connection->tls, buffer, sizeof(buffer));
if (r > 0)
{
_http_write_internal(connection, buffer, r);
again = true;
}
}
if (connection->tls)
{
char buffer[8192];
int r = tf_tls_session_read_plain(connection->tls, buffer, sizeof(buffer));
if (r > 0)
{
_http_on_read_plain(connection, buffer, r);
again = true;
}
}
}
}
static void _http_write(tf_http_connection_t* connection, const void* data, size_t size)
{
_http_timer_reset(connection);
if (connection->tls)
{
int r = tf_tls_session_write_plain(connection->tls, data, size);
if (r < (ssize_t)size)
{
char buffer[8192];
tf_tls_session_get_error(connection->tls, buffer, sizeof(buffer));
tf_printf("tf_tls_session_write_plain: %s\n", buffer);
}
_http_tls_update(connection);
}
else
{
_http_write_internal(connection, data, size);
}
_http_write_internal(connection, data, size);
}
void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size)
@@ -1215,3 +1133,19 @@ const char* tf_http_get_cookie(const char* cookie_header, const char* name)
}
return NULL;
}
void tf_http_debug_destroy()
{
for (int i = 0; i < s_http_instance_count; i++)
{
tf_http_t* http = s_http_instances[i];
tf_printf("http %p[%d]\n", http, i);
tf_printf(" connections = %d\n", http->connections_count);
for (int j = 0; j < http->connections_count; j++)
{
tf_http_connection_t* connection = http->connections[j];
tf_printf(" connection %p[%d] %s tcp=%p timeout=%p shutdown=%p rc=%d\n", connection, j, connection->trace_name, connection->tcp.data, connection->timeout.data,
connection->shutdown.data, connection->ref_count);
}
}
}

View File

@@ -23,9 +23,6 @@ typedef struct _tf_http_request_t tf_http_request_t;
/** An HTTP instance. */
typedef struct _tf_http_t tf_http_t;
/** A TLS context. */
typedef struct _tf_tls_context_t tf_tls_context_t;
/** A trace instance. */
typedef struct _tf_trace_t tf_trace_t;
@@ -68,8 +65,6 @@ typedef struct _tf_http_request_t
tf_http_t* http;
/** The HTTP connection associated with this request. */
tf_http_connection_t* connection;
/** True if this is an HTTPS session. */
bool is_tls;
/** The HTTP method of the request (GET/POST/...). */
const char* method;
/** The HTTP request path. */
@@ -117,12 +112,11 @@ void tf_http_set_trace(tf_http_t* http, tf_trace_t* trace);
** @param http The HTTP instance.
** @param port The port on which to listen, or 0 to assign a free port.
** @param local_only Only access connections on localhost, otherwise any address.
** @param tls An optional TLS context to use for HTTPS requests.
** @param cleanup A function called when the HTTP instance is being cleaned up.
** @param user_data User data passed to the cleanup callback.
** @return The port number on which the HTTP instance is now listening.
*/
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data);
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_http_cleanup_t* cleanup, void* user_data);
/**
** Add an HTTP request handler.
@@ -238,4 +232,9 @@ const char* tf_http_status_text(int status);
*/
bool tf_http_pattern_matches(const char* pattern, const char* path);
/**
** Log debug information to diagnose shutdown problems.
*/
void tf_http_debug_destroy();
/** @} */

View File

@@ -7,7 +7,6 @@
#include "sha1.h"
#include "ssb.db.h"
#include "task.h"
#include "tls.h"
#include "trace.h"
#include "util.js.h"
#include "version.h"
@@ -257,11 +256,6 @@ JSValue tf_httpd_make_response_object(JSContext* context, tf_http_request_t* req
bool tf_httpd_redirect(tf_http_request_t* request)
{
if (request->is_tls)
{
return false;
}
http_user_data_t* user_data = tf_http_get_user_data(request->http);
if (!user_data || !*user_data->redirect)
{
@@ -276,16 +270,12 @@ bool tf_httpd_redirect(tf_http_request_t* request)
typedef struct _httpd_listener_t
{
tf_tls_context_t* tls;
int padding;
} httpd_listener_t;
static void _httpd_listener_cleanup(void* user_data)
{
httpd_listener_t* listener = user_data;
if (listener->tls)
{
tf_tls_context_destroy(listener->tls);
}
tf_free(listener);
}
@@ -574,7 +564,7 @@ static void _httpd_endpoint_add_slash(tf_http_request_t* request)
host = tf_http_request_get_header(request, "host");
}
char url[1024];
snprintf(url, sizeof(url), "%s%s%s/", request->is_tls ? "https://" : "http://", host, request->path);
snprintf(url, sizeof(url), "%s%s%s/", "http://", host, request->path);
const char* headers[] = {
"Location",
url,
@@ -860,31 +850,6 @@ bool tf_httpd_is_name_valid(const char* name)
return true;
}
static void _httpd_free_user_data(void* user_data)
{
tf_free(user_data);
}
static const char* _httpd_read_file(tf_task_t* task, const char* path)
{
const char* actual = tf_task_get_path_with_root(task, path);
const size_t k_max_read = 8 * 1024 * 1024;
char* result = NULL;
char* buffer = tf_malloc(k_max_read);
FILE* file = fopen(actual, "rb");
if (file)
{
size_t size = fread(buffer, 1, k_max_read, file);
result = tf_malloc(size + 1);
memcpy(result, buffer, size);
result[size] = '\0';
fclose(file);
}
tf_free(buffer);
tf_free((char*)actual);
return result;
}
void tf_httpd_register(JSContext* context)
{
JS_NewClassID(&_httpd_request_class_id);
@@ -913,36 +878,14 @@ tf_http_t* tf_httpd_create(JSContext* context)
tf_http_set_trace(http, tf_task_get_trace(task));
int64_t http_port = 0;
int64_t https_port = 0;
char out_http_port_file[512] = "";
bool local_only = false;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port);
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port);
tf_ssb_db_get_global_setting_string(db, "out_http_port_file", out_http_port_file, sizeof(out_http_port_file));
tf_ssb_db_get_global_setting_bool(db, "http_local_only", &local_only);
tf_ssb_release_db_reader(ssb, db);
if (https_port)
{
http_user_data_t* user_data = tf_http_get_user_data(http);
if (!user_data)
{
user_data = tf_malloc(sizeof(http_user_data_t));
memset(user_data, 0, sizeof(http_user_data_t));
tf_http_set_user_data(http, user_data, _httpd_free_user_data);
}
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_get_global_setting_string(db, "http_redirect", user_data->redirect, sizeof(user_data->redirect));
tf_ssb_release_db_reader(ssb, db);
/* Workaround. */
if (strcmp(user_data->redirect, "0") == 0)
{
*user_data->redirect = '\0';
}
}
tf_http_add_handler(http, "/", _httpd_endpoint_root, NULL, task);
tf_http_add_handler(http, "/codemirror/*", tf_httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/lit/*", tf_httpd_endpoint_static, NULL, task);
@@ -977,7 +920,7 @@ tf_http_t* tf_httpd_create(JSContext* context)
{
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
*listener = (httpd_listener_t) { 0 };
int assigned_port = tf_http_listen(http, http_port, local_only, NULL, _httpd_listener_cleanup, listener);
int assigned_port = tf_http_listen(http, http_port, local_only, _httpd_listener_cleanup, listener);
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http://127.0.0.1:%d/" RESET ".\n", assigned_port);
if (*out_http_port_file)
@@ -996,26 +939,6 @@ tf_http_t* tf_httpd_create(JSContext* context)
}
tf_free((char*)actual_http_port_file);
}
if (https_port)
{
const char* k_certificate = "data/httpd/certificate.pem";
const char* k_private_key = "data/httpd/privatekey.pem";
const char* certificate = _httpd_read_file(task, k_certificate);
const char* private_key = _httpd_read_file(task, k_private_key);
if (certificate && private_key)
{
tf_tls_context_t* tls = tf_tls_context_create();
tf_tls_context_set_certificate(tls, certificate);
tf_tls_context_set_private_key(tls, private_key);
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
*listener = (httpd_listener_t) { .tls = tls };
int assigned_port = tf_http_listen(http, https_port, local_only, tls, _httpd_listener_cleanup, listener);
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "https://127.0.0.1:%d/" RESET ".\n", assigned_port);
}
tf_free((char*)certificate);
tf_free((char*)private_key);
}
}
return http;
}

View File

@@ -37,11 +37,11 @@ typedef struct _login_request_t
const char* tf_httpd_make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie)
{
const char* k_pattern = "session=%s; path=/; Max-Age=%" PRId64 "; %sSameSite=Strict; HttpOnly";
int length = session_cookie ? snprintf(NULL, 0, k_pattern, session_cookie, k_httpd_auth_refresh_interval, request->is_tls ? "Secure; " : "") : 0;
int length = session_cookie ? snprintf(NULL, 0, k_pattern, session_cookie, k_httpd_auth_refresh_interval, "") : 0;
char* cookie = length ? tf_malloc(length + 1) : NULL;
if (cookie)
{
snprintf(cookie, length + 1, k_pattern, session_cookie, k_httpd_auth_refresh_interval, request->is_tls ? "Secure; " : "");
snprintf(cookie, length + 1, k_pattern, session_cookie, k_httpd_auth_refresh_interval, "");
}
return cookie;
}
@@ -226,7 +226,7 @@ static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
}
else
{
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", "http://", tf_http_request_get_header(request, "host"));
}
goto done;
}
@@ -332,7 +332,7 @@ static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
}
else
{
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", "http://", tf_http_request_get_header(request, "host"));
}
login->set_cookie_header = tf_httpd_make_set_session_cookie_header(request, send_session);
tf_free((void*)send_session);
@@ -416,8 +416,7 @@ void tf_httpd_endpoint_login(tf_http_request_t* request)
void tf_httpd_endpoint_logout(tf_http_request_t* request)
{
const char* k_set_cookie = request->is_tls ? "session=; path=/; Secure; SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly"
: "session=; path=/; SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly";
const char* k_set_cookie = "session=; path=/; SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly";
const char* k_location_format = "/login%s%s";
int length = snprintf(NULL, 0, k_location_format, request->query ? "?" : "", request->query);
char* location = alloca(length + 1);

View File

@@ -1502,13 +1502,11 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
tf_ssb_t* ssb = tf_task_get_ssb(task);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
int64_t http_port = 0;
int64_t https_port = 0;
char out_http_port_file[512] = "";
tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port);
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port);
tf_ssb_db_get_global_setting_string(db, "out_http_port_file", out_http_port_file, sizeof(out_http_port_file));
tf_ssb_release_db_reader(ssb, db);
if (http_port || https_port || *out_http_port_file)
if (http_port || *out_http_port_file)
{
if (args->zip)
{
@@ -1869,7 +1867,6 @@ static void _startup(int argc, char* argv[])
prctl(PR_SET_PDEATHSIG, SIGKILL);
#endif
tf_mem_replace_uv_allocator();
tf_mem_replace_tls_allocator();
tf_mem_replace_sqlite_allocator();
uv_setup_args(argc, argv);
tf_taskstub_startup();

View File

@@ -7,8 +7,6 @@
#include "sqlite3.h"
#include "uv.h"
#include <openssl/crypto.h>
#include <stdbool.h>
#include <string.h>
@@ -19,7 +17,6 @@ static bool s_mem_tracking;
static tf_mem_node_t* s_mem_tracked;
static int64_t s_tf_malloc_size;
static int64_t s_uv_malloc_size;
static int64_t s_tls_malloc_size;
static int64_t s_js_malloc_size;
static int64_t s_sqlite_malloc_size;
@@ -387,31 +384,6 @@ size_t tf_mem_get_uv_malloc_size()
return s_uv_malloc_size;
}
static void* _tf_tls_alloc(size_t size, const char* file, int line)
{
return _tf_alloc(&s_tls_malloc_size, size);
}
static void* _tf_tls_realloc(void* ptr, size_t size, const char* file, int line)
{
return _tf_realloc(&s_tls_malloc_size, ptr, size);
}
static void _tf_tls_free(void* ptr, const char* file, int line)
{
_tf_free(&s_tls_malloc_size, ptr);
}
void tf_mem_replace_tls_allocator()
{
CRYPTO_set_mem_functions(_tf_tls_alloc, _tf_tls_realloc, _tf_tls_free);
}
size_t tf_mem_get_tls_malloc_size()
{
return s_tls_malloc_size;
}
void* tf_malloc(size_t size)
{
return _tf_alloc(&s_tf_malloc_size, size);

View File

@@ -3,7 +3,7 @@
/**
** \defgroup mem Memory management
** tf_malloc() and friends use malloc() behind the scenes but optionally
** track memory per system (OpenSSL, sqlite, libuv, ...) and store callstacks
** track memory per system (sqlite, libuv, ...) and store callstacks
** to help debug leaks.
** @{
*/
@@ -38,17 +38,6 @@ void tf_mem_replace_uv_allocator();
*/
size_t tf_mem_get_uv_malloc_size();
/**
** Register a custom allocator with OpenSSL.
*/
void tf_mem_replace_tls_allocator();
/**
** Get the number of bytes currently allocated by OpenSSL.
** @return The allocated size in bytes.
*/
size_t tf_mem_get_tls_malloc_size();
/**
** Register a custom allocator with SQLite.
*/

View File

@@ -1,5 +1,6 @@
#include "ssb.h"
#include "http.h"
#include "log.h"
#include "mem.h"
#include "ssb.connections.h"
@@ -2711,6 +2712,7 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
tf_printf("--\n");
uv_print_all_handles(ssb->loop, stdout);
}
tf_http_debug_destroy();
uv_run(ssb->loop, UV_RUN_ONCE);
}

View File

@@ -2465,6 +2465,32 @@ bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* ou
return result;
}
const char* tf_ssb_db_get_global_setting_string_alloc(sqlite3* db, const char* name)
{
const char* result = NULL;
sqlite3_stmt* statement;
if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_type(statement, 0) != SQLITE_NULL)
{
result = tf_strdup((const char*)sqlite3_column_text(statement, 0));
}
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
}
if (!result)
{
result = tf_strdup(tf_util_get_default_global_setting_string(name));
}
return result;
}
bool tf_ssb_db_set_global_setting_from_string(sqlite3* db, const char* name, char* value)
{
tf_setting_kind_t kind = tf_util_get_global_setting_kind(name);

View File

@@ -500,6 +500,14 @@ bool tf_ssb_db_get_global_setting_int64(sqlite3* db, const char* name, int64_t*
*/
bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* out_value, size_t size);
/**
** Get a string global setting value.
** @param db The database.
** @param name The setting name.
** @return The setting if found.
*/
const char* tf_ssb_db_get_global_setting_string_alloc(sqlite3* db, const char* name);
/**
** Set a global setting from a string representation of its value.
** @param db The database.

View File

@@ -363,85 +363,6 @@ static JSValue _tf_ssb_swap_with_server_identity(JSContext* context, JSValueCons
return result;
}
typedef struct _identities_visit_t
{
JSContext* context;
JSValue promise[2];
const char** identities;
int count;
char user[];
} identities_visit_t;
static void _tf_ssb_getIdentities_visit(const char* identity, void* user_data)
{
identities_visit_t* work = user_data;
work->identities = tf_resize_vec(work->identities, (work->count + 1) * sizeof(const char*));
char id[k_id_base64_len];
snprintf(id, sizeof(id), "@%s", identity);
work->identities[work->count++] = tf_strdup(id);
}
static void _tf_ssb_get_identities_work(tf_ssb_t* ssb, void* user_data)
{
identities_visit_t* work = user_data;
if (tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
{
char id[k_id_base64_len] = "";
if (tf_ssb_whoami(ssb, id, sizeof(id)))
{
_tf_ssb_getIdentities_visit(*id == '@' ? id + 1 : id, work);
}
}
tf_ssb_db_identity_visit(ssb, work->user, _tf_ssb_getIdentities_visit, user_data);
}
static void _tf_ssb_get_all_identities_work(tf_ssb_t* ssb, void* user_data)
{
tf_ssb_db_identity_visit_all(ssb, _tf_ssb_getIdentities_visit, user_data);
}
static void _tf_ssb_get_identities_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
identities_visit_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_NewArray(context);
for (int i = 0; i < work->count; i++)
{
JS_SetPropertyUint32(context, result, i, JS_NewString(context, work->identities[i]));
tf_free((void*)work->identities[i]);
}
tf_free(work->identities);
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static JSValue _tf_ssb_getIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
if (ssb)
{
size_t user_length = 0;
const char* user = JS_ToCStringLen(context, &user_length, argv[0]);
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t) + user_length + 1);
*work = (identities_visit_t) {
.context = context,
};
memcpy(work->user, user, user_length + 1);
JS_FreeCString(context, user);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_get_identities_work, _tf_ssb_get_identities_after_work, work);
}
return result;
}
typedef struct _get_private_key_t
{
JSContext* context;
@@ -513,109 +434,6 @@ static JSValue _tf_ssb_getServerIdentity(JSContext* context, JSValueConst this_v
return result;
}
static JSValue _tf_ssb_getAllIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
if (ssb)
{
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t));
*work = (identities_visit_t) {
.context = context,
};
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_get_all_identities_work, _tf_ssb_get_identities_after_work, work);
}
return result;
}
typedef struct _active_identity_work_t
{
JSContext* context;
const char* name;
const char* package_owner;
const char* package_name;
char identity[k_id_base64_len];
int result;
JSValue promise[2];
} active_identity_work_t;
static void _tf_ssb_getActiveIdentity_visit(const char* identity, void* user_data)
{
active_identity_work_t* request = user_data;
if (!*request->identity)
{
snprintf(request->identity, sizeof(request->identity), "@%s", identity);
}
}
static void _tf_ssb_getActiveIdentity_work(tf_ssb_t* ssb, void* user_data)
{
active_identity_work_t* request = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_identity_get_active(db, request->name, request->package_owner, request->package_name, request->identity, sizeof(request->identity));
tf_ssb_release_db_reader(ssb, db);
if (!*request->identity)
{
tf_ssb_db_identity_visit(ssb, request->name, _tf_ssb_getActiveIdentity_visit, request);
}
if (!*request->identity && tf_ssb_db_user_has_permission(ssb, NULL, request->name, "administration"))
{
tf_ssb_whoami(ssb, request->identity, sizeof(request->identity));
}
}
static void _tf_ssb_getActiveIdentity_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
active_identity_work_t* request = user_data;
JSContext* context = request->context;
if (request->result == 0)
{
JSValue identity = JS_NewString(context, request->identity);
JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 1, &identity);
JS_FreeValue(context, identity);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
}
else
{
JSValue error = JS_Call(context, request->promise[1], JS_UNDEFINED, 0, NULL);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
}
JS_FreeValue(context, request->promise[0]);
JS_FreeValue(context, request->promise[1]);
tf_free((void*)request->name);
tf_free((void*)request->package_owner);
tf_free((void*)request->package_name);
tf_free(request);
}
static JSValue _tf_ssb_getActiveIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
const char* name = JS_ToCString(context, argv[0]);
const char* package_owner = JS_ToCString(context, argv[1]);
const char* package_name = JS_ToCString(context, argv[2]);
active_identity_work_t* work = tf_malloc(sizeof(active_identity_work_t));
*work = (active_identity_work_t) {
.context = context,
.name = tf_strdup(name),
.package_owner = tf_strdup(package_owner),
.package_name = tf_strdup(package_name),
};
JSValue result = JS_NewPromiseCapability(context, work->promise);
JS_FreeCString(context, name);
JS_FreeCString(context, package_owner);
JS_FreeCString(context, package_name);
tf_ssb_run_work(ssb, _tf_ssb_getActiveIdentity_work, _tf_ssb_getActiveIdentity_after_work, work);
return result;
}
typedef struct _identity_info_work_t
{
JSContext* context;
@@ -2379,7 +2197,6 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
JS_SetPropertyStr(context, object, "addIdentity", JS_NewCFunction(context, _tf_ssb_addIdentity, "addIdentity", 2));
JS_SetPropertyStr(context, object, "deleteIdentity", JS_NewCFunction(context, _tf_ssb_deleteIdentity, "deleteIdentity", 2));
JS_SetPropertyStr(context, object, "swapWithServerIdentity", JS_NewCFunction(context, _tf_ssb_swap_with_server_identity, "swapWithServerIdentity", 2));
JS_SetPropertyStr(context, object, "getIdentities", JS_NewCFunction(context, _tf_ssb_getIdentities, "getIdentities", 1));
JS_SetPropertyStr(context, object, "getPrivateKey", JS_NewCFunction(context, _tf_ssb_getPrivateKey, "getPrivateKey", 2));
JS_SetPropertyStr(context, object, "privateMessageEncrypt", JS_NewCFunction(context, _tf_ssb_private_message_encrypt, "privateMessageEncrypt", 4));
JS_SetPropertyStr(context, object, "privateMessageDecrypt", JS_NewCFunction(context, _tf_ssb_private_message_decrypt, "privateMessageDecrypt", 3));
@@ -2389,8 +2206,6 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
/* Does not require an identity. */
JS_SetPropertyStr(context, object, "getServerIdentity", JS_NewCFunction(context, _tf_ssb_getServerIdentity, "getServerIdentity", 0));
JS_SetPropertyStr(context, object, "getAllIdentities", JS_NewCFunction(context, _tf_ssb_getAllIdentities, "getAllIdentities", 0));
JS_SetPropertyStr(context, object, "getActiveIdentity", JS_NewCFunction(context, _tf_ssb_getActiveIdentity, "getActiveIdentity", 3));
JS_SetPropertyStr(context, object, "blobGet", JS_NewCFunction(context, _tf_ssb_blobGet, "blobGet", 1));
JS_SetPropertyStr(context, object, "connections", JS_NewCFunction(context, _tf_ssb_connections, "connections", 0));
JS_SetPropertyStr(context, object, "storedConnections", JS_NewCFunction(context, _tf_ssb_storedConnections, "storedConnections", 0));

View File

@@ -923,7 +923,7 @@ static void _write_file(const char* path, const char* contents)
fclose(file);
}
#define TEST_ARGS " --args=ssb_port=0,http_port=0,https_port=0"
#define TEST_ARGS " --args=ssb_port=0,http_port=0"
void tf_ssb_test_encrypt(const tf_test_options_t* options)
{
@@ -1553,12 +1553,6 @@ void tf_ssb_test_invite(const tf_test_options_t* options)
tf_ssb_release_db_writer(ssb0, writer);
tf_printf("invite: %s\n", invite);
int count0 = 0;
int count1 = 0;
tf_ssb_add_message_added_callback(ssb0, _message_added, NULL, &count0);
tf_ssb_add_message_added_callback(ssb1, _message_added, NULL, &count1);
tf_ssb_connect_str(ssb1, invite, 0, NULL, NULL);
tf_printf("Waiting for connection.\n");
@@ -1574,11 +1568,19 @@ void tf_ssb_test_invite(const tf_test_options_t* options)
tf_printf("waiting for messages\n");
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
while (count0 != 3 || count1 != 3)
int32_t sequence0 = 0;
int32_t sequence1 = 0;
while (sequence0 != 1 || sequence1 != 2)
{
uv_run(&loop, UV_RUN_ONCE);
tf_printf("count0=%d count1=%d\n", count0, count1);
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);
tf_ssb_db_get_latest_message_by_author(ssb0, id0, &sequence0, NULL, 0);
tf_ssb_db_get_latest_message_by_author(ssb1, id1, &sequence1, NULL, 0);
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
tf_printf("sequence0=%d sequence1=%d\n", sequence0, sequence1);
}
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);

View File

@@ -3,6 +3,7 @@
#include "api.js.h"
#include "database.js.h"
#include "file.js.h"
#include "http.h"
#include "httpd.js.h"
#include "log.h"
#include "mem.h"
@@ -25,8 +26,6 @@
#include "uv.h"
#include "zlib.h"
#include <openssl/crypto.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
@@ -706,11 +705,6 @@ static JSValue _tf_task_version(JSContext* context, JSValueConst this_val, int a
JS_SetPropertyStr(context, version, "name", JS_NewString(context, VERSION_NAME));
JS_SetPropertyStr(context, version, "libuv", JS_NewString(context, uv_version_string()));
JS_SetPropertyStr(context, version, "sqlite", JS_NewString(context, sqlite3_libversion()));
#if defined(OPENSSL_VERSION_STRING)
JS_SetPropertyStr(context, version, "openssl", JS_NewString(context, OpenSSL_version(OPENSSL_VERSION_STRING)));
#else
JS_SetPropertyStr(context, version, "openssl", JS_NewString(context, OpenSSL_version(OPENSSL_VERSION)));
#endif
const char* sodium_version_string();
JS_SetPropertyStr(context, version, "c-ares", JS_NewString(context, ares_version(NULL)));
JS_SetPropertyStr(context, version, "libsodium", JS_NewString(context, sodium_version_string()));
@@ -821,7 +815,6 @@ static JSValue _tf_task_getStats(JSContext* context, JSValueConst this_val, int
JS_SetPropertyStr(context, result, "sqlite3_memory_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_sqlite_malloc_size() / total_memory));
JS_SetPropertyStr(context, result, "js_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_js_malloc_size() / total_memory));
JS_SetPropertyStr(context, result, "uv_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_uv_malloc_size() / total_memory));
JS_SetPropertyStr(context, result, "tls_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_tls_malloc_size() / total_memory));
JS_SetPropertyStr(context, result, "tf_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_tf_malloc_size() / total_memory));
if (task->_ssb)
@@ -1875,6 +1868,7 @@ void tf_task_destroy(tf_task_t* task)
tf_printf("--\n");
uv_print_all_handles(&task->_loop, stdout);
}
tf_http_debug_destroy();
uv_run(&task->_loop, UV_RUN_ONCE);
}
if (task->_trace)

View File

@@ -32,7 +32,7 @@
#include <TargetConditionals.h>
#endif
#define TEST_ARGS " --args=ssb_port=0,http_port=0,https_port=0"
#define TEST_ARGS " --args=ssb_port=0,http_port=0"
#if !TARGET_OS_IPHONE
static void _write_file(const char* path, const char* contents)
@@ -694,7 +694,7 @@ static void _test_http(const tf_test_options_t* options)
tf_http_t* http = tf_http_create(&loop);
tf_http_add_handler(http, "/hello", _test_http_handler, NULL, NULL);
tf_http_add_handler(http, "/post", _test_http_handler_post, NULL, NULL);
tf_http_listen(http, 23456, true, NULL, NULL, NULL);
tf_http_listen(http, 23456, true, NULL, NULL);
test_http_t test = { .loop = &loop };
uv_async_init(&loop, &test.async, _test_http_async);
@@ -786,7 +786,7 @@ static void _test_httpd(const tf_test_options_t* options)
uv_spawn(&loop, &process,
&(uv_process_options_t) {
.file = options->exe_path,
.args = (char*[]) { (char*)options->exe_path, "run", "--db-path=out/test_db0.sqlite", "--args=ssb_port=0,http_port=8080,https_port=0", NULL },
.args = (char*[]) { (char*)options->exe_path, "run", "--db-path=out/test_db0.sqlite", "--args=ssb_port=0,http_port=8080", NULL },
.stdio_count = sizeof(stdio) / sizeof(*stdio),
.stdio = stdio,
});

384
src/tls.c
View File

@@ -1,384 +0,0 @@
#include "tls.h"
#include "mem.h"
#include <string.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
typedef enum _direction_t
{
k_direction_undetermined,
k_direction_accept,
k_direction_connect,
} direction_t;
typedef struct _tf_tls_context_t
{
SSL_CTX* context;
} tf_tls_context_t;
typedef struct _tf_tls_session_t
{
tf_tls_context_t* context;
BIO* bio_in;
BIO* bio_out;
SSL* ssl;
const char* hostname;
direction_t direction;
} tf_tls_session_t;
tf_tls_context_t* tf_tls_context_create()
{
tf_tls_context_t* context = tf_malloc(sizeof(tf_tls_context_t));
memset(context, 0, sizeof(*context));
OPENSSL_init_ssl(0, NULL);
context->context = SSL_CTX_new(SSLv23_method());
SSL_CTX_set_default_verify_paths(context->context);
return context;
}
bool tf_tls_context_set_certificate(tf_tls_context_t* context, const char* certificate)
{
int result = 0;
BIO* bio = BIO_new(BIO_s_mem());
BIO_puts(bio, certificate);
X509* x509 = PEM_read_bio_X509(bio, 0, 0, 0);
result = SSL_CTX_use_certificate(context->context, x509);
X509_free(x509);
while (true)
{
x509 = PEM_read_bio_X509(bio, 0, 0, 0);
if (x509)
{
SSL_CTX_add_extra_chain_cert(context->context, x509);
/* Docs say don't x509_free: https://www.openssl.org/docs/man3.2/man3/SSL_CTX_add_extra_chain_cert.html. */
}
else
{
break;
}
}
BIO_free(bio);
return result == 1;
}
bool tf_tls_context_set_private_key(tf_tls_context_t* context, const char* private_key)
{
int result = 0;
BIO* bio = BIO_new(BIO_s_mem());
BIO_puts(bio, private_key);
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, 0, 0, 0);
result = SSL_CTX_use_PrivateKey(context->context, key);
EVP_PKEY_free(key);
BIO_free(bio);
return result == 1;
}
bool tf_tls_context_add_trusted_certificate(tf_tls_context_t* context, const char* certificate)
{
bool result = false;
BIO* bio = BIO_new_mem_buf(certificate, -1);
X509* x509 = PEM_read_bio_X509(bio, 0, 0, 0);
BIO_free(bio);
if (x509)
{
X509_STORE* store = SSL_CTX_get_cert_store(context->context);
if (store && X509_STORE_add_cert(store, x509) == 1)
{
result = true;
}
X509_free(x509);
}
return result;
}
tf_tls_session_t* tf_tls_context_create_session(tf_tls_context_t* context)
{
tf_tls_session_t* session = tf_malloc(sizeof(tf_tls_session_t));
memset(session, 0, sizeof(*session));
session->context = context;
session->bio_in = BIO_new(BIO_s_mem());
session->bio_out = BIO_new(BIO_s_mem());
return session;
}
void tf_tls_context_destroy(tf_tls_context_t* context)
{
SSL_CTX_free(context->context);
OPENSSL_cleanup();
tf_free(context);
}
void tf_tls_session_destroy(tf_tls_session_t* session)
{
if (session->ssl)
{
SSL_free(session->ssl);
}
if (session->hostname)
{
tf_free((void*)session->hostname);
}
tf_free(session);
}
void tf_tls_session_set_hostname(tf_tls_session_t* session, const char* hostname)
{
if (session->hostname)
{
tf_free((void*)session->hostname);
session->hostname = NULL;
}
if (hostname)
{
session->hostname = tf_strdup(hostname);
}
}
void tf_tls_session_start_accept(tf_tls_session_t* session)
{
session->direction = k_direction_accept;
session->ssl = SSL_new(session->context->context);
SSL_set_bio(session->ssl, session->bio_in, session->bio_out);
SSL_accept(session->ssl);
tf_tls_session_handshake(session);
}
void tf_tls_session_start_connect(tf_tls_session_t* session)
{
session->direction = k_direction_connect;
session->ssl = SSL_new(session->context->context);
X509_VERIFY_PARAM* param = SSL_get0_param(session->ssl);
X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
X509_VERIFY_PARAM_set1_host(param, session->hostname, 0);
SSL_set_tlsext_host_name(session->ssl, session->hostname);
SSL_set_bio(session->ssl, session->bio_in, session->bio_out);
SSL_connect(session->ssl);
tf_tls_session_handshake(session);
}
void tf_tls_session_shutdown(tf_tls_session_t* session)
{
SSL_shutdown(session->ssl);
}
int tf_tls_session_get_peer_certificate(tf_tls_session_t* session, char* buffer, size_t bytes)
{
int result = -1;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
X509* certificate = SSL_get_peer_certificate(session->ssl);
#else
X509* certificate = SSL_get1_peer_certificate(session->ssl);
#endif
BIO* bio = BIO_new(BIO_s_mem());
PEM_write_bio_X509(bio, certificate);
X509_free(certificate);
BUF_MEM* mem;
BIO_get_mem_ptr(bio, &mem);
if (mem->length <= bytes)
{
memcpy(buffer, mem->data, mem->length);
result = mem->length;
}
BIO_free(bio);
return result;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static bool _tls_session_wildcard_match(const char* pattern, size_t pattern_length, const char* name)
{
const char* it = pattern;
while (it - pattern < pattern_length && *name)
{
if (*it == '*')
{
for (const char* p = name; *p; ++p)
{
if (_tls_session_wildcard_match(it + 1, pattern_length - 1, p))
{
return true;
}
}
return false;
}
else if (tolower(*it) == tolower(*name))
{
++it;
++name;
}
else
{
break;
}
}
return it - pattern <= pattern_length && *name == 0;
}
static bool _tls_session_verify_hostname(X509* certificate, const char* hostname)
{
bool verified = false;
void* names = X509_get_ext_d2i(certificate, NID_subject_alt_name, 0, 0);
if (names)
{
int count = sk_GENERAL_NAME_num(names);
for (int i = 0; i < count; ++i)
{
const GENERAL_NAME* check = sk_GENERAL_NAME_value(names, i);
if (!verified)
{
#if OPENSSL_VERSION_NUMBER <= 0x1000211fL
const unsigned char* name = ASN1_STRING_data(check->d.ia5);
#else
const char* name = ASN1_STRING_get0_data(check->d.ia5);
#endif
size_t length = ASN1_STRING_length(check->d.ia5);
if (_tls_session_wildcard_match((const char*)name, length, hostname))
{
verified = true;
}
}
}
sk_GENERAL_NAMES_free(names);
}
if (!verified)
{
int index = X509_NAME_get_index_by_NID(X509_get_subject_name(certificate), NID_commonName, -1);
if (index >= 0)
{
X509_NAME_ENTRY* entry = X509_NAME_get_entry(X509_get_subject_name(certificate), index);
if (entry)
{
ASN1_STRING* asn1 = X509_NAME_ENTRY_get_data(entry);
if (asn1)
{
#if OPENSSL_VERSION_NUMBER <= 0x1000211fL
const unsigned char* commonName = ASN1_STRING_data(asn1);
#else
const char* commonName = ASN1_STRING_get0_data(asn1);
#endif
if ((size_t)(ASN1_STRING_length(asn1)) == strlen((const char*)commonName))
{
verified = _tls_session_wildcard_match((const char*)commonName, ASN1_STRING_length(asn1), hostname);
}
}
}
}
}
return verified;
}
#endif
static bool _tls_session_verify_peer_certificate(tf_tls_session_t* session)
{
bool verified = false;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
X509* certificate = SSL_get_peer_certificate(session->ssl);
#else
X509* certificate = SSL_get1_peer_certificate(session->ssl);
#endif
if (certificate)
{
if (SSL_get_verify_result(session->ssl) == X509_V_OK)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000L
if (_tls_session_verify_hostname(certificate, session->hostname))
{
verified = true;
}
#else
verified = true;
#endif
}
X509_free(certificate);
}
return verified;
}
tf_tls_handshake_t tf_tls_session_handshake(tf_tls_session_t* session)
{
tf_tls_handshake_t result = k_tls_handshake_done;
if (!SSL_is_init_finished(session->ssl))
{
int value = SSL_do_handshake(session->ssl);
if (value <= 0)
{
int error = SSL_get_error(session->ssl, value);
if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE)
{
result = k_tls_handshake_failed;
}
else
{
result = k_tls_handshake_more;
}
}
}
if (result == k_tls_handshake_done && session->direction == k_direction_connect && !_tls_session_verify_peer_certificate(session))
{
result = k_tls_handshake_failed;
}
return result;
}
int tf_tls_session_read_plain(tf_tls_session_t* session, char* buffer, size_t bytes)
{
int result = SSL_read(session->ssl, buffer, bytes);
if (result <= 0)
{
int error = SSL_get_error(session->ssl, result);
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)
{
result = 0;
}
else if (error == SSL_ERROR_ZERO_RETURN)
{
if ((SSL_get_shutdown(session->ssl) & SSL_RECEIVED_SHUTDOWN) != 0)
{
result = k_tls_read_zero;
}
else
{
result = 0;
}
}
else
{
result = k_tls_read_failed;
}
}
return result;
}
int tf_tls_session_write_plain(tf_tls_session_t* session, const char* buffer, size_t bytes)
{
return SSL_write(session->ssl, buffer, bytes);
}
int tf_tls_session_read_encrypted(tf_tls_session_t* session, char* buffer, size_t bytes)
{
return BIO_read(session->bio_out, buffer, bytes);
}
int tf_tls_session_write_encrypted(tf_tls_session_t* session, const char* buffer, size_t bytes)
{
return BIO_write(session->bio_in, buffer, bytes);
}
bool tf_tls_session_get_error(tf_tls_session_t* session, char* buffer, size_t bytes)
{
unsigned long error = ERR_get_error();
if (error != 0)
{
ERR_error_string_n(error, buffer, bytes);
}
return error != 0;
}

177
src/tls.h
View File

@@ -1,177 +0,0 @@
#pragma once
/**
** \defgroup tls TLS
** A minimal wrapper around OpenSSL.
** @{
*/
#include <stdbool.h>
#include <stddef.h>
/**
** A TLS context. May have many tf_tls_session_t instances.
*/
typedef struct _tf_tls_context_t tf_tls_context_t;
/**
** A TLS session. Belongs to one tf_tls_context_t and represents a single connection.
*/
typedef struct _tf_tls_session_t tf_tls_session_t;
/**
** The state of a TLS handshake.
*/
typedef enum _tf_tls_handshake_t
{
k_tls_handshake_done,
k_tls_handshake_more,
k_tls_handshake_failed,
} tf_tls_handshake_t;
/**
** Possible error statuses from tf_tls_session_read_plain.
*/
typedef enum _tf_tls_read_t
{
k_tls_read_zero = -1,
k_tls_read_failed = -2,
} tf_tls_read_t;
/**
** Create a TLS context. Clean up with tf_tls_context_destroy().
** @return A new TLS context.
*/
tf_tls_context_t* tf_tls_context_create();
/**
** Set the TLS context's server certificate.
** @param context The TLS context.
** @param certificate The certificate in PEM format.
** @return true if set successfully.
*/
bool tf_tls_context_set_certificate(tf_tls_context_t* context, const char* certificate);
/**
** Set the TLS context's server certificate's private key.
** @param context The TLS context.
** @param private_key The private key in PEM format.
** @return true if set successfully.
*/
bool tf_tls_context_set_private_key(tf_tls_context_t* context, const char* private_key);
/**
** Add a trusted certificate.
** @param context The TLS context.
** @param certificate The certificate in PEM format.
** @return true if the certificate was added to the trusted list successfully.
*/
bool tf_tls_context_add_trusted_certificate(tf_tls_context_t* context, const char* certificate);
/**
** Create a TLS session from a context. Once created, call
** tf_tls_session_handshake() until it returns k_tls_handshake_done. Call
** tf_tls_session_[read/write]_[plain/encrypted]() as data is available.
** @param context The TLS context. Clean up with tf_tls_session_destroy().
** @return A new TLS session.
*/
tf_tls_session_t* tf_tls_context_create_session(tf_tls_context_t* context);
/**
** Destroy a TLS context.
** @param context The TLS contextx created by tf_tls_context_create().
*/
void tf_tls_context_destroy(tf_tls_context_t* context);
/**
** Destroy a TLS session.
** @param session A TLS sesssion created by tf_tls_context_create_session().
*/
void tf_tls_session_destroy(tf_tls_session_t* session);
/**
** Set the remote hostname for a session.
** @param session The TLS session.
** @param hostname The hostname.
*/
void tf_tls_session_set_hostname(tf_tls_session_t* session, const char* hostname);
/**
** Begin an outgoing TLS session.
** @param session The TLS session.
*/
void tf_tls_session_start_accept(tf_tls_session_t* session);
/**
** Begin an incoming TLS session.
** @param session The TLS session.
*/
void tf_tls_session_start_connect(tf_tls_session_t* session);
/**
** Begin the clean shutdown process for a TLS session.
** @param session The TLS session.
*/
void tf_tls_session_shutdown(tf_tls_session_t* session);
/**
** Get the certificate from the remote end of a TLS session if available.
** @param session The TLS session.
** @param buffer A buffer to receive the certificate.
** @param bytes The size of the buffer.
** @return The size of the returned certificate, or -1 on failure.
*/
int tf_tls_session_get_peer_certificate(tf_tls_session_t* session, char* buffer, size_t bytes);
/**
** Update the TLS handshake. Call repeatedly as new data is available until it returns done.
** @param session The TLS session.
** @return The current state of the handshake process.
*/
tf_tls_handshake_t tf_tls_session_handshake(tf_tls_session_t* session);
/**
** Read decrypted data from the TLS session.
** @param session The TLS session.
** @param buffer A buffer to receive the data.
** @param bytes The size of the buffer.
** @return The number of bytes returned.
*/
int tf_tls_session_read_plain(tf_tls_session_t* session, char* buffer, size_t bytes);
/**
** Write unencrypted data to the TLS session.
** @param session The TLS session.
** @param buffer The data to encrypt.
** @param bytes The size of the data.
** @return 1 on success, 0 on failure.
*/
int tf_tls_session_write_plain(tf_tls_session_t* session, const char* buffer, size_t bytes);
/**
** Read encrypted data from the TLS session that needs to be sent.
** @param session The TLS session.
** @param buffer A buffer to receive the data.
** @param bytes The size of the buffer.
** @return The number of bytes returned.
*/
int tf_tls_session_read_encrypted(tf_tls_session_t* session, char* buffer, size_t bytes);
/**
** Write encrypted data to the TLS session.
** @param session The TLS session.
** @param buffer The encrypted data.
** @param bytes The number of bytes written.
*/
int tf_tls_session_write_encrypted(tf_tls_session_t* session, const char* buffer, size_t bytes);
/**
** Retrieve the last error from a TLS session.
** @param session The TLS session.
** @param buffer A buffer to receive the error text.
** @param bytes The size of the buffer.
** @return true if an error was retrieved.
*/
bool tf_tls_session_get_error(tf_tls_session_t* session, char* buffer, size_t bytes);
/** @} */

View File

@@ -289,7 +289,6 @@ static const setting_t k_settings[] = {
.description = "Whether to bind http(s) to the loopback address. Otherwise any.",
.default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE ? true : false } },
{ .name = "http_port", .type = "integer", .description = "Port on which to listen for HTTP connections.", .default_value = { .kind = k_kind_int, .int_value = 12345 } },
{ .name = "https_port", .type = "integer", .description = "Port on which to listen for secure HTTP connections.", .default_value = { .kind = k_kind_int, .int_value = 0 } },
{ .name = "out_http_port_file", .type = "hidden", .description = "File to which to write bound HTTP port.", .default_value = { .kind = k_kind_string, .string_value = NULL } },
{ .name = "blob_fetch_age_seconds",
.type = "integer",
@@ -381,6 +380,31 @@ const char* tf_util_get_default_global_setting_string(const char* name)
return setting && setting->default_value.string_value ? setting->default_value.string_value : "";
}
bool tf_util_get_global_setting_by_index(int index, const char** out_name, const char** out_type, tf_setting_kind_t* out_kind, const char** out_description)
{
if (index >= 0 && index < tf_countof(k_settings))
{
if (out_name)
{
*out_name = k_settings[index].name;
}
if (out_type)
{
*out_type = k_settings[index].type;
}
if (out_kind)
{
*out_kind = k_settings[index].default_value.kind;
}
if (out_description)
{
*out_description = k_settings[index].description;
}
return true;
}
return false;
}
static JSValue _util_defaultGlobalSettings(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue settings = JS_NewObject(context);

View File

@@ -212,6 +212,16 @@ const char* tf_util_get_default_global_setting_string(const char* name);
*/
tf_setting_kind_t tf_util_get_global_setting_kind(const char* name);
/**
** Get the index-th global setting.
** @param index The index.
** @param out_name Populated with the setting name.
** @param out_type Populated with the setting type.
** @param out_kind Populated with the setting kind.
** @param out_description Populated with the setting description.
*/
bool tf_util_get_global_setting_by_index(int index, const char** out_name, const char** out_type, tf_setting_kind_t* out_kind, const char** out_description);
/**
** Log documentation for the available settings.
** @param line_prefix Text to prefix each line with."

View File

@@ -1,99 +0,0 @@
#!/usr/bin/env bash
if [[ -z $BUILD_PLATFORM ]]; then
BUILD_PLATFORM=$(uname -s)
fi
if [[ -z $BUILD_TARGET ]]; then
BUILD_TARGET=$(uname -m)
WORK_DIR=out/openssl-local
else
WORK_DIR=out/openssl-$BUILD_PLATFORM-$BUILD_TARGET
if [[ -z $SSL_TARGET ]]; then
SSL_TARGET=linux-$BUILD_PLATFORM-$BUILD_TARGET
fi
fi
rm -rf $WORK_DIR
mkdir -p out/
cp -aRf deps/openssl_src/ $WORK_DIR
echo "Building"
pwd
pushd $WORK_DIR || exit 128
rm -rf $DESTDIR
echo $PATH
export GLOBAL_OPTIONS=" \
no-apps \
no-asm \
no-async \
no-autoerrinit \
no-autoload-config \
no-cmp \
no-cms \
no-comp \
no-deprecated \
no-dgram \
no-docs \
no-dsa \
no-dso \
no-dtls \
no-dtls1 \
no-dtls1-method \
no-dynamic-engine \
no-ec2m \
no-egd \
no-engine \
no-err \
no-filenames \
no-gost \
no-http \
no-idea \
no-legacy \
no-md2 \
no-md4 \
no-module \
no-multiblock \
no-nextprotoneg \
no-ocsp \
no-psk \
no-shared \
no-sock \
no-srp \
no-ssl \
no-ssl3 \
no-ssl-trace \
no-stdio \
no-tests \
no-thread-pool \
no-threads \
no-tls1 \
no-tls1-method \
no-trace \
no-ui-console \
no-uplink \
no-weak-ssl-ciphers \
no-whirlpool \
no-zlib \
-Os \
-DOPENSSL_SMALL_FOOTPRINT \
-Wno-error \
-ffunction-sections \
-fdata-sections \
--static \
-static \
"
pwd
echo "./Configure $SSL_TARGET $OPTIONS $GLOBAL_OPTIONS" && \
./Configure $SSL_TARGET $OPTIONS $GLOBAL_OPTIONS && \
make -s clean && \
make -s build_generated && \
make -s libcrypto.a libssl.a || exit 128
popd
echo WORK_DIR=$WORK_DIR
rm -rf out/openssl/$BUILD_PLATFORM/$BUILD_TARGET/
mkdir -p out/openssl/$BUILD_PLATFORM/$BUILD_TARGET/usr/local/include/
mkdir -p out/openssl/$BUILD_PLATFORM/$BUILD_TARGET/usr/local/lib/
cp -R $WORK_DIR/include/* out/openssl/$BUILD_PLATFORM/$BUILD_TARGET/usr/local/include/
cp $WORK_DIR/*.a out/openssl/$BUILD_PLATFORM/$BUILD_TARGET/usr/local/lib/
echo Success