Compare commits

..

No commits in common. "main" and "tasiaiso-nix" have entirely different histories.

106 changed files with 5823 additions and 12296 deletions

View File

@ -1,35 +0,0 @@
name: Build Tilde Friends
run-name: ${{ gitea.actor }} running 🚀
on: [push]
jobs:
Build-All:
runs-on: ubuntu-latest
container:
valid_volumes: ['/opt/keys']
volumes:
- /opt/keys:/opt/keys
steps:
- name: check out code
uses: actions/checkout@v4
with:
submodules: true
- run: ln -s /opt/keys .keys
- name: Setup JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
packages: 'tools platform-tools build-tools;34.0.0 platforms;android-34 ndk;26.3.11579264'
- run: sudo apt update && sudo apt install -y doxygen graphviz mingw-w64
- run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all docs
- run: docker build .
- uses: actions/upload-artifact@v3
with:
path: out/TildeFriends-release.fdroid.apk
- uses: actions/upload-artifact@v3
with:
path: out/winrelease/tildefriends.exe

8
.gitignore vendored
View File

@ -1,17 +1,11 @@
build/
*.core
db.* db.*
deps/ios_toolchain/ deps/ios_toolchain/
deps/openssl/ deps/openssl/
dist/ dist/
.keys .keys
logs/
**/node_modules **/node_modules
out out
repo/
result
*.swo *.swo
*.swp *.swp
tmp/
unsigned/
.zsign_cache/ .zsign_cache/
result

7
.gitmodules vendored
View File

@ -19,10 +19,3 @@
[submodule "deps/picohttpparser"] [submodule "deps/picohttpparser"]
path = deps/picohttpparser path = deps/picohttpparser
url = https://github.com/h2o/picohttpparser.git 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

@ -2,7 +2,6 @@ node_modules
src src
deps deps
.clang-format .clang-format
flake.lock
# Minified files # Minified files
**/*.min.css **/*.min.css

659
Doxyfile

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,12 @@
MAKEFLAGS += --warn-undefined-variables MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 27 VERSION_CODE := 19
VERSION_NUMBER := 0.0.23-wip VERSION_NUMBER := 0.0.19-wip
VERSION_NAME := Me upon my pony on my boat. VERSION_NAME := Don't let your loyalty become a burden.
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3460100.zip SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3450300.zip
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar LIBUV_URL := https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz
PROJECT = tildefriends PROJECT = tildefriends
BUILD_DIR ?= out BUILD_DIR ?= out
@ -16,13 +16,9 @@ UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m) UNAME_M := $(shell uname -m)
ANDROID_SDK ?= ~/Android/Sdk ANDROID_SDK ?= ~/Android/Sdk
BUNDLETOOL = out/bundletool.jar
HAVE_WIN := 0 HAVE_WIN := 0
export SOURCE_DATE_EPOCH=1
export TZ=UTC
ifeq ($(UNAME_S),Darwin) ifeq ($(UNAME_S),Darwin)
BUILD_TYPES := macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease BUILD_TYPES := macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease
else ifeq ($(UNAME_S),Linux) else ifeq ($(UNAME_S),Linux)
@ -35,8 +31,7 @@ BUILD_TYPES := debug release
CFLAGS += -Dstatic_assert=_Static_assert CFLAGS += -Dstatic_assert=_Static_assert
LDFLAGS += \ LDFLAGS += \
-lbsd \ -lbsd \
-lnetwork \ -lnetwork
-Wno-stringop-overflow
else ifeq ($(UNAME_S),OpenBSD) else ifeq ($(UNAME_S),OpenBSD)
BUILD_TYPES := debug release BUILD_TYPES := debug release
CFLAGS += \ CFLAGS += \
@ -55,22 +50,18 @@ CFLAGS += \
-Wall \ -Wall \
-Wextra \ -Wextra \
-Wno-unused-parameter \ -Wno-unused-parameter \
-Wno-unknown-warning-option \
-MMD \ -MMD \
-MP \ -MP \
-ffunction-sections \ -ffunction-sections \
-fdata-sections \ -fdata-sections \
-fno-exceptions \ -fno-exceptions \
-g -g
LDFLAGS += \
-Wno-attributes \
-flto=auto
ANDROID_MIN_SDK_VERSION := 24 ANDROID_MIN_SDK_VERSION := 24
ANDROID_TARGET_SDK_VERSION := 34 ANDROID_TARGET_SDK_VERSION := 34
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0 ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION) ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.3.11579264 ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
ANDROID_ARMV7A_TARGETS := \ ANDROID_ARMV7A_TARGETS := \
out/androiddebug-armv7a/tildefriends \ out/androiddebug-armv7a/tildefriends \
@ -99,7 +90,7 @@ BUILD_TYPES += \
androidrelease-x86 \ androidrelease-x86 \
androiddebug-x86_64 \ androiddebug-x86_64 \
androidrelease-x86_64 androidrelease-x86_64
all: out/TildeFriends-arm-debug.apk out/TildeFriends-arm-release.apk out/TildeFriends-x86-debug.apk out/TildeFriends-x86-release.apk out/TildeFriends-release.fdroid.apk all: out/TildeFriends-arm-debug.apk out/TildeFriends-arm-release.apk out/TildeFriends-x86-debug.apk out/TildeFriends-x86-release.apk
endif endif
WINDOWS_TARGETS := \ WINDOWS_TARGETS := \
@ -159,24 +150,19 @@ ANDROID_RELEASE_TARGETS := $(filter-out $(DEBUG_TARGETS),$(ANDROID_TARGETS))
NONANDROID_RELEASE_TARGETS := $(filter-out $(ANDROID_ARM64_TARGETS),$(RELEASE_TARGETS)) NONANDROID_RELEASE_TARGETS := $(filter-out $(ANDROID_ARM64_TARGETS),$(RELEASE_TARGETS))
NONANDROID_TARGETS := $(filter-out $(ANDROID_TARGETS),$(ALL_TARGETS)) NONANDROID_TARGETS := $(filter-out $(ANDROID_TARGETS),$(ALL_TARGETS))
NONMACOS_TARGETS := $(filter-out $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS),$(ALL_TARGETS)) NONMACOS_TARGETS := $(filter-out $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS),$(ALL_TARGETS))
DEADSTRIP_TARGETS := $(filter-out $(ANDROID_TARGETS),$(NONMACOS_TARGETS))
$(NONMACOS_TARGETS): LDFLAGS += -static-libgcc
$(NONANDROID_TARGETS): CFLAGS += -fno-omit-frame-pointer $(NONANDROID_TARGETS): CFLAGS += -fno-omit-frame-pointer
$(filter-out $(WINDOWS_TARGETS),$(ALL_TARGETS)): LDFLAGS += -rdynamic $(filter-out $(ANDROID_TARGETS) $(WINDOWS_TARGETS),$(ALL_TARGETS)): LDFLAGS += -rdynamic
$(ANDROID_TARGETS): CFLAGS += \ $(ANDROID_TARGETS): CFLAGS += \
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
-fPIC \ -fPIC \
-fdebug-compilation-dir . \ -fdebug-compilation-dir . \
-fomit-frame-pointer \ -fomit-frame-pointer \
-fno-asynchronous-unwind-tables \ -fno-asynchronous-unwind-tables \
-funwind-tables \ -funwind-tables
-Wno-unknown-warning-option
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC $(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og $(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
$(RELEASE_TARGETS): CFLAGS += \ $(RELEASE_TARGETS): CFLAGS += -DNDEBUG
-DNDEBUG \
-flto
$(NONANDROID_RELEASE_TARGETS): CFLAGS += -O3 $(NONANDROID_RELEASE_TARGETS): CFLAGS += -O3
$(ANDROID_RELEASE_TARGETS): CFLAGS += -Oz $(ANDROID_RELEASE_TARGETS): CFLAGS += -Oz
$(WINDOWS_TARGETS): CC = x86_64-w64-mingw32-gcc-win32 $(WINDOWS_TARGETS): CC = x86_64-w64-mingw32-gcc-win32
@ -219,7 +205,7 @@ $(ANDROID_X86_TARGETS): LDFLAGS += -Ldeps/openssl/android/x86/usr/local/lib
$(ANDROID_X86_64_TARGETS): CFLAGS += -Ideps/openssl/android/x86_64/usr/local/include $(ANDROID_X86_64_TARGETS): CFLAGS += -Ideps/openssl/android/x86_64/usr/local/include
$(ANDROID_X86_64_TARGETS): LDFLAGS += -Ldeps/openssl/android/x86_64/usr/local/lib $(ANDROID_X86_64_TARGETS): LDFLAGS += -Ldeps/openssl/android/x86_64/usr/local/lib
$(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type $(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type
$(DEADSTRIP_TARGETS): LDFLAGS += -Wl,--gc-sections $(NONMACOS_TARGETS): LDFLAGS += -Wl,--gc-sections
$(IOS_TARGETS): CFLAGS += -mios-version-min=9.0 -Ideps/openssl/ios/ios64-xcrun/usr/local/include $(IOS_TARGETS): CFLAGS += -mios-version-min=9.0 -Ideps/openssl/ios/ios64-xcrun/usr/local/include
$(IOS_TARGETS): LDFLAGS += -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib $(IOS_TARGETS): LDFLAGS += -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib
$(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include $(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include
@ -252,8 +238,6 @@ APP_SOURCES_ios := $(wildcard src/*.m)
APP_OBJS := $(call get_objs,APP_SOURCES) APP_OBJS := $(call get_objs,APP_SOURCES)
$(APP_OBJS): CFLAGS += \ $(APP_OBJS): CFLAGS += \
-Ideps/base64c/include \ -Ideps/base64c/include \
-Ideps/c-ares/include \
-Ideps/c-ares_config \
-Ideps/crypt_blowfish \ -Ideps/crypt_blowfish \
-Ideps/libbacktrace \ -Ideps/libbacktrace \
-Ideps/libsodium \ -Ideps/libsodium \
@ -272,105 +256,6 @@ $(filter-out $(BUILD_DIR)/android% $(BUILD_DIR)/macos% $(BUILD_DIR)/ios%,$(APP_O
-fanalyzer -fanalyzer
endif endif
ARES_SOURCES := \
deps/c-ares/src/lib/ares_platform.c \
deps/c-ares/src/lib/record/ares_dns_mapping.c \
deps/c-ares/src/lib/record/ares_dns_parse.c \
deps/c-ares/src/lib/record/ares_dns_write.c \
deps/c-ares/src/lib/record/ares_dns_name.c \
deps/c-ares/src/lib/record/ares_dns_record.c \
deps/c-ares/src/lib/record/ares_dns_multistring.c \
deps/c-ares/src/lib/ares_destroy.c \
deps/c-ares/src/lib/ares_data.c \
deps/c-ares/src/lib/ares_sysconfig.c \
deps/c-ares/src/lib/ares_cancel.c \
deps/c-ares/src/lib/ares_metrics.c \
deps/c-ares/src/lib/ares_getnameinfo.c \
deps/c-ares/src/lib/legacy/ares_parse_txt_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_naptr_reply.c \
deps/c-ares/src/lib/legacy/ares_create_query.c \
deps/c-ares/src/lib/legacy/ares_parse_mx_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_srv_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_ptr_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_caa_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_aaaa_reply.c \
deps/c-ares/src/lib/legacy/ares_expand_name.c \
deps/c-ares/src/lib/legacy/ares_parse_uri_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_a_reply.c \
deps/c-ares/src/lib/legacy/ares_expand_string.c \
deps/c-ares/src/lib/legacy/ares_fds.c \
deps/c-ares/src/lib/legacy/ares_parse_ns_reply.c \
deps/c-ares/src/lib/legacy/ares_parse_soa_reply.c \
deps/c-ares/src/lib/legacy/ares_getsock.c \
deps/c-ares/src/lib/windows_port.c \
deps/c-ares/src/lib/ares_qcache.c \
deps/c-ares/src/lib/ares_update_servers.c \
deps/c-ares/src/lib/ares_process.c \
deps/c-ares/src/lib/ares_getenv.c \
deps/c-ares/src/lib/ares_gethostbyname.c \
deps/c-ares/src/lib/ares_send.c \
deps/c-ares/src/lib/dsa/ares__slist.c \
deps/c-ares/src/lib/dsa/ares__htable.c \
deps/c-ares/src/lib/dsa/ares__llist.c \
deps/c-ares/src/lib/dsa/ares__htable_szvp.c \
deps/c-ares/src/lib/dsa/ares__htable_asvp.c \
deps/c-ares/src/lib/dsa/ares__htable_vpvp.c \
deps/c-ares/src/lib/dsa/ares__htable_strvp.c \
deps/c-ares/src/lib/dsa/ares__array.c \
deps/c-ares/src/lib/ares__socket.c \
deps/c-ares/src/lib/event/ares_event_poll.c \
deps/c-ares/src/lib/event/ares_event_thread.c \
deps/c-ares/src/lib/event/ares_event_select.c \
deps/c-ares/src/lib/event/ares_event_kqueue.c \
deps/c-ares/src/lib/event/ares_event_configchg.c \
deps/c-ares/src/lib/event/ares_event_epoll.c \
deps/c-ares/src/lib/event/ares_event_wake_pipe.c \
deps/c-ares/src/lib/event/ares_event_win32.c \
deps/c-ares/src/lib/ares_search.c \
deps/c-ares/src/lib/ares__parse_into_addrinfo.c \
deps/c-ares/src/lib/ares__hosts_file.c \
deps/c-ares/src/lib/ares_getaddrinfo.c \
deps/c-ares/src/lib/ares__addrinfo2hostent.c \
deps/c-ares/src/lib/ares_freeaddrinfo.c \
deps/c-ares/src/lib/ares_strerror.c \
deps/c-ares/src/lib/ares_version.c \
deps/c-ares/src/lib/ares_gethostbyaddr.c \
deps/c-ares/src/lib/ares__addrinfo_localhost.c \
deps/c-ares/src/lib/ares_free_hostent.c \
deps/c-ares/src/lib/ares__close_sockets.c \
deps/c-ares/src/lib/ares_free_string.c \
deps/c-ares/src/lib/ares_init.c \
deps/c-ares/src/lib/ares_options.c \
deps/c-ares/src/lib/str/ares_strcasecmp.c \
deps/c-ares/src/lib/str/ares__buf.c \
deps/c-ares/src/lib/str/ares_strsplit.c \
deps/c-ares/src/lib/str/ares_str.c \
deps/c-ares/src/lib/ares_sysconfig_mac.c \
deps/c-ares/src/lib/ares__sortaddrinfo.c \
deps/c-ares/src/lib/ares_sysconfig_files.c \
deps/c-ares/src/lib/util/ares__iface_ips.c \
deps/c-ares/src/lib/util/ares__timeval.c \
deps/c-ares/src/lib/util/ares_math.c \
deps/c-ares/src/lib/util/ares_rand.c \
deps/c-ares/src/lib/util/ares__threads.c \
deps/c-ares/src/lib/ares_query.c \
deps/c-ares/src/lib/ares_cookie.c \
deps/c-ares/src/lib/inet_net_pton.c \
deps/c-ares/src/lib/inet_ntop.c \
deps/c-ares/src/lib/ares_library_init.c \
deps/c-ares/src/lib/ares_android.c \
deps/c-ares/src/lib/ares_sysconfig_win.c \
deps/c-ares/src/lib/ares_timeout.c
ARES_OBJS := $(call get_objs,ARES_SOURCES)
$(ARES_OBJS): CFLAGS += \
-Ideps/c-ares/include \
-Ideps/c-ares/src/lib \
-Ideps/c-ares_config/ \
-D_GNU_SOURCE \
-Wno-unused-function \
-Wno-deprecated-declarations \
-Wno-unused-result
BLOWFISH_SOURCES := \ BLOWFISH_SOURCES := \
deps/crypt_blowfish/crypt_blowfish.c \ deps/crypt_blowfish/crypt_blowfish.c \
deps/crypt_blowfish/crypt_gensalt.c \ deps/crypt_blowfish/crypt_gensalt.c \
@ -502,8 +387,6 @@ $(UV_OBJS): CFLAGS += \
-Wno-unused-but-set-variable \ -Wno-unused-but-set-variable \
-Wno-unused-result \ -Wno-unused-result \
-Wno-unused-variable -Wno-unused-variable
$(UV_OBJS): CFLAGS += -fno-lto
$(filter out/win%,$(UV_OBJS)): CFLAGS += -Wno-cast-function-type
ifeq ($(UNAME_S),Linux) ifeq ($(UNAME_S),Linux)
$(UV_OBJS): CFLAGS += \ $(UV_OBJS): CFLAGS += \
-D_GNU_SOURCE -D_GNU_SOURCE
@ -579,7 +462,6 @@ ifneq ($(UNAME_S),OpenBSD)
$(filter-out $(BUILD_DIR)/win%,$(SODIUM_OBJS)): CFLAGS += \ $(filter-out $(BUILD_DIR)/win%,$(SODIUM_OBJS)): CFLAGS += \
-DHAVE_ALLOCA_H -DHAVE_ALLOCA_H
endif endif
$(SODIUM_OBJS): CFLAGS := $(filter-out -flto,$(CFLAGS))
SQLITE_SOURCES := deps/sqlite/sqlite3.c SQLITE_SOURCES := deps/sqlite/sqlite3.c
SQLITE_OBJS := $(call get_objs,SQLITE_SOURCES) SQLITE_OBJS := $(call get_objs,SQLITE_SOURCES)
@ -739,7 +621,6 @@ all: $(BUILD_TYPES)
ALL_APP_OBJS := \ ALL_APP_OBJS := \
$(APP_OBJS) \ $(APP_OBJS) \
$(ARES_OBJS) \
$(BLOWFISH_OBJS) \ $(BLOWFISH_OBJS) \
$(LIBBACKTRACE_OBJS) \ $(LIBBACKTRACE_OBJS) \
$(MINIUNZIP_OBJS) \ $(MINIUNZIP_OBJS) \
@ -804,25 +685,8 @@ out/res/drawable_icon.xml.flat: src/android/res/drawable/icon.xml
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/drawable/icon.xml @$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/drawable/icon.xml
out/apk/res.apk out/gen/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat src/android/AndroidManifest.xml out/apk/res.apk out/gen/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat src/android/AndroidManifest.xml
@echo [aapt2 link] res.apk @mkdir -p $(dir $@)
@mkdir -p out/apk/ @$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat --manifest src/android/AndroidManifest.xml -o out/apk/res.apk --java out/gen/
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat \
--min-sdk-version $(ANDROID_MIN_SDK_VERSION) \
--target-sdk-version $(ANDROID_TARGET_SDK_VERSION) \
--manifest src/android/AndroidManifest.xml \
-o out/apk/res.apk \
--java out/gen/
out/apk/res.fdroid.apk out/gen_fdroid/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat src/android/AndroidManifest.xml
@echo [aapt2 link] res.fdroid.apk
@mkdir -p out/apk/
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat \
--min-sdk-version $(ANDROID_MIN_SDK_VERSION) \
--target-sdk-version $(ANDROID_TARGET_SDK_VERSION) \
--rename-manifest-package com.unprompted.tildefriends.fdroid \
--manifest src/android/AndroidManifest.xml \
-o out/apk/res.fdroid.apk \
--java out/gen_fdroid/
JAVA_FILES := out/gen/com/unprompted/tildefriends/R.java $(wildcard src/android/com/unprompted/tildefriends/*.java) JAVA_FILES := out/gen/com/unprompted/tildefriends/R.java $(wildcard src/android/com/unprompted/tildefriends/*.java)
CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefriends/$(notdir $(src:.java=.class))) CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefriends/$(notdir $(src:.java=.class)))
@ -834,7 +698,7 @@ $(CLASS_FILES) &: $(JAVA_FILES)
out/apk/classes.dex: $(CLASS_FILES) out/apk/classes.dex: $(CLASS_FILES)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@echo "[d8] $@" @echo "[d8] $@"
@$(ANDROID_BUILD_TOOLS)/d8 --lib $(ANDROID_PLATFORM)/android.jar --output $(dir $@) out/classes/com/unprompted/tildefriends/*.class @$(ANDROID_BUILD_TOOLS)/d8 --$(BUILD_TYPE) --lib $(ANDROID_PLATFORM)/android.jar --output $(dir $@) out/classes/com/unprompted/tildefriends/*.class
PACKAGE_DIRS := \ PACKAGE_DIRS := \
apps/ \ apps/ \
@ -843,79 +707,25 @@ PACKAGE_DIRS := \
deps/prettier/ \ deps/prettier/ \
deps/lit/ deps/lit/
RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f -not -name '.*'))) RAW_FILES := $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f))
out/apk/TildeFriends-arm-debug.unsigned.apk: BUILD_TYPE := debug out/apk/TildeFriends-arm-debug.unsigned.apk: BUILD_TYPE := debug
out/apk/TildeFriends-arm-release.unsigned.apk: BUILD_TYPE := release out/apk/TildeFriends-arm-release.unsigned.apk: BUILD_TYPE := release
out/apk/TildeFriends-x86-debug.unsigned.apk: BUILD_TYPE := debug out/apk/TildeFriends-x86-debug.unsigned.apk: BUILD_TYPE := debug
out/apk/TildeFriends-x86-release.unsigned.apk: BUILD_TYPE := release out/apk/TildeFriends-x86-release.unsigned.apk: BUILD_TYPE := release
out/apk/TildeFriends-release.fdroid.unsigned.apk: BUILD_TYPE := release
out/apk/TildeFriends-arm-debug.unsigned.apk: out/apk/classes.dex out/androiddebug/tildefriends out/androiddebug-armv7a/tildefriends $(RAW_FILES) out/apk/res.apk out/apk/TildeFriends-arm-debug.unsigned.apk: out/apk/classes.dex out/androiddebug/tildefriends out/androiddebug-armv7a/tildefriends $(RAW_FILES) out/apk/res.apk
out/apk/TildeFriends-arm-release.unsigned.apk: out/apk/classes.dex out/androidrelease/tildefriends out/androidrelease-armv7a/tildefriends $(RAW_FILES) out/apk/res.apk out/apk/TildeFriends-arm-release.unsigned.apk: out/apk/classes.dex out/androidrelease/tildefriends out/androidrelease-armv7a/tildefriends $(RAW_FILES) out/apk/res.apk
out/apk/TildeFriends-x86-debug.unsigned.apk: out/apk/classes.dex out/androiddebug-x86_64/tildefriends out/androiddebug-x86/tildefriends $(RAW_FILES) out/apk/res.apk out/apk/TildeFriends-x86-debug.unsigned.apk: out/apk/classes.dex out/androiddebug-x86_64/tildefriends out/androiddebug-x86/tildefriends $(RAW_FILES) out/apk/res.apk
out/apk/TildeFriends-x86-release.unsigned.apk: out/apk/classes.dex out/androidrelease-x86_64/tildefriends out/androidrelease-x86/tildefriends $(RAW_FILES) out/apk/res.apk out/apk/TildeFriends-x86-release.unsigned.apk: out/apk/classes.dex out/androidrelease-x86_64/tildefriends out/androidrelease-x86/tildefriends $(RAW_FILES) out/apk/res.apk
out/apk/TildeFriends-release.fdroid.unsigned.apk: out/apk/classes.dex out/androidrelease/tildefriends out/androidrelease-armv7a/tildefriends out/androidrelease-x86_64/tildefriends out/androidrelease-x86/tildefriends $(RAW_FILES) out/apk/res.fdroid.apk
$(BUNDLETOOL):
@echo [curl] $(BUNDLETOOL_URL) TO $@
@curl -q -L --create-dirs -o $@ $(BUNDLETOOL_URL)
out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGETS)) $(RAW_FILES) out/apk/res.apk src/android/AndroidManifest.xml $(BUNDLETOOL)
@rm -rf out/aab/staging/
@mkdir -p out/aab/staging
@$(ANDROID_BUILD_TOOLS)/aapt2 link --proto-format -o out/aab/temporary.apk \
-I $(ANDROID_PLATFORM)/android.jar \
--min-sdk-version $(ANDROID_MIN_SDK_VERSION) \
--target-sdk-version $(ANDROID_TARGET_SDK_VERSION) \
--manifest src/android/AndroidManifest.xml \
-R out/res/layout_activity_main.xml.flat \
-R out/res/drawable_icon.xml.flat \
--auto-add-overlay
@unzip out/aab/temporary.apk -d out/aab/staging/
@mkdir -p out/aab/staging/root/deps
@mkdir -p out/aab/staging/classes
@mkdir -p out/aab/staging/dex
@mkdir -p out/aab/staging/manifest
@mv out/aab/staging/AndroidManifest.xml out/aab/staging/manifest/AndroidManifest.xml
@cp out/apk/classes.dex out/aab/staging/dex/
@rm -fv out/base.zip
@mkdir -p out/aab/staging/lib/arm64-v8a out/aab/staging/lib/armeabi-v7a out/aab/staging/lib/x86_64 out/aab/staging/lib/x86
@cp out/androidrelease/tildefriends out/aab/staging/lib/arm64-v8a/libtildefriends.so
@cp out/androidrelease-armv7a/tildefriends out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@cp out/androidrelease-x86_64/tildefriends out/aab/staging/lib/x86_64/libtildefriends.so
@cp out/androidrelease-x86/tildefriends out/aab/staging/lib/x86/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/arm64-v8a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/armeabi-v7a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/aab/staging/lib/x86/libtildefriends.so
@cp -r apps/ out/aab/staging/root/
@rm -rf out/aab/staging/root/apps/welcome*
@cp -r core/ out/aab/staging/root/
@cp -r deps/prettier/ out/aab/staging/root/deps/
@cp -r deps/lit/ out/aab/staging/root/deps/
@cp -r deps/codemirror/ out/aab/staging/root/deps/
@cd out/aab/staging/; zip -r ../base.zip *; cd ../../../
@java -jar $(BUNDLETOOL) build-bundle --overwrite --config=src/android/BundleConfig.json --modules=out/aab/base.zip --output=$@
@jarsigner -keystore .keys/android.jks $@ androidKey -storepass android
aab: out/TildeFriends.aab
.PHONY: aab
out/TildeFriends.apks: out/TildeFriends.aab $(BUNDLETOOL)
@java -jar $(BUNDLETOOL) build-apks --bundle out/TildeFriends.aab --overwrite --output $@ --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android
aabgo: out/TildeFriends.apks $(BUNDLETOOL)
@java -jar $(BUNDLETOOL) install-apks --apks out/TildeFriends.apks
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
out/apk/TildeFriends-arm-%.unsigned.apk: out/apk/TildeFriends-arm-%.unsigned.apk:
@mkdir -p $(dir $@) out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/ out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/ @mkdir -p $(dir $@) out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/ out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/
@echo "[aapt] $@" @echo "[aapt] $@"
@cp out/android$(BUILD_TYPE)/tildefriends out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/libtildefriends.so @cp out/android$(BUILD_TYPE)/tildefriends out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/tildefriends.so
@cp out/android$(BUILD_TYPE)-armv7a/tildefriends out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/libtildefriends.so @cp out/android$(BUILD_TYPE)-armv7a/tildefriends out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-arm-$(BUILD_TYPE)/lib/arm64-v8a/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-arm-$(BUILD_TYPE)/lib/armeabi-v7a/tildefriends.so
@cp out/apk/res.apk $@.zip @cp out/apk/res.apk $@.zip
@cp out/apk/classes.dex out/apk-arm-$(BUILD_TYPE)/ @cp out/apk/classes.dex out/apk-arm-$(BUILD_TYPE)/
@cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../ @cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@ -925,38 +735,16 @@ out/apk/TildeFriends-arm-%.unsigned.apk:
out/apk/TildeFriends-x86-%.unsigned.apk: out/apk/TildeFriends-x86-%.unsigned.apk:
@mkdir -p $(dir $@) out/apk-x86-$(BUILD_TYPE)/lib/x86_64/ out/apk-x86-$(BUILD_TYPE)/lib/x86/ @mkdir -p $(dir $@) out/apk-x86-$(BUILD_TYPE)/lib/x86_64/ out/apk-x86-$(BUILD_TYPE)/lib/x86/
@echo "[aapt] $@" @echo "[aapt] $@"
@cp out/android$(BUILD_TYPE)-x86_64/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86_64/libtildefriends.so @cp out/android$(BUILD_TYPE)-x86_64/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86_64/tildefriends.so
@cp out/android$(BUILD_TYPE)-x86/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86/libtildefriends.so @cp out/android$(BUILD_TYPE)-x86/tildefriends out/apk-x86-$(BUILD_TYPE)/lib/x86/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86_64/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86_64/tildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86/libtildefriends.so @$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-x86-$(BUILD_TYPE)/lib/x86/tildefriends.so
@cp out/apk/res.apk $@.zip @cp out/apk/res.apk $@.zip
@cp out/apk/classes.dex out/apk-x86-$(BUILD_TYPE)/ @cp out/apk/classes.dex out/apk-x86-$(BUILD_TYPE)/
@cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../ @cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@zip -u $@.zip -q -9 $(RAW_FILES) @zip -u $@.zip -q -9 $(RAW_FILES)
@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@ @$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
out/apk/TildeFriends-%.fdroid.unsigned.apk:
@rm -rf out/apk-fdroid-$(BUILD_TYPE) out/apk-fdroid-$(BUILD_TYPE)-raw
@mkdir -p $(dir $@) out/apk-fdroid-$(BUILD_TYPE)/lib/x86_64/ out/apk-fdroid-$(BUILD_TYPE)/lib/x86/ out/apk-fdroid-$(BUILD_TYPE)/lib/arm64-v8a/ out/apk-fdroid-$(BUILD_TYPE)/lib/armeabi-v7a/
@echo "[aapt] $@"
@cp out/android$(BUILD_TYPE)-x86_64/tildefriends out/apk-fdroid-$(BUILD_TYPE)/lib/x86_64/libtildefriends.so
@cp out/android$(BUILD_TYPE)-x86/tildefriends out/apk-fdroid-$(BUILD_TYPE)/lib/x86/libtildefriends.so
@cp out/android$(BUILD_TYPE)/tildefriends out/apk-fdroid-$(BUILD_TYPE)/lib/arm64-v8a/libtildefriends.so
@cp out/android$(BUILD_TYPE)-armv7a/tildefriends out/apk-fdroid-$(BUILD_TYPE)/lib/armeabi-v7a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-fdroid-$(BUILD_TYPE)/lib/x86_64/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-fdroid-$(BUILD_TYPE)/lib/x86/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-fdroid-$(BUILD_TYPE)/lib/arm64-v8a/libtildefriends.so
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk-fdroid-$(BUILD_TYPE)/lib/armeabi-v7a/libtildefriends.so
@cp out/apk/res.fdroid.apk $@.zip
@cp out/apk/classes.dex out/apk-fdroid-$(BUILD_TYPE)/classes.dex
@touch -d @0 out/apk-fdroid-$(BUILD_TYPE)/classes.dex out/apk-fdroid-$(BUILD_TYPE)/lib/*/libtildefriends.so
@chmod 755 out/apk-fdroid-$(BUILD_TYPE)/classes.dex out/apk-fdroid-$(BUILD_TYPE)/lib/*/libtildefriends.so
@cd out/apk-fdroid-$(BUILD_TYPE) && zip -X -u ../../$@.zip -q classes.dex lib/*/libtildefriends.so && cd ../../
@mkdir out/apk-fdroid-$(BUILD_TYPE)-raw
@for i in $(RAW_FILES); do mkdir -p $$(dirname out/apk-fdroid-$(BUILD_TYPE)-raw/$$i) && cp $$i out/apk-fdroid-$(BUILD_TYPE)-raw/$$i && touch -d @0 out/apk-fdroid-$(BUILD_TYPE)-raw/$$i && chmod 644 out/apk-fdroid-$(BUILD_TYPE)-raw/$$i; done
@cd out/apk-fdroid-$(BUILD_TYPE)-raw && zip -X -u ../../$@.zip -q $(RAW_FILES) && cd ../../
@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
out/%.apk: out/apk/%.unsigned.apk out/%.apk: out/apk/%.unsigned.apk
@echo "[apksigner] $(notdir $@)" @echo "[apksigner] $(notdir $@)"
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $< @$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $<
@ -969,11 +757,6 @@ out/%.zopfli.apk: out/%.apk
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk
.PHONY: release-apk .PHONY: release-apk
apkgo: out/TildeFriends-arm-debug.apk
@adb install -r $<
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
.PHONY: apkgo
releaseapkgo: out/TildeFriends-arm-release.apk releaseapkgo: out/TildeFriends-arm-release.apk
@adb install -r $< @adb install -r $<
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity @adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
@ -1037,6 +820,10 @@ apklog:
.PHONY: apklog .PHONY: apklog
fetchdeps: fetchdeps:
@echo "[fetch] libuv"
@test -f out/deps/libuv.tar.gz && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (mkdir -p out/deps/ && curl -q $(LIBUV_URL) -o out/deps/libuv.tar.gz)
@test -d deps/libuv/ && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (rm -rf deps/libuv/ && mkdir -p deps/libuv/ && tar -C deps/libuv/ -m --strip=1 -xf out/deps/libuv.tar.gz)
@echo -n $(LIBUV_URL) > out/deps/libuv.txt
@echo "[fetch] sqlite" @echo "[fetch] sqlite"
@test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip) @test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip)
@test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip) @test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip)
@ -1050,7 +837,7 @@ fetchdeps:
ANDROID_DEPS := deps/openssl/android/arm64-v8a/usr/local/lib/libssl.a ANDROID_DEPS := deps/openssl/android/arm64-v8a/usr/local/lib/libssl.a
$(ANDROID_DEPS): $(ANDROID_DEPS):
+@ANDROID_NDK_ROOT=$(ANDROID_NDK) tools/ssl-android +@tools/ssl-android
$(filter $(BUILD_DIR)/android%,$(APP_OBJS)): | $(ANDROID_DEPS) $(filter $(BUILD_DIR)/android%,$(APP_OBJS)): | $(ANDROID_DEPS)
ifeq ($(HAVE_WIN),1) ifeq ($(HAVE_WIN),1)
@ -1067,27 +854,14 @@ $(IOS_DEPS):
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS) $(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
endif endif
out/Tilde_Friends-x86_64.AppImage: out/release/tildefriends out/data.zip
@mkdir -p out/AppDir/usr/bin
@mkdir -p out/AppDir/usr/share/applications
@mkdir -p out/AppDir/usr/share/icons/hicolor/scalable/apps
@echo "[Desktop Entry]\nName=Tilde Friends\nExec=tildefriends\nIcon=tildefriends\nType=Application\nCategories=Network" > out/AppDir/usr/share/applications/tildefriends.desktop
@cp src/ios/tildefriends.svg out/AppDir/usr/share/icons/hicolor/scalable/apps/
@cat out/release/tildefriends out/data.zip > out/AppDir/usr/bin/tildefriends
@chmod +x out/AppDir/usr/bin/tildefriends
@unset SOURCE_DATE_EPOCH; cd out; linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage; cd ..
appimage: out/Tilde_Friends-x86_64.AppImage
.PHONY: appimage
clean: clean:
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)
.PHONY: clean .PHONY: clean
tarball: dist: release-apk iosrelease-ipa $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe)
@echo [archive] out/tildefriends-$(VERSION_NUMBER).tar.xz @echo [archive] dist/tildefriends-$(VERSION_NUMBER).tar.xz
@rm -rf out/tildefriends-$(VERSION_NUMBER) @rm -rf out/tildefriends-$(VERSION_NUMBER)
@mkdir -p out/tildefriends-$(VERSION_NUMBER) @mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER)
@git ls-files --recurse-submodules | tar -c -T- | tar -x -C out/tildefriends-$(VERSION_NUMBER) @git ls-files --recurse-submodules | tar -c -T- | tar -x -C out/tildefriends-$(VERSION_NUMBER)
@tar \ @tar \
--exclude=apps/welcome* \ --exclude=apps/welcome* \
@ -1104,15 +878,9 @@ tarball:
--exclude=deps/sqlite/shell.c \ --exclude=deps/sqlite/shell.c \
--exclude=deps/zlib/contrib/vstudio \ --exclude=deps/zlib/contrib/vstudio \
--exclude=deps/zlib/doc \ --exclude=deps/zlib/doc \
-caf out/tildefriends-$(VERSION_NUMBER).tar.xz \ -caf dist/tildefriends-$(VERSION_NUMBER).tar.xz \
-C out/ \ -C out/ \
tildefriends-$(VERSION_NUMBER) tildefriends-$(VERSION_NUMBER)
.PHONY: tarball
dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe) out/TildeFriends-release.fdroid.apk appimage
@mkdir -p dist/
@echo "[cp] tildefriends-$(VERSION_NUMBER).tar.xz"
@cp out/tildefriends-$(VERSION_NUMBER).tar.xz dist/tildefriends-$(VERSION_NUMBER).tar.xz
@echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk" @echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk"
@cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk @cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk" @echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"
@ -1121,12 +889,6 @@ dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefrien
@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa @cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
@test $(HAVE_WIN) && echo "[cp] tildefriends-$(VERSION_NUMBER).exe" @test $(HAVE_WIN) && echo "[cp] tildefriends-$(VERSION_NUMBER).exe"
@test $(HAVE_WIN) && cp out/winrelease/tildefriends.standalone.exe dist/tildefriends-$(VERSION_NUMBER).exe @test $(HAVE_WIN) && cp out/winrelease/tildefriends.standalone.exe dist/tildefriends-$(VERSION_NUMBER).exe
@echo "[cp] TildeFriends-$(VERSION_NUMBER).aab"
@cp out/TildeFriends.aab dist/TildeFriends-$(VERSION_NUMBER).aab
@echo "[cp] TildeFriends-$(VERSION_NUMBER).fdroid.apk"
@cp out/TildeFriends-release.fdroid.apk dist/TildeFriends-$(VERSION_NUMBER).fdroid.apk
@echo "[cp] TildeFriends-x86_64-$(VERSION_NUMBER).AppImage"
@cp out/Tilde_Friends-x86_64.AppImage dist/TildeFriends-x86_64-$(VERSION_NUMBER).AppImage
.PHONY: dist .PHONY: dist
dist-test: dist dist-test: dist
@ -1147,6 +909,3 @@ prettier:
docs: docs:
@doxygen @doxygen
.PHONY: docs .PHONY: docs
fdroid: out/apk/TildeFriends-release.fdroid.unsigned.apk
.PHONY: fdroid

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🎛", "emoji": "🎛",
"previous": "&R49FywYF8CXPhoSEydLbSCgvCddeyTiBwGuDU/gqY+M=.sha256" "previous": "&vrpS/vE7n588iYv1p8HafDxHB+YDHTrtUbJiu9nGA9I=.sha256"
} }

View File

@ -27,30 +27,23 @@ function global_settings_set(key, value) {
}); });
} }
function title_case(name) {
return name
.split('_')
.map((x) => x.charAt(0).toUpperCase() + x.substring(1))
.join(' ');
}
window.addEventListener('load', function () { window.addEventListener('load', function () {
const permission_template = (permission) => html` <code>${permission}</code>`; const permission_template = (permission) => html` <code>${permission}</code>`;
function input_template(key, description) { function input_template(key, description) {
if (description.type === 'boolean') { if (description.type === 'boolean') {
return html` return html`
<li class="w3-row"> <li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label> <label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label>
<div class="w3-quarter w3-padding">${description.description}</div> <div class="w3-quarter w3-padding">${description.description}</div>
<div class="w3-quarter w3-padding w3-center"><input class="w3-check" type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input></div> <input class="w3-quarter w3-check" type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input>
<button class="w3-quarter w3-button w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.firstChild.checked)}>Set</button> <button class="w3-quarter w3-button w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.checked)}>Set</button>
</li> </li>
`; `;
} else if (description.type === 'textarea') { } else if (description.type === 'textarea') {
return html` return html`
<li class="w3-row"> <li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold" <label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold"
>${title_case(key)}</label >${key}</label
> >
<div class="w3-rest w3-padding">${description.description}</div> <div class="w3-rest w3-padding">${description.description}</div>
<textarea <textarea
@ -75,7 +68,7 @@ ${description.value}</textarea
} else { } else {
return html` return html`
<li class="w3-row"> <li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label> <label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label>
<div class="w3-quarter w3-padding">${description.description}</div> <div class="w3-quarter w3-padding">${description.description}</div>
<input class="w3-input w3-quarter" type="text" value="${description.value}" id=${'gs_' + key}></input> <input class="w3-input w3-quarter" type="text" value="${description.value}" id=${'gs_' + key}></input>
<button class="w3-button w3-quarter w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button> <button class="w3-button w3-quarter w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🪪", "emoji": "🪪",
"previous": "&zxsmzdLKsiG/WZt/Gw7JOxepgypoktNNbIyWiyFiJVc=.sha256" "previous": "&de7q4A59auHP/34bXgeNH05JZoxsGr5TjwXPvehWH30=.sha256"
} }

View File

@ -18,7 +18,6 @@ tfrpc.register(async function reload() {
async function main() { async function main() {
let ids = await ssb.getIdentities(); let ids = await ssb.getIdentities();
let server_id = await ssb.getServerIdentity();
await app.setDocument( await app.setDocument(
` `
<head> <head>
@ -79,7 +78,7 @@ async function main() {
alert('Successfully created: ' + id); alert('Successfully created: ' + id);
await tfrpc.rpc.reload(); await tfrpc.rpc.reload();
} catch (e) { } catch (e) {
alert('Error creating identity: ' + e.message); alert('Error creating identity: ' + e);
} }
} }
handler.hide_id = function hide_id(event, element) { handler.hide_id = function hide_id(event, element) {
@ -124,7 +123,7 @@ async function main() {
) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis"> ) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis">
<button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button> <button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button>
<button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button> <button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button>
${id}${id == server_id ? ' <div class="w3-tag w3-theme-l4 w3-round">🖥 local server</div>' : ''} ${id}
</li>` </li>`
) )
.join('\n') + .join('\n') +

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🐌", "emoji": "🐌",
"previous": "&6oHPQCA26v+4nBXv+YUdCT43j2DpXDspxhHSSRydkiw=.sha256" "previous": "&wA6sLaDxtYeFdVCCu8jyhPsGYtGZEjbWQHeGOn0Yifg=.sha256"
} }

View File

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

View File

@ -1,6 +1,4 @@
import * as tfrpc from '/static/tfrpc.js'; import * as tfrpc from '/static/tfrpc.js';
import {html, render} from './lit-all.min.js';
import {styles} from './tf-styles.js';
let g_emojis; let g_emojis;
@ -38,6 +36,11 @@ export async function picker(callback, anchor, author) {
div.style.background = '#fff'; div.style.background = '#fff';
div.style.border = '1px solid #000'; div.style.border = '1px solid #000';
div.style.display = 'block'; div.style.display = 'block';
div.style.position = 'absolute';
div.style.minWidth = 'min(16em, 90vw)';
div.style.width = 'min(16em, 90vw)';
div.style.maxWidth = 'min(16em, 90vw)';
div.style.maxHeight = '16em';
div.style.overflow = 'scroll'; div.style.overflow = 'scroll';
div.style.fontWeight = 'bold'; div.style.fontWeight = 'bold';
div.style.fontSize = 'xx-large'; div.style.fontSize = 'xx-large';
@ -55,6 +58,14 @@ export async function picker(callback, anchor, author) {
event.stopPropagation(); event.stopPropagation();
}); });
function cleanup() {
console.log('emoji cleanup');
div.parentElement.removeChild(div);
window.removeEventListener('keydown', key_down);
console.log('removing click');
document.body.removeEventListener('mousedown', cleanup);
}
function key_down(event) { function key_down(event) {
if (event.key == 'Escape') { if (event.key == 'Escape') {
cleanup(); cleanup();
@ -142,23 +153,13 @@ export async function picker(callback, anchor, author) {
} }
refresh(); refresh();
input.oninput = refresh; input.oninput = refresh;
let modal = html` document.body.appendChild(div);
<style> div.style.position = 'fixed';
${styles} div.style.top = '50%';
</style> div.style.left = '50%';
<div class="w3-modal" style="display: block"> div.style.transform = 'translate(-50%, -50%)';
<div class="w3-modal-content w3-card-4">${div}</div>
</div>
`;
let parent = document.createElement('div');
document.body.appendChild(parent);
function cleanup() {
parent.parentElement.removeChild(parent);
window.removeEventListener('keydown', key_down);
document.body.removeEventListener('mousedown', cleanup);
}
render(modal, parent);
input.focus(); input.focus();
console.log('adding click');
document.body.addEventListener('mousedown', cleanup); document.body.addEventListener('mousedown', cleanup);
window.addEventListener('keydown', key_down); window.addEventListener('keydown', key_down);
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -76,9 +76,15 @@ class TfComposeElement extends LitElement {
let preview = this.renderRoot.getElementById('preview'); let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = this.process_text(edit.innerText); preview.innerHTML = this.process_text(edit.innerText);
let content_warning = this.renderRoot.getElementById('content_warning'); let content_warning = this.renderRoot.getElementById('content_warning');
let content_warning_preview = this.renderRoot.getElementById(
'content_warning_preview'
);
if (content_warning && content_warning_preview) {
content_warning_preview.innerText = content_warning.value;
}
let draft = this.get_draft(); let draft = this.get_draft();
draft.text = edit.innerText; draft.text = edit.innerText;
draft.content_warning = content_warning?.value; draft.content_warning = content_warning?.innerText;
setTimeout(() => this.notify(draft), 0); setTimeout(() => this.notify(draft), 0);
} }
@ -215,8 +221,12 @@ class TfComposeElement extends LitElement {
console.log('encrypted as', message); console.log('encrypted as', message);
} }
try { try {
await tfrpc.rpc.appendMessage(this.whoami, message); await tfrpc.rpc.appendMessage(this.whoami, message).then(function () {
self.notify(undefined); edit.innerText = '';
self.input();
self.notify(undefined);
self.requestUpdate();
});
} catch (error) { } catch (error) {
alert(error.message); alert(error.message);
} }
@ -243,9 +253,9 @@ class TfComposeElement extends LitElement {
try { try {
let rows = await tfrpc.rpc.query( let rows = await tfrpc.rpc.query(
` `
SELECT json(messages.content) AS content FROM messages_fts(?) SELECT json(messages.content) FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid JOIN messages ON messages.rowid = messages_fts.rowid
WHERE json(messages.content) LIKE ? WHERE messages.content LIKE ?
ORDER BY timestamp DESC LIMIT 10 ORDER BY timestamp DESC LIMIT 10
`, `,
['"' + text.replace('"', '""') + '"', `%![%${text}%](%)%`] ['"' + text.replace('"', '""') + '"', `%![%${text}%](%)%`]
@ -281,7 +291,6 @@ class TfComposeElement extends LitElement {
); );
} }
let tribute = new Tribute({ let tribute = new Tribute({
iframe: this.shadowRoot,
collection: [ collection: [
{ {
values: values, values: values,
@ -316,7 +325,6 @@ class TfComposeElement extends LitElement {
let encrypt = this.renderRoot.getElementById('encrypt_to'); let encrypt = this.renderRoot.getElementById('encrypt_to');
if (encrypt) { if (encrypt) {
let tribute = new Tribute({ let tribute = new Tribute({
iframe: this.shadowRoot,
values: Object.entries(this.users).map((x) => ({ values: Object.entries(this.users).map((x) => ({
key: x[1].name, key: x[1].name,
value: x[0], value: x[0],
@ -449,7 +457,7 @@ class TfComposeElement extends LitElement {
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input> <input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
<label for="cw">CW</label> <label for="cw">CW</label>
</p> </p>
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input> <input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
</div> </div>
`; `;
} else { } else {
@ -538,7 +546,7 @@ class TfComposeElement extends LitElement {
id="edit" id="edit"
@input=${this.input} @input=${this.input}
@paste=${this.paste} @paste=${this.paste}
contenteditable="plaintext-only" contenteditable
.innerText=${live(draft.text ?? '')} .innerText=${live(draft.text ?? '')}
></span> ></span>
</div> </div>

View File

@ -172,7 +172,7 @@ class TfMessageElement extends LitElement {
event.srcElement.classList.contains('img_caption') event.srcElement.classList.contains('img_caption')
) { ) {
let next = event.srcElement.nextSibling; let next = event.srcElement.nextSibling;
if (next.style.display != 'none') { if (next.style.display == 'block') {
next.style.display = 'none'; next.style.display = 'none';
} else { } else {
next.style.display = 'block'; next.style.display = 'block';

View File

@ -188,10 +188,6 @@ class TfProfileElement extends LitElement {
} }
} }
copy_id() {
navigator.clipboard.writeText(this.id);
}
render() { render() {
if ( if (
this.id == this.whoami && this.id == this.whoami &&
@ -291,14 +287,6 @@ class TfProfileElement extends LitElement {
let description = this.editing?.description ?? profile.description; let description = this.editing?.description ?? profile.description;
return html`<div style="border: 2px solid black; background-color: rgba(255, 255, 255, 0.2); padding: 16px"> return html`<div style="border: 2px solid black; background-color: rgba(255, 255, 255, 0.2); padding: 16px">
<tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)}) <tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})
<div class="w3-row">
<div class="w3-col s1 w3-container w3-right">
<button class="w3-button w3-theme-d1 w3-ripple" @click=${this.copy_id}>Copy</button>
</div>
<div class="w3-rest w3-container">
<input type="text" class="w3-theme-d1" style="width: 100%; vertical-align: middle" readonly value=${this.id}></input>
</div>
</div>
<div style="display: flex; flex-direction: row; gap: 1em"> <div style="display: flex; flex-direction: row; gap: 1em">
${edit_profile} ${edit_profile}
<div style="flex: 1 0 50%"> <div style="flex: 1 0 50%">

View File

@ -17,12 +17,6 @@ class TfTabConnectionsElement extends LitElement {
static styles = styles; static styles = styles;
static k_broadcast_emojis = {
discovery: '🏓',
room: '🚪',
peer_exchange: '🕸',
};
constructor() { constructor() {
super(); super();
let self = this; let self = this;
@ -98,7 +92,6 @@ class TfTabConnectionsElement extends LitElement {
Connect Connect
</button> </button>
<div class="w3-bar-item"> <div class="w3-bar-item">
${TfTabConnectionsElement.k_broadcast_emojis[connection.origin]}
<tf-user id=${connection.pubkey} .users=${this.users}></tf-user> <tf-user id=${connection.pubkey} .users=${this.users}></tf-user>
${this.render_connection_summary(connection)} ${this.render_connection_summary(connection)}
</div> </div>

View File

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

View File

@ -5,7 +5,6 @@ import {styles} from './tf-styles.js';
class TfTabSearchElement extends LitElement { class TfTabSearchElement extends LitElement {
static get properties() { static get properties() {
return { return {
drafts: {type: Object},
whoami: {type: String}, whoami: {type: String},
users: {type: Object}, users: {type: Object},
following: {type: Array}, following: {type: Array},
@ -23,10 +22,6 @@ class TfTabSearchElement extends LitElement {
this.users = {}; this.users = {};
this.following = []; this.following = [];
this.expanded = {}; this.expanded = {};
this.drafts = {};
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}');
});
} }
async search(query) { async search(query) {
@ -75,18 +70,6 @@ class TfTabSearchElement extends LitElement {
} }
} }
draft(event) {
let id = event.detail.id || '';
let previous = this.drafts[id];
if (event.detail.draft !== undefined) {
this.drafts[id] = event.detail.draft;
} else {
delete this.drafts[id];
}
this.drafts = Object.assign({}, this.drafts);
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
}
render() { render() {
if (this.query !== this.last_query) { if (this.query !== this.last_query) {
this.last_query = this.query; this.last_query = this.query;
@ -98,7 +81,7 @@ class TfTabSearchElement extends LitElement {
<input type="text" class="w3-input w3-theme-d1" id="search" value=${this.query} style="flex: 1" @keydown=${this.search_keydown}></input> <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> <button class="w3-button w3-theme-d1" @click=${(event) => self.search(self.renderRoot.getElementById('search').value)}>Search</button>
</div> </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> <tf-news id="news" whoami=${this.whoami} .messages=${this.messages} .users=${this.users} .expanded=${this.expanded} @tf-expand=${this.on_expand}></tf-news>
`; `;
} }
} }

View File

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

View File

@ -482,7 +482,16 @@ class TributeRange {
} }
getDocument() { getDocument() {
return document; let iframe;
if (this.tribute.current.collection) {
iframe = this.tribute.current.collection.iframe;
}
if (!iframe) {
return document
}
return iframe.contentWindow.document
} }
positionMenuAtCaret(scrollTo) { positionMenuAtCaret(scrollTo) {
@ -644,8 +653,8 @@ class TributeRange {
} }
getWindowSelection() { getWindowSelection() {
if (this.tribute.collection[0].iframe?.getSelection) { if (this.tribute.collection.iframe) {
return this.tribute.collection[0].iframe.getSelection() return this.tribute.collection.iframe.contentWindow.getSelection()
} }
return window.getSelection() return window.getSelection()

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "👋", "emoji": "👋",
"previous": "&7Pqk5nBAcbjzp0etv6WgiyTD3UF++ID0mW6qIbhwt3s=.sha256" "previous": "&W5aJp2DgOW5rQ0AOIC9Ut3DpsahPrO6PjkJ1PQbNRdM=.sha256"
} }

View File

@ -1,124 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Original Author: Unknown (if you are the original creator of the F-Droid button, please contact laura@ind.ie so I can credit you!) -->
<!-- Author: Created by Laura Kalbag and Released with ❤ by ind.ie (laura@ind.ie) -->
<!-- License: This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/. (As attribution is included in this file, you needn't include additional attribution on your site.) -->
<!-- How to use: The original blog post about this SVG button and how I use SVG on the Ind.ie website is at https://ind.ie/blog/f-droid-button/ -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="button_1_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="1490 188 300 104" enable-background="new 1490 188 300 104" xml:space="preserve">
<g id="get_it_on_f-droid_2_">
<path id="button" d="M1780,292h-280c-5.5,0-10-4.5-10-10v-84c0-5.5,4.5-10,10-10h280c5.5,0,10,4.5,10,10v84
C1790,287.5,1785.5,292,1780,292z"/>
<g id="f-droid_2_">
<path fill="#FFFFFF" d="M1621.2,236.8v3.6v1.2v5h-0.3h-0.6h-2.2c-0.7,0-1-0.3-1.2-1c-0.1-0.4-0.2-1.6-0.3-3.4h-13.5v11.1h13.2v5.5
h-13.2v10.1c0.9,0.2,1.7,0.3,2.6,0.4c0.9,0.2,1.3,0.7,1.3,1.6v3h-3.9h-7.1h-3.9v-3c0-0.9,0.4-1.5,1.3-1.6c0.9-0.2,1.7-0.3,2.6-0.4
V242c-0.9-0.2-1.7-0.3-2.6-0.4c-0.9-0.2-1.3-0.7-1.3-1.6v-3h3.9h22.7h1.2L1621.2,236.8L1621.2,236.8z"/>
<path fill="#FFFFFF" d="M1637.2,256v5.4h-13.5V256H1637.2z"/>
<path fill="#FFFFFF" d="M1676.7,255.6c0,5-1.7,10-5.3,13.5c-3.7,3.6-8.8,5.3-13.9,5.3h-13.9h-3.9v-3c0-0.9,0.4-1.5,1.3-1.6
c0.9-0.2,1.7-0.3,2.6-0.4V242c-0.9-0.2-1.7-0.3-2.6-0.4c-0.9-0.2-1.3-0.7-1.3-1.6v-3h3.9h13.9c5.1,0,10.2,1.6,13.9,5.3
C1675.1,245.6,1676.7,250.7,1676.7,255.6C1676.7,258.4,1676.7,252.9,1676.7,255.6z M1669.6,255.6c0-3.4-0.8-6.9-3.1-9.5
s-5.6-3.7-8.9-3.7h-6.8v26.4h6.8c3.4,0,6.7-1.1,8.9-3.7C1668.8,262.5,1669.6,259,1669.6,255.6z"/>
<path fill="#FFFFFF" d="M1699.7,248.1l-0.9,4.8c-0.2,1.1-1.3,0.9-2.1,0.7c-1-0.3-2.2-0.3-3.1,0c-2.2,0.5-3.7,2.3-4.5,4.2v11.4
c2.3,0.4,2.3,0.4,2.6,0.4c0.9,0.2,1.3,0.7,1.3,1.6v3h-3.9l0,0h-6.5h-3.9v-3c0-0.9,0.4-1.5,1.3-1.6c0.4-0.1,0.4-0.1,2.6-0.4v-16.3
c-2.3-0.4-2.3-0.4-2.6-0.4c-0.9-0.2-1.3-0.7-1.3-1.6v-3h3.9l0,0h3.8c1.1,0,1.7,0.4,1.9,1.6l0.3,3.2c1.1-1.9,2.6-3.7,4.7-4.7
C1695.2,247,1697.8,246.9,1699.7,248.1z"/>
<path fill="#FFFFFF" d="M1719.4,248.3c5.7,2.3,8,8,7.8,13.7c-0.3,5.6-3.4,10.7-9.1,12.3c-5.4,1.5-11.9,0-15.1-4.9
c-3.1-4.6-3.2-11.6-0.3-16.4C1706,247.6,1713.6,246,1719.4,248.3C1721,248.9,1717.8,247.6,1719.4,248.3z M1718.9,267.6
c2-2.8,2-7.1,1.2-10.2c-0.3-1.5-1-2.9-2.2-3.9c-1.3-1-3-1.4-4.6-1.2c-3.5,0.2-5.3,2.7-5.8,5.9c-0.5,3.1-0.5,7.5,1.8,10
C1711.8,270.7,1716.8,270.5,1718.9,267.6C1720,266.2,1717.9,269.1,1718.9,267.6z"/>
<path fill="#FFFFFF" d="M1743.3,271.4v3h-3.9h-6.5h-4v-3c0-0.9,0.4-1.5,1.3-1.6c0.9-0.2,1.7-0.3,2.6-0.4v-16.4
c-0.9-0.2-1.7-0.3-2.6-0.4c-0.9-0.2-1.3-0.7-1.3-1.6v-3h3.9h6.5v21.5c0.9,0.2,1.7,0.3,2.6,0.4
C1742.9,269.9,1743.3,270.5,1743.3,271.4z M1740,241.6c-1,2-3.4,3-5.4,2.2c-2-0.9-3.1-3.3-2.2-5.3c0.9-2.1,3.3-3,5.4-2.2
C1739.9,237.1,1741,239.5,1740,241.6C1739.8,242,1740.3,241,1740,241.6z"/>
<path fill="#FFFFFF" d="M1773.4,274.3h-3.7h-3.9c-0.8,0-1.5-0.3-1.7-1.2l-0.5-2.4c-2.4,2.8-5.9,4.5-9.8,4.1
c-3.8-0.4-6.5-3.2-7.8-6.6c-2.4-6.6-1.3-16.3,5.6-19.8c3.8-1.9,8.5-1.5,11.6,1.5V241c-0.9-0.2-1.7-0.3-2.6-0.4
c-0.9-0.2-1.3-0.7-1.3-1.6v-3h3.9h6.5v33.5c0.8,0.2,1.6,0.3,2.4,0.4c0.9,0.2,1.3,0.7,1.3,1.6V274.3z M1763.3,254.6
c-1.7-2-4.2-2.9-6.7-2.3c-2.5,0.6-3.9,2.8-4.4,5.1c-0.5,2.3-0.5,5-0.1,7.4c0.4,2.3,1.7,4.4,4.1,4.9c2.9,0.5,5.4-1,7.2-3.1V254.6z"
/>
</g>
<g id="get_it_on_2_">
<path fill="#FFFFFF" d="M1597.5,218.2v-2.9h7.6v6.9c-3.7,3.6-11.2,3.9-14.5-0.3c-3.3-4.2-2.4-11.5,2.6-14c2.2-1.1,5.1-1.1,7.3-0.4
s3.8,2.4,4.3,4.7l-3.5,0.7c-0.7-2.5-3.5-3.3-5.8-2.5c-2.2,0.7-3.1,2.8-3.2,4.9c-0.1,2.2,0.3,4.6,2.1,5.9c2.1,1.6,5.1,0.9,7-0.6
v-2.3H1597.5z"/>
<path fill="#FFFFFF" d="M1608.3,224.7v-17.3h13v2.9h-9.4v3.8h8.8v2.9h-8.8v4.8h9.8v2.9L1608.3,224.7L1608.3,224.7z"/>
<path fill="#FFFFFF" d="M1628.6,224.7v-14.4h-5.1v-2.9h13.9v2.9h-5.1v14.5L1628.6,224.7L1628.6,224.7z"/>
<path fill="#FFFFFF" d="M1646.3,224.7v-17.3h3.5v17.3H1646.3z"/>
<path fill="#FFFFFF" d="M1657.1,224.7v-14.4h-5.1v-2.9h13.9v2.9h-5.1v14.5L1657.1,224.7L1657.1,224.7z"/>
<path fill="#FFFFFF" d="M1674.1,216.1c0-1.7,0.3-3.3,0.8-4.4c0.8-1.7,2.2-3.2,3.9-4c2.8-1.1,6.5-1,9,0.9c2.5,1.8,3.4,4.9,3.3,7.8
c-0.1,2.9-1.1,5.8-3.8,7.5c-2.5,1.6-6.1,1.6-8.7,0.3C1675.4,222.7,1674.1,219.4,1674.1,216.1z M1677.8,216c0,2.3,0.7,4.8,3.1,5.6
c2.2,0.9,4.8,0,5.8-2c1-1.9,1-4.9,0.3-6.8c-0.9-2.1-3.1-3.1-5.3-2.7C1678.7,210.6,1677.8,213.4,1677.8,216z"/>
<path fill="#FFFFFF" d="M1693.9,224.7v-17.3h3.4l7.2,11.6v-11.6h3.3v17.3h-3.6l-7.1-11.4v11.4H1693.9z"/>
</g>
<g id="droid_2_">
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="413.9844" y1="440.5436" x2="413.9844" y2="452.6552" gradientTransform="matrix(3.7209 0 0 -3.7209 0 1914.4187)">
<stop offset="0" style="stop-color:#2B6099"/>
<stop offset="0.1299" style="stop-color:#2F69A1"/>
<stop offset="0.3451" style="stop-color:#3B83B6"/>
<stop offset="0.5" style="stop-color:#4699C8"/>
<stop offset="0.9944" style="stop-color:#479ECB"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M1570.1,275.2h-59.3c-2.9,0-5.2-2.3-5.2-5.2v-34.7c0-2.9,2.4-5.2,5.2-5.2h59.3
c2.9,0,5.2,2.3,5.2,5.2V270C1575.3,272.8,1572.9,275.2,1570.1,275.2z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="413.9844" y1="440.5436" x2="413.9844" y2="452.6552" gradientTransform="matrix(3.7209 0 0 -3.7209 0 1914.4187)">
<stop offset="0" style="stop-color:#2B6099"/>
<stop offset="0.5" style="stop-color:#58A4CD"/>
<stop offset="0.7865" style="stop-color:#7FB8D9"/>
<stop offset="1" style="stop-color:#9EC9E2"/>
</linearGradient>
<path fill="url(#SVGID_2_)" d="M1570.1,231.9c1.9,0,3.5,1.6,3.5,3.5V270c0,1.9-1.6,3.5-3.5,3.5h-59.3c-1.9,0-3.5-1.6-3.5-3.5
v-34.7c0-1.9,1.6-3.5,3.5-3.5H1570.1 M1570.1,230.1h-59.3c-2.9,0-5.2,2.3-5.2,5.2V270c0,2.9,2.4,5.2,5.2,5.2h59.3
c2.9,0,5.2-2.3,5.2-5.2v-34.7C1575.3,232.5,1572.9,230.1,1570.1,230.1L1570.1,230.1z"/>
</g>
<g>
<path fill="#295384" d="M1540.4,236.6c8.9,0,16.1,7.2,16.1,16c0,8.8-7.2,16-16.1,16s-16.1-7.2-16.1-16S1531.5,236.6,1540.4,236.6
M1540.4,235.3c-9.6,0-17.4,7.8-17.4,17.3s7.8,17.3,17.4,17.3s17.4-7.8,17.4-17.3S1550.1,235.3,1540.4,235.3L1540.4,235.3z"/>
</g>
<path fill="#295384" d="M1535.4,251.1c1.9-5.7,10.7-3.9,10.2,2.2c-0.5,5.6-8.5,6.2-10.2,1.2c0,0,0,0.1,0,0l0,0
c-2.4,0.2-4.7,0.4-7.1,0.6c1.7,10.1,15.3,13.1,21.6,5.3c6.5-8,0.3-20.2-10-19.8c-5.6,0.3-10.5,4.3-11.5,9.8"/>
<path fill="#7B952D" d="M1580.3,198.7l-2-1.6c-0.3-0.3-1-0.3-1.2,0.1l-6.8,7.9c-0.3,0.3-0.3,1,0.1,1.2l2,1.6
c0.3,0.3,1,0.3,1.2-0.1l6.8-7.9C1580.7,199.6,1580.6,199,1580.3,198.7z"/>
<path fill="#7B952D" d="M1500.6,198.1l2-1.6c0.3-0.3,1-0.3,1.2,0.1l6.8,7.9c0.3,0.3,0.3,1-0.1,1.2l-2,1.6c-0.3,0.3-1,0.3-1.2-0.1
l-6.8-7.9C1500.2,199,1500.3,198.5,1500.6,198.1z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="413.9844" y1="453.354" x2="413.9844" y2="459.4099" gradientTransform="matrix(3.7209 0 0 -3.7209 0 1914.4187)">
<stop offset="0" style="stop-color:#BCDB52"/>
<stop offset="0.3206" style="stop-color:#C5E358"/>
<stop offset="0.5" style="stop-color:#CDEA5C"/>
<stop offset="0.9944" style="stop-color:#DCF285"/>
</linearGradient>
<path fill="url(#SVGID_3_)" stroke="#7B952D" stroke-miterlimit="10" d="M1572.7,227.5h-64.5c-1,0-1.7-0.8-1.7-1.7v-19.1
c0-1,0.8-1.7,1.7-1.7h64.5c1,0,1.7,0.8,1.7,1.7v19.1C1574.4,226.7,1573.6,227.5,1572.7,227.5z"/>
<g>
<ellipse fill="#FFFFFF" cx="1523" cy="215.4" rx="6.1" ry="6.1"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="409.8439" y1="458.401" x2="408.7539" y2="454.8358" gradientTransform="matrix(3.7209 0 0 -3.7209 0 1914.4187)">
<stop offset="0" style="stop-color:#8BA53D"/>
<stop offset="8.484717e-02" style="stop-color:#94AE45"/>
<stop offset="0.2259" style="stop-color:#AEC65C"/>
<stop offset="0.4048" style="stop-color:#D7ED81"/>
<stop offset="0.4246" style="stop-color:#DCF285"/>
</linearGradient>
<path fill="url(#SVGID_4_)" d="M1523,222.3c-3.8,0-7-3.1-7-6.9c0-3.8,3.1-6.9,7-6.9c3.8,0,7,3.1,7,6.9
C1529.9,219.2,1526.9,222.3,1523,222.3z M1523,210.2c-2.9,0-5.2,2.3-5.2,5.2s2.4,5.2,5.2,5.2s5.2-2.3,5.2-5.2
S1525.9,210.2,1523,210.2z"/>
</g>
<g>
<ellipse fill="#FFFFFF" cx="1557.8" cy="215.4" rx="6.1" ry="6.1"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="419.2189" y1="458.401" x2="418.129" y2="454.8359" gradientTransform="matrix(3.7209 0 0 -3.7209 0 1914.4187)">
<stop offset="0" style="stop-color:#8BA53D"/>
<stop offset="8.484717e-02" style="stop-color:#94AE45"/>
<stop offset="0.2259" style="stop-color:#AEC65C"/>
<stop offset="0.4048" style="stop-color:#D7ED81"/>
<stop offset="0.4246" style="stop-color:#DCF285"/>
</linearGradient>
<path fill="url(#SVGID_5_)" d="M1557.8,222.3c-3.8,0-7-3.1-7-6.9c0-3.8,3.1-6.9,7-6.9c3.8,0,7,3.1,7,6.9
C1564.8,219.2,1561.8,222.3,1557.8,222.3z M1557.8,210.2c-2.9,0-5.2,2.3-5.2,5.2s2.4,5.2,5.2,5.2c2.9,0,5.2-2.3,5.2-5.2
S1560.8,210.2,1557.8,210.2z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -68,9 +68,6 @@
href="https://dev.tildefriends.net/" href="https://dev.tildefriends.net/"
><i class="fa fa-mug-hot"></i> Code</a ><i class="fa fa-mug-hot"></i> Code</a
> >
<p>
<a href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"><img src="f-droid.svg" style="height: 3em"></a>
</p>
</div> </div>
<div class="w3-col l4 m6"> <div class="w3-col l4 m6">
<img src="tildefriends.png" class="w3-image w3-right w3-hide-small" /> <img src="tildefriends.png" class="w3-image w3-right w3-hide-small" />
@ -273,13 +270,6 @@
<i class="fa fa-fire w3-text-cyan w3-jumbo"></i> <i class="fa fa-fire w3-text-cyan w3-jumbo"></i>
<p>Lit</p> <p>Lit</p>
</a> </a>
<a href="https://github.com/c-ares/c-ares" class="w3-col s3">
<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"> <a href="https://www.gnu.org/software/make/" class="w3-col s3">
<i class="fa fa-hammer w3-text-teal w3-jumbo"></i> <i class="fa fa-hammer w3-text-teal w3-jumbo"></i>
<p>GNU Make</p> <p>GNU Make</p>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

@ -83,10 +83,10 @@ App.prototype.send = function (message) {
* @param {*} response * @param {*} response
* @param {*} client * @param {*} client
*/ */
async function socket(request, response, client) { function socket(request, response, client) {
let process; let process;
let options = {}; let options = {};
let credentials = await httpd.auth_query(request.headers); let credentials = httpd.auth_query(request.headers);
response.onClose = async function () { response.onClose = async function () {
if (process && process.task) { if (process && process.task) {
@ -211,6 +211,10 @@ async function socket(request, response, client) {
if (process && process.timeout > 0) { if (process && process.timeout > 0) {
setTimeout(ping, process.timeout); setTimeout(ping, process.timeout);
} }
} else if (message.action == 'enableStats') {
if (process) {
core.enableStats(process, message.enabled);
}
} else if (message.action == 'resetPermission') { } else if (message.action == 'resetPermission') {
if (process) { if (process) {
process.resetPermission(message.permission); process.resetPermission(message.permission);
@ -218,7 +222,7 @@ async function socket(request, response, client) {
} else if (message.action == 'setActiveIdentity') { } else if (message.action == 'setActiveIdentity') {
process.setActiveIdentity(message.identity); process.setActiveIdentity(message.identity);
} else if (message.action == 'createIdentity') { } else if (message.action == 'createIdentity') {
await process.createIdentity(); process.createIdentity();
} else if (message.message == 'tfrpc') { } else if (message.message == 'tfrpc') {
if (message.id && g_calls[message.id]) { if (message.id && g_calls[message.id]) {
if (message.error !== undefined) { if (message.error !== undefined) {

View File

@ -3,7 +3,7 @@
<head> <head>
<title>Tilde Friends Sign-in</title> <title>Tilde Friends Sign-in</title>
<link type="text/css" rel="stylesheet" href="/static/style.css" /> <link type="text/css" rel="stylesheet" href="/static/style.css" />
<link type="image/svg+xml" rel="icon" href="/static/tildefriends.svg" /> <link type="image/png" rel="shortcut icon" href="/static/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
</head> </head>
<body> <body>

View File

@ -10,7 +10,6 @@ let gEditor;
let gOriginalInput; let gOriginalInput;
let kErrorColor = '#dc322f'; let kErrorColor = '#dc322f';
let kDisconnectColor = '#f00';
let kStatusColor = '#fff'; let kStatusColor = '#fff';
// Functions that server-side app code can call through the app object. // Functions that server-side app code can call through the app object.
@ -118,6 +117,28 @@ class TfNavigationElement extends LitElement {
return this.spark_lines[key]; return this.spark_lines[key];
} }
/**
* TODOC
* @returns
*/
render_login() {
if (this?.credentials?.session?.name) {
return html`<a
class="w3-bar-item w3-right"
id="login"
href="/login/logout?return=${url() + hash()}"
>logout ${this.credentials.session.name}</a
>`;
} else {
return html`<a
class="w3-bar-item w3-right"
id="login"
href="/login?return=${url() + hash()}"
>login</a
>`;
}
}
set_active_identity(id) { set_active_identity(id) {
send({action: 'setActiveIdentity', identity: id}); send({action: 'setActiveIdentity', identity: id});
this.renderRoot.getElementById('id_dropdown').classList.remove('w3-show'); this.renderRoot.getElementById('id_dropdown').classList.remove('w3-show');
@ -137,105 +158,67 @@ class TfNavigationElement extends LitElement {
window.location.href = '/~core/ssb/#' + this.identity; window.location.href = '/~core/ssb/#' + this.identity;
} }
logout() {
window.location.href = `/login/logout?return=${encodeURIComponent(url() + hash())}`;
}
render_identity() { render_identity() {
let self = this; let self = this;
if (this.identities?.length) {
if (this?.credentials?.session?.name) { return html`
if (this.identities?.length) { <link type="text/css" rel="stylesheet" href="/static/w3.css" />
return html` <div class="w3-dropdown-click w3-right" style="max-width: 100%">
<link type="text/css" rel="stylesheet" href="/static/w3.css" /> <button
<div class="w3-dropdown-click w3-right" style="max-width: 100%"> class="w3-button w3-rest w3-cyan"
style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap; max-width: 100%"
@click=${self.toggle_id_dropdown}
>
${self.names[this.identity]}${self.names[this.identity] ===
this.identity
? ''
: html` - ${this.identity}`}
</button>
<div
id="id_dropdown"
class="w3-dropdown-content w3-bar-block w3-card-4"
style="max-width: 100%"
>
<button <button
class="w3-button w3-rest w3-cyan" class="w3-bar-item w3-button w3-border"
style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap; max-width: 100%" @click=${() => (window.location.href = '/~core/identity')}
id="identity"
@click=${self.toggle_id_dropdown}
> >
${self.names[this.identity]} Manage Identities...
</button> </button>
<div <button
id="id_dropdown" class="w3-bar-item w3-button w3-border"
class="w3-dropdown-content w3-bar-block w3-card-4" @click=${self.edit_profile}
style="max-width: 100%; right: 0"
> >
<button Edit Profile...
class="w3-bar-item w3-button w3-border" </button>
@click=${() => (window.location.href = '/~core/identity')} ${this.identities.map(
> (x) => html`
Manage Identities... <button
</button> class="w3-bar-item w3-button ${x === self.identity
<button ? 'w3-cyan'
class="w3-bar-item w3-button w3-border" : ''}"
@click=${self.edit_profile} @click=${() => self.set_active_identity(x)}
> style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
Edit Profile... >
</button> ${self.names[x]}${self.names[x] === x ? '' : html` - ${x}`}
${this.identities.map( </button>
(x) => html` `
<button )}
class="w3-bar-item w3-button ${x === self.identity
? 'w3-cyan'
: ''}"
@click=${() => self.set_active_identity(x)}
style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
>
${self.names[x]}${self.names[x] === x ? '' : html` - ${x}`}
</button>
`
)}
<button
class="w3-bar-item w3-button w3-border"
id="logout"
@click=${self.logout}
>
Logout ${this.credentials.session.name}
</button>
</div>
</div> </div>
`; </div>
} else if ( `;
this.credentials?.session?.name &&
this.credentials.session.name !== 'guest'
) {
return html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<button
class="w3-bar-item w3-button w3-right w3-cyan"
id="logout"
@click=${self.logout}
>
Logout ${this.credentials.session.name}
</button>
<button
id="create_identity"
@click=${this.create_identity}
class="w3-button w3-mobile w3-red w3-right"
>
Create an Identity
</button>
`;
} else {
return html`
<button
class="w3-bar-item w3-button w3-right w3-cyan"
id="logout"
@click=${self.logout}
>
Logout ${this.credentials.session.name}
</button>
`;
}
} else { } else {
return html`<a return html`
class="w3-bar-item w3-cyan w3-right" <link type="text/css" rel="stylesheet" href="/static/w3.css" />
id="login" <button
href="/login?return=${url() + hash()}" id="create_identity"
>login</a @click=${this.create_identity}
>`; class="w3-button w3-mobile w3-blue w3-right"
>
Create an Identity
</button>
`;
} }
} }
@ -375,7 +358,7 @@ class TfNavigationElement extends LitElement {
${Object.keys(this.spark_lines) ${Object.keys(this.spark_lines)
.sort() .sort()
.map((x) => this.spark_lines[x])} .map((x) => this.spark_lines[x])}
${this.render_identity()} ${this.render_login()} ${this.render_identity()}
</div> </div>
${this.status?.is_error ${this.status?.is_error
? html` ? html`
@ -1274,6 +1257,7 @@ function _receive_websocket_message(message) {
document.getElementById('viewPane').style.display = message.edit_only document.getElementById('viewPane').style.display = message.edit_only
? 'none' ? 'none'
: 'flex'; : 'flex';
send({action: 'enableStats', enabled: true});
} else if (message && message.action == 'ping') { } else if (message && message.action == 'ping') {
send({action: 'pong'}); send({action: 'pong'});
} else if (message && message.action == 'stats') { } else if (message && message.action == 'stats') {
@ -1561,7 +1545,7 @@ function connectSocket(path) {
}; };
setStatusMessage( setStatusMessage(
'🔴 Closed: ' + (k_codes[event.code] || event.code), '🔴 Closed: ' + (k_codes[event.code] || event.code),
kDisconnectColor kErrorColor
); );
}; };
} }

View File

@ -8,6 +8,120 @@ let gStatsTimer = false;
const k_content_security_policy = const k_content_security_policy =
'sandbox allow-downloads allow-top-navigation-by-user-activation'; 'sandbox allow-downloads allow-top-navigation-by-user-activation';
const k_mime_types = {
css: 'text/css',
html: 'text/html',
js: 'text/javascript',
json: 'text/json',
map: 'application/json',
svg: 'image/svg+xml',
};
const k_magic_bytes = [
{bytes: [0xff, 0xd8, 0xff, 0xdb], type: 'image/jpeg'},
{
bytes: [
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
],
type: 'image/jpeg',
},
{bytes: [0xff, 0xd8, 0xff, 0xee], type: 'image/jpeg'},
{
bytes: [
0xff,
0xd8,
0xff,
0xe1,
null,
null,
0x45,
0x78,
0x69,
0x66,
0x00,
0x00,
],
type: 'image/jpeg',
},
{bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], type: 'image/png'},
{bytes: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], type: 'image/gif'},
{bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], type: 'image/gif'},
{
bytes: [
0x52,
0x49,
0x46,
0x46,
null,
null,
null,
null,
0x57,
0x45,
0x42,
0x50,
],
type: 'image/webp',
},
{bytes: [0x3c, 0x73, 0x76, 0x67], type: 'image/svg+xml'},
{
bytes: [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
],
type: 'audio/mpeg',
},
{
bytes: [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x69,
0x73,
0x6f,
0x6d,
],
type: 'video/mp4',
},
{
bytes: [
null,
null,
null,
null,
0x66,
0x74,
0x79,
0x70,
0x6d,
0x70,
0x34,
0x32,
],
type: 'video/mp4',
},
{bytes: [0x4d, 0x54, 0x68, 0x64], type: 'audio/midi'},
];
let k_static_files = [
{uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'},
];
const k_global_settings = { const k_global_settings = {
index: { index: {
type: 'string', type: 'string',
@ -23,18 +137,13 @@ const k_global_settings = {
room: { room: {
type: 'boolean', type: 'boolean',
default_value: true, default_value: true,
description: 'Enable peers to tunnel through this instance as a room.', description: 'Whether this instance should behave as a room.',
}, },
room_name: { room_name: {
type: 'string', type: 'string',
default_value: 'tilde friends tunnel', default_value: 'tilde friends tunnel',
description: 'Name of the room.', description: 'Name of the room.',
}, },
replicator: {
type: 'boolean',
default_value: true,
description: 'Enable message and blob replication.',
},
code_of_conduct: { code_of_conduct: {
type: 'textarea', type: 'textarea',
default_value: undefined, default_value: undefined,
@ -69,22 +178,6 @@ const k_global_settings = {
: undefined, : undefined,
description: 'Blobs older than this will be automatically deleted.', description: 'Blobs older than this will be automatically deleted.',
}, },
seeds_host: {
type: 'string',
default_value: 'seeds.tildefriends.net',
description: 'Hostname for seed connections.',
},
peer_exchange: {
type: 'boolean',
default_value: false,
description:
'Enable discovery of, sharing of, and connecting to internet peer strangers, including announcing this instance.',
},
account_registration: {
type: 'boolean',
default_value: true,
description: 'Allow registration of new accounts.',
},
}; };
let gGlobalSettings = { let gGlobalSettings = {
@ -223,7 +316,7 @@ function getUser(caller, process) {
* @param {*} process * @param {*} process
* @returns * @returns
*/ */
async function getApps(user, process) { function getApps(user, process) {
if ( if (
process.credentials && process.credentials &&
process.credentials.session && process.credentials.session &&
@ -238,12 +331,10 @@ async function getApps(user, process) {
if (user) { if (user) {
let db = new Database(user); let db = new Database(user);
try { try {
let names = JSON.parse(await db.get('apps')); let names = JSON.parse(db.get('apps'));
let result = {}; return Object.fromEntries(
for (let name of names) { names.map((name) => [name, db.get('path:' + name)])
result[name] = await db.get('path:' + name); );
}
return result;
} catch {} } catch {}
} }
return {}; return {};
@ -307,6 +398,7 @@ async function getProcessBlob(blobId, key, options) {
process.lastActive = Date.now(); process.lastActive = Date.now();
process.lastPing = null; process.lastPing = null;
process.timeout = options.timeout; process.timeout = options.timeout;
process.stats = false;
process.ready = new Promise(function (resolve, reject) { process.ready = new Promise(function (resolve, reject) {
resolveReady = resolve; resolveReady = resolve;
rejectReady = reject; rejectReady = reject;
@ -338,9 +430,9 @@ async function getProcessBlob(blobId, key, options) {
} }
}, },
user: getUser(process, process), user: getUser(process, process),
users: async function () { users: function () {
try { try {
return JSON.parse(await new Database('auth').get('users')); return JSON.parse(new Database('auth').get('users'));
} catch { } catch {
return []; return [];
} }
@ -485,10 +577,9 @@ async function getProcessBlob(blobId, key, options) {
if ( if (
process.credentials && process.credentials &&
process.credentials.session && process.credentials.session &&
process.credentials.session.name && process.credentials.session.name
process.credentials.session.name !== 'guest'
) { ) {
let id = await ssb.createIdentity(process.credentials.session.name); let id = ssb.createIdentity(process.credentials.session.name);
await process.sendIdentities(); await process.sendIdentities();
broadcastAppEventToUser( broadcastAppEventToUser(
process?.credentials?.session?.name, process?.credentials?.session?.name,
@ -504,8 +595,6 @@ async function getProcessBlob(blobId, key, options) {
] ]
); );
return id; return id;
} else {
throw new Error('Must be signed-in to create an account.');
} }
}; };
if (process.credentials?.permissions?.administration) { if (process.credentials?.permissions?.administration) {
@ -521,27 +610,31 @@ async function getProcessBlob(blobId, key, options) {
imports.core.globalSettingsGet = function (key) { imports.core.globalSettingsGet = function (key) {
return gGlobalSettings[key]; return gGlobalSettings[key];
}; };
imports.core.globalSettingsSet = async function (key, value) { imports.core.globalSettingsSet = function (key, value) {
print('Setting', key, value); print('Setting', key, value);
await loadSettings();
gGlobalSettings[key] = value; gGlobalSettings[key] = value;
setGlobalSettings(gGlobalSettings); setGlobalSettings(gGlobalSettings);
print('Done.'); print('Done.');
}; };
imports.core.deleteUser = async function (user) { imports.core.deleteUser = function (user) {
await imports.core.permissionTest('delete_user'); return Promise.resolve(
let db = new Database('auth'); imports.core.permissionTest('delete_user')
db.remove('user:' + user); ).then(function () {
let users = new Set(); let db = new Database('auth');
let users_original = await db.get('users');
try { db.remove('user:' + user);
users = new Set(JSON.parse(users_original));
} catch {} let users = new Set();
users.delete(user); let users_original = db.get('users');
users = JSON.stringify([...users].sort()); try {
if (users !== users_original) { users = new Set(JSON.parse(users_original));
await db.set('users', users); } catch {}
} users.delete(user);
users = JSON.stringify([...users].sort());
if (users !== users_original) {
db.set('users', users);
}
});
}; };
} }
if (options.api) { if (options.api) {
@ -692,8 +785,6 @@ async function getProcessBlob(blobId, key, options) {
); );
} }
}; };
imports.ssb.addEventListener = undefined;
imports.ssb.removeEventListener = undefined;
imports.ssb.getIdentityInfo = undefined; imports.ssb.getIdentityInfo = undefined;
imports.fetch = function (url, options) { imports.fetch = function (url, options) {
return http.fetch(url, options, gGlobalSettings.fetch_hosts); return http.fetch(url, options, gGlobalSettings.fetch_hosts);
@ -766,7 +857,7 @@ async function getProcessBlob(blobId, key, options) {
}; };
process.task.setImports(imports); process.task.setImports(imports);
process.task.activate(); process.task.activate();
let source = await ssb.blobGet(blobId); let source = await getBlobOrContent(blobId);
let appSourceName = blobId; let appSourceName = blobId;
let appSource = utf8Decode(source); let appSource = utf8Decode(source);
try { try {
@ -774,7 +865,7 @@ async function getProcessBlob(blobId, key, options) {
if (appObject.type == 'tildefriends-app') { if (appObject.type == 'tildefriends-app') {
appSourceName = options?.script ?? 'app.js'; appSourceName = options?.script ?? 'app.js';
let id = appObject.files[appSourceName]; let id = appObject.files[appSourceName];
let blob = await ssb.blobGet(id); let blob = await getBlobOrContent(id);
appSource = utf8Decode(blob); appSource = utf8Decode(blob);
await process.task.loadFile([ await process.task.loadFile([
'/tfrpc.js', '/tfrpc.js',
@ -784,7 +875,7 @@ async function getProcessBlob(blobId, key, options) {
Object.keys(appObject.files).map(async function (f) { Object.keys(appObject.files).map(async function (f) {
await process.task.loadFile([ await process.task.loadFile([
f, f,
await ssb.blobGet(appObject.files[f]), await getBlobOrContent(appObject.files[f]),
]); ]);
}) })
); );
@ -799,10 +890,6 @@ async function getProcessBlob(blobId, key, options) {
} }
await process.task.execute({name: appSourceName, source: appSource}); await process.task.execute({name: appSourceName, source: appSource});
resolveReady(process); resolveReady(process);
if (!gStatsTimer) {
gStatsTimer = true;
sendStats();
}
} catch (error) { } catch (error) {
if (process.app) { if (process.app) {
if (process?.task?.onError) { if (process?.task?.onError) {
@ -824,15 +911,56 @@ async function getProcessBlob(blobId, key, options) {
* @param {*} settings * @param {*} settings
* @returns * @returns
*/ */
async function setGlobalSettings(settings) { function setGlobalSettings(settings) {
gGlobalSettings = settings; gGlobalSettings = settings;
try { try {
return await new Database('core').set('settings', JSON.stringify(settings)); return new Database('core').set('settings', JSON.stringify(settings));
} catch (error) { } catch (error) {
print('Error storing settings:', error); print('Error storing settings:', error);
} }
} }
/**
* TODOC
* @param {*} data
* @param {*} bytes
* @returns
*/
function startsWithBytes(data, bytes) {
if (data.byteLength >= bytes.length) {
let dataBytes = new Uint8Array(data.slice(0, bytes.length));
for (let i = 0; i < bytes.length; i++) {
if (dataBytes[i] !== bytes[i] && bytes[i] !== null) {
return;
}
}
return true;
}
}
/**
* TODOC
* @param {*} path
* @returns
*/
function guessTypeFromName(path) {
let extension = path.split('.').pop();
return k_mime_types[extension];
}
/**
* TODOC
* @param {*} data
* @returns
*/
function guessTypeFromMagicBytes(data) {
for (let magic of k_magic_bytes) {
if (startsWithBytes(data, magic.bytes)) {
return magic.type;
}
}
}
/** /**
* TODOC * TODOC
* @param {*} response * @param {*} response
@ -848,9 +976,7 @@ function sendData(response, data, type, headers, status_code) {
Object.assign( Object.assign(
{ {
'Content-Type': 'Content-Type':
type || type || guessTypeFromMagicBytes(data) || 'application/binary',
httpd.mime_type_from_magic_bytes(data) ||
'application/binary',
'Content-Length': data.byteLength, 'Content-Length': data.byteLength,
}, },
headers || {} headers || {}
@ -872,6 +998,21 @@ function sendData(response, data, type, headers, status_code) {
} }
} }
/**
* TODOC
* @param {*} id
* @returns
*/
async function getBlobOrContent(id) {
if (!id) {
return;
} else if (id.startsWith('&')) {
return ssb.blobGet(id);
} else if (id.startsWith('%')) {
return ssb.messageContentGet(id);
}
}
let g_handler_index = 0; let g_handler_index = 0;
/** /**
@ -914,7 +1055,7 @@ async function useAppHandler(
}, },
respond: do_resolve, respond: do_resolve,
}, },
credentials: await httpd.auth_query(headers), credentials: httpd.auth_query(headers),
packageOwner: packageOwner, packageOwner: packageOwner,
packageName: packageName, packageName: packageName,
} }
@ -939,6 +1080,34 @@ async function useAppHandler(
* @returns * @returns
*/ */
async function blobHandler(request, response, blobId, uri) { async function blobHandler(request, response, blobId, uri) {
// TODO(tasiaiso): break this down ?
for (let i in k_static_files) {
if (uri === k_static_files[i].uri && k_static_files[i].path) {
let stat = await File.stat('core/' + k_static_files[i].path);
let id = `${stat.mtime}_${stat.size}`;
if (request.headers['if-none-match'] === '"' + id + '"') {
response.writeHead(304, {'Content-Length': '0'});
response.end();
} else {
let data = await File.readFile('core/' + k_static_files[i].path);
response.writeHead(
200,
Object.assign(
{
'Content-Type': k_static_files[i].type,
'Content-Length': data.byteLength,
etag: '"' + id + '"',
},
k_static_files[i].headers || {}
)
);
response.end(data);
}
return;
}
}
if (!uri) { if (!uri) {
response.writeHead(303, { response.writeHead(303, {
Location: Location:
@ -971,7 +1140,7 @@ async function blobHandler(request, response, blobId, uri) {
response.writeHead(304, headers); response.writeHead(304, headers);
response.end(); response.end();
} else { } else {
data = await ssb.blobGet(id); data = await getBlobOrContent(id);
if (match[3]) { if (match[3]) {
let appObject = JSON.parse(data); let appObject = JSON.parse(data);
data = appObject.files[match[3]]; data = appObject.files[match[3]];
@ -1003,7 +1172,7 @@ async function blobHandler(request, response, blobId, uri) {
response.writeHead(304, headers); response.writeHead(304, headers);
response.end(); response.end();
} else { } else {
data = await ssb.blobGet(blobId); data = await getBlobOrContent(blobId);
sendData( sendData(
response, response,
data, data,
@ -1017,7 +1186,7 @@ async function blobHandler(request, response, blobId, uri) {
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) { if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1]; let user = match[1];
let appName = match[2]; let appName = match[2];
let credentials = await httpd.auth_query(request.headers); let credentials = httpd.auth_query(request.headers);
if ( if (
credentials && credentials &&
credentials.session && credentials.session &&
@ -1027,7 +1196,7 @@ async function blobHandler(request, response, blobId, uri) {
let database = new Database(user); let database = new Database(user);
let app_object = JSON.parse(utf8Decode(request.body)); let app_object = JSON.parse(utf8Decode(request.body));
let previous_id = await database.get('path:' + appName); let previous_id = database.get('path:' + appName);
if (previous_id) { if (previous_id) {
try { try {
let previous_object = JSON.parse( let previous_object = JSON.parse(
@ -1048,7 +1217,7 @@ async function blobHandler(request, response, blobId, uri) {
let newBlobId = await ssb.blobStore(JSON.stringify(app_object)); let newBlobId = await ssb.blobStore(JSON.stringify(app_object));
let apps = new Set(); let apps = new Set();
let apps_original = await database.get('apps'); let apps_original = database.get('apps');
try { try {
apps = new Set(JSON.parse(apps_original)); apps = new Set(JSON.parse(apps_original));
} catch {} } catch {}
@ -1057,9 +1226,9 @@ async function blobHandler(request, response, blobId, uri) {
} }
apps = JSON.stringify([...apps].sort()); apps = JSON.stringify([...apps].sort());
if (apps != apps_original) { if (apps != apps_original) {
await database.set('apps', apps); database.set('apps', apps);
} }
await database.set('path:' + appName, newBlobId); database.set('path:' + appName, newBlobId);
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'}); response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('/' + newBlobId); response.end('/' + newBlobId);
} else { } else {
@ -1080,7 +1249,7 @@ async function blobHandler(request, response, blobId, uri) {
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) { if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1]; let user = match[1];
let appName = match[2]; let appName = match[2];
let credentials = await httpd.auth_query(request.headers); let credentials = httpd.auth_query(request.headers);
if ( if (
credentials && credentials &&
credentials.session && credentials.session &&
@ -1090,10 +1259,10 @@ async function blobHandler(request, response, blobId, uri) {
let database = new Database(user); let database = new Database(user);
let apps = new Set(); let apps = new Set();
try { try {
apps = new Set(JSON.parse(await database.get('apps'))); apps = new Set(JSON.parse(database.get('apps')));
} catch {} } catch {}
if (apps.delete(appName)) { if (apps.delete(appName)) {
await database.set('apps', JSON.stringify([...apps].sort())); database.set('apps', JSON.stringify([...apps].sort()));
} }
database.remove('path:' + appName); database.remove('path:' + appName);
} else { } else {
@ -1119,9 +1288,9 @@ async function blobHandler(request, response, blobId, uri) {
app_id = await db.get('path:' + match[2]); app_id = await db.get('path:' + match[2]);
} }
let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app_id))); let app_object = JSON.parse(utf8Decode(await getBlobOrContent(app_id)));
id = app_object?.files[uri.substring(1)]; id = app_object.files[uri.substring(1)];
if (!id && app_object?.files['handler.js']) { if (!id && app_object.files['handler.js']) {
let answer; let answer;
try { try {
answer = await useAppHandler( answer = await useAppHandler(
@ -1175,10 +1344,8 @@ async function blobHandler(request, response, blobId, uri) {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Content-Security-Policy': k_content_security_policy, 'Content-Security-Policy': k_content_security_policy,
}; };
data = await ssb.blobGet(id); data = await getBlobOrContent(id);
let type = let type = guessTypeFromName(uri) || guessTypeFromMagicBytes(data);
httpd.mime_type_from_extension(uri) ||
httpd.mime_type_from_magic_bytes(data);
sendData(response, data, type, headers); sendData(response, data, type, headers);
} }
} else { } else {
@ -1187,10 +1354,6 @@ async function blobHandler(request, response, blobId, uri) {
} }
} }
ssb.addEventListener('message', function () {
broadcastEvent('onMessage', [...arguments]);
});
ssb.addEventListener('broadcasts', function () { ssb.addEventListener('broadcasts', function () {
broadcastEvent('onBroadcastsChanged', []); broadcastEvent('onBroadcastsChanged', []);
}); });
@ -1205,7 +1368,7 @@ ssb.addEventListener('connections', function () {
async function loadSettings() { async function loadSettings() {
let data = {}; let data = {};
try { try {
let settings = await new Database('core').get('settings'); let settings = new Database('core').get('settings');
if (settings) { if (settings) {
data = JSON.parse(settings); data = JSON.parse(settings);
} }
@ -1225,7 +1388,7 @@ async function loadSettings() {
*/ */
function sendStats() { function sendStats() {
let apps = Object.values(gProcesses) let apps = Object.values(gProcesses)
.filter((process) => process.app) .filter((process) => process.app && process.stats)
.map((process) => process.app); .map((process) => process.app);
if (apps.length) { if (apps.length) {
let stats = getStats(); let stats = getStats();
@ -1238,6 +1401,19 @@ function sendStats() {
} }
} }
/**
* TODOC
* @param {*} process
* @param {*} enabled
*/
function enableStats(process, enabled) {
process.stats = enabled;
if (!gStatsTimer) {
gStatsTimer = true;
sendStats();
}
}
/** /**
* TODOC * TODOC
*/ */
@ -1351,4 +1527,10 @@ function storePermission(user, packageOwner, packageName, permission, allow) {
} }
} }
export {gGlobalSettings as globalSettings, invoke, getSessionProcessBlob}; export {
gGlobalSettings as globalSettings,
setGlobalSettings,
enableStats,
invoke,
getSessionProcessBlob,
};

BIN
core/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

View File

@ -4,7 +4,7 @@
<title>Tilde Friends</title> <title>Tilde Friends</title>
<link type="text/css" rel="stylesheet" href="/static/style.css" /> <link type="text/css" rel="stylesheet" href="/static/style.css" />
<link type="text/css" rel="stylesheet" href="/static/w3.css" /> <link type="text/css" rel="stylesheet" href="/static/w3.css" />
<link type="image/svg+xml" rel="icon" href="/static/tildefriends.svg" /> <link type="image/png" rel="shortcut icon" href="/static/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<script> <script>
function set_access_key_title(event) { function set_access_key_title(event) {
@ -25,17 +25,6 @@
max-height: 100%; max-height: 100%;
" "
> >
<noscript>
<div class="w3-container">
<div class="w3-panel w3-red w3-padding w3-card-4">
<h1>TildeFriends requires JavaScript.</h1>
<p>
It looks like JavaScript is disabled or unsupported. This isn't
going to work.
</p>
</div>
</div>
</noscript>
<tf-navigation></tf-navigation> <tf-navigation></tf-navigation>
<div id="content" class="hbox" style="flex: 1 0; overflow: auto"> <div id="content" class="hbox" style="flex: 1 0; overflow: auto">
<div <div

View File

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="65"
height="65"
viewBox="0 0 61 65"
fill="none"
version="1.1"
id="svg910"
sodipodi:docname="tildefriends.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs914" />
<sodipodi:namedview
id="namedview912"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="18.369231"
inkscape:cx="32.472781"
inkscape:cy="32.5"
inkscape:window-width="2256"
inkscape:window-height="1447"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg910" />
<path
style="fill:#0af;stroke-width:.712717;fill-opacity:1"
d="M6 0h49a8 8 45 0 1 8 8v49a8 8 135 0 1-8 8H6a8 8 45 0 1-8-8V8a8 8 135 0 1 8-8Z"
id="path886" />
<g
aria-label="~"
id="text890"
style="font-size:40px;line-height:1.25;fill:#000000">
<path
d="m 1.6762187,36.689095 v -4.003907 q 2.0703125,-2.34375 5.4296875,-2.34375 1.171875,0 2.4609375,0.351563 1.2890623,0.332031 3.6718753,1.347656 1.347656,0.566406 2.011718,0.742188 0.683594,0.175781 1.367188,0.175781 1.269531,0 2.617187,-0.761719 1.367188,-0.761719 2.421875,-1.914062 v 4.140625 q -1.25,1.171875 -2.539062,1.699218 -1.269531,0.527344 -2.871094,0.527344 -1.171875,0 -2.246094,-0.273437 -1.054687,-0.273438 -3.378906,-1.308594 -2.3046873,-1.035156 -3.847656,-1.035156 -1.25,0 -2.3632813,0.546875 -1.09375,0.527343 -2.734375,2.109375 z"
style="font-family:Arial;-inkscape-font-specification:'Arial, Normal'"
id="path1704" />
</g>
<g
transform="translate(16.213 5.975) scale(.72923)"
id="g896">
<circle
cx="36"
cy="36"
r="23"
fill="#fcea2b"
id="circle892" />
<path
fill="#3f3f3f"
d="M45.331 38.564c3.963 0 7.178-2.862 7.178-6.389 0-1.765.448-3.53-.852-4.685-1.299-1.156-4.345-1.704-6.326-1.704-2.357 0-5.143.143-6.451 1.704-.894 1.065-.727 3.253-.727 4.685 0 3.527 3.213 6.389 7.178 6.389zM25.738 38.564c3.963 0 7.179-2.862 7.179-6.389 0-1.765.447-3.53-.852-4.685-1.3-1.156-4.345-1.704-6.327-1.704-2.356 0-5.142.143-6.451 1.704-.893 1.065-.727 3.253-.727 4.685 0 3.527 3.213 6.389 7.178 6.389z"
id="path894" />
</g>
<g
stroke="#000"
stroke-linecap="round"
stroke-linejoin="round"
stroke-miterlimit="10"
stroke-width="2"
transform="translate(16.213 5.975) scale(.72923)"
id="g908">
<circle
cx="35.887"
cy="36.056"
r="23"
id="circle898" />
<path
d="M45.702 44.862c-6.574 3.525-14.045 3.658-19.63 0M18.883 30.464s-.953 8.55 6.86 7.918c2.62-.212 7.817-.65 7.867-8.342.005-.698-.007-1.6-.81-2.63-1.065-1.367-3.572-1.971-9.945-1.422 0 0-3.446-.1-3.972 4.476z"
id="path900" />
<path
d="m18.953 29.931-.433-3.372 3.833-.527M52.741 30.464s.953 8.55-6.86 7.918c-2.62-.212-7.817-.65-7.868-8.342-.004-.698.008-1.6.811-2.63 1.065-1.367 3.572-1.971 9.945-1.422 0 0 3.446-.1 3.972 4.476z"
id="path902" />
<path
d="M31.505 26.416s4.124 2.534 8.657 0M33.536 31.318s2.202-3.751 4.536 0M52.664 29.933l.433-3.371-3.833-.528"
id="path904" />
<path
d="M33.955 30.027s1.795-3.75 3.699 0"
id="path906" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -15,32 +15,34 @@
# - Build again, this time it should work. # - Build again, this time it should work.
# - Check the release notes, if there's a new dependency or a change to `GNUMakefile`, this file might need to be changed too. # - Check the release notes, if there's a new dependency or a change to `GNUMakefile`, this file might need to be changed too.
# For more details, contact tasiaiso @ https://tilde.club/~tasiaiso/ # For more details, contact tasiaiso @ https://tilde.club/~tasiaiso/
#
# WARNING: currently it is pinned to `47838d5e482cb4aac40190fa0414f08b8cf94d40`. I couldn't get v0.0.18 to work for some reason.
# I'll change this in the next release - tasiaiso
{ {
pkgs ? import <nixpkgs> {}, pkgs ? import <nixpkgs> {},
lib ? import <nixpkgs/lib>, lib ? import <nixpkgs/lib>,
}: }:
pkgs.stdenv.mkDerivation rec { pkgs.stdenv.mkDerivation rec {
pname = "tildefriends"; pname = "tildefriends";
version = "0.0.22"; version = "0.0.19";
src = pkgs.fetchFromGitea { src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net"; domain = "dev.tildefriends.net";
owner = "cory"; owner = "cory";
repo = "tildefriends"; repo = "tildefriends";
rev = "v${version}"; # rev = "v${version}";
hash = "sha256-Su+y++zVXmYNbwfhCP6w5e36oxW5fkURPFzFLjbyFEI="; rev = "47838d5e482cb4aac40190fa0414f08b8cf94d40";
hash = "sha256-mb5KYvWPIqgV64FOaXKHm2ownBJiiSRtdH8+YWiXwvE="; # 47838d5e482cb4aac40190fa0414f08b8cf94d40
fetchSubmodules = true; fetchSubmodules = true;
}; };
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
glibc
gnumake gnumake
openssl openssl
which which
]; ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
glibc
openssl openssl
which which
]; ];

1
deps/c-ares vendored

@ -1 +0,0 @@
Subproject commit 27b98d96eff6122fb981e338bddef3d6a57d8d44

View File

@ -1,177 +0,0 @@
#ifndef __CARES_BUILD_H
#define __CARES_BUILD_H
/*
* Copyright (C) The c-ares project and its contributors
* SPDX-License-Identifier: MIT
*/
#define CARES_STATICLIB
#ifdef CARES_HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef CARES_HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#ifdef CARES_HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#if defined(_WIN32)
#undef HAVE_REGISTERWAITFORSINGLEOBJECT
#define CARES_HAVE_WINSOCK2_H
#define CARES_HAVE_WINDOWS_H
#define CARES_HAVE_WS2TCPIP_H
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#define CARES_TYPEOF_ARES_SOCKLEN_T int
#define CARES_TYPEOF_ARES_SSIZE_T ssize_t
#else
#define CARES_TYPEOF_ARES_SOCKLEN_T socklen_t
#define CARES_TYPEOF_ARES_SSIZE_T ssize_t
#endif
#if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(__OpenBSD__) && !defined(__HAIKU__)
#define GETSERVBYNAME_R_ARGS 6
#define GETSERVBYPORT_R_ARGS 6
#define HAVE_GETSERVBYNAME_R 1
#define HAVE_GETSERVBYPORT_R 1
#endif
#if !defined(__APPLE__) && !defined(_WIN32) && !defined(__OpenBSD__) && !defined(__HAIKU__)
#define HAVE_PIPE2 1
#endif
#if defined(__OpenBSD__) || defined(__HAIKU__)
#define GETSERVBYNAME_R_ARGS 4
#define GETSERVBYPORT_R_ARGS 4
#endif
#if !defined(__APPLE__) && !defined(_WIN32) && !defined(__OpenBSD__) && !defined(__HAIKU__)
#define HAVE_MALLOC_H 1
#define HAVE_EPOLL 1
#define HAVE_SYS_EPOLL_H 1
#define HAVE_SYS_RANDOM_H 1
#endif
#if !defined(__WIN32)
#undef AC_APPLE_UNIVERSAL_BUILD
#undef ETC_INET
#define GETHOSTNAME_TYPE_ARG2 size_t
#define GETNAMEINFO_QUAL_ARG1
#define GETNAMEINFO_TYPE_ARG1 struct sockaddr *
#define GETNAMEINFO_TYPE_ARG2 socklen_t
#define GETNAMEINFO_TYPE_ARG46 socklen_t
#define GETNAMEINFO_TYPE_ARG7 int
#define HAVE_AF_INET6 1
#define HAVE_ARPA_INET_H 1
#define HAVE_ARPA_NAMESER_COMPAT_H 1
#define HAVE_ARPA_NAMESER_H 1
#define HAVE_ASSERT_H 1
#define HAVE_CLOCK_GETTIME_MONOTONIC 1
#define HAVE_CONNECT 1
#define HAVE_DLFCN_H 1
#define HAVE_ERRNO_H 1
#define HAVE_POLL_H 1
#define HAVE_POLL 1
#define HAVE_PIPE 1
#define HAVE_FCNTL 1
#define HAVE_FCNTL_H 1
#define HAVE_FCNTL_O_NONBLOCK 1
#define HAVE_FREEADDRINFO 1
#define HAVE_GETADDRINFO 1
#define HAVE_GETENV 1
#define HAVE_GETHOSTNAME 1
#define HAVE_GETNAMEINFO 1
#if !defined(__HAIKU__)
#define HAVE_GETRANDOM 1
#endif
#define HAVE_GETTIMEOFDAY 1
#define HAVE_IF_INDEXTONAME 1
#define HAVE_IF_NAMETOINDEX 1
#define HAVE_INET_NTOP 1
#define HAVE_INET_PTON 1
#define HAVE_INTTYPES_H 1
#define HAVE_IOCTL 1
#define HAVE_IOCTL_FIONBIO 1
#define HAVE_IOCTL_SIOCGIFADDR 1
#define HAVE_LIMITS_H 1
#define HAVE_LONGLONG 1
#define HAVE_MEMORY_H 1
#define HAVE_MSG_NOSIGNAL 1
#define HAVE_NETDB_H 1
#define HAVE_NETINET_IN_H 1
#define HAVE_NETINET_TCP_H 1
#define HAVE_NET_IF_H 1
#define HAVE_PF_INET6 1
#define HAVE_RECV 1
#define HAVE_RECVFROM 1
#define HAVE_SEND 1
#define HAVE_SETSOCKOPT 1
#define HAVE_SIGNAL_H 1
#define HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID 1
#define HAVE_SOCKET 1
#define HAVE_STDBOOL_H 1
#define HAVE_STDINT_H 1
#define HAVE_STDLIB_H 1
#define HAVE_STRCASECMP 1
#define HAVE_STRDUP 1
#define HAVE_STRINGS_H 1
#define HAVE_STRING_H 1
#define HAVE_STRNCASECMP 1
#define HAVE_STRUCT_ADDRINFO 1
#define HAVE_STRUCT_IN6_ADDR 1
#define HAVE_STRUCT_SOCKADDR_IN6 1
#define HAVE_STRUCT_SOCKADDR_STORAGE 1
#define HAVE_STRUCT_TIMEVAL 1
#define HAVE_SYS_IOCTL_H 1
#define HAVE_SYS_PARAM_H 1
#define HAVE_SYS_SELECT_H 1
#define HAVE_SYS_SOCKET_H 1
#define HAVE_SYS_STAT_H 1
#define HAVE_SYS_TIME_H 1
#define HAVE_SYS_TYPES_H 1
#define HAVE_SYS_UIO_H 1
#define HAVE_TIME_H 1
#define HAVE_IFADDRS_H 1
#define HAVE_UNISTD_H 1
#define HAVE_WRITEV 1
#if defined(__ANDROID__) || defined(__APPLE__) || defined(__OpenBSD__)
#define HAVE_ARC4RANDOM_BUF 1
#else
#undef HAVE_ARC4RANDOM_BUF
#endif
#define HAVE_GETIFADDRS 1
#define HAVE_STAT 1
#define CARES_RANDOM_FILE "/dev/urandom"
#define RECVFROM_QUAL_ARG5
#define RECVFROM_TYPE_ARG1 int
#define RECVFROM_TYPE_ARG2 void *
#define RECVFROM_TYPE_ARG2_IS_VOID 0
#define RECVFROM_TYPE_ARG3 size_t
#define RECVFROM_TYPE_ARG4 int
#define RECVFROM_TYPE_ARG5 struct sockaddr *
#define RECVFROM_TYPE_ARG5_IS_VOID 0
#define RECVFROM_TYPE_ARG6 socklen_t *
#define RECVFROM_TYPE_ARG6_IS_VOID 0
#define RECVFROM_TYPE_RETV ssize_t
#define RECV_TYPE_ARG1 int
#define RECV_TYPE_ARG2 void *
#define RECV_TYPE_ARG3 size_t
#define RECV_TYPE_ARG4 int
#define RECV_TYPE_RETV ssize_t
#define SEND_TYPE_ARG1 int
#define SEND_TYPE_ARG2 const void *
#define SEND_TYPE_ARG3 size_t
#define SEND_TYPE_ARG4 int
#define SEND_TYPE_RETV ssize_t
#undef USE_BLOCKING_SOCKETS
#undef WIN32_LEAN_AND_MEAN
#define HAVE_PTHREAD_H 1
#define CARES_THREADS 1
#endif
#endif /* __CARES_BUILD_H */

File diff suppressed because one or more lines are too long

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

@ -19,10 +19,9 @@
} }
}, },
"node_modules/@codemirror/autocomplete": { "node_modules/@codemirror/autocomplete": {
"version": "6.18.1", "version": "6.16.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.0.tgz",
"integrity": "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA==", "integrity": "sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@ -37,35 +36,32 @@
} }
}, },
"node_modules/@codemirror/commands": { "node_modules/@codemirror/commands": {
"version": "6.6.2", "version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz",
"integrity": "sha512-Fq7eWOl1Rcbrfn6jD8FPCj9Auaxdm5nIK5RYOeW7ughnd/rY5AmPg6b+CfsG39ZHdwiwe8lde3q8uR7CF5S0yQ==", "integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.27.0", "@codemirror/view": "^6.0.0",
"@lezer/common": "^1.1.0" "@lezer/common": "^1.1.0"
} }
}, },
"node_modules/@codemirror/lang-css": { "node_modules/@codemirror/lang-css": {
"version": "6.3.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.2.1.tgz",
"integrity": "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA==", "integrity": "sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@lezer/common": "^1.0.2", "@lezer/common": "^1.0.2",
"@lezer/css": "^1.1.7" "@lezer/css": "^1.0.0"
} }
}, },
"node_modules/@codemirror/lang-html": { "node_modules/@codemirror/lang-html": {
"version": "6.4.9", "version": "6.4.9",
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/lang-css": "^6.0.0", "@codemirror/lang-css": "^6.0.0",
@ -82,7 +78,6 @@
"version": "6.2.2", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
"integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==", "integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0", "@codemirror/language": "^6.6.0",
@ -97,17 +92,15 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@lezer/json": "^1.0.0" "@lezer/json": "^1.0.0"
} }
}, },
"node_modules/@codemirror/language": { "node_modules/@codemirror/language": {
"version": "6.10.2", "version": "6.10.1",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
"integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==", "integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0", "@codemirror/view": "^6.23.0",
@ -118,10 +111,9 @@
} }
}, },
"node_modules/@codemirror/lint": { "node_modules/@codemirror/lint": {
"version": "6.8.1", "version": "6.7.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.1.tgz", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.7.0.tgz",
"integrity": "sha512-IZ0Y7S4/bpaunwggW2jYqwLuHj0QtESf5xcROewY6+lDNwZ/NzvR4t+vpYgg9m7V8UXLPYqG+lu3DF470E5Oxg==", "integrity": "sha512-LTLOL2nT41ADNSCCCCw8Q/UmdAFzB23OUYSjsHTdsVaH0XEo+orhuqbDNWzrzodm14w6FOxqxpmy4LF8Lixqjw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
@ -132,7 +124,6 @@
"version": "6.5.6", "version": "6.5.6",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==", "integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
@ -142,14 +133,12 @@
"node_modules/@codemirror/state": { "node_modules/@codemirror/state": {
"version": "6.4.1", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
"license": "MIT"
}, },
"node_modules/@codemirror/theme-one-dark": { "node_modules/@codemirror/theme-one-dark": {
"version": "6.1.2", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz", "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==", "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0", "@codemirror/state": "^6.0.0",
@ -158,10 +147,9 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.33.0", "version": "6.26.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.33.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz",
"integrity": "sha512-AroaR3BvnjRW8fiZBalAaK+ZzB5usGgI014YKElYZvQdNH5ZIidHlO+cyf/2rWzyBFRkvG6VhiXeAEbC53P2YQ==", "integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0", "style-mod": "^4.1.0",
@ -173,7 +161,6 @@
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.2.1", "@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/sourcemap-codec": "^1.4.10",
@ -188,7 +175,6 @@
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@ -198,7 +184,6 @@
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@ -208,25 +193,22 @@
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25" "@jridgewell/trace-mapping": "^0.3.25"
} }
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0", "version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true, "dev": true
"license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25", "version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
@ -235,14 +217,12 @@
"node_modules/@lezer/common": { "node_modules/@lezer/common": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==", "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
"license": "MIT"
}, },
"node_modules/@lezer/css": { "node_modules/@lezer/css": {
"version": "1.1.9", "version": "1.1.8",
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.9.tgz", "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.8.tgz",
"integrity": "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==", "integrity": "sha512-7JhxupKuMBaWQKjQoLtzhGj83DdnZY9MckEOG5+/iLKNK2ZJqKc6hf6uc0HjwCX7Qlok44jBNqZhHKDhEhZYLA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
@ -250,19 +230,17 @@
} }
}, },
"node_modules/@lezer/highlight": { "node_modules/@lezer/highlight": {
"version": "1.2.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", "integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"node_modules/@lezer/html": { "node_modules/@lezer/html": {
"version": "1.3.10", "version": "1.3.9",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.9.tgz",
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", "integrity": "sha512-MXxeCMPyrcemSLGaTQEZx0dBUH0i+RPl8RN5GwMAzo53nTsd/Unc/t5ZxACeQoyPUM5/GkPLRUs2WliOImzkRA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
@ -270,10 +248,9 @@
} }
}, },
"node_modules/@lezer/javascript": { "node_modules/@lezer/javascript": {
"version": "1.4.18", "version": "1.4.15",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.18.tgz", "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.15.tgz",
"integrity": "sha512-Y8BeHOt4LtcxJgXwadtfSeWPrh0XzklcCHnCVT+vOsxqH4gWmunP2ykX+VVOlM/dusyVyiNfG3lv0f10UK+mgA==", "integrity": "sha512-B082ZdjI0vo2AgLqD834GlRTE9gwRX8NzHzKq5uDwEnQ9Dq+A/CEhd3nf68tiNA2f9O+8jS1NeSTUYT9IAqcTw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3", "@lezer/highlight": "^1.1.3",
@ -284,7 +261,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz",
"integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==", "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@lezer/common": "^1.2.0", "@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
@ -292,10 +268,9 @@
} }
}, },
"node_modules/@lezer/lr": { "node_modules/@lezer/lr": {
"version": "1.4.2", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", "integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"license": "MIT",
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
@ -304,7 +279,6 @@
"version": "15.2.3", "version": "15.2.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@rollup/pluginutils": "^5.0.1", "@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2", "@types/resolve": "1.20.2",
@ -330,7 +304,6 @@
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"serialize-javascript": "^6.0.1", "serialize-javascript": "^6.0.1",
"smob": "^1.0.0", "smob": "^1.0.0",
@ -352,7 +325,6 @@
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
"integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/estree": "^1.0.0", "@types/estree": "^1.0.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
@ -371,231 +343,212 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz",
"integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==", "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz",
"integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==", "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz",
"integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==", "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz",
"integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==", "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz",
"integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==", "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz",
"integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==", "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz",
"integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==", "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz",
"integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==", "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz",
"integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==", "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz",
"integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==", "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz",
"integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==", "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz",
"integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==", "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz",
"integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==", "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz",
"integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==", "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz",
"integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==", "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz",
"integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==", "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
] ]
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.6", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
"license": "MIT"
}, },
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "1.20.2", "version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
"license": "MIT"
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.12.1", "version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true, "dev": true,
"license": "MIT",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -607,14 +560,12 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true, "dev": true
"license": "MIT"
}, },
"node_modules/builtin-modules": { "node_modules/builtin-modules": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
}, },
@ -626,7 +577,6 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
"license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0", "@codemirror/commands": "^6.0.0",
@ -641,20 +591,17 @@
"version": "2.20.3", "version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true, "dev": true
"license": "MIT"
}, },
"node_modules/crelt": { "node_modules/crelt": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
"license": "MIT"
}, },
"node_modules/deepmerge": { "node_modules/deepmerge": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -662,15 +609,13 @@
"node_modules/estree-walker": { "node_modules/estree-walker": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
"license": "MIT"
}, },
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -683,7 +628,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
@ -692,7 +636,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
}, },
@ -704,7 +647,6 @@
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
"license": "MIT",
"dependencies": { "dependencies": {
"builtin-modules": "^3.3.0" "builtin-modules": "^3.3.0"
}, },
@ -716,15 +658,11 @@
} }
}, },
"node_modules/is-core-module": { "node_modules/is-core-module": {
"version": "2.15.1", "version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"license": "MIT",
"dependencies": { "dependencies": {
"hasown": "^2.0.2" "hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -733,20 +671,17 @@
"node_modules/is-module": { "node_modules/is-module": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
"license": "MIT"
}, },
"node_modules/path-parse": { "node_modules/path-parse": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
"license": "MIT"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT",
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
}, },
@ -759,7 +694,6 @@
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"safe-buffer": "^5.1.0" "safe-buffer": "^5.1.0"
} }
@ -768,7 +702,6 @@
"version": "1.22.8", "version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"license": "MIT",
"dependencies": { "dependencies": {
"is-core-module": "^2.13.0", "is-core-module": "^2.13.0",
"path-parse": "^1.0.7", "path-parse": "^1.0.7",
@ -782,10 +715,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.21.3", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
"integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==", "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/estree": "1.0.5" "@types/estree": "1.0.5"
}, },
@ -797,31 +729,25 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.21.3", "@rollup/rollup-android-arm-eabi": "4.17.2",
"@rollup/rollup-android-arm64": "4.21.3", "@rollup/rollup-android-arm64": "4.17.2",
"@rollup/rollup-darwin-arm64": "4.21.3", "@rollup/rollup-darwin-arm64": "4.17.2",
"@rollup/rollup-darwin-x64": "4.21.3", "@rollup/rollup-darwin-x64": "4.17.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.21.3", "@rollup/rollup-linux-arm-gnueabihf": "4.17.2",
"@rollup/rollup-linux-arm-musleabihf": "4.21.3", "@rollup/rollup-linux-arm-musleabihf": "4.17.2",
"@rollup/rollup-linux-arm64-gnu": "4.21.3", "@rollup/rollup-linux-arm64-gnu": "4.17.2",
"@rollup/rollup-linux-arm64-musl": "4.21.3", "@rollup/rollup-linux-arm64-musl": "4.17.2",
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.3", "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2",
"@rollup/rollup-linux-riscv64-gnu": "4.21.3", "@rollup/rollup-linux-riscv64-gnu": "4.17.2",
"@rollup/rollup-linux-s390x-gnu": "4.21.3", "@rollup/rollup-linux-s390x-gnu": "4.17.2",
"@rollup/rollup-linux-x64-gnu": "4.21.3", "@rollup/rollup-linux-x64-gnu": "4.17.2",
"@rollup/rollup-linux-x64-musl": "4.21.3", "@rollup/rollup-linux-x64-musl": "4.17.2",
"@rollup/rollup-win32-arm64-msvc": "4.21.3", "@rollup/rollup-win32-arm64-msvc": "4.17.2",
"@rollup/rollup-win32-ia32-msvc": "4.21.3", "@rollup/rollup-win32-ia32-msvc": "4.17.2",
"@rollup/rollup-win32-x64-msvc": "4.21.3", "@rollup/rollup-win32-x64-msvc": "4.17.2",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/rollup/node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"license": "MIT"
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -840,15 +766,13 @@
"type": "consulting", "type": "consulting",
"url": "https://feross.org/support" "url": "https://feross.org/support"
} }
], ]
"license": "MIT"
}, },
"node_modules/serialize-javascript": { "node_modules/serialize-javascript": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true, "dev": true,
"license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"randombytes": "^2.1.0" "randombytes": "^2.1.0"
} }
@ -857,15 +781,13 @@
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
"integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
"dev": true, "dev": true
"license": "MIT"
}, },
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true, "dev": true,
"license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -875,7 +797,6 @@
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
"source-map": "^0.6.0" "source-map": "^0.6.0"
@ -884,14 +805,12 @@
"node_modules/style-mod": { "node_modules/style-mod": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
"license": "MIT"
}, },
"node_modules/supports-preserve-symlinks-flag": { "node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@ -900,11 +819,10 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.33.0", "version": "5.31.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.33.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz",
"integrity": "sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==", "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==",
"dev": true, "dev": true,
"license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2", "acorn": "^8.8.2",
@ -921,8 +839,7 @@
"node_modules/w3c-keyname": { "node_modules/w3c-keyname": {
"version": "2.2.8", "version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
"license": "MIT"
} }
} }
} }

2
deps/libbacktrace vendored

@ -1 +1 @@
Subproject commit 86885d14049fab06ef8a33aac51664230ca09200 Subproject commit 11427f31a64b11583fec94b4c2a265c7dafb1ab3

2
deps/libsodium vendored

@ -1 +1 @@
Subproject commit 0217d07326f0ffbe79d6ce09793843e135a67487 Subproject commit fb4533b0a941b3a5b1db5687d1b008a5853d1f29

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
deps/openssl_src vendored

@ -1 +0,0 @@
Subproject commit fb7fab9fa6f4869eaa8fbb97e0d593159f03ffe4

1723
deps/sqlite/shell.c vendored

File diff suppressed because it is too large Load Diff

8615
deps/sqlite/sqlite3.c vendored

File diff suppressed because it is too large Load Diff

97
deps/sqlite/sqlite3.h vendored
View File

@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()]. ** [sqlite_version()] and [sqlite_source_id()].
*/ */
#define SQLITE_VERSION "3.46.1" #define SQLITE_VERSION "3.45.3"
#define SQLITE_VERSION_NUMBER 3046001 #define SQLITE_VERSION_NUMBER 3045003
#define SQLITE_SOURCE_ID "2024-08-13 09:16:08 c9c2ab54ba1f5f46360f1b4f35d849cd3f080e6fc2b6c60e91b16c63f69a1e33" #define SQLITE_SOURCE_ID "2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355"
/* /*
** CAPI3REF: Run-Time Library Version Numbers ** CAPI3REF: Run-Time Library Version Numbers
@ -764,11 +764,11 @@ struct sqlite3_file {
** </ul> ** </ul>
** xLock() upgrades the database file lock. In other words, xLock() moves the ** xLock() upgrades the database file lock. In other words, xLock() moves the
** database file lock in the direction NONE toward EXCLUSIVE. The argument to ** database file lock in the direction NONE toward EXCLUSIVE. The argument to
** xLock() is always one of SHARED, RESERVED, PENDING, or EXCLUSIVE, never ** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never
** SQLITE_LOCK_NONE. If the database file lock is already at or above the ** SQLITE_LOCK_NONE. If the database file lock is already at or above the
** requested lock, then the call to xLock() is a no-op. ** requested lock, then the call to xLock() is a no-op.
** xUnlock() downgrades the database file lock to either SHARED or NONE. ** xUnlock() downgrades the database file lock to either SHARED or NONE.
** If the lock is already at or below the requested lock state, then the call * If the lock is already at or below the requested lock state, then the call
** to xUnlock() is a no-op. ** to xUnlock() is a no-op.
** The xCheckReservedLock() method checks whether any database connection, ** The xCheckReservedLock() method checks whether any database connection,
** either in this process or in some other process, is holding a RESERVED, ** either in this process or in some other process, is holding a RESERVED,
@ -3305,8 +3305,8 @@ SQLITE_API int sqlite3_set_authorizer(
#define SQLITE_RECURSIVE 33 /* NULL NULL */ #define SQLITE_RECURSIVE 33 /* NULL NULL */
/* /*
** CAPI3REF: Deprecated Tracing And Profiling Functions ** CAPI3REF: Tracing And Profiling Functions
** DEPRECATED ** METHOD: sqlite3
** **
** These routines are deprecated. Use the [sqlite3_trace_v2()] interface ** These routines are deprecated. Use the [sqlite3_trace_v2()] interface
** instead of the routines described here. ** instead of the routines described here.
@ -6887,12 +6887,6 @@ SQLITE_API int sqlite3_autovacuum_pages(
** The exceptions defined in this paragraph might change in a future ** The exceptions defined in this paragraph might change in a future
** release of SQLite. ** release of SQLite.
** **
** Whether the update hook is invoked before or after the
** corresponding change is currently unspecified and may differ
** depending on the type of change. Do not rely on the order of the
** hook call with regards to the final result of the operation which
** triggers the hook.
**
** The update hook implementation must not do anything that will modify ** The update hook implementation must not do anything that will modify
** the database connection that invoked the update hook. Any actions ** the database connection that invoked the update hook. Any actions
** to modify the database connection must be deferred until after the ** to modify the database connection must be deferred until after the
@ -8363,7 +8357,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
** The sqlite3_keyword_count() interface returns the number of distinct ** The sqlite3_keyword_count() interface returns the number of distinct
** keywords understood by SQLite. ** keywords understood by SQLite.
** **
** The sqlite3_keyword_name(N,Z,L) interface finds the 0-based N-th keyword and ** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and
** makes *Z point to that keyword expressed as UTF8 and writes the number ** makes *Z point to that keyword expressed as UTF8 and writes the number
** of bytes in the keyword into *L. The string that *Z points to is not ** of bytes in the keyword into *L. The string that *Z points to is not
** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns ** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns
@ -9942,45 +9936,24 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
** <li value="2"><p> ** <li value="2"><p>
** ^(If the sqlite3_vtab_distinct() interface returns 2, that means ** ^(If the sqlite3_vtab_distinct() interface returns 2, that means
** that the query planner does not need the rows returned in any particular ** that the query planner does not need the rows returned in any particular
** order, as long as rows with the same values in all columns identified ** order, as long as rows with the same values in all "aOrderBy" columns
** by "aOrderBy" are adjacent.)^ ^(Furthermore, when two or more rows ** are adjacent.)^ ^(Furthermore, only a single row for each particular
** contain the same values for all columns identified by "colUsed", all but ** combination of values in the columns identified by the "aOrderBy" field
** one such row may optionally be omitted from the result.)^ ** needs to be returned.)^ ^It is always ok for two or more rows with the same
** The virtual table is not required to omit rows that are duplicates ** values in all "aOrderBy" columns to be returned, as long as all such rows
** over the "colUsed" columns, but if the virtual table can do that without ** are adjacent. ^The virtual table may, if it chooses, omit extra rows
** too much extra effort, it could potentially help the query to run faster. ** that have the same value for all columns identified by "aOrderBy".
** ^However omitting the extra rows is optional.
** This mode is used for a DISTINCT query. ** This mode is used for a DISTINCT query.
** <li value="3"><p> ** <li value="3"><p>
** ^(If the sqlite3_vtab_distinct() interface returns 3, that means the ** ^(If the sqlite3_vtab_distinct() interface returns 3, that means
** virtual table must return rows in the order defined by "aOrderBy" as ** that the query planner needs only distinct rows but it does need the
** if the sqlite3_vtab_distinct() interface had returned 0. However if ** rows to be sorted.)^ ^The virtual table implementation is free to omit
** two or more rows in the result have the same values for all columns ** rows that are identical in all aOrderBy columns, if it wants to, but
** identified by "colUsed", then all but one such row may optionally be ** it is not required to omit any rows. This mode is used for queries
** omitted.)^ Like when the return value is 2, the virtual table
** is not required to omit rows that are duplicates over the "colUsed"
** columns, but if the virtual table can do that without
** too much extra effort, it could potentially help the query to run faster.
** This mode is used for queries
** that have both DISTINCT and ORDER BY clauses. ** that have both DISTINCT and ORDER BY clauses.
** </ol> ** </ol>
** **
** <p>The following table summarizes the conditions under which the
** virtual table is allowed to set the "orderByConsumed" flag based on
** the value returned by sqlite3_vtab_distinct(). This table is a
** restatement of the previous four paragraphs:
**
** <table border=1 cellspacing=0 cellpadding=10 width="90%">
** <tr>
** <td valign="top">sqlite3_vtab_distinct() return value
** <td valign="top">Rows are returned in aOrderBy order
** <td valign="top">Rows with the same value in all aOrderBy columns are adjacent
** <td valign="top">Duplicates over all colUsed columns may be omitted
** <tr><td>0<td>yes<td>yes<td>no
** <tr><td>1<td>no<td>yes<td>no
** <tr><td>2<td>no<td>yes<td>yes
** <tr><td>3<td>yes<td>yes<td>yes
** </table>
**
** ^For the purposes of comparing virtual table output values to see if the ** ^For the purposes of comparing virtual table output values to see if the
** values are same value for sorting purposes, two NULL values are considered ** values are same value for sorting purposes, two NULL values are considered
** to be the same. In other words, the comparison operator is "IS" ** to be the same. In other words, the comparison operator is "IS"
@ -12025,30 +11998,6 @@ SQLITE_API int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const c
*/ */
SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
/*
** CAPI3REF: Add A Single Change To A Changegroup
** METHOD: sqlite3_changegroup
**
** This function adds the single change currently indicated by the iterator
** passed as the second argument to the changegroup object. The rules for
** adding the change are just as described for [sqlite3changegroup_add()].
**
** If the change is successfully added to the changegroup, SQLITE_OK is
** returned. Otherwise, an SQLite error code is returned.
**
** The iterator must point to a valid entry when this function is called.
** If it does not, SQLITE_ERROR is returned and no change is added to the
** changegroup. Additionally, the iterator must not have been opened with
** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also
** returned.
*/
SQLITE_API int sqlite3changegroup_add_change(
sqlite3_changegroup*,
sqlite3_changeset_iter*
);
/* /*
** CAPI3REF: Obtain A Composite Changeset From A Changegroup ** CAPI3REF: Obtain A Composite Changeset From A Changegroup
** METHOD: sqlite3_changegroup ** METHOD: sqlite3_changegroup
@ -12853,8 +12802,8 @@ struct Fts5PhraseIter {
** EXTENSION API FUNCTIONS ** EXTENSION API FUNCTIONS
** **
** xUserData(pFts): ** xUserData(pFts):
** Return a copy of the pUserData pointer passed to the xCreateFunction() ** Return a copy of the context pointer the extension function was
** API when the extension function was registered. ** registered with.
** **
** xColumnTotalSize(pFts, iCol, pnToken): ** xColumnTotalSize(pFts, iCol, pnToken):
** If parameter iCol is less than zero, set output variable *pnToken ** If parameter iCol is less than zero, set output variable *pnToken

View File

@ -1,63 +0,0 @@
# Tilde Friends Cheat Sheet
Making apps for the impatient tilde friend.
## Prerequisites
- either run your own instance or use [tildefriends.net](https://www.tildefriends.net/)
- register and login
- [optional] use the `ssb` app to create yourself an SSB identity
## Development Process
1. hit the `edit` link from any app or new app URL
2. make sure the path in the text box is under your username: `/~username/app/`
3. write server-side code in `app.js`
4. click the `save` button or press the save hotkey (Alt+S or _[browser-specific modifiers]_+S)
5. see the app reload on the right side
## Output
- **`app.setDocument(html)`** - send HTML to the browser
- **`print(...)`** - send values to the browser's developer console
## Persistence
- **`app.localStorageGet(key)`** -> **`value`**
- **`app.localStorageSet(key, value)`**
- **`database()`**, **`shared_database(key)`**, **`my_shared_database(package, key)`**
- **`db.get(key)`** -> **`value`**
- **`db.set(key, value)`**
- **`db.exchange(key, expected, value)`** -> **`exchanged`**
- **`db.remove(key)`**
- **`db.getAll()`** -> **`[key1, ...]`**
- **`db.getLike(pattern)`** -> **`{key1: value1, ...}`**
## SSB
- **`ssb.createIdentity()`** -> **`id`**
- **`ssb.getIdentities()`** -> **`[id1, ...]`**
- **`ssb.appendMessageWithIdentity(id, content)`** -> **`message_id`**
- **`ssb.blobStore(blob)`** -> **`blob_id`**
- **`ssb.blobGet(id)`** -> **`blob`**
- **`ssb.sqlAsync(query, args, row_callback)`**
## TF-RPC
Stock helper code for calling functions across the web server and browser boundary.
- on the server: `import * as tfrpc from '/tfrpc.js';`
- in the browser: `import * as tfrpc from '/static/tfrpc.js';`
- either direction:
- register a function: `tfrpc.register(function my_function() {});`
- call a remote function: `let promise = tfrpc.rpc.my_function();`
## Share
- give out web links: [https://www.tildefriends.net/~cory/screwble/](https://www.tildefriends.net/~cory/screwble/)
- use the `Attach App` button when composing a post in [the SSB app](https://www.tildefriends.net/~core/ssb/)
## More Docs
- [api reference](https://www.tildefriends.net/~cory/api/)
- [source code](https://dev.tildefriends.net/cory/tildefriends/releases)

View File

@ -1,166 +1,209 @@
# Tilde Friends Developer's Guide # Philosophy
A Tilde Friends application starts with code that runs on a Tilde Friends server, possibly far away from where you wrote it, in a little JavaScript environment, in its own restricted process, with the only access to the outside world being the ability to send messages to the server. This document gives some recipes showing how that can be used to build a functional user-facing application in light of the unique constraints present. Tilde Friends is a platform for making, running, and sharing web applications.
## Example 1: Hello, world! When you visit Tilde Friends in a web browser, you are presented with a
terminal interface, typically with a big text output box covering most of the
page and an input box at the bottom, into which text or commands can be
entered. A script runs to produce text output and consume user input.
Of course we must start with a classic. The script is a Tilde Friends application, and it runs on the server, which
means that unlike client-side JavaScript, it can have the ability to read and
write files on the server or create network connections to other machines.
Unlike node.js or other server-side runtime environments, applications are
limited for security reasons to not interfere with each other or bring the
entire server down.
### app.js Above the terminal, an "Edit" link brings a visitor to the source code for the
current Tilde Friends application, which they can then edit, save as their own,
and run.
``` # Architecture
app.setDocument('<h1 style="color: #fff">Hello, world!</h1>');
```
### Output Tilde Friends is a C++ application with a JavaScript runtime that provides
restricted access to filesystem, network, and other system resources. The core
process runs a core set of scripts that implement a web server, typically
starting a new process for each visitor's session which runs scripts for the
active application and stopping it when the visitor leaves.
<iframe srcdoc="&lt;h1 style=&quot;color: #fff&quot;&gt;Hello, world!&lt;/h1&gt;"></iframe> Only the core process has access to most system resources, but session
processes can be given accesss through the core process.
### Explanation Service processes are identical to session processes, but they are not tied to
a user session.
At a glance, this might seem mundane, but for it to work: ## Communication
- the server starts a real process for your app and loads your code into it In the same way that web browsers expose APIs for scripts running in the
- your code runs browser to modify the document, play sounds and video, and draw, Tilde Friends
- `app.setDocument()` sends a message back to the server exposes APIs for scripts running on a Tilde Friends server to interact with a
- the server interprets the message and redirects it to the browser visitor's web browser, read and write files on the server, and otherwise
- `core/client.js` in the browser receives the message and puts your HTML into an iframe interact with the world.
- your HTML is presented by the browser in an iframe sandbox
But you don't have to think about all that. Call a function, and you see the result. There are several distinct classes of APIs.
## Example 2: Hit Counter First, there are low-level functions exposed from C++ to JavaScript. Most of
these are only available to the core process. These typically only go through
a basic JavaScript to C++ transition and are relatively fast and immediate.
Let's take advantage of code running on the server and create a little hit counter using a key value store shared between all visitors. // Displays some text to the server's console.
print("Hello, world!");
### app.js There is a mechanism for communicating between processes. Functions can be
exported and called across process boundaries. When this is done, any
arguments are serialized to a network protocol, deserialized by the other
process, the function called, and finally any return value is passed back in
the same way. Any functions referenced by the arguments or return value are
also exported and can be subsequently called across process boundaries.
Functions called across process boundaries are always asynchronous, returning a
Promise. Care must be taken for security reasons to not pass dangerous
functions ("deleteAllMydata()") to untrusted processes, and it is best for
performance reasons to minimize the data size transferred between processes.
``` // Send an "add" function to any other running processes. When called, it
async function main() { // will run in this process.
let db = await shared_database('visitors'); core.broadcast({add: function(x, y) { return x + y; }});
let count = parseInt((await db.get('visitors')) ?? '0') + 1;
await db.set('visitors', count.toString());
await app.setDocument(`
<h1 style="color: #fff">Welcome, visitor #${count}!</h1>
`);
}
main(); // Receive the above message and call the function.
``` core.register("onMessage", function(sender, message) {
message.add(3, 4).then(x => terminal.print(x.toString()));
});
### Output Finally, there is a core web interface that runs on the client's browser that
extends access to a running Tilde Friends script.
<iframe srcdoc="&lt;h1 style=&quot;color: #fff&quot;&gt;Welcome, visitor #1!&lt;/h1&gt;"></iframe> // Displays a message in the client's browser.
terminal.print("Hello, world!");
### Explanation ## API Documentation
Just as pure browser apps have access to `localStorage`, Tilde Friends apps have access to key-value storage on the server. The Tilde Friends API is very much evolving.
The interface is a bit clunky and will likely change someday, but this example gets a database object, from which you can get and set string values by key. There are various on `shared_database` that let you store data that is private to the user or shared by different criteria. All currently registered methods can be explored in the
[documentation](https://www.tildefriends.net/~cory/documentation) app.
Also, even though any browser-side code is sandboxed, it is allowed to access browser local storage by going through Tilde Friends API, because sometimes that is useful. All browser-facing methods are implemented in [client.js](core/client.js).
Most process-related methods are implemented in [core.js](core/core.js).
## Example 3: Files Higher-level behaviors are often implemented within library-style apps
themselves and are beyond the scope of this document.
Suppose you don't want to create your entire app in a single server-side file as we've done with the previous examples. There are some tools to allow you to begin to organize. ### Terminal
### app.js All interaction with a human user is through a terminal-like interface. Though
it is somewhat limiting, it makes simple things easy, and it is possible to
construct complicated interfaces by creating and interacting with an iframe.
``` #### terminal.print(arguments...)
async function main() {
let html = utf8Decode(await getFile('index.html'));
app.setDocument(html);
}
main(); Print to the terminal. Arguments and lists are recursively expanded. Numerous
``` special values are supported as implemented in client.cs.
### index.html // Create a link.
terminal.print({href: "http://www.tildefriends.net/", value: "Tilde Friends!"});
``` // Create an iframe.
<html> terminal.print({iframe: "&lt;b&gt;Hello, world!&lt;/b&gt;", width: 640, height: 480});
<head>
<script type="module" src="script.js"></script>
</head>
<body style="color: #fff">
<h1>File Test</h1>
</body>
</html>
```
### script.js // Use style.
terminal.print({style: "color: #f00", value: "Hello, world!"});
``` // Create a link that when clicked will act as if the user typed a command.
window.addEventListener('load', function() { terminal.print({command: "exit", value: "Get out of here."});
document.body.appendChild(document.createTextNode('Hello, world');
});
```
### Output #### terminal.clear()
<iframe srcdoc="&lt;body style=&quot;color: #fff&quot;&gt;<h1>File Test</h1>Hello, world!&lt;/body&gt;"></iframe> Clears the terminal output.
### Explanation #### terminal.readLine()
On the server, `utf8Decode(await getFile(fileName))` lets you load a file from your app. In the browser, your app files are made available by HTTP, so you can `<script src="my_script.js"></script>` and such to access them. Read a line of input from the user.
## Example 4: Remote Procedure Call #### terminal.setEcho(echo)
While making calls between the client and the server, it is possible to pass functions across that boundary. `tfrpc.js` is a tiny script which builds on that feature to try to hide some of the complexities. Controls whether the terminal will automatically echo user input. Defaults to true.
### app.js #### terminal.setPrompt(prompt)
``` Sets the terminal prompt. The default is "&gt;".
import * as tf from '/tfrpc.js';
function sum() { #### terminal.setTitle(title)
let s = 0
for (let x of arguments) {
s += x;
}
return s;
}
tf.register(sum);
async function main() { Sets the browser window/tab title.
app.setDocument(utf8Decode(await getFile('index.html')));
}
main();
```
### index.html #### terminal.split(terminalList)
``` Reconfigures the terminal layout, potentially into multiple split panes.
<html>
<body>
<h1 id='result'>Calculating...</h1>
</body>
<script type="module" src="script.js"></script>
</html>
```
### script.js terminal.split([
{
type: "horizontal",
children: [
{name: "left", basis: "2in", grow: 0, shrink: 0},
{name: "middle", grow: 1},
{name: "right", basis: "2in", grow: 0, shrink: 0},
],
},
]);
``` #### terminal.select(name)
import * as tf from '/static/tfrpc.js';
window.addEventListener('load', async function() { Directs subsequent output to the named terminal.
document.getElementById('result').innerText = await tf.rpc.sum(1, 2, 3);
});
```
### Output #### terminal.postMessageToIframe(iframeName, message)
<iframe srcdoc="&lt;body style=&quot;color: #fff&quot;&gt;<h1>6</h1>&lt;/body&gt;"></iframe> Sends a message to the iframe that was created with the given name, using the
browser's window.postMessage.
### Explanation ### Database
Here the browser makes an asynchronous call to the server to do some basic math and update its DOM with the result. Tilde Friends uses lmdb as a basic key value store. Keys and values are all
expected to be of type String. Each application gets its own isolated
database.
With your favorite Vue/Lit/React/... library on the client-side and your favorite Tilde Friends API calls registered with tfrpc, it becomes pretty easy to start extracting interesting information from, say, SQL queries over Secure Scuttlebutt data, and generating complicated, dynamic user interface. These are the building blocks I used to make the current Tilde Friends SSB client interface. #### database.get(key)
## Conclusion Retrieve the database value associated with the given key.
Tilde Friends is currently a pile of all the parts that I thought I needed to build interesting web applications, tied together by code that tries to walk the fine line between being secure enough to let us safely run code on the same device and being usable enough that you can open a tab in your browser and start building just by typing code. #### database.set(key, value)
I don't claim it thoroughly accomplishes either yet, but I believe it is at a stage where it is showing how promising this approach can be, and I am excited for you to take it for a spin and share. Sets the database value for the given key, overwriting any existing value.
#### database.remove(key)
Remove the database entry for the given key.
#### database.getAlll()
Retrieve a list of all key names.
### Network
Network access is generally not extended to untrusted users.
It is necessary to grant network permissions to an app owner through the
administration app.
Apps that require network access must declare it like this:
//! { "permissions": ["network"] }
#### network.newConnection()
Creates a Connection object.
#### connection.connect(host, port)
Opens a TCP connection to host:port.
#### connection.read(readCallback)
Begins reading and calls readCallback(data) for all data received.
#### connection.write(data)
Writes data to the connection.
#### connection.close()
Closes the connection.

View File

@ -1,64 +0,0 @@
# Tilde Friends Vision
Tilde Friends is a tool for making and sharing.
It is both a peer-to-peer social network client, participating in Secure
Scuttlebutt, and an environment for creating and running web applications.
## Why
This is a thing that I wanted to exist and wanted to work on. No other reason.
There is not a business model. I believe it is interesting and unique.
## Goals
1. Make it **easy and fun** to run all sorts of web applications.
2. Provide **security** that is easy to understand and protects your data.
3. Make **creating and sharing** web applications accessible to anyone with a
browser.
## Ways to Use Tilde Friends
1. **Social Network User**: This is a social network first. You are just here,
because your friends are. Or you like how we limit your message length or
short videos or whatever the trend is. If you are ambitious, you click links
and see interactive experiences (apps) that you wouldn't see elsewhere.
2. **Web Visitor**: You get links from a friend to meeting invites, polls, games,
lists, wiki pages, ..., and you interact with them as though they were
cloud-hosted by a megacorporation. They just work, and you don't think twice.
3. **Group leader**: You host or use a small public instance, installing apps for
a group of friends to use as web visitors.
4. **Developer**: You like to write code and make or improve apps for fun or to
solve problems. When you encounter a Tilde Friends app on a strange server,
you know you can trivially modify it or download it to your own instance.
## Future Goals / Endgame
1. Mobile apps. This can run on your old phone. Maybe you won't be hosting
the web interface publicly, but you can sync, install and edit apps, and
otherwise get the full experience from a tiny touch screen.
2. The universal application runtime. The web browser is the universal
platform, but even for the simplest application that you might want to host
for your friends, cloud hosting, containers, and complicated dependencies might
all enter the mix. Tilde Friends, though it is yet another thing to host,
includes everything you need out of the box to run a vast variety of interesting
apps.
Tilde Friends will be built out, gradually providing safe access to host
resources and client resources the same way web browsers extended access to
resources like GPU, persistent storage, cameras, ... over the years.
Not much effort has been put forward yet to having a robust, long-lasting API,
but since the client side longevity is already handled by web browsers, it
seems possible that the server-side API can be managed in a similar way.
3. An awesome development environment. Right now it runs JavaScript from the
first embeddable text editor I could poorly configure enough to edit code,
but it could incorporate a debugger, source control integration a la ssb-git,
merge tools, and transpiling from all sorts of different languages.

View File

@ -20,16 +20,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1717281328, "lastModified": 1715395895,
"narHash": "sha256-evZPzpf59oNcDUXxh2GHcxHkTEG4fjae2ytWP85jXRo=", "narHash": "sha256-DreMqi6+qa21ffLQqhMQL2XRUkAGt3N7iVB5FhJKie4=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b3b2b28c1daa04fe2ae47c21bb76fd226eac4ca1", "rev": "71bae31b7dbc335528ca7e96f479ec93462323ff",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-24.05", "ref": "nixos-23.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View File

@ -2,7 +2,7 @@
description = "Tilde Friends is a platform for making, running, and sharing web applications."; description = "Tilde Friends is a platform for making, running, and sharing web applications.";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
@ -31,8 +31,6 @@
openssl openssl
llvmPackages_17.clang-unwrapped llvmPackages_17.clang-unwrapped
unzip unzip
doxygen
graphviz
]; ];
}; };
}); });

View File

@ -1,7 +0,0 @@
* Reworked Android to launch its sandbox processes the prescribed way so that
a Google Play-shippable android .aab is possible.
* Reworked the build to generate what F-Droid wants, too.
* Updated libbacktrace.
* Updated CodeMirror.
* Updated Android NDK.
* Enabled LTO for smaller code sizes.

View File

@ -1,12 +0,0 @@
* Took an initial whack at encouraging internet-based discovery of open peers.
* Added settings to control whether replication, room, peer exchange, and account registration are allowed.
* Implemented prompt() on Android.
* Fixed some incorrect cross-thread use of the main JS context.
* Fixed yet another incorrect use of the DB from the main thread, from an RPC that isn't ever hit. Hmm.
* Minor admin layout fixes.
* Added c-ares for TXT record lookups.
* Latest libsodium-stable.
* Latest libbacktrace.
* Latest CodeMirror.
* Updated to Lit 3.2.0.
* Updated sqlite to 3.46.1.

View File

@ -1,3 +0,0 @@
Tilde Friends is both a peer-to-peer social network client, participating in
Secure Scuttlebutt, as well as a platform for writing and running web
applications.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

View File

@ -1 +0,0 @@
A tool for making and sharing

View File

@ -1 +0,0 @@
Tilde Friends

View File

@ -1,13 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends" package="com.unprompted.tildefriends"
android:versionCode="27" android:versionCode="19"
android:versionName="0.0.23-wip"> android:versionName="0.0.19-wip">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="Tilde Friends" android:label="Tilde Friends"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true"
android:debuggable="true"
android:extractNativeLibs="true">
<meta-data android:name="android.max_aspect" android:value="2.1"/> <meta-data android:name="android.max_aspect" android:value="2.1"/>
<activity <activity
android:name=".TildeFriendsActivity" android:name=".TildeFriendsActivity"
@ -19,10 +21,5 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<service
android:name=".TildeFriendsSandboxService"
android:exported="false"
android:isolatedProcess="true"
android:process=":sandbox"/>
</application> </application>
</manifest> </manifest>

View File

@ -1,5 +0,0 @@
{
"optimizations" : {
"uncompress_native_libraries" : {}
}
}

View File

@ -1,6 +0,0 @@
<project>
<target name="clean"></target>
<target name="release">
<echo>Creating ../../../out/apk/TildeFriends-release.fdroid.unsigned.apk for release.</echo>
</target>
</project>

View File

@ -3,20 +3,15 @@ package com.unprompted.tildefriends;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.DownloadManager; import android.app.DownloadManager;
import android.content.ComponentName;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection;
import android.net.ConnectivityManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Environment; import android.os.Environment;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.StrictMode; import android.os.StrictMode;
import android.text.InputType; import android.os.SystemClock;
import android.os.strictmode.Violation;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -34,14 +29,19 @@ import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest; import android.webkit.WebResourceRequest;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.EditText; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import java.io.BufferedInputStream;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.lang.Process;
import java.lang.Thread;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds; import java.nio.file.StandardWatchEventKinds;
@ -51,30 +51,17 @@ import java.nio.file.WatchService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class TildeFriendsActivity extends Activity { public class TildeFriendsActivity extends Activity {
static TildeFriendsActivity s_activity;
TildeFriendsWebView web_view; TildeFriendsWebView web_view;
String base_url; String base_url;
String port_file_path; Process process;
Thread thread; Thread thread;
Thread server_thread;
ServiceConnection service_connection;
private ValueCallback<Uri[]> upload_message; private ValueCallback<Uri[]> upload_message;
private final static int FILECHOOSER_RESULT = 1; private final static int FILECHOOSER_RESULT = 1;
private float touch_down_y; private float touch_down_y;
static {
Log.w("tildefriends", "Calling system.loadLibrary().");
System.loadLibrary("tildefriends");
Log.w("tildefriends", "system.loadLibrary() completed.");
}
public static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path, ConnectivityManager connectivity_manager);
public static native int tf_sandbox_main(int pipe_fd);
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
s_activity = this;
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects() .detectLeakedClosableObjects()
.penaltyLog() .penaltyLog()
@ -89,7 +76,7 @@ public class TildeFriendsActivity extends Activity {
Log.w("tildefriends", String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString())); Log.w("tildefriends", String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString()));
Log.w("tildefriends", String.format("nativeLibraryDir is %s", getApplicationInfo().nativeLibraryDir)); Log.w("tildefriends", String.format("nativeLibraryDir is %s", getApplicationInfo().nativeLibraryDir));
port_file_path = getFilesDir().toString() + "/port.txt"; String port_file_path = getFilesDir().toString() + "/port.txt";
new File(port_file_path).delete(); new File(port_file_path).delete();
base_url = "http://127.0.0.1:12345/"; base_url = "http://127.0.0.1:12345/";
@ -147,26 +134,22 @@ public class TildeFriendsActivity extends Activity {
thread.start(); thread.start();
set_status("Starting server..."); set_status("Starting server...");
server_thread = new Thread(new Runnable() { String exe = getApplicationInfo().nativeLibraryDir + "/tildefriends.so";
@Override ProcessBuilder builder = new ProcessBuilder(exe, "run", "-z", getPackageResourcePath().toString(), "-a", "out_http_port_file=" + port_file_path, "-p", "0");
public void run() { Log.w("tildefriends", "files = " + getFilesDir().toString());
Log.w("tildefriends", "Calling tf_server_main."); Log.w("tildefriends", "exe = " + exe);
int result = tf_server_main( builder.directory(getFilesDir());
getFilesDir().toString(), builder.inheritIO();
getPackageResourcePath().toString(), try {
port_file_path, process = builder.start();
(ConnectivityManager)getApplicationContext().getSystemService(CONNECTIVITY_SERVICE)); } catch (java.io.IOException e) {
Log.w("tildefriends", "tf_server_main returned " + result + "."); Log.w("tildefriends", "IOException starting process: " + e.toString());
} }
});
server_thread.start();
web_view.getSettings().setJavaScriptEnabled(true); web_view.getSettings().setJavaScriptEnabled(true);
web_view.getSettings().setDatabaseEnabled(true); web_view.getSettings().setDatabaseEnabled(true);
web_view.getSettings().setDomStorageEnabled(true); web_view.getSettings().setDomStorageEnabled(true);
set_database_path();
web_view.setDownloadListener(new DownloadListener() { web_view.setDownloadListener(new DownloadListener() {
public void onDownloadStart(String url, String userAgent, String content_disposition, String mime_type, long content_length) { public void onDownloadStart(String url, String userAgent, String content_disposition, String mime_type, long content_length) {
Log.w("tildefriends", "Let's download: " + url + " (" + content_disposition + ")"); Log.w("tildefriends", "Let's download: " + url + " (" + content_disposition + ")");
@ -200,6 +183,30 @@ public class TildeFriendsActivity extends Activity {
}); });
web_view.setWebChromeClient(new WebChromeClient() { web_view.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("Tilde Friends")
.setMessage(message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
result.confirm();
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
result.cancel();
}
})
.create()
.show();
return true;
}
@Override @Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) { public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
new AlertDialog.Builder(view.getContext()) new AlertDialog.Builder(view.getContext())
@ -217,59 +224,6 @@ public class TildeFriendsActivity extends Activity {
return true; return true;
} }
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("Tilde Friends")
.setMessage(message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
result.confirm();
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
result.cancel();
}
})
.create()
.show();
return true;
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
EditText input = new EditText(view.getContext());
input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(defaultValue);
new AlertDialog.Builder(view.getContext())
.setTitle("Tilde Friends")
.setMessage(message)
.setView(input)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
result.confirm(input.getText().toString());
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
result.cancel();
}
})
.create()
.show();
return true;
}
/* /*
** https://stackoverflow.com/questions/5907369/file-upload-in-webview ** https://stackoverflow.com/questions/5907369/file-upload-in-webview
** https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android ** https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android
@ -312,8 +266,13 @@ public class TildeFriendsActivity extends Activity {
@Override @Override
protected void onDestroy() protected void onDestroy()
{ {
if (process != null) {
Log.w("tildefriends", "Killing process.");
process.destroyForcibly();
Log.w("tildefriends", "Process killed.");
process = null;
}
super.onDestroy(); super.onDestroy();
s_activity = null;
} }
@Override @Override
@ -415,57 +374,4 @@ public class TildeFriendsActivity extends Activity {
web_view.setVisibility(View.VISIBLE); web_view.setVisibility(View.VISIBLE);
text_view.setVisibility(View.GONE); text_view.setVisibility(View.GONE);
} }
public static void start_sandbox(int pipe_fd) {
Log.w("tildefriends", "starting service with fd: " + pipe_fd);
Intent intent = new Intent(s_activity, TildeFriendsSandboxService.class);
s_activity.service_connection = new ServiceConnection() {
@Override
public void onBindingDied(ComponentName name) {
Log.w("tildefriends", "onBindingDied");
}
@Override
public void onNullBinding(ComponentName name) {
Log.w("tildefriends", "onNullBinding");
}
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.w("tildefriends", "onServiceConnected");
Parcel data = Parcel.obtain();
ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(pipe_fd);
data.writeParcelable(pfd, 0);
try {
binder.transact(TildeFriendsSandboxService.START_CALL, data, null, IBinder.FLAG_ONEWAY);
} catch (RemoteException e) {
Log.w("tildefriends", "RemoteException");
} finally {
data.recycle();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.w("tildefriends", "onServiceDisconnected");
}
};
s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE);
}
public static void stop_sandbox() {
Log.w("tildefriends", "stop_sandbox");
if (s_activity.service_connection != null) {
s_activity.unbindService(s_activity.service_connection);
s_activity.service_connection = null;
}
}
@SuppressWarnings("deprecation")
private void set_database_path()
{
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
web_view.getSettings().setDatabasePath(getDatabasePath("webview").getPath());
}
}
} }

View File

@ -1,59 +0,0 @@
package com.unprompted.tildefriends;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.util.Log;
public class TildeFriendsSandboxService extends Service {
public static final int START_CALL = IBinder.FIRST_CALL_TRANSACTION;
Thread thread;
public int onStartCommand(Intent intent, int flags, int start_id) {
Log.w("tildefriends", "TildeFriendsSandboxService: onStartCommand");
return super.onStartCommand(intent, flags, start_id);
}
public void onDestroy() {
Log.w("tildefriends", "TildeFriendsSandboxService: onDestroy");
super.onDestroy();
}
private void start_thread(int pipe_fd) {
thread = new Thread(new Runnable() {
@Override
public void run() {
Log.w("tildefriends", "Calling tf_sandbox_main.");
int result = TildeFriendsActivity.tf_sandbox_main(pipe_fd);
Log.w("tildefriends", "tf_sandbox_main returned " + result + ".");
}
});
thread.start();
}
@Override
public IBinder onBind(Intent intent) {
return new Binder() {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
if (code == START_CALL) {
ParcelFileDescriptor pfd = data.readParcelable(ParcelFileDescriptor.class.getClassLoader(), ParcelFileDescriptor.class);
if (pfd != null) {
Log.w("tildefriends", "fd is " + pfd.getFd());
start_thread(pfd.detachFd());
try {
pfd.close();
} catch (java.io.IOException e) {
}
}
return true;
}
return false;
}
};
}
}

View File

@ -2,6 +2,7 @@ package com.unprompted.tildefriends;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
public class TildeFriendsWebView extends android.webkit.WebView { public class TildeFriendsWebView extends android.webkit.WebView {
boolean overscrolledY = false; boolean overscrolledY = false;

View File

@ -1,55 +1,78 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="65dp" android:width="72dp"
android:height="65dp" android:height="72dp"
android:viewportWidth="61" android:viewportWidth="72"
android:viewportHeight="65"> android:viewportHeight="72">
<path <path
android:pathData="M6,0h49a8,8 45,0 1,8 8v49a8,8 135,0 1,-8 8H6a8,8 45,0 1,-8 -8V8a8,8 135,0 1,8 -8Z" android:pathData="M36,36m-23,0a23,23 0,1 1,46 0a23,23 0,1 1,-46 0"
android:strokeWidth=".712717" android:fillColor="#FCEA2B"/>
android:fillColor="#0af"
android:fillAlpha="1"/>
<path <path
android:pathData="m1.6762,36.6891v-4.0039q2.0703,-2.3438 5.4297,-2.3438 1.1719,0 2.4609,0.3516 1.2891,0.332 3.6719,1.3477 1.3477,0.5664 2.0117,0.7422 0.6836,0.1758 1.3672,0.1758 1.2695,0 2.6172,-0.7617 1.3672,-0.7617 2.4219,-1.9141v4.1406q-1.25,1.1719 -2.5391,1.6992 -1.2695,0.5273 -2.8711,0.5273 -1.1719,0 -2.2461,-0.2734 -1.0547,-0.2734 -3.3789,-1.3086 -2.3047,-1.0352 -3.8477,-1.0352 -1.25,0 -2.3633,0.5469 -1.0938,0.5273 -2.7344,2.1094z" android:pathData="M45.331,38.564c3.963,0 7.178,-2.862 7.178,-6.389c0,-1.765 0.447,-3.529 -0.852,-4.685s-4.345,-1.704 -6.326,-1.704c-2.357,0 -5.143,0.143 -6.451,1.704c-0.893,1.065 -0.727,3.253 -0.727,4.685C38.153,35.702 41.366,38.564 45.331,38.564z"
android:fillColor="#000000"/> android:fillColor="#3F3F3F"/>
<path <path
android:pathData="M42.4653,32.2273m-16.7723,0a16.7723,16.7723 0,1 1,33.5446 0a16.7723,16.7723 0,1 1,-33.5446 0" android:pathData="M25.738,38.564c3.963,0 7.178,-2.862 7.178,-6.389c0,-1.765 0.447,-3.529 -0.852,-4.685s-4.345,-1.704 -6.326,-1.704c-2.357,0 -5.143,0.143 -6.451,1.704c-0.893,1.065 -0.727,3.253 -0.727,4.685C18.56,35.702 21.773,38.564 25.738,38.564z"
android:fillColor="#fcea2b"/> android:fillColor="#3F3F3F"/>
<path <path
android:pathData="M49.2697,34.097c2.8899,0 5.2344,-2.0871 5.2344,-4.6591 0,-1.2871 0.3267,-2.5742 -0.6213,-3.4164 -0.9473,-0.843 -3.1685,-1.2426 -4.6131,-1.2426 -1.7188,0 -3.7504,0.1043 -4.7043,1.2426 -0.6519,0.7766 -0.5302,2.3722 -0.5302,3.4164 0,2.572 2.343,4.6591 5.2344,4.6591zM34.9819,34.097c2.8899,0 5.2351,-2.0871 5.2351,-4.6591 0,-1.2871 0.326,-2.5742 -0.6213,-3.4164 -0.948,-0.843 -3.1685,-1.2426 -4.6138,-1.2426 -1.7181,0 -3.7497,0.1043 -4.7043,1.2426 -0.6512,0.7766 -0.5302,2.3722 -0.5302,3.4164 0,2.572 2.343,4.6591 5.2344,4.6591z" android:pathData="M35.887,36.056m-23,0a23,23 0,1 1,46 0a23,23 0,1 1,-46 0"
android:fillColor="#3f3f3f"/>
<path
android:pathData="M42.3829,32.2681m-16.7723,0a16.7723,16.7723 0,1 1,33.5446 0a16.7723,16.7723 0,1 1,-33.5446 0"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="2" android:strokeWidth="2"
android:fillColor="#00000000" android:fillColor="#00000000"
android:strokeColor="#000" android:strokeColor="#000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
<path <path
android:pathData="M49.5403,38.6897c-4.794,2.5705 -10.242,2.6675 -14.3148,0M29.983,28.1903s-0.695,6.2349 5.0025,5.774c1.9106,-0.1546 5.7004,-0.474 5.7369,-6.0832 0.0036,-0.509 -0.0051,-1.1668 -0.5907,-1.9179 -0.7766,-0.9969 -2.6048,-1.4373 -7.2522,-1.037 0,0 -2.5129,-0.0729 -2.8965,3.264z" android:pathData="M45.702,44.862c-6.574,3.525 -14.045,3.658 -19.629,0"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="2" android:strokeWidth="2"
android:fillColor="#00000000" android:fillColor="#00000000"
android:strokeColor="#000" android:strokeColor="#000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
<path <path
android:pathData="m30.0341,27.8016 l-0.3158,-2.459 2.7951,-0.3843M54.6733,28.1903s0.695,6.2349 -5.0025,5.774c-1.9106,-0.1546 -5.7004,-0.474 -5.7376,-6.0832 -0.0029,-0.509 0.0058,-1.1668 0.5914,-1.9179 0.7766,-0.9969 2.6048,-1.4373 7.2522,-1.037 0,0 2.5129,-0.0729 2.8965,3.264z" android:pathData="M18.883,30.464c0,0 -0.953,8.551 6.861,7.918c2.62,-0.212 7.816,-0.651 7.867,-8.343c0.005,-0.698 -0.008,-1.599 -0.811,-2.63c-1.065,-1.367 -3.572,-1.971 -9.945,-1.422C22.855,25.988 19.409,25.889 18.883,30.464z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="2" android:strokeWidth="2"
android:fillColor="#00000000" android:fillColor="#00000000"
android:strokeColor="#000" android:strokeColor="#000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
<path <path
android:pathData="M39.1874,25.2383s3.0073,1.8479 6.3129,0M40.6685,28.813s1.6058,-2.7353 3.3078,0M54.6172,27.803l0.3158,-2.4582 -2.7951,-0.385" android:pathData="M18.953,29.931l-0.433,-3.371l3.833,-0.528"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="2" android:strokeWidth="2"
android:fillColor="#00000000" android:fillColor="#00000000"
android:strokeColor="#000" android:strokeColor="#000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
<path <path
android:pathData="M40.974,27.8716s1.309,-2.7346 2.6974,0" android:pathData="M52.741,30.464c0,0 0.953,8.551 -6.861,7.918c-2.62,-0.212 -7.816,-0.651 -7.867,-8.343c-0.005,-0.698 0.008,-1.599 0.811,-2.63c1.065,-1.367 3.572,-1.971 9.945,-1.422C48.769,25.988 52.215,25.889 52.741,30.464z"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="2" android:strokeWidth="2"
android:fillColor="#00000000" android:fillColor="#00000000"
android:strokeColor="#000" android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M31.505,26.416c0,0 4.124,2.534 8.657,0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M33.536,31.318c0,0 2.202,-3.751 4.536,0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M52.664,29.933l0.433,-3.371l-3.833,-0.528"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M33.955,30.027c0,0 1.795,-3.751 3.699,0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
</vector> </vector>

View File

@ -4,7 +4,6 @@
#include "mem.h" #include "mem.h"
#include "ssb.h" #include "ssb.h"
#include "task.h" #include "task.h"
#include "util.js.h"
#include "sqlite3.h" #include "sqlite3.h"
@ -51,7 +50,7 @@ void tf_database_register(JSContext* context)
JS_SetPropertyStr(context, global, "Database", constructor); JS_SetPropertyStr(context, global, "Database", constructor);
JSValue databases = JS_NewObject(context); JSValue databases = JS_NewObject(context);
JS_SetPropertyStr(context, global, "databases", databases); JS_SetPropertyStr(context, global, "databases", databases);
JS_SetPropertyStr(context, databases, "list", JS_NewCFunctionData(context, _databases_list, 1, 0, 0, NULL)); JS_SetPropertyStr(context, databases, "list", JS_NewCFunctionData(context, _databases_list, 0, 0, 0, NULL));
JS_FreeValue(context, global); JS_FreeValue(context, global);
} }
@ -92,455 +91,159 @@ static void _database_finalizer(JSRuntime* runtime, JSValue value)
--_database_count; --_database_count;
} }
typedef struct _database_get_t
{
const char* id;
const char* key;
size_t key_length;
char* out_value;
size_t out_length;
JSValue promise[2];
} database_get_t;
static void _database_get_work(tf_ssb_t* ssb, void* user_data)
{
database_get_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
{
size_t length = sqlite3_column_bytes(statement, 0);
char* data = tf_malloc(length + 1);
memcpy(data, sqlite3_column_text(statement, 0), length);
data[length] = '\0';
work->out_value = data;
work->out_length = length;
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _database_get_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_get_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_UNDEFINED;
if (work->out_value)
{
result = JS_NewStringLen(context, work->out_value, work->out_length);
}
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(work->out_value);
tf_free(work);
}
static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{ {
JSValue result = JS_UNDEFINED; JSValue entry = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id); database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database) if (database)
{ {
tf_ssb_t* ssb = tf_task_get_ssb(database->task); tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3_stmt* statement;
size_t length; sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* key = JS_ToCStringLen(context, &length, argv[0]); if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
database_get_t* work = tf_malloc(sizeof(database_get_t) + strlen(database->id) + 1 + length + 1);
*work = (database_get_t) {
.id = (const char*)(work + 1),
.key = (const char*)(work + 1) + strlen(database->id) + 1,
.key_length = length,
};
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
memcpy((char*)work->key, key, length + 1);
JS_FreeCString(context, key);
tf_ssb_run_work(ssb, _database_get_work, _database_get_after_work, work);
result = JS_NewPromiseCapability(context, work->promise);
}
return result;
}
typedef struct _database_set_t
{
const char* id;
const char* key;
size_t key_length;
const char* value;
size_t value_length;
bool result;
JSValue promise[2];
} database_set_t;
static void _database_set_work(tf_ssb_t* ssb, void* user_data)
{
database_set_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
{ {
work->result = true; size_t length;
const char* keyString = JS_ToCStringLen(context, &length, argv[0]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
{
entry = JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0));
}
JS_FreeCString(context, keyString);
sqlite3_finalize(statement);
} }
sqlite3_finalize(statement); tf_ssb_release_db_reader(ssb, db);
} }
tf_ssb_release_db_writer(ssb, db); return entry;
}
static void _database_set_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_set_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = work->result ? JS_TRUE : JS_UNDEFINED;
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(work);
} }
static JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) static JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{ {
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id); database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database) if (database)
{ {
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task); tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
size_t key_length = 0; if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
size_t value_length = 0;
const char* value = JS_ToCStringLen(context, &value_length, argv[1]);
database_set_t* work = tf_malloc(sizeof(database_set_t) + strlen(database->id) + 1 + key_length + 1 + value_length + 1);
*work = (database_set_t) {
.id = (const char*)(work + 1),
.key = (const char*)(work + 1) + strlen(database->id) + 1,
.value = (const char*)(work + 1) + strlen(database->id) + 1 + key_length + 1,
.key_length = key_length,
.value_length = value_length,
};
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
memcpy((char*)work->key, key, key_length + 1);
memcpy((char*)work->value, value, value_length + 1);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _database_set_work, _database_set_after_work, work);
JS_FreeCString(context, key);
JS_FreeCString(context, value);
}
return result;
}
typedef struct _database_exchange_t
{
const char* id;
const char* key;
size_t key_length;
const char* expected;
size_t expected_length;
const char* value;
size_t value_length;
bool result;
JSValue promise[2];
} database_exchange_t;
static void _database_exchange_work(tf_ssb_t* ssb, void* user_data)
{
database_exchange_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
if (!work->expected)
{
if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK && size_t keyLength;
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE) const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
size_t valueLength;
const char* valueString = JS_ToCStringLen(context, &valueLength, argv[1]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, valueString, valueLength, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK)
{ {
work->result = sqlite3_changes(db) != 0;
} }
JS_FreeCString(context, keyString);
JS_FreeCString(context, valueString);
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
tf_ssb_release_db_writer(ssb, db);
} }
else if (sqlite3_prepare(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK) return JS_UNDEFINED;
{
if (sqlite3_bind_text(statement, 1, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->id, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 4, work->expected, work->expected_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
{
work->result = sqlite3_changes(db) != 0;
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
static void _database_exchange_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_exchange_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = work->result ? JS_TRUE : JS_UNDEFINED;
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);
JS_FreeCString(context, work->expected);
JS_FreeCString(context, work->value);
tf_free((char*)work->id);
tf_free(work);
} }
static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{ {
JSValue result = JS_UNDEFINED; JSValue exchanged = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id); database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database) if (database)
{ {
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task); tf_ssb_t* ssb = tf_task_get_ssb(database->task);
database_exchange_t* work = tf_malloc(sizeof(database_exchange_t)); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
*work = (database_exchange_t) { if (JS_IsNull(argv[1]) || JS_IsUndefined(argv[1]))
.id = tf_strdup(database->id),
};
work->key = JS_ToCStringLen(context, &work->key_length, argv[0]);
work->expected = (JS_IsNull(argv[1]) || JS_IsUndefined(argv[1])) ? NULL : JS_ToCStringLen(context, &work->expected_length, argv[1]);
work->value = JS_ToCStringLen(context, &work->value_length, argv[2]);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _database_exchange_work, _database_exchange_after_work, work);
}
return result;
}
typedef struct _database_remove_t
{
const char* id;
size_t key_length;
JSValue promise[2];
char key[];
} database_remove_t;
static void _database_remove_work(tf_ssb_t* ssb, void* user_data)
{
database_remove_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_OK)
{ {
if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{
size_t key_length;
size_t set_length;
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
const char* set = JS_ToCStringLen(context, &set_length, argv[2]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, set, set_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
{
exchanged = sqlite3_changes(db) != 0 ? JS_TRUE : JS_FALSE;
}
JS_FreeCString(context, key);
JS_FreeCString(context, set);
sqlite3_finalize(statement);
}
} }
sqlite3_finalize(statement); else if (sqlite3_prepare(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK)
{
size_t key_length;
size_t expected_length;
size_t set_length;
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
const char* expected = JS_ToCStringLen(context, &expected_length, argv[1]);
const char* set = JS_ToCStringLen(context, &set_length, argv[2]);
if (sqlite3_bind_text(statement, 1, set, set_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, database->id, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, key, key_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 4, expected, expected_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE)
{
exchanged = sqlite3_changes(db) != 0 ? JS_TRUE : JS_FALSE;
}
JS_FreeCString(context, key);
JS_FreeCString(context, expected);
JS_FreeCString(context, set);
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
} }
tf_ssb_release_db_writer(ssb, db); return exchanged;
}
static void _database_remove_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_remove_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_UNDEFINED;
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((char*)work->id);
tf_free(work);
} }
static JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) static JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{ {
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id); database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database) if (database)
{ {
size_t key_length = 0; sqlite3_stmt* statement;
const char* key = JS_ToCStringLen(context, &key_length, argv[0]); tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
database_remove_t* work = tf_malloc(sizeof(database_remove_t) + key_length + 1); if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
*work = (database_remove_t) {
.id = tf_strdup(database->id),
.key_length = key_length,
};
memcpy(work->key, key, key_length + 1);
JS_FreeCString(context, key);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(tf_task_get_ssb(database->task), _database_remove_work, _database_remove_after_work, work);
}
return result;
}
typedef struct _database_get_all_t
{
const char* id;
const char* key;
size_t key_length;
char** out_values;
size_t* out_lengths;
int out_values_length;
JSValue promise[2];
} database_get_all_t;
static void _database_get_all_work(tf_ssb_t* ssb, void* user_data)
{
database_get_all_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK)
{ {
while (sqlite3_step(statement) == SQLITE_ROW) size_t keyLength;
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_OK)
{ {
work->out_values = tf_resize_vec(work->out_values, sizeof(char*) * (work->out_values_length + 1));
work->out_lengths = tf_resize_vec(work->out_lengths, sizeof(size_t) * (work->out_values_length + 1));
size_t length = sqlite3_column_bytes(statement, 0);
char* data = tf_malloc(length + 1);
memcpy(data, sqlite3_column_text(statement, 0), length);
data[length] = '\0';
work->out_values[work->out_values_length] = data;
work->out_lengths[work->out_values_length] = length;
work->out_values_length++;
} }
JS_FreeCString(context, keyString);
sqlite3_finalize(statement);
} }
sqlite3_finalize(statement); tf_ssb_release_db_writer(ssb, db);
} }
tf_ssb_release_db_reader(ssb, db); return JS_UNDEFINED;
}
static void _database_get_all_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_get_all_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_NewArray(context);
;
for (int i = 0; i < work->out_values_length; i++)
{
JS_SetPropertyUint32(context, result, i, JS_NewStringLen(context, work->out_values[i], work->out_lengths[i]));
tf_free((void*)work->out_values[i]);
}
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(work->out_values);
tf_free(work->out_lengths);
tf_free(work);
} }
static JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) static JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{ {
JSValue result = JS_UNDEFINED; JSValue array = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id); database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database) if (database)
{ {
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task); tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
size_t length; if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ?1", -1, &statement, NULL) == SQLITE_OK)
const char* key = JS_ToCStringLen(context, &length, argv[0]);
database_get_all_t* work = tf_malloc(sizeof(database_get_all_t) + strlen(database->id) + 1 + length + 1);
*work = (database_get_all_t) {
.id = (const char*)(work + 1),
.key = (const char*)(work + 1) + strlen(database->id) + 1,
.key_length = length,
};
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
memcpy((char*)work->key, key, length + 1);
JS_FreeCString(context, key);
tf_ssb_run_work(ssb, _database_get_all_work, _database_get_all_after_work, work);
result = JS_NewPromiseCapability(context, work->promise);
}
return result;
}
typedef struct _key_value_t
{
char* key;
size_t key_length;
char* value;
size_t value_length;
} key_value_t;
typedef struct _database_get_like_t
{
const char* id;
const char* pattern;
key_value_t* results;
int results_length;
JSValue promise[2];
} database_get_like_t;
static void _database_get_like_work(tf_ssb_t* ssb, void* user_data)
{
database_get_like_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->pattern, -1, NULL) == SQLITE_OK)
{ {
while (sqlite3_step(statement) == SQLITE_ROW) if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK)
{ {
work->results = tf_resize_vec(work->results, sizeof(key_value_t) * (work->results_length + 1)); array = JS_NewArray(context);
key_value_t* out = &work->results[work->results_length]; uint32_t index = 0;
*out = (key_value_t) { while (sqlite3_step(statement) == SQLITE_ROW)
.key_length = sqlite3_column_bytes(statement, 0), {
.value_length = sqlite3_column_bytes(statement, 1), JS_SetPropertyUint32(context, array, index++, JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0)));
}; }
out->key = tf_malloc(out->key_length + 1);
memcpy(out->key, sqlite3_column_text(statement, 0), out->key_length + 1);
out->value = tf_malloc(out->value_length + 1);
memcpy(out->value, sqlite3_column_text(statement, 1), out->value_length + 1);
work->results_length++;
} }
sqlite3_finalize(statement);
} }
sqlite3_finalize(statement); tf_ssb_release_db_reader(ssb, db);
} }
tf_ssb_release_db_reader(ssb, db); return array;
}
static void _database_get_like_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_get_like_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_NewObject(context);
for (int i = 0; i < work->results_length; i++)
{
const key_value_t* row = &work->results[i];
JS_SetPropertyStr(context, result, row->key, JS_NewStringLen(context, row->value, row->value_length));
tf_free(row->key);
tf_free(row->value);
}
JS_FreeCString(context, work->pattern);
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->id);
tf_free(work->results);
tf_free(work);
} }
static JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) static JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
@ -549,77 +252,51 @@ static JSValue _database_get_like(JSContext* context, JSValueConst this_val, int
database_t* database = JS_GetOpaque(this_val, _database_class_id); database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database) if (database)
{ {
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task); tf_ssb_t* ssb = tf_task_get_ssb(database->task);
database_get_like_t* work = tf_malloc(sizeof(database_get_like_t)); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
*work = (database_get_like_t) { if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
.id = tf_strdup(database->id), {
.pattern = JS_ToCString(context, argv[0]), const char* pattern = JS_ToCString(context, argv[0]);
}; if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, pattern, -1, NULL) == SQLITE_OK)
result = JS_NewPromiseCapability(context, work->promise); {
tf_ssb_run_work(ssb, _database_get_like_work, _database_get_like_after_work, work); result = JS_NewObject(context);
while (sqlite3_step(statement) == SQLITE_ROW)
{
JS_SetPropertyStr(context, result, (const char*)sqlite3_column_text(statement, 0),
JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 1), sqlite3_column_bytes(statement, 1)));
}
}
JS_FreeCString(context, pattern);
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
} }
return result; return result;
} }
typedef struct _databases_list_t
{
const char* pattern;
char** names;
int names_length;
JSValue promise[2];
} databases_list_t;
static void _databases_list_work(tf_ssb_t* ssb, void* user_data)
{
databases_list_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
work->names = tf_resize_vec(work->names, sizeof(char*) * (work->names_length + 1));
work->names[work->names_length] = tf_strdup((const char*)sqlite3_column_text(statement, 0));
work->names_length++;
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _databases_list_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
databases_list_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_NewArray(context);
for (int i = 0; i < work->names_length; i++)
{
JS_SetPropertyUint32(context, result, i, JS_NewString(context, work->names[i]));
tf_free(work->names[i]);
}
JS_FreeCString(context, work->pattern);
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(work->names);
tf_free(work);
}
static JSValue _databases_list(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data) static JSValue _databases_list(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{ {
tf_task_t* task = tf_task_get(context); tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task); tf_ssb_t* ssb = tf_task_get_ssb(task);
databases_list_t* work = tf_malloc(sizeof(databases_list_t)); sqlite3* db = tf_ssb_acquire_db_reader(ssb);
*work = (databases_list_t) { JSValue array = JS_UNDEFINED;
.pattern = JS_ToCString(context, argv[0]), sqlite3_stmt* statement;
}; if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
JSValue result = JS_NewPromiseCapability(context, work->promise); {
tf_ssb_run_work(ssb, _databases_list_work, _databases_list_after_work, work); const char* pattern = JS_ToCString(context, argv[0]);
return result; if (sqlite3_bind_text(statement, 1, pattern, -1, NULL) == SQLITE_OK)
{
array = JS_NewArray(context);
uint32_t index = 0;
while (sqlite3_step(statement) == SQLITE_ROW)
{
JS_SetPropertyUint32(context, array, index++, JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0)));
}
}
JS_FreeCString(context, pattern);
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return array;
} }

View File

@ -1019,7 +1019,7 @@ void tf_http_request_unref(tf_http_request_t* request)
tf_free(request); tf_free(request);
} }
if (connection && --connection->ref_count == 0) if (--connection->ref_count == 0)
{ {
if (connection->http->is_shutting_down) if (connection->http->is_shutting_down)
{ {
@ -1071,7 +1071,7 @@ void* tf_http_get_user_data(tf_http_t* http)
const char* tf_http_get_cookie(const char* cookie_header, const char* name) const char* tf_http_get_cookie(const char* cookie_header, const char* name)
{ {
if (!cookie_header || !name) if (!cookie_header)
{ {
return NULL; return NULL;
} }

View File

@ -37,9 +37,9 @@
const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000; const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000;
static JSValue _authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char* jwt); static JSValue _authenticate_jwt(JSContext* context, const char* jwt);
static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
static const char* _make_session_jwt(JSContext* context, tf_ssb_t* ssb, const char* name); static const char* _make_session_jwt(tf_ssb_t* ssb, const char* name);
static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie); static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie);
static JSClassID _httpd_class_id; static JSClassID _httpd_class_id;
@ -330,11 +330,11 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va
tf_ssb_t* ssb = tf_task_get_ssb(tf_task_get(context)); tf_ssb_t* ssb = tf_task_get_ssb(tf_task_get(context));
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session"); const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
JSValue jwt = _authenticate_jwt(ssb, context, session); JSValue jwt = _authenticate_jwt(context, session);
tf_free((void*)session); tf_free((void*)session);
JSValue name = !JS_IsUndefined(jwt) ? JS_GetPropertyStr(context, jwt, "name") : JS_UNDEFINED; JSValue name = !JS_IsUndefined(jwt) ? JS_GetPropertyStr(context, jwt, "name") : JS_UNDEFINED;
const char* name_string = !JS_IsUndefined(name) ? JS_ToCString(context, name) : NULL; const char* name_string = !JS_IsUndefined(name) ? JS_ToCString(context, name) : NULL;
const char* session_token = _make_session_jwt(tf_ssb_get_context(ssb), ssb, name_string); const char* session_token = _make_session_jwt(ssb, name_string);
const char* cookie = _make_set_session_cookie_header(request, session_token); const char* cookie = _make_set_session_cookie_header(request, session_token);
tf_free((void*)session_token); tf_free((void*)session_token);
JS_FreeCString(context, name_string); JS_FreeCString(context, name_string);
@ -446,55 +446,6 @@ static JSValue _httpd_set_http_redirect(JSContext* context, JSValueConst this_va
return JS_UNDEFINED; return JS_UNDEFINED;
} }
typedef struct _auth_query_work_t
{
const char* settings;
JSValue entry;
JSValue result;
JSValue promise[2];
} auth_query_work_t;
static void _httpd_auth_query_work(tf_ssb_t* ssb, void* user_data)
{
auth_query_work_t* work = user_data;
work->settings = tf_ssb_db_get_property(ssb, "core", "settings");
}
static void _httpd_auth_query_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
auth_query_work_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue name = JS_GetPropertyStr(context, work->entry, "name");
const char* name_string = JS_ToCString(context, name);
JSValue settings_value = work->settings ? JS_ParseJSON(context, work->settings, strlen(work->settings), NULL) : JS_UNDEFINED;
JSValue out_permissions = JS_NewObject(context);
JS_SetPropertyStr(context, work->result, "permissions", out_permissions);
JSValue permissions = !JS_IsUndefined(settings_value) ? JS_GetPropertyStr(context, settings_value, "permissions") : JS_UNDEFINED;
JSValue user_permissions = !JS_IsUndefined(permissions) ? JS_GetPropertyStr(context, permissions, name_string) : JS_UNDEFINED;
int length = !JS_IsUndefined(user_permissions) ? tf_util_get_length(context, user_permissions) : 0;
for (int i = 0; i < length; i++)
{
JSValue permission = JS_GetPropertyUint32(context, user_permissions, i);
const char* permission_string = JS_ToCString(context, permission);
JS_SetPropertyStr(context, out_permissions, permission_string, JS_TRUE);
JS_FreeCString(context, permission_string);
JS_FreeValue(context, permission);
}
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &work->result);
JS_FreeValue(context, work->result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
JS_FreeValue(context, user_permissions);
JS_FreeValue(context, permissions);
JS_FreeValue(context, settings_value);
tf_free((void*)work->settings);
JS_FreeCString(context, name_string);
JS_FreeValue(context, name);
tf_free(work);
}
static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{ {
tf_task_t* task = tf_task_get(context); tf_task_t* task = tf_task_get(context);
@ -508,7 +459,7 @@ static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int
JSValue cookie = JS_GetPropertyStr(context, headers, "cookie"); JSValue cookie = JS_GetPropertyStr(context, headers, "cookie");
const char* cookie_string = JS_ToCString(context, cookie); const char* cookie_string = JS_ToCString(context, cookie);
const char* session = tf_http_get_cookie(cookie_string, "session"); const char* session = tf_http_get_cookie(cookie_string, "session");
JSValue entry = _authenticate_jwt(ssb, context, session); JSValue entry = _authenticate_jwt(context, session);
tf_free((void*)session); tf_free((void*)session);
JS_FreeCString(context, cookie_string); JS_FreeCString(context, cookie_string);
JS_FreeValue(context, cookie); JS_FreeValue(context, cookie);
@ -516,165 +467,37 @@ static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int
JSValue result = JS_UNDEFINED; JSValue result = JS_UNDEFINED;
if (!JS_IsUndefined(entry)) if (!JS_IsUndefined(entry))
{ {
JSValue value = JS_NewObject(context); result = JS_NewObject(context);
JS_SetPropertyStr(context, value, "session", entry); JS_SetPropertyStr(context, result, "session", entry);
JSValue out_permissions = JS_NewObject(context);
JS_SetPropertyStr(context, result, "permissions", out_permissions);
auth_query_work_t* work = tf_malloc(sizeof(auth_query_work_t)); JSValue name = JS_GetPropertyStr(context, entry, "name");
*work = (auth_query_work_t) { const char* name_string = JS_ToCString(context, name);
.entry = entry,
.result = value, const char* settings = tf_ssb_db_get_property(ssb, "core", "settings");
}; JSValue settings_value = settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
result = JS_NewPromiseCapability(context, work->promise); JSValue permissions = !JS_IsUndefined(settings_value) ? JS_GetPropertyStr(context, settings_value, "permissions") : JS_UNDEFINED;
tf_ssb_run_work(ssb, _httpd_auth_query_work, _httpd_auth_query_after_work, work); JSValue user_permissions = !JS_IsUndefined(permissions) ? JS_GetPropertyStr(context, permissions, name_string) : JS_UNDEFINED;
int length = !JS_IsUndefined(user_permissions) ? tf_util_get_length(context, user_permissions) : 0;
for (int i = 0; i < length; i++)
{
JSValue permission = JS_GetPropertyUint32(context, user_permissions, i);
const char* permission_string = JS_ToCString(context, permission);
JS_SetPropertyStr(context, out_permissions, permission_string, JS_TRUE);
JS_FreeCString(context, permission_string);
JS_FreeValue(context, permission);
}
JS_FreeValue(context, user_permissions);
JS_FreeValue(context, permissions);
JS_FreeValue(context, settings_value);
tf_free((void*)settings);
JS_FreeCString(context, name_string);
JS_FreeValue(context, name);
} }
return result; return result;
} }
typedef struct _magic_bytes_t
{
const char* type;
uint8_t bytes[12];
uint8_t ignore[12];
} magic_bytes_t;
static bool _magic_bytes_match(const magic_bytes_t* magic, const uint8_t* actual, size_t size)
{
if (size < sizeof(magic->bytes))
{
return false;
}
int length = (int)tf_min(sizeof(magic->bytes), size);
for (int i = 0; i < length; i++)
{
if ((magic->bytes[i] & ~magic->ignore[i]) != (actual[i] & ~magic->ignore[i]))
{
return false;
}
}
return true;
}
static JSValue _httpd_mime_type_from_magic_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
size_t size = 0;
uint8_t* bytes = tf_util_try_get_array_buffer(context, &size, argv[0]);
if (bytes)
{
const magic_bytes_t k_magic_bytes[] = {
{
.type = "image/jpeg",
.bytes = { 0xff, 0xd8, 0xff, 0xdb },
},
{
.type = "image/jpeg",
.bytes = { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01 },
},
{
.type = "image/jpeg",
.bytes = { 0xff, 0xd8, 0xff, 0xee },
},
{
.type = "image/jpeg",
.bytes = { 0xff, 0xd8, 0xff, 0xe1, 0x00, 0x00, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 },
.ignore = { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "image/png",
.bytes = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a },
},
{
.type = "image/gif",
.bytes = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 },
},
{
.type = "image/gif",
.bytes = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 },
},
{
.type = "image/webp",
.bytes = { 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50 },
.ignore = { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "image/svg+xml",
.bytes = { 0x3c, 0x73, 0x76, 0x67 },
},
{
.type = "audio/mpeg",
.bytes = { 0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32 },
.ignore = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "video/mp4",
.bytes = { 0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d },
.ignore = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "video/mp4",
.bytes = { 0x00, 0x00, 0x00, 0x00, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32 },
.ignore = { 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
},
{
.type = "audio/midi",
.bytes = { 0x4d, 0x54, 0x68, 0x64 },
},
};
for (int i = 0; i < tf_countof(k_magic_bytes); i++)
{
if (_magic_bytes_match(&k_magic_bytes[i], bytes, size))
{
result = JS_NewString(context, k_magic_bytes[i].type);
break;
}
}
}
return result;
}
static const char* _ext_to_content_type(const char* ext, bool use_fallback)
{
if (ext)
{
typedef struct _ext_type_t
{
const char* ext;
const char* type;
} ext_type_t;
const ext_type_t k_types[] = {
{ .ext = ".html", .type = "text/html; charset=UTF-8" },
{ .ext = ".js", .type = "text/javascript; charset=UTF-8" },
{ .ext = ".mjs", .type = "text/javascript; charset=UTF-8" },
{ .ext = ".css", .type = "text/css; charset=UTF-8" },
{ .ext = ".png", .type = "image/png" },
{ .ext = ".json", .type = "application/json" },
{ .ext = ".map", .type = "application/json" },
{ .ext = ".svg", .type = "image/svg+xml" },
};
for (int i = 0; i < tf_countof(k_types); i++)
{
if (strcmp(ext, k_types[i].ext) == 0)
{
return k_types[i].type;
}
}
}
return use_fallback ? "application/binary" : NULL;
}
static JSValue _httpd_mime_type_from_extension(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
const char* name = JS_ToCString(context, argv[0]);
const char* type = _ext_to_content_type(strrchr(name, '.'), false);
JS_FreeCString(context, name);
return type ? JS_NewString(context, type) : JS_UNDEFINED;
}
static void _httpd_finalizer(JSRuntime* runtime, JSValue value) static void _httpd_finalizer(JSRuntime* runtime, JSValue value)
{ {
tf_http_t* http = JS_GetOpaque(value, _httpd_class_id); tf_http_t* http = JS_GetOpaque(value, _httpd_class_id);
@ -804,6 +627,30 @@ typedef struct _http_file_t
char etag[512]; char etag[512];
} http_file_t; } http_file_t;
static const char* _ext_to_content_type(const char* ext)
{
if (ext)
{
if (strcmp(ext, ".html") == 0)
{
return "text/html; charset=UTF-8";
}
else if (strcmp(ext, ".js") == 0 || strcmp(ext, ".mjs") == 0)
{
return "text/javascript; charset=UTF-8";
}
else if (strcmp(ext, ".css") == 0)
{
return "text/css; charset=UTF-8";
}
else if (strcmp(ext, ".png") == 0)
{
return "image/png";
}
}
return "application/binary";
}
static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data) static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
{ {
http_file_t* file = user_data; http_file_t* file = user_data;
@ -812,7 +659,7 @@ static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int r
{ {
if (strcmp(path, "core/tfrpc.js") == 0) if (strcmp(path, "core/tfrpc.js") == 0)
{ {
const char* content_type = _ext_to_content_type(strrchr(path, '.'), true); const char* content_type = _ext_to_content_type(strrchr(path, '.'));
const char* headers[] = { const char* headers[] = {
"Content-Type", "Content-Type",
content_type, content_type,
@ -825,7 +672,7 @@ static void _httpd_endpoint_static_read(tf_task_t* task, const char* path, int r
} }
else else
{ {
const char* content_type = _ext_to_content_type(strrchr(path, '.'), true); const char* content_type = _ext_to_content_type(strrchr(path, '.'));
const char* headers[] = { const char* headers[] = {
"Content-Type", "Content-Type",
content_type, content_type,
@ -884,7 +731,7 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
const char* k_static_files[] = { const char* k_static_files[] = {
"index.html", "index.html",
"client.js", "client.js",
"tildefriends.svg", "favicon.png",
"jszip.min.js", "jszip.min.js",
"style.css", "style.css",
"tfrpc.js", "tfrpc.js",
@ -905,23 +752,14 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
const char* file_path = NULL; const char* file_path = NULL;
for (int i = 0; i < tf_countof(k_map) && !after; i++) for (int i = 0; i < tf_countof(k_map) && !after; i++)
{ {
const char* next_after = _after(request->path, k_map[i][0]); after = _after(request->path, k_map[i][0]);
if (next_after) file_path = k_map[i][1];
{ is_core = is_core || (after && i == 0);
after = next_after;
file_path = k_map[i][1];
is_core = after && i == 0;
}
} }
if ((!after || !*after) && request->path[strlen(request->path) - 1] == '/') if (strcmp(request->path, "/speedscope/") == 0)
{ {
after = "index.html"; after = "index.html";
if (!file_path)
{
file_path = "core/";
is_core = true;
}
} }
if (!after || strstr(after, "..")) if (!after || strstr(after, ".."))
@ -1103,17 +941,13 @@ const char* _form_data_get(const char** form_data, const char* key)
typedef struct _login_request_t typedef struct _login_request_t
{ {
tf_http_request_t* request; tf_http_request_t* request;
const char* session_cookie;
JSValue jwt;
const char* name; const char* name;
const char* error; const char* error;
const char* settings;
const char* code_of_conduct; const char* code_of_conduct;
bool have_administrator; bool have_administrator;
bool session_is_new; bool session_is_new;
char location_header[1024];
const char* set_cookie_header;
int pending;
} login_request_t; } login_request_t;
static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie) static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie)
@ -1128,29 +962,18 @@ static const char* _make_set_session_cookie_header(tf_http_request_t* request, c
return cookie; return cookie;
} }
static void _login_release(login_request_t* login)
{
int ref_count = --login->pending;
if (ref_count == 0)
{
tf_free((void*)login->name);
tf_free((void*)login->code_of_conduct);
tf_free((void*)login->set_cookie_header);
tf_free(login);
}
}
static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char* path, int result, const void* data, void* user_data) static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
{ {
login_request_t* login = user_data; login_request_t* login = user_data;
tf_http_request_t* request = login->request; tf_http_request_t* request = login->request;
if (result >= 0) if (result >= 0)
{ {
const char* cookie = _make_set_session_cookie_header(request, login->session_cookie);
const char* headers[] = { const char* headers[] = {
"Content-Type", "Content-Type",
"text/html; charset=utf-8", "text/html; charset=utf-8",
"Set-Cookie", "Set-Cookie",
login->set_cookie_header ? login->set_cookie_header : "", cookie ? cookie : "",
}; };
const char* replace_me = "$AUTH_DATA"; const char* replace_me = "$AUTH_DATA";
const char* auth = strstr(data, replace_me); const char* auth = strstr(data, replace_me);
@ -1184,6 +1007,7 @@ static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char
{ {
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result); tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
} }
tf_free((void*)cookie);
} }
else else
{ {
@ -1191,7 +1015,10 @@ static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload)); tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
} }
tf_http_request_unref(request); tf_http_request_unref(request);
_login_release(login); tf_free((void*)login->name);
tf_free((void*)login->code_of_conduct);
tf_free((void*)login->session_cookie);
tf_free(login);
} }
static bool _string_property_equals(JSContext* context, JSValue object, const char* name, const char* value) static bool _string_property_equals(JSContext* context, JSValue object, const char* name, const char* value)
@ -1204,7 +1031,12 @@ static bool _string_property_equals(JSContext* context, JSValue object, const ch
return equals; return equals;
} }
static JSValue _authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char* jwt) static void _public_key_visit(const char* identity, void* user_data)
{
snprintf(user_data, k_id_base64_len, "%s", identity);
}
static JSValue _authenticate_jwt(JSContext* context, const char* jwt)
{ {
if (!jwt) if (!jwt)
{ {
@ -1245,8 +1077,10 @@ static JSValue _authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char*
return JS_UNDEFINED; return JS_UNDEFINED;
} }
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
char public_key_b64[k_id_base64_len] = { 0 }; char public_key_b64[k_id_base64_len] = { 0 };
tf_ssb_whoami(ssb, public_key_b64, sizeof(public_key_b64)); tf_ssb_db_identity_visit(ssb, ":admin", _public_key_visit, public_key_b64);
const char* payload = jwt + dot[0] + 1; const char* payload = jwt + dot[0] + 1;
size_t payload_length = dot[1] - dot[0] - 1; size_t payload_length = dot[1] - dot[0] - 1;
@ -1305,7 +1139,26 @@ static bool _is_name_valid(const char* name)
return true; return true;
} }
static const char* _make_session_jwt(JSContext* context, tf_ssb_t* ssb, const char* name) static void _visit_auth_identity(const char* identity, void* user_data)
{
if (!*(char*)user_data)
{
snprintf((char*)user_data, k_id_base64_len, "%s", identity);
}
}
static bool _get_auth_private_key(tf_ssb_t* ssb, uint8_t* out_private_key)
{
char id[k_id_base64_len] = { 0 };
tf_ssb_db_identity_visit(ssb, ":admin", _visit_auth_identity, id);
if (*id)
{
return tf_ssb_db_identity_get_private_key(ssb, ":admin", id, out_private_key, crypto_sign_SECRETKEYBYTES);
}
return false;
}
static const char* _make_session_jwt(tf_ssb_t* ssb, const char* name)
{ {
if (!name || !*name) if (!name || !*name)
{ {
@ -1319,6 +1172,7 @@ static const char* _make_session_jwt(JSContext* context, tf_ssb_t* ssb, const ch
char header_base64[256]; char header_base64[256];
sodium_bin2base64(header_base64, sizeof(header_base64), (uint8_t*)header_json, strlen(header_json), sodium_base64_VARIANT_URLSAFE_NO_PADDING); sodium_bin2base64(header_base64, sizeof(header_base64), (uint8_t*)header_json, strlen(header_json), sodium_base64_VARIANT_URLSAFE_NO_PADDING);
JSContext* context = tf_ssb_get_context(ssb);
JSValue payload = JS_NewObject(context); JSValue payload = JS_NewObject(context);
JS_SetPropertyStr(context, payload, "name", JS_NewString(context, name)); JS_SetPropertyStr(context, payload, "name", JS_NewString(context, name));
JS_SetPropertyStr(context, payload, "exp", JS_NewInt64(context, now.tv_sec * 1000 + now.tv_nsec / 1000000LL + k_refresh_interval)); JS_SetPropertyStr(context, payload, "exp", JS_NewInt64(context, now.tv_sec * 1000 + now.tv_nsec / 1000000LL + k_refresh_interval));
@ -1334,16 +1188,17 @@ static const char* _make_session_jwt(JSContext* context, tf_ssb_t* ssb, const ch
char signature_base64[256] = { 0 }; char signature_base64[256] = { 0 };
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 }; uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
tf_ssb_get_private_key(ssb, private_key, sizeof(private_key)); if (_get_auth_private_key(ssb, private_key))
if (crypto_sign_detached(signature, &signature_length, (const uint8_t*)payload_base64, strlen(payload_base64), private_key) == 0)
{ {
sodium_bin2base64(signature_base64, sizeof(signature_base64), signature, sizeof(signature), sodium_base64_VARIANT_URLSAFE_NO_PADDING); if (crypto_sign_detached(signature, &signature_length, (const uint8_t*)payload_base64, strlen(payload_base64), private_key) == 0)
size_t size = strlen(header_base64) + 1 + strlen(payload_base64) + 1 + strlen(signature_base64) + 1; {
result = tf_malloc(size); sodium_bin2base64(signature_base64, sizeof(signature_base64), signature, sizeof(signature), sodium_base64_VARIANT_URLSAFE_NO_PADDING);
snprintf(result, size, "%s.%s.%s", header_base64, payload_base64, signature_base64); size_t size = strlen(header_base64) + 1 + strlen(payload_base64) + 1 + strlen(signature_base64) + 1;
result = tf_malloc(size);
snprintf(result, size, "%s.%s.%s", header_base64, payload_base64, signature_base64);
}
sodium_memzero(private_key, sizeof(private_key));
} }
sodium_memzero(private_key, sizeof(private_key));
JS_FreeCString(context, payload_string); JS_FreeCString(context, payload_string);
JS_FreeValue(context, payload_json); JS_FreeValue(context, payload_json);
@ -1359,10 +1214,26 @@ static bool _verify_password(const char* password, const char* hash)
return out_hash && strcmp(hash, out_hash) == 0; return out_hash && strcmp(hash, out_hash) == 0;
} }
static bool _make_administrator_if_first(tf_ssb_t* ssb, JSContext* context, const char* account_name_copy, bool may_become_first_admin) static const char* _get_code_of_conduct(tf_ssb_t* ssb)
{ {
JSContext* context = tf_ssb_get_context(ssb);
const char* settings = tf_ssb_db_get_property(ssb, "core", "settings"); const char* settings = tf_ssb_db_get_property(ssb, "core", "settings");
JSValue settings_value = settings && *settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED; JSValue settings_value = settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
JSValue code_of_conduct_value = JS_GetPropertyStr(context, settings_value, "code_of_conduct");
const char* code_of_conduct = JS_ToCString(context, code_of_conduct_value);
const char* result = tf_strdup(code_of_conduct);
JS_FreeCString(context, code_of_conduct);
JS_FreeValue(context, code_of_conduct_value);
JS_FreeValue(context, settings_value);
tf_free((void*)settings);
return result;
}
static bool _make_administrator_if_first(tf_ssb_t* ssb, const char* account_name_copy, bool may_become_first_admin)
{
JSContext* context = tf_ssb_get_context(ssb);
const char* settings = tf_ssb_db_get_property(ssb, "core", "settings");
JSValue settings_value = settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
if (JS_IsUndefined(settings_value)) if (JS_IsUndefined(settings_value))
{ {
settings_value = JS_NewObject(context); settings_value = JS_NewObject(context);
@ -1431,32 +1302,30 @@ static bool _make_administrator_if_first(tf_ssb_t* ssb, JSContext* context, cons
return have_administrator; return have_administrator;
} }
static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data) static void _httpd_endpoint_login(tf_http_request_t* request)
{ {
login_request_t* login = user_data; tf_task_t* task = request->user_data;
tf_http_request_t* request = login->request; JSContext* context = tf_task_get_context(task);
tf_ssb_t* ssb = tf_task_get_ssb(task);
JSMallocFunctions funcs = { 0 };
tf_get_js_malloc_functions(&funcs);
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
JSContext* context = JS_NewContext(runtime);
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session"); const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
const char** form_data = _form_data_decode(request->query, request->query ? strlen(request->query) : 0); const char** form_data = _form_data_decode(request->query, request->query ? strlen(request->query) : 0);
const char* account_name_copy = NULL; const char* account_name_copy = NULL;
JSValue jwt = _authenticate_jwt(ssb, context, session); JSValue jwt = _authenticate_jwt(context, session);
if (_session_is_authenticated_as_user(context, jwt)) if (_session_is_authenticated_as_user(context, jwt))
{ {
const char* return_url = _form_data_get(form_data, "return"); const char* return_url = _form_data_get(form_data, "return");
if (return_url) char url[1024];
if (!return_url)
{ {
snprintf(login->location_header, sizeof(login->location_header), "%s", return_url); snprintf(url, sizeof(url), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
} return_url = url;
else
{
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
} }
const char* headers[] = {
"Location",
return_url,
};
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
goto done; goto done;
} }
@ -1483,39 +1352,27 @@ static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
if (form_register && strcmp(form_register, "1") == 0) if (form_register && strcmp(form_register, "1") == 0)
{ {
bool registered = false; if (!have_account && _is_name_valid(account_name) && password && confirm && strcmp(password, confirm) == 0 &&
if (!have_account && _is_name_valid(account_name) && password && confirm && strcmp(password, confirm) == 0) tf_ssb_db_register_account(ssb, account_name, password))
{ {
sqlite3* db = tf_ssb_acquire_db_writer(ssb); tf_free((void*)send_session);
registered = tf_ssb_db_register_account(tf_ssb_get_loop(ssb), db, context, account_name, password); send_session = _make_session_jwt(ssb, account_name);
tf_ssb_release_db_writer(ssb, db); may_become_first_admin = true;
if (registered)
{
tf_free((void*)send_session);
send_session = _make_session_jwt(context, ssb, account_name);
may_become_first_admin = true;
}
} }
if (!registered) else
{ {
login_error = "Error registering account."; login_error = "Error registering account.";
} }
} }
else if (change && strcmp(change, "1") == 0) else if (change && strcmp(change, "1") == 0)
{ {
bool set = false; if (have_account && _is_name_valid(account_name) && new_password && confirm && strcmp(new_password, confirm) == 0 && _verify_password(password, account_passwd) &&
if (have_account && _is_name_valid(account_name) && new_password && confirm && strcmp(new_password, confirm) == 0 && _verify_password(password, account_passwd)) tf_ssb_db_set_account_password(ssb, account_name, new_password))
{ {
sqlite3* db = tf_ssb_acquire_db_writer(ssb); tf_free((void*)send_session);
set = tf_ssb_db_set_account_password(tf_ssb_get_loop(ssb), db, context, account_name, new_password); send_session = _make_session_jwt(ssb, account_name);
tf_ssb_release_db_writer(ssb, db);
if (set)
{
tf_free((void*)send_session);
send_session = _make_session_jwt(context, ssb, account_name);
}
} }
if (!set) else
{ {
login_error = "Error changing password."; login_error = "Error changing password.";
} }
@ -1525,7 +1382,7 @@ static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
if (have_account && *account_passwd && _verify_password(password, account_passwd)) if (have_account && *account_passwd && _verify_password(password, account_passwd))
{ {
tf_free((void*)send_session); tf_free((void*)send_session);
send_session = _make_session_jwt(context, ssb, account_name); send_session = _make_session_jwt(ssb, account_name);
may_become_first_admin = true; may_become_first_admin = true;
} }
else else
@ -1537,56 +1394,52 @@ static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
else else
{ {
tf_free((void*)send_session); tf_free((void*)send_session);
send_session = _make_session_jwt(context, ssb, "guest"); send_session = _make_session_jwt(ssb, "guest");
} }
tf_free(post_form_data); tf_free(post_form_data);
} }
bool have_administrator = _make_administrator_if_first(ssb, context, account_name_copy, may_become_first_admin); bool have_administrator = _make_administrator_if_first(ssb, account_name_copy, may_become_first_admin);
if (session_is_new && _form_data_get(form_data, "return") && !login_error) if (session_is_new && _form_data_get(form_data, "return") && !login_error)
{ {
const char* return_url = _form_data_get(form_data, "return"); const char* return_url = _form_data_get(form_data, "return");
if (return_url) char url[1024];
if (!return_url)
{ {
snprintf(login->location_header, sizeof(login->location_header), "%s", return_url); snprintf(url, sizeof(url), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
return_url = url;
} }
else const char* cookie = _make_set_session_cookie_header(request, send_session);
{ const char* headers[] = {
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host")); "Location",
} return_url,
login->set_cookie_header = _make_set_session_cookie_header(request, send_session); "Set-Cookie",
cookie ? cookie : "",
};
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
tf_free((void*)cookie);
tf_free((void*)send_session); tf_free((void*)send_session);
} }
else else
{ {
login->name = account_name_copy;
login->error = login_error;
login->set_cookie_header = _make_set_session_cookie_header(request, send_session);
tf_free((void*)send_session);
login->session_is_new = session_is_new;
login->have_administrator = have_administrator;
login->settings = tf_ssb_db_get_property(ssb, "core", "settings");
if (login->settings)
{
JSValue settings_value = JS_ParseJSON(context, login->settings, strlen(login->settings), NULL);
JSValue code_of_conduct_value = JS_GetPropertyStr(context, settings_value, "code_of_conduct");
const char* code_of_conduct = JS_ToCString(context, code_of_conduct_value);
const char* result = tf_strdup(code_of_conduct);
JS_FreeCString(context, code_of_conduct);
JS_FreeValue(context, code_of_conduct_value);
JS_FreeValue(context, settings_value);
tf_free((void*)login->settings);
login->settings = NULL;
login->code_of_conduct = result;
}
login->pending++;
tf_http_request_ref(request); tf_http_request_ref(request);
tf_file_read(login->request->user_data, "core/auth.html", _httpd_endpoint_login_file_read_callback, login);
login_request_t* login = tf_malloc(sizeof(login_request_t));
const char* code_of_conduct = _get_code_of_conduct(ssb);
*login = (login_request_t) {
.request = request,
.name = account_name_copy,
.jwt = jwt,
.error = login_error,
.session_cookie = send_session,
.session_is_new = session_is_new,
.code_of_conduct = code_of_conduct,
.have_administrator = have_administrator,
};
tf_file_read(request->user_data, "core/auth.html", _httpd_endpoint_login_file_read_callback, login);
jwt = JS_UNDEFINED;
account_name_copy = NULL; account_name_copy = NULL;
} }
@ -1595,44 +1448,6 @@ done:
tf_free(form_data); tf_free(form_data);
tf_free((void*)account_name_copy); tf_free((void*)account_name_copy);
JS_FreeValue(context, jwt); JS_FreeValue(context, jwt);
JS_FreeContext(context);
JS_FreeRuntime(runtime);
}
static void _httpd_endpoint_login_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
login_request_t* login = user_data;
tf_http_request_t* request = login->request;
if (login->pending == 1)
{
if (*login->location_header)
{
const char* headers[] = {
"Location",
login->location_header,
"Set-Cookie",
login->set_cookie_header ? login->set_cookie_header : "",
};
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
}
}
tf_http_request_unref(request);
_login_release(login);
}
static void _httpd_endpoint_login(tf_http_request_t* request)
{
tf_task_t* task = request->user_data;
tf_http_request_ref(request);
tf_ssb_t* ssb = tf_task_get_ssb(task);
login_request_t* login = tf_malloc(sizeof(login_request_t));
*login = (login_request_t) {
.request = request,
};
login->pending++;
tf_ssb_run_work(ssb, _httpd_endpoint_login_work, _httpd_endpoint_login_after_work, login);
} }
static void _httpd_endpoint_logout(tf_http_request_t* request) static void _httpd_endpoint_logout(tf_http_request_t* request)
@ -1688,8 +1503,6 @@ void tf_httpd_register(JSContext* context)
tf_http_add_handler(http, "/speedscope/*", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/speedscope/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/static/*", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/static/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/.well-known/*", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/.well-known/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/~*/*/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/&*.sha256/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL); tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL);
tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task); tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task);
@ -1706,8 +1519,6 @@ void tf_httpd_register(JSContext* context)
JS_SetPropertyStr(context, httpd, "start", JS_NewCFunction(context, _httpd_endpoint_start, "start", 2)); JS_SetPropertyStr(context, httpd, "start", JS_NewCFunction(context, _httpd_endpoint_start, "start", 2));
JS_SetPropertyStr(context, httpd, "set_http_redirect", JS_NewCFunction(context, _httpd_set_http_redirect, "set_http_redirect", 1)); JS_SetPropertyStr(context, httpd, "set_http_redirect", JS_NewCFunction(context, _httpd_set_http_redirect, "set_http_redirect", 1));
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1)); JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
JS_SetPropertyStr(context, httpd, "mime_type_from_magic_bytes", JS_NewCFunction(context, _httpd_mime_type_from_magic_bytes, "mime_type_from_magic_bytes", 1));
JS_SetPropertyStr(context, httpd, "mime_type_from_extension", JS_NewCFunction(context, _httpd_mime_type_from_extension, "mime_type_from_extension", 1));
JS_SetPropertyStr(context, global, "httpd", httpd); JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global); JS_FreeValue(context, global);
} }

View File

@ -9,7 +9,6 @@
#include "tests.h" #include "tests.h"
#include "util.js.h" #include "util.js.h"
#include "ares.h"
#include "backtrace.h" #include "backtrace.h"
#include "sqlite3.h" #include "sqlite3.h"
#include "unzip.h" #include "unzip.h"
@ -35,10 +34,6 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#if defined(__ANDROID__)
#include "jni.h"
#endif
#if !defined(_countof) #if !defined(_countof)
#define _countof(a) ((int)(sizeof((a)) / sizeof(*(a)))) #define _countof(a) ((int)(sizeof((a)) / sizeof(*(a))))
#endif #endif
@ -386,7 +381,6 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
tf_ssb_import(tf_task_get_ssb(task), "core", "apps"); tf_ssb_import(tf_task_get_ssb(task), "core", "apps");
} }
} }
tf_ssb_set_main_thread(tf_task_get_ssb(task), true);
if (tf_task_execute(task, args->script)) if (tf_task_execute(task, args->script))
{ {
tf_task_run(task); tf_task_run(task);
@ -608,16 +602,14 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
static int _tf_command_sandbox(const char* file, int argc, char* argv[]) static int _tf_command_sandbox(const char* file, int argc, char* argv[])
{ {
bool show_usage = false; bool show_usage = false;
int fd = STDIN_FILENO;
while (!show_usage) while (!show_usage)
{ {
static const struct option k_options[] = { static const struct option k_options[] = {
{ "fd", required_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ 0 }, { 0 },
}; };
int c = getopt_long(argc, argv, "f:h", k_options, NULL); int c = getopt_long(argc, argv, "h", k_options, NULL);
if (c == -1) if (c == -1)
{ {
break; break;
@ -629,9 +621,6 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[])
default: default:
show_usage = true; show_usage = true;
break; break;
case 'f':
fd = atoi(optarg);
break;
} }
} }
@ -640,7 +629,6 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[])
tf_printf("\nUsage: %s sandbox [options]\n\n", file); tf_printf("\nUsage: %s sandbox [options]\n\n", file);
tf_printf("options:\n"); tf_printf("options:\n");
tf_printf(" -h, --help Show this usage information.\n"); tf_printf(" -h, --help Show this usage information.\n");
tf_printf(" -f, --fd File descriptor with which to communicate with parent process.\n");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -648,7 +636,7 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[])
prctl(PR_SET_PDEATHSIG, SIGHUP); prctl(PR_SET_PDEATHSIG, SIGHUP);
#endif #endif
tf_task_t* task = tf_task_create(); tf_task_t* task = tf_task_create();
tf_task_configure_from_fd(task, fd); tf_task_configure_from_fd(task, STDIN_FILENO);
_shed_privileges(); _shed_privileges();
/* The caller will trigger tf_task_activate with a message. */ /* The caller will trigger tf_task_activate with a message. */
tf_task_run(task); tf_task_run(task);
@ -696,6 +684,10 @@ static void _startup(int argc, char* argv[])
} }
} }
#if defined(__ANDROID__)
setenv("UV_USE_IO_URING", "0", 1);
#endif
tf_mem_startup(tracking); tf_mem_startup(tracking);
g_backtrace_state = backtrace_create_state(argv[0], 0, _backtrace_error, NULL); g_backtrace_state = backtrace_create_state(argv[0], 0, _backtrace_error, NULL);
@ -723,7 +715,7 @@ static void _startup(int argc, char* argv[])
{ {
if ( if (
#if !defined(_WIN32) #if !defined(_WIN32)
signal(SIGSYS, _error_handler) == SIG_ERR || signal(SIGABRT, _error_handler) == SIG_ERR || signal(SIGSYS, _error_handler) == SIG_ERR ||
#endif #endif
signal(SIGSEGV, _error_handler) == SIG_ERR) signal(SIGSEGV, _error_handler) == SIG_ERR)
{ {
@ -733,143 +725,23 @@ static void _startup(int argc, char* argv[])
} }
#if defined(__ANDROID__) #if defined(__ANDROID__)
static JNIEnv* s_jni_env;
static void _tf_service_start(int pipe_fd)
{
tf_printf("_tf_service_start\n");
jclass c = (*s_jni_env)->FindClass(s_jni_env, "com/unprompted/tildefriends/TildeFriendsActivity");
jmethodID start_sandbox = (*s_jni_env)->GetStaticMethodID(s_jni_env, c, "start_sandbox", "(I)V");
(*s_jni_env)->CallStaticVoidMethod(s_jni_env, c, start_sandbox, pipe_fd);
}
static void _tf_service_stop()
{
tf_printf("_tf_service_stop\n");
jclass c = (*s_jni_env)->FindClass(s_jni_env, "com/unprompted/tildefriends/TildeFriendsActivity");
jmethodID stop_sandbox = (*s_jni_env)->GetStaticMethodID(s_jni_env, c, "stop_sandbox", "()V");
(*s_jni_env)->CallStaticVoidMethod(s_jni_env, c, stop_sandbox);
}
static jint _tf_server_main(JNIEnv* env, jobject this_object, jstring files_dir, jstring apk_path, jstring out_port_file_path, jobject connectivity_manager)
{
s_jni_env = env;
tf_printf("This is tf_server_main main.\n");
_startup(0, (char*[]) { NULL });
tf_printf("That was startup.\n");
ares_library_init_android(connectivity_manager);
ares_library_init(0);
const char* files = (*env)->GetStringUTFChars(env, files_dir, NULL);
const char* apk = (*env)->GetStringUTFChars(env, apk_path, NULL);
const char* out_port_file = (*env)->GetStringUTFChars(env, out_port_file_path, NULL);
tf_printf("FILES = %s\n", files);
tf_printf("APK = %s\n", apk);
tf_printf("OUT_PORT = %s\n", out_port_file);
int result = uv_chdir(files);
if (result)
{
tf_printf("uv_chdir: %s\n", uv_strerror(result));
}
size_t port_file_arg_length = strlen(out_port_file) + strlen("out_http_port_file=") + 1;
char* port_file_arg = alloca(port_file_arg_length);
snprintf(port_file_arg, port_file_arg_length, "out_http_port_file=%s", out_port_file);
const char* args[] = {
"run",
"-z",
apk,
"-a",
port_file_arg,
"-p",
"0",
};
tf_task_set_android_service_callbacks(_tf_service_start, _tf_service_stop);
result = _tf_command_run(apk, _countof(args), (char**)args);
tf_task_set_android_service_callbacks(NULL, NULL);
(*env)->ReleaseStringUTFChars(env, files_dir, files);
(*env)->ReleaseStringUTFChars(env, apk_path, apk);
(*env)->ReleaseStringUTFChars(env, out_port_file_path, out_port_file);
ares_library_cleanup();
tf_mem_shutdown();
tf_printf("tf_server_main finished with %d.", result);
s_jni_env = NULL;
return result;
}
static jint _tf_sandbox_main(JNIEnv* env, jobject this_object, int pipe_fd)
{
s_jni_env = env;
tf_printf("This is tf_sandbox_main main (fd=%d).\n", pipe_fd);
_startup(0, (char*[]) { NULL });
tf_printf("That was startup.\n");
char fd[32] = { 0 };
snprintf(fd, sizeof(fd), "%d", pipe_fd);
const char* args[] = {
"sandbox",
"-f",
fd,
};
int result = _tf_command_sandbox(NULL, _countof(args), (char**)args);
tf_mem_shutdown();
tf_printf("tf_sandbox_main finished with %d.", result);
s_jni_env = NULL;
return result;
}
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
tf_printf("JNI_Onload called.\n");
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK)
{
tf_printf("Failed to get JNI environment.\n");
return JNI_ERR;
}
tf_printf("Finding class.\n");
jclass c = (*env)->FindClass(env, "com/unprompted/tildefriends/TildeFriendsActivity");
if (!c)
{
tf_printf("Failed to find TildeFriendsActivity class.\n");
return JNI_ERR;
}
tf_printf("Registering method.\n");
static const JNINativeMethod methods[] = {
{ "tf_server_main", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/net/ConnectivityManager;)I", _tf_server_main },
{ "tf_sandbox_main", "(I)I", _tf_sandbox_main },
};
int result = (*env)->RegisterNatives(env, c, methods, (int)_countof(methods));
if (result != JNI_OK)
{
return result;
}
ares_library_init_jvm(vm);
tf_printf("Done.\n");
return JNI_VERSION_1_6;
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
tf_printf("Welcome to Tilde Friends. This is not the way to run on Android.\n"); _startup(argc, argv);
return EXIT_FAILURE; int result = -1;
if (argc > 1)
{
if (strcmp(argv[1], "run") == 0)
{
result = _tf_command_run(argv[0], argc - 1, argv + 1);
}
else if (strcmp(argv[1], "sandbox") == 0)
{
result = _tf_command_sandbox(argv[0], argc - 1, argv + 1);
}
}
tf_mem_shutdown();
return result;
} }
#elif TARGET_OS_IPHONE #elif TARGET_OS_IPHONE
void tf_run_thread_start(const char* zip_path) void tf_run_thread_start(const char* zip_path)
@ -896,7 +768,6 @@ void tf_run_thread_start(const char* zip_path)
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
_startup(argc, argv); _startup(argc, argv);
ares_library_init(0);
int result = 0; int result = 0;
if (argc >= 2) if (argc >= 2)
@ -917,7 +788,6 @@ int main(int argc, char* argv[])
result = _tf_command_run(argv[0], argc, argv); result = _tf_command_run(argv[0], argc, argv);
} }
done: done:
ares_library_cleanup();
tf_mem_shutdown(); tf_mem_shutdown();
return result; return result;
} }

382
src/ssb.c
View File

@ -8,7 +8,6 @@
#include "trace.h" #include "trace.h"
#include "util.js.h" #include "util.js.h"
#include "ares.h"
#include "quickjs.h" #include "quickjs.h"
#include "sodium/crypto_auth.h" #include "sodium/crypto_auth.h"
#include "sodium/crypto_box.h" #include "sodium/crypto_box.h"
@ -21,10 +20,6 @@
#include "sqlite3.h" #include "sqlite3.h"
#include "uv.h" #include "uv.h"
#if !defined(_WIN32)
#include <arpa/nameser.h>
#endif
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
@ -77,10 +72,6 @@ enum
k_tf_ssb_rpc_message_body_length_max = 1 * 1024 * 1024, k_tf_ssb_rpc_message_body_length_max = 1 * 1024 * 1024,
k_debug_close_message_count = 256, k_debug_close_message_count = 256,
k_debug_close_connection_count = 32, k_debug_close_connection_count = 32,
k_seed_expire_seconds = 10 * 60,
k_seed_check_interval_seconds = 5 * 60,
k_udp_discovery_expires_seconds = 10,
k_handshake_timeout_ms = 15000,
}; };
typedef struct _tf_ssb_broadcast_t tf_ssb_broadcast_t; typedef struct _tf_ssb_broadcast_t tf_ssb_broadcast_t;
@ -120,9 +111,7 @@ typedef struct _tf_ssb_broadcast_t
tf_ssb_broadcast_t* next; tf_ssb_broadcast_t* next;
time_t ctime; time_t ctime;
time_t mtime; time_t mtime;
time_t expires_at;
char host[256]; char host[256];
tf_ssb_broadcast_origin_t origin;
struct sockaddr_in addr; struct sockaddr_in addr;
tf_ssb_connection_t* tunnel_connection; tf_ssb_connection_t* tunnel_connection;
uint8_t pub[crypto_sign_PUBLICKEYBYTES]; uint8_t pub[crypto_sign_PUBLICKEYBYTES];
@ -266,11 +255,7 @@ typedef struct _tf_ssb_t
uv_thread_t thread_self; uv_thread_t thread_self;
bool is_room; bool is_room;
bool is_replicator;
bool is_peer_exchange;
char* room_name; char* room_name;
char seeds_host[256];
time_t last_seed_check;
tf_ssb_timer_t** timers; tf_ssb_timer_t** timers;
int timers_count; int timers_count;
@ -295,7 +280,6 @@ typedef struct _tf_ssb_connection_t
uv_tcp_t tcp; uv_tcp_t tcp;
uv_connect_t connect; uv_connect_t connect;
uv_async_t async; uv_async_t async;
uv_timer_t handshake_timer;
bool closing; bool closing;
tf_ssb_connection_t* tunnel_connection; tf_ssb_connection_t* tunnel_connection;
@ -358,16 +342,12 @@ typedef struct _tf_ssb_connection_t
tf_ssb_debug_message_t* debug_messages[k_debug_close_message_count]; tf_ssb_debug_message_t* debug_messages[k_debug_close_message_count];
int ref_count; int ref_count;
int read_back_pressure;
int active_write_count;
} tf_ssb_connection_t; } tf_ssb_connection_t;
static JSClassID _connection_class_id; static JSClassID _connection_class_id;
static int s_connection_index; static int s_connection_index;
static int s_tunnel_index; static int s_tunnel_index;
static void _tf_ssb_add_broadcast(tf_ssb_t* ssb, const tf_ssb_broadcast_t* broadcast, int expires_seconds);
static void _tf_ssb_connection_client_send_hello(tf_ssb_connection_t* connection); static void _tf_ssb_connection_client_send_hello(tf_ssb_connection_t* connection);
static void _tf_ssb_connection_close(tf_ssb_connection_t* connection, const char* reason); static void _tf_ssb_connection_close(tf_ssb_connection_t* connection, const char* reason);
static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const char* reason); static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const char* reason);
@ -375,7 +355,6 @@ static void _tf_ssb_connection_finalizer(JSRuntime* runtime, JSValue value);
static void _tf_ssb_connection_on_close(uv_handle_t* handle); static void _tf_ssb_connection_on_close(uv_handle_t* handle);
static void _tf_ssb_nonce_inc(uint8_t* nonce); static void _tf_ssb_nonce_inc(uint8_t* nonce);
static void _tf_ssb_notify_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection); static void _tf_ssb_notify_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection);
static bool _tf_ssb_parse_broadcast(const char* in_broadcast, tf_ssb_broadcast_t* out_broadcast);
static void _tf_ssb_start_update_settings(tf_ssb_t* ssb); static void _tf_ssb_start_update_settings(tf_ssb_t* ssb);
static void _tf_ssb_update_settings(tf_ssb_t* ssb); static void _tf_ssb_update_settings(tf_ssb_t* ssb);
static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t size); static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t size);
@ -479,10 +458,9 @@ static void _tf_ssb_connection_on_tcp_alloc(uv_handle_t* handle, size_t suggeste
static void _tf_ssb_connection_on_write(uv_write_t* req, int status) static void _tf_ssb_connection_on_write(uv_write_t* req, int status)
{ {
tf_ssb_connection_t* connection = req->data;
tf_ssb_connection_adjust_write_count(connection, -1);
if (status) if (status)
{ {
tf_ssb_connection_t* connection = req->data;
char buffer[256]; char buffer[256];
snprintf(buffer, sizeof(buffer), "write failed asynchronously: %s", uv_strerror(status)); snprintf(buffer, sizeof(buffer), "write failed asynchronously: %s", uv_strerror(status));
_tf_ssb_connection_close(connection, buffer); _tf_ssb_connection_close(connection, buffer);
@ -497,11 +475,9 @@ static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t si
uv_write_t* write = tf_malloc(sizeof(uv_write_t) + size); uv_write_t* write = tf_malloc(sizeof(uv_write_t) + size);
*write = (uv_write_t) { .data = connection }; *write = (uv_write_t) { .data = connection };
memcpy(write + 1, data, size); memcpy(write + 1, data, size);
tf_ssb_connection_adjust_write_count(connection, 1);
int result = uv_write(write, (uv_stream_t*)&connection->tcp, &(uv_buf_t) { .base = (char*)(write + 1), .len = size }, 1, _tf_ssb_connection_on_write); int result = uv_write(write, (uv_stream_t*)&connection->tcp, &(uv_buf_t) { .base = (char*)(write + 1), .len = size }, 1, _tf_ssb_connection_on_write);
if (result) if (result)
{ {
tf_ssb_connection_adjust_write_count(connection, -1);
_tf_ssb_connection_close(connection, "write failed"); _tf_ssb_connection_close(connection, "write failed");
tf_free(write); tf_free(write);
} }
@ -628,19 +604,6 @@ static void _tf_ssb_connection_box_stream_send(tf_ssb_connection_t* connection,
} }
} }
static void _tf_ssb_connection_dispatch_scheduled(tf_ssb_connection_t* connection)
{
while ((connection->active_write_count == 0 || connection->closing) && connection->scheduled_count && connection->scheduled)
{
tf_ssb_connection_scheduled_t scheduled = connection->scheduled[0];
memmove(connection->scheduled, connection->scheduled + 1, sizeof(tf_ssb_connection_scheduled_t) * (connection->scheduled_count - 1));
connection->scheduled_count--;
tf_trace_begin(connection->ssb->trace, "scheduled callback");
scheduled.callback(connection, scheduled.user_data);
tf_trace_end(connection->ssb->trace);
}
}
void tf_ssb_connection_schedule_idle(tf_ssb_connection_t* connection, tf_ssb_scheduled_callback_t* callback, void* user_data) void tf_ssb_connection_schedule_idle(tf_ssb_connection_t* connection, tf_ssb_scheduled_callback_t* callback, void* user_data)
{ {
connection->scheduled = tf_resize_vec(connection->scheduled, sizeof(tf_ssb_connection_scheduled_t) * (connection->scheduled_count + 1)); connection->scheduled = tf_resize_vec(connection->scheduled, sizeof(tf_ssb_connection_scheduled_t) * (connection->scheduled_count + 1));
@ -648,7 +611,7 @@ void tf_ssb_connection_schedule_idle(tf_ssb_connection_t* connection, tf_ssb_sch
.callback = callback, .callback = callback,
.user_data = user_data, .user_data = user_data,
}; };
_tf_ssb_connection_dispatch_scheduled(connection); uv_async_send(&connection->async);
} }
static int _request_compare(const void* a, const void* b) static int _request_compare(const void* a, const void* b)
@ -1260,10 +1223,6 @@ static void _tf_ssb_connection_verify_identity(tf_ssb_connection_t* connection,
JS_SetPropertyStr(context, connection->object, "is_client", JS_TRUE); JS_SetPropertyStr(context, connection->object, "is_client", JS_TRUE);
connection->state = k_tf_ssb_state_verified; connection->state = k_tf_ssb_state_verified;
if (connection->handshake_timer.data)
{
uv_timer_stop(&connection->handshake_timer);
}
_tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_connect, connection); _tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_connect, connection);
} }
@ -1492,10 +1451,6 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne
JS_SetPropertyStr(context, connection->object, "is_client", JS_FALSE); JS_SetPropertyStr(context, connection->object, "is_client", JS_FALSE);
connection->state = k_tf_ssb_state_server_verified; connection->state = k_tf_ssb_state_server_verified;
if (connection->handshake_timer.data)
{
uv_timer_stop(&connection->handshake_timer);
}
_tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_connect, connection); _tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_connect, connection);
} }
@ -1663,7 +1618,6 @@ static void _tf_ssb_connection_rpc_recv(tf_ssb_connection_t* connection, uint8_t
} }
if (!found && !_tf_ssb_name_equals(context, val, (const char*[]) { "Error", NULL })) if (!found && !_tf_ssb_name_equals(context, val, (const char*[]) { "Error", NULL }))
{ {
tf_ssb_connection_add_request(connection, -request_number, namebuf, NULL, NULL, NULL, NULL);
char buffer[256]; char buffer[256];
_tf_ssb_name_to_string(context, val, buffer, sizeof(buffer)); _tf_ssb_name_to_string(context, val, buffer, sizeof(buffer));
tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, buffer); tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, buffer);
@ -1700,6 +1654,10 @@ static void _tf_ssb_connection_rpc_recv(tf_ssb_connection_t* connection, uint8_t
tf_trace_end(connection->ssb->trace); tf_trace_end(connection->ssb->trace);
} }
} }
else
{
tf_printf("No request callback for %p %d\n", connection, -request_number);
}
} }
if (close_connection) if (close_connection)
@ -1806,28 +1764,18 @@ static bool _tf_ssb_connection_box_stream_recv(tf_ssb_connection_t* connection)
return true; return true;
} }
JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message, const char* previous_id, int64_t previous_sequence) JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message)
{ {
char actual_previous_id[crypto_hash_sha256_BYTES * 2]; char previous_id[crypto_hash_sha256_BYTES * 2];
int64_t actual_previous_sequence = 0; int64_t previous_sequence = 0;
bool have_previous = false; bool have_previous = tf_ssb_db_get_latest_message_by_author(ssb, author, &previous_sequence, previous_id, sizeof(previous_id));
if (previous_id)
{
have_previous = *previous_id && previous_sequence > 0;
snprintf(actual_previous_id, sizeof(actual_previous_id), "%s", previous_id);
actual_previous_sequence = previous_sequence;
}
else
{
have_previous = tf_ssb_db_get_latest_message_by_author(ssb, author, &actual_previous_sequence, actual_previous_id, sizeof(actual_previous_id));
}
JSContext* context = ssb->context; JSContext* context = ssb->context;
JSValue root = JS_NewObject(context); JSValue root = JS_NewObject(context);
JS_SetPropertyStr(context, root, "previous", have_previous ? JS_NewString(context, actual_previous_id) : JS_NULL); JS_SetPropertyStr(context, root, "previous", have_previous ? JS_NewString(context, previous_id) : JS_NULL);
JS_SetPropertyStr(context, root, "author", JS_NewString(context, author)); JS_SetPropertyStr(context, root, "author", JS_NewString(context, author));
JS_SetPropertyStr(context, root, "sequence", JS_NewInt64(context, actual_previous_sequence + 1)); JS_SetPropertyStr(context, root, "sequence", JS_NewInt64(context, previous_sequence + 1));
int64_t now = (int64_t)time(NULL); int64_t now = (int64_t)time(NULL);
JS_SetPropertyStr(context, root, "timestamp", JS_NewInt64(context, now * 1000LL)); JS_SetPropertyStr(context, root, "timestamp", JS_NewInt64(context, now * 1000LL));
@ -1864,6 +1812,24 @@ JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* pr
return root; return root;
} }
static void _tf_ssb_connection_dispatch_scheduled(tf_ssb_connection_t* connection)
{
const int k_scheduled_batch_count = 8;
for (int i = 0; i < k_scheduled_batch_count && connection->scheduled_count && connection->scheduled; i++)
{
tf_ssb_connection_scheduled_t scheduled = connection->scheduled[0];
memmove(connection->scheduled, connection->scheduled + 1, sizeof(tf_ssb_connection_scheduled_t) * (connection->scheduled_count - 1));
connection->scheduled_count--;
tf_trace_begin(connection->ssb->trace, "scheduled callback");
scheduled.callback(connection, scheduled.user_data);
tf_trace_end(connection->ssb->trace);
}
if (connection->scheduled_count)
{
uv_async_send(&connection->async);
}
}
static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const char* reason) static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const char* reason)
{ {
tf_ssb_t* ssb = connection->ssb; tf_ssb_t* ssb = connection->ssb;
@ -1872,7 +1838,10 @@ static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const ch
{ {
connection->destroy_reason = reason; connection->destroy_reason = reason;
} }
_tf_ssb_connection_dispatch_scheduled(connection); while (connection->scheduled_count)
{
_tf_ssb_connection_dispatch_scheduled(connection);
}
tf_free(connection->scheduled); tf_free(connection->scheduled);
connection->scheduled = NULL; connection->scheduled = NULL;
while (connection->requests) while (connection->requests)
@ -1941,13 +1910,8 @@ static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const ch
{ {
uv_close((uv_handle_t*)&connection->tcp, _tf_ssb_connection_on_close); uv_close((uv_handle_t*)&connection->tcp, _tf_ssb_connection_on_close);
} }
if (connection->handshake_timer.data && !uv_is_closing((uv_handle_t*)&connection->handshake_timer))
{
uv_close((uv_handle_t*)&connection->handshake_timer, _tf_ssb_connection_on_close);
}
if (JS_IsUndefined(connection->object) && !connection->async.data && !connection->tcp.data && !connection->connect.data && !connection->handshake_timer.data && if (JS_IsUndefined(connection->object) && !connection->async.data && !connection->tcp.data && !connection->connect.data && connection->ref_count == 0)
connection->ref_count == 0)
{ {
tf_free(connection->message_requests); tf_free(connection->message_requests);
connection->message_requests = NULL; connection->message_requests = NULL;
@ -1971,7 +1935,7 @@ static void _tf_ssb_connection_on_close(uv_handle_t* handle)
{ {
tf_ssb_connection_t* connection = handle->data; tf_ssb_connection_t* connection = handle->data;
handle->data = NULL; handle->data = NULL;
if (connection && connection->closing) if (connection)
{ {
_tf_ssb_connection_destroy(connection, "handle closed"); _tf_ssb_connection_destroy(connection, "handle closed");
} }
@ -2097,30 +2061,6 @@ static void _tf_ssb_connection_client_send_hello(tf_ssb_connection_t* connection
connection->state = k_tf_ssb_state_sent_hello; connection->state = k_tf_ssb_state_sent_hello;
} }
static bool _tf_ssb_connection_read_start(tf_ssb_connection_t* connection)
{
int result = uv_read_start((uv_stream_t*)&connection->tcp, _tf_ssb_connection_on_tcp_alloc, _tf_ssb_connection_on_tcp_recv);
if (result && result != UV_EALREADY)
{
tf_printf("uv_read_start => %s\n", uv_strerror(result));
_tf_ssb_connection_close(connection, "uv_read_start failed");
return false;
}
return true;
}
static bool _tf_ssb_connection_read_stop(tf_ssb_connection_t* connection)
{
int result = uv_read_stop((uv_stream_t*)&connection->tcp);
if (result && result != UV_EALREADY)
{
tf_printf("uv_read_stop => %s\n", uv_strerror(result));
_tf_ssb_connection_close(connection, "uv_read_stop failed");
return false;
}
return true;
}
static void _tf_ssb_connection_on_connect(uv_connect_t* connect, int status) static void _tf_ssb_connection_on_connect(uv_connect_t* connect, int status)
{ {
tf_ssb_connection_t* connection = connect->data; tf_ssb_connection_t* connection = connect->data;
@ -2128,7 +2068,13 @@ static void _tf_ssb_connection_on_connect(uv_connect_t* connect, int status)
if (status == 0) if (status == 0)
{ {
connection->state = k_tf_ssb_state_connected; connection->state = k_tf_ssb_state_connected;
if (_tf_ssb_connection_read_start(connection)) int result = uv_read_start(connect->handle, _tf_ssb_connection_on_tcp_alloc, _tf_ssb_connection_on_tcp_recv);
if (result)
{
tf_printf("uv_read_start => %s\n", uv_strerror(status));
_tf_ssb_connection_close(connection, "uv_read_start failed");
}
else
{ {
_tf_ssb_connection_client_send_hello(connection); _tf_ssb_connection_client_send_hello(connection);
} }
@ -2213,7 +2159,6 @@ tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path
{ {
tf_ssb_t* ssb = tf_malloc(sizeof(tf_ssb_t)); tf_ssb_t* ssb = tf_malloc(sizeof(tf_ssb_t));
memset(ssb, 0, sizeof(*ssb)); memset(ssb, 0, sizeof(*ssb));
ssb->is_replicator = true;
const char* actual_key = network_key ? network_key : k_ssb_network_string; const char* actual_key = network_key ? network_key : k_ssb_network_string;
if (sodium_hex2bin(ssb->network_key, sizeof(ssb->network_key), actual_key, strlen(actual_key), ": ", NULL, NULL)) if (sodium_hex2bin(ssb->network_key, sizeof(ssb->network_key), actual_key, strlen(actual_key), ": ", NULL, NULL))
@ -2298,7 +2243,6 @@ static void _tf_ssb_assert_not_main_thread(tf_ssb_t* ssb)
const char* bt = tf_util_backtrace_string(); const char* bt = tf_util_backtrace_string();
tf_printf("Acquiring DB from the main thread:\n%s\n", bt); tf_printf("Acquiring DB from the main thread:\n%s\n", bt);
tf_free((void*)bt); tf_free((void*)bt);
abort();
} }
} }
@ -2417,14 +2361,6 @@ tf_trace_t* tf_ssb_get_trace(tf_ssb_t* ssb)
JSContext* tf_ssb_get_context(tf_ssb_t* ssb) JSContext* tf_ssb_get_context(tf_ssb_t* ssb)
{ {
if (ssb->thread_self && uv_thread_self() != ssb->thread_self)
{
const char* bt = tf_util_backtrace_string();
tf_printf("Acquiring JS context from non-main thread:\n%s\n", bt);
tf_free((void*)bt);
abort();
}
return ssb->context; return ssb->context;
} }
@ -2629,11 +2565,6 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
} }
} }
bool tf_ssb_is_shutting_down(tf_ssb_t* ssb)
{
return ssb->shutting_down;
}
void tf_ssb_run(tf_ssb_t* ssb) void tf_ssb_run(tf_ssb_t* ssb)
{ {
uv_run(ssb->loop, UV_RUN_DEFAULT); uv_run(ssb->loop, UV_RUN_DEFAULT);
@ -2656,15 +2587,7 @@ static void _tf_ssb_connection_process_message_async(uv_async_t* async)
{ {
uv_async_send(&connection->async); uv_async_send(&connection->async);
} }
} _tf_ssb_connection_dispatch_scheduled(connection);
static void _tf_ssb_connection_handshake_timer_callback(uv_timer_t* timer)
{
tf_ssb_connection_t* connection = timer->data;
if (connection && connection->state != k_tf_ssb_state_verified && connection->state != k_tf_ssb_state_server_verified)
{
_tf_ssb_connection_destroy(connection, "handshake timeout");
}
} }
tf_ssb_connection_t* tf_ssb_connection_create(tf_ssb_t* ssb, const char* host, const struct sockaddr_in* addr, const uint8_t* public_key) tf_ssb_connection_t* tf_ssb_connection_create(tf_ssb_t* ssb, const char* host, const struct sockaddr_in* addr, const uint8_t* public_key)
@ -2701,10 +2624,6 @@ tf_ssb_connection_t* tf_ssb_connection_create(tf_ssb_t* ssb, const char* host, c
connection->async.data = connection; connection->async.data = connection;
uv_async_init(ssb->loop, &connection->async, _tf_ssb_connection_process_message_async); uv_async_init(ssb->loop, &connection->async, _tf_ssb_connection_process_message_async);
connection->handshake_timer.data = connection;
uv_timer_init(ssb->loop, &connection->handshake_timer);
uv_timer_start(&connection->handshake_timer, _tf_ssb_connection_handshake_timer_callback, k_handshake_timeout_ms, 0);
connection->object = JS_NewObjectClass(ssb->context, _connection_class_id); connection->object = JS_NewObjectClass(ssb->context, _connection_class_id);
JS_SetOpaque(connection->object, connection); JS_SetOpaque(connection->object, connection);
char public_key_str[k_id_base64_len] = { 0 }; char public_key_str[k_id_base64_len] = { 0 };
@ -2765,10 +2684,6 @@ tf_ssb_connection_t* tf_ssb_connection_tunnel_create(tf_ssb_t* ssb, const char*
tunnel->async.data = tunnel; tunnel->async.data = tunnel;
uv_async_init(ssb->loop, &tunnel->async, _tf_ssb_connection_process_message_async); uv_async_init(ssb->loop, &tunnel->async, _tf_ssb_connection_process_message_async);
tunnel->handshake_timer.data = tunnel;
uv_timer_init(ssb->loop, &tunnel->handshake_timer);
uv_timer_start(&tunnel->handshake_timer, _tf_ssb_connection_handshake_timer_callback, k_handshake_timeout_ms, 0);
tunnel->object = JS_NewObjectClass(ssb->context, _connection_class_id); tunnel->object = JS_NewObjectClass(ssb->context, _connection_class_id);
JS_SetOpaque(tunnel->object, tunnel); JS_SetOpaque(tunnel->object, tunnel);
JS_SetPropertyStr(context, tunnel->object, "id", JS_NewString(context, target_id)); JS_SetPropertyStr(context, tunnel->object, "id", JS_NewString(context, target_id));
@ -2807,30 +2722,22 @@ typedef struct _connect_t
static void _tf_on_connect_getaddrinfo(uv_getaddrinfo_t* addrinfo, int result, struct addrinfo* info) static void _tf_on_connect_getaddrinfo(uv_getaddrinfo_t* addrinfo, int result, struct addrinfo* info)
{ {
connect_t* connect = addrinfo->data; connect_t* connect = addrinfo->data;
if (!connect->ssb->shutting_down) if (result == 0 && info)
{ {
if (result == 0 && info) struct sockaddr_in addr = *(struct sockaddr_in*)info->ai_addr;
{ addr.sin_port = htons(connect->port);
struct sockaddr_in addr = *(struct sockaddr_in*)info->ai_addr; tf_ssb_connection_create(connect->ssb, connect->host, &addr, connect->key);
addr.sin_port = htons(connect->port); }
tf_ssb_connection_create(connect->ssb, connect->host, &addr, connect->key); else
} {
else tf_printf("getaddrinfo(%s) => %s\n", connect->host, uv_strerror(result));
{
tf_printf("getaddrinfo(%s) => %s\n", connect->host, uv_strerror(result));
}
} }
uv_freeaddrinfo(info);
tf_ssb_unref(connect->ssb);
tf_free(connect); tf_free(connect);
uv_freeaddrinfo(info);
} }
void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* key) void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* key)
{ {
if (ssb->shutting_down)
{
return;
}
connect_t* connect = tf_malloc(sizeof(connect_t)); connect_t* connect = tf_malloc(sizeof(connect_t));
*connect = (connect_t) { *connect = (connect_t) {
.ssb = ssb, .ssb = ssb,
@ -2842,13 +2749,11 @@ void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* ke
tf_ssb_connections_store(ssb->connections_tracker, host, port, id); tf_ssb_connections_store(ssb->connections_tracker, host, port, id);
snprintf(connect->host, sizeof(connect->host), "%s", host); snprintf(connect->host, sizeof(connect->host), "%s", host);
memcpy(connect->key, key, k_id_bin_len); memcpy(connect->key, key, k_id_bin_len);
tf_ssb_ref(ssb);
int r = uv_getaddrinfo(ssb->loop, &connect->req, _tf_on_connect_getaddrinfo, host, NULL, &(struct addrinfo) { .ai_family = AF_INET }); int r = uv_getaddrinfo(ssb->loop, &connect->req, _tf_on_connect_getaddrinfo, host, NULL, &(struct addrinfo) { .ai_family = AF_INET });
if (r < 0) if (r < 0)
{ {
tf_printf("uv_getaddrinfo: %s\n", uv_strerror(r)); tf_printf("uv_getaddrinfo: %s\n", uv_strerror(r));
tf_free(connect); tf_free(connect);
tf_ssb_unref(ssb);
} }
} }
@ -2891,10 +2796,6 @@ static void _tf_ssb_on_connection(uv_stream_t* stream, int status)
return; return;
} }
connection->handshake_timer.data = connection;
uv_timer_init(ssb->loop, &connection->handshake_timer);
uv_timer_start(&connection->handshake_timer, _tf_ssb_connection_handshake_timer_callback, k_handshake_timeout_ms, 0);
struct sockaddr_storage addr = { 0 }; struct sockaddr_storage addr = { 0 };
int size = sizeof(addr); int size = sizeof(addr);
if (uv_tcp_getpeername(&connection->tcp, (struct sockaddr*)&addr, &size) == 0) if (uv_tcp_getpeername(&connection->tcp, (struct sockaddr*)&addr, &size) == 0)
@ -2906,10 +2807,10 @@ static void _tf_ssb_on_connection(uv_stream_t* stream, int status)
connection->next = ssb->connections; connection->next = ssb->connections;
ssb->connections = connection; ssb->connections = connection;
ssb->connections_count++; ssb->connections_count++;
_tf_ssb_notify_connections_changed(ssb, k_tf_ssb_change_create, connection);
connection->state = k_tf_ssb_state_server_wait_hello; connection->state = k_tf_ssb_state_server_wait_hello;
_tf_ssb_connection_read_start(connection); uv_read_start((uv_stream_t*)&connection->tcp, _tf_ssb_connection_on_tcp_alloc, _tf_ssb_connection_on_tcp_recv);
_tf_ssb_notify_connections_changed(ssb, k_tf_ssb_change_create, connection);
} }
static void _tf_ssb_send_broadcast(tf_ssb_t* ssb, struct sockaddr_in* address, struct sockaddr_in* netmask) static void _tf_ssb_send_broadcast(tf_ssb_t* ssb, struct sockaddr_in* address, struct sockaddr_in* netmask)
@ -2953,60 +2854,6 @@ static void _tf_ssb_send_broadcast(tf_ssb_t* ssb, struct sockaddr_in* address, s
} }
} }
typedef struct _seeds_t
{
char** seeds;
int seeds_count;
} seeds_t;
static void _tf_ssb_update_seed_callback(void* arg, ares_status_t status, size_t timeouts, const ares_dns_record_t* record)
{
seeds_t* seeds = arg;
for (int i = 0; i < (int)ares_dns_record_rr_cnt(record, ARES_SECTION_ANSWER); i++)
{
const ares_dns_rr_t* rr = ares_dns_record_rr_get_const(record, ARES_SECTION_ANSWER, i);
size_t len = 0;
const unsigned char* str = ares_dns_rr_get_bin(rr, ARES_RR_TXT_DATA, &len);
seeds->seeds = tf_resize_vec(seeds->seeds, sizeof(char*) * (seeds->seeds_count + 1));
seeds->seeds[seeds->seeds_count++] = tf_strdup((const char*)str);
}
}
static void _tf_ssb_update_seeds_work(tf_ssb_t* ssb, void* user_data)
{
if (ares_library_init(0) == ARES_SUCCESS)
{
ares_channel channel;
struct ares_options options = { .evsys = ARES_EVSYS_DEFAULT };
int result = ares_init_options(&channel, &options, ARES_OPT_EVENT_THREAD);
if (result == ARES_SUCCESS)
{
if (ares_query_dnsrec(channel, ssb->seeds_host, ARES_CLASS_IN, ARES_REC_TYPE_TXT, _tf_ssb_update_seed_callback, user_data, NULL) == ARES_SUCCESS)
{
ares_queue_wait_empty(channel, -1);
}
ares_destroy(channel);
}
ares_library_cleanup();
}
}
static void _tf_ssb_update_seeds_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
seeds_t* seeds = user_data;
for (int i = 0; i < seeds->seeds_count; i++)
{
tf_ssb_broadcast_t broadcast = { .origin = k_tf_ssb_broadcast_origin_peer_exchange };
if (_tf_ssb_parse_broadcast(seeds->seeds[i], &broadcast))
{
_tf_ssb_add_broadcast(ssb, &broadcast, k_seed_expire_seconds);
}
tf_free(seeds->seeds[i]);
}
tf_free(seeds->seeds);
tf_free(seeds);
}
static void _tf_ssb_broadcast_timer(uv_timer_t* timer) static void _tf_ssb_broadcast_timer(uv_timer_t* timer)
{ {
tf_ssb_t* ssb = timer->data; tf_ssb_t* ssb = timer->data;
@ -3023,15 +2870,6 @@ static void _tf_ssb_broadcast_timer(uv_timer_t* timer)
} }
uv_free_interface_addresses(info, count); uv_free_interface_addresses(info, count);
} }
time_t now = time(NULL);
if (ssb->is_peer_exchange && *ssb->seeds_host && now - ssb->last_seed_check > k_seed_check_interval_seconds)
{
seeds_t* seeds = tf_malloc(sizeof(seeds_t));
*seeds = (seeds_t) { 0 };
ssb->last_seed_check = now;
tf_ssb_run_work(ssb, _tf_ssb_update_seeds_work, _tf_ssb_update_seeds_after_work, seeds);
}
} }
int tf_ssb_server_open(tf_ssb_t* ssb, int port) int tf_ssb_server_open(tf_ssb_t* ssb, int port)
@ -3075,21 +2913,6 @@ int tf_ssb_server_open(tf_ssb_t* ssb, int port)
return status == 0 ? assigned_port : 0; return status == 0 ? assigned_port : 0;
} }
int tf_ssb_server_get_port(tf_ssb_t* ssb)
{
int port = 0;
if (ssb && ssb->server.data)
{
struct sockaddr_storage name = { 0 };
int size = (int)sizeof(name);
if (uv_tcp_getsockname(&ssb->server, (struct sockaddr*)&name, &size) == 0)
{
port = ntohs(((struct sockaddr_in*)&name)->sin_port);
}
}
return port;
}
void tf_ssb_server_close(tf_ssb_t* ssb) void tf_ssb_server_close(tf_ssb_t* ssb)
{ {
if (ssb->server.data && !uv_is_closing((uv_handle_t*)&ssb->server)) if (ssb->server.data && !uv_is_closing((uv_handle_t*)&ssb->server))
@ -3159,7 +2982,7 @@ static void _tf_ssb_notify_broadcasts_changed(tf_ssb_t* ssb)
} }
} }
static void _tf_ssb_add_broadcast(tf_ssb_t* ssb, const tf_ssb_broadcast_t* broadcast, int expires_seconds) static void _tf_ssb_add_broadcast(tf_ssb_t* ssb, const tf_ssb_broadcast_t* broadcast)
{ {
if (memcmp(broadcast->pub, ssb->pub, sizeof(ssb->pub)) == 0) if (memcmp(broadcast->pub, ssb->pub, sizeof(ssb->pub)) == 0)
{ {
@ -3173,7 +2996,6 @@ static void _tf_ssb_add_broadcast(tf_ssb_t* ssb, const tf_ssb_broadcast_t* broad
if (node->tunnel_connection == broadcast->tunnel_connection && memcmp(node->pub, broadcast->pub, sizeof(node->pub)) == 0) if (node->tunnel_connection == broadcast->tunnel_connection && memcmp(node->pub, broadcast->pub, sizeof(node->pub)) == 0)
{ {
node->mtime = time(NULL); node->mtime = time(NULL);
node->expires_at = node->mtime + expires_seconds;
return; return;
} }
} }
@ -3186,7 +3008,6 @@ static void _tf_ssb_add_broadcast(tf_ssb_t* ssb, const tf_ssb_broadcast_t* broad
node->addr.sin_addr.s_addr == broadcast->addr.sin_addr.s_addr && memcmp(node->pub, broadcast->pub, sizeof(node->pub)) == 0) node->addr.sin_addr.s_addr == broadcast->addr.sin_addr.s_addr && memcmp(node->pub, broadcast->pub, sizeof(node->pub)) == 0)
{ {
node->mtime = time(NULL); node->mtime = time(NULL);
node->expires_at = node->mtime + expires_seconds;
return; return;
} }
} }
@ -3203,22 +3024,12 @@ static void _tf_ssb_add_broadcast(tf_ssb_t* ssb, const tf_ssb_broadcast_t* broad
node->next = ssb->broadcasts; node->next = ssb->broadcasts;
node->ctime = time(NULL); node->ctime = time(NULL);
node->mtime = node->ctime; node->mtime = node->ctime;
node->expires_at = node->mtime + expires_seconds;
ssb->broadcasts = node; ssb->broadcasts = node;
ssb->broadcasts_count++; ssb->broadcasts_count++;
_tf_ssb_notify_broadcasts_changed(ssb); _tf_ssb_notify_broadcasts_changed(ssb);
} }
void tf_ssb_add_broadcast(tf_ssb_t* ssb, const char* connection, tf_ssb_broadcast_origin_t origin, int64_t expires_seconds)
{
tf_ssb_broadcast_t broadcast = { .origin = origin };
if (_tf_ssb_parse_broadcast(connection, &broadcast))
{
_tf_ssb_add_broadcast(ssb, &broadcast, expires_seconds);
}
}
static void _tf_ssb_on_broadcast_listener_recv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) static void _tf_ssb_on_broadcast_listener_recv(uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags)
{ {
if (nread <= 0) if (nread <= 0)
@ -3235,19 +3046,18 @@ static void _tf_ssb_on_broadcast_listener_recv(uv_udp_t* handle, ssize_t nread,
char* entry = strtok_r(buf->base, k_delim, &state); char* entry = strtok_r(buf->base, k_delim, &state);
while (entry) while (entry)
{ {
tf_ssb_broadcast_t broadcast = { .origin = k_tf_ssb_broadcast_origin_discovery }; tf_ssb_broadcast_t broadcast = { 0 };
if (_tf_ssb_parse_broadcast(entry, &broadcast)) if (_tf_ssb_parse_broadcast(entry, &broadcast))
{ {
_tf_ssb_add_broadcast(ssb, &broadcast, k_udp_discovery_expires_seconds); _tf_ssb_add_broadcast(ssb, &broadcast);
} }
entry = strtok_r(NULL, k_delim, &state); entry = strtok_r(NULL, k_delim, &state);
} }
tf_free(buf->base); tf_free(buf->base);
} }
void tf_ssb_visit_broadcasts(tf_ssb_t* ssb, void tf_ssb_visit_broadcasts(
void (*callback)(const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data), tf_ssb_t* ssb, void (*callback)(const char* host, const struct sockaddr_in* addr, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data), void* user_data)
void* user_data)
{ {
time_t now = time(NULL); time_t now = time(NULL);
tf_ssb_broadcast_t* next = NULL; tf_ssb_broadcast_t* next = NULL;
@ -3257,7 +3067,7 @@ void tf_ssb_visit_broadcasts(tf_ssb_t* ssb,
if (node->mtime - now < 60) if (node->mtime - now < 60)
{ {
tf_trace_begin(ssb->trace, "broadcast"); tf_trace_begin(ssb->trace, "broadcast");
callback(node->host, &node->addr, node->origin, node->tunnel_connection, node->pub, user_data); callback(node->host, &node->addr, node->tunnel_connection, node->pub, user_data);
tf_trace_end(ssb->trace); tf_trace_end(ssb->trace);
} }
} }
@ -3270,7 +3080,7 @@ static void _tf_ssb_broadcast_cleanup_timer(uv_timer_t* timer)
time_t now = time(NULL); time_t now = time(NULL);
for (tf_ssb_broadcast_t** it = &ssb->broadcasts; *it;) for (tf_ssb_broadcast_t** it = &ssb->broadcasts; *it;)
{ {
if (!(*it)->tunnel_connection && now > (*it)->expires_at) if (!(*it)->tunnel_connection && (*it)->mtime < now - 10)
{ {
tf_ssb_broadcast_t* node = *it; tf_ssb_broadcast_t* node = *it;
*it = node->next; *it = node->next;
@ -3683,11 +3493,10 @@ void tf_ssb_notify_blob_want_added(tf_ssb_t* ssb, const char* id)
void tf_ssb_connection_add_room_attendant(tf_ssb_connection_t* connection, const char* id) void tf_ssb_connection_add_room_attendant(tf_ssb_connection_t* connection, const char* id)
{ {
tf_ssb_broadcast_t broadcast = { tf_ssb_broadcast_t broadcast = {
.origin = k_tf_ssb_broadcast_origin_room,
.tunnel_connection = connection, .tunnel_connection = connection,
}; };
tf_ssb_id_str_to_bin(broadcast.pub, id); tf_ssb_id_str_to_bin(broadcast.pub, id);
_tf_ssb_add_broadcast(connection->ssb, &broadcast, 0); _tf_ssb_add_broadcast(connection->ssb, &broadcast);
} }
void tf_ssb_connection_remove_room_attendant(tf_ssb_connection_t* connection, const char* id) void tf_ssb_connection_remove_room_attendant(tf_ssb_connection_t* connection, const char* id)
@ -3909,12 +3718,7 @@ void tf_ssb_ref(tf_ssb_t* ssb)
void tf_ssb_unref(tf_ssb_t* ssb) void tf_ssb_unref(tf_ssb_t* ssb)
{ {
int new_count = --ssb->ref_count; ssb->ref_count--;
if (new_count < 0)
{
tf_printf("tf_ssb_unref past 0: %d\n", new_count);
abort();
}
} }
void tf_ssb_set_main_thread(tf_ssb_t* ssb, bool main_thread) void tf_ssb_set_main_thread(tf_ssb_t* ssb, bool main_thread)
@ -4068,26 +3872,6 @@ const char* tf_ssb_get_room_name(tf_ssb_t* ssb)
return ssb->room_name; return ssb->room_name;
} }
bool tf_ssb_is_replicator(tf_ssb_t* ssb)
{
return ssb->is_replicator;
}
void tf_ssb_set_is_replicator(tf_ssb_t* ssb, bool is_replicator)
{
ssb->is_replicator = is_replicator;
}
bool tf_ssb_is_peer_exchange(tf_ssb_t* ssb)
{
return ssb->is_peer_exchange;
}
void tf_ssb_set_is_peer_exchange(tf_ssb_t* ssb, bool is_peer_exchange)
{
ssb->is_peer_exchange = is_peer_exchange;
}
void tf_ssb_set_room_name(tf_ssb_t* ssb, const char* room_name) void tf_ssb_set_room_name(tf_ssb_t* ssb, const char* room_name)
{ {
tf_free(ssb->room_name); tf_free(ssb->room_name);
@ -4097,9 +3881,6 @@ void tf_ssb_set_room_name(tf_ssb_t* ssb, const char* room_name)
typedef struct _update_settings_t typedef struct _update_settings_t
{ {
bool is_room; bool is_room;
bool is_replicator;
bool is_peer_exchange;
char seeds_host[256];
char room_name[1024]; char room_name[1024];
} update_settings_t; } update_settings_t;
@ -4156,10 +3937,7 @@ static void _tf_ssb_update_settings_work(tf_ssb_t* ssb, void* user_data)
{ {
update_settings_t* update = user_data; update_settings_t* update = user_data;
update->is_room = _get_global_setting_bool(ssb, "room", true); update->is_room = _get_global_setting_bool(ssb, "room", true);
update->is_replicator = _get_global_setting_bool(ssb, "replicator", true);
update->is_peer_exchange = _get_global_setting_bool(ssb, "peer_exchange", true);
_get_global_setting_string(ssb, "room_name", update->room_name, sizeof(update->room_name)); _get_global_setting_string(ssb, "room_name", update->room_name, sizeof(update->room_name));
_get_global_setting_string(ssb, "seeds_host", update->seeds_host, sizeof(update->seeds_host));
} }
static void _tf_ssb_update_settings_after_work(tf_ssb_t* ssb, int result, void* user_data) static void _tf_ssb_update_settings_after_work(tf_ssb_t* ssb, int result, void* user_data)
@ -4167,9 +3945,6 @@ static void _tf_ssb_update_settings_after_work(tf_ssb_t* ssb, int result, void*
update_settings_t* update = user_data; update_settings_t* update = user_data;
tf_ssb_set_is_room(ssb, update->is_room); tf_ssb_set_is_room(ssb, update->is_room);
tf_ssb_set_room_name(ssb, update->room_name); tf_ssb_set_room_name(ssb, update->room_name);
tf_ssb_set_is_peer_exchange(ssb, update->is_peer_exchange);
tf_ssb_set_is_replicator(ssb, update->is_replicator);
snprintf(ssb->seeds_host, sizeof(ssb->seeds_host), "%s", update->seeds_host);
_tf_ssb_start_update_settings(ssb); _tf_ssb_start_update_settings(ssb);
tf_free(update); tf_free(update);
} }
@ -4274,32 +4049,3 @@ JSValue tf_ssb_connection_requests_to_object(tf_ssb_connection_t* connection)
} }
return object; return object;
} }
void tf_ssb_connection_adjust_read_backpressure(tf_ssb_connection_t* connection, int delta)
{
const int k_threshold = 256;
int old_pressure = connection->read_back_pressure;
connection->read_back_pressure += delta;
if (!connection->closing)
{
if (old_pressure < k_threshold && connection->read_back_pressure >= k_threshold)
{
_tf_ssb_connection_read_stop(connection);
}
else if (old_pressure >= k_threshold && connection->read_back_pressure < k_threshold)
{
_tf_ssb_connection_read_start(connection);
}
}
connection->ref_count += delta;
if (connection->ref_count == 0 && connection->closing)
{
_tf_ssb_connection_destroy(connection, "backpressure released");
}
}
void tf_ssb_connection_adjust_write_count(tf_ssb_connection_t* connection, int delta)
{
connection->active_write_count += delta;
_tf_ssb_connection_dispatch_scheduled(connection);
}

View File

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

View File

@ -133,7 +133,6 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_sequence_index ON messages (author, sequence)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_sequence_index ON messages (author, sequence)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_timestamp_index ON messages (author, timestamp)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_timestamp_index ON messages (author, timestamp)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_timestamp_index ON messages (timestamp)"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_timestamp_index ON messages (timestamp)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_type_timestamp_index ON messages (content ->> 'type', timestamp)");
_tf_ssb_db_exec(db, _tf_ssb_db_exec(db,
"CREATE TABLE IF NOT EXISTS blobs (" "CREATE TABLE IF NOT EXISTS blobs ("
" id TEXT PRIMARY KEY," " id TEXT PRIMARY KEY,"
@ -282,7 +281,7 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
tf_ssb_release_db_writer(ssb, db); tf_ssb_release_db_writer(ssb, db);
} }
static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author, int64_t sequence, const char* previous, bool* out_id_mismatch) static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author, int64_t sequence, const char* previous)
{ {
bool exists = false; bool exists = false;
if (sequence == 1) if (sequence == 1)
@ -292,13 +291,12 @@ static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author,
else else
{ {
sqlite3_stmt* statement; sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT COUNT(*), id != ?3 AS is_mismatch FROM messages WHERE author = ?1 AND sequence = ?2", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare(db, "SELECT COUNT(*) FROM messages WHERE author = ?1 AND sequence = ?2 AND id = ?3", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, sequence - 1) == SQLITE_OK && if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, sequence - 1) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, previous, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) sqlite3_bind_text(statement, 3, previous, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{ {
exists = sqlite3_column_int(statement, 0) != 0; exists = sqlite3_column_int(statement, 0) != 0;
*out_id_mismatch = sqlite3_column_int(statement, 1) != 0;
} }
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
@ -311,9 +309,8 @@ static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const
{ {
sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3* db = tf_ssb_acquire_db_writer(ssb);
int64_t last_row_id = -1; int64_t last_row_id = -1;
bool id_mismatch = false;
if (_tf_ssb_db_previous_message_exists(db, author, sequence, previous, &id_mismatch)) if (_tf_ssb_db_previous_message_exists(db, author, sequence, previous))
{ {
const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, flags) VALUES (?, ?, ?, ?, ?, jsonb(?), " const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, flags) VALUES (?, ?, ?, ?, ?, jsonb(?), "
"?, ?, ?) ON CONFLICT DO NOTHING"; "?, ?, ?) ON CONFLICT DO NOTHING";
@ -348,14 +345,8 @@ static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db)); tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
} }
} }
else if (id_mismatch) else
{ {
/*
** Only warn if we find a previous message with the wrong ID.
** If a feed is forked, we would otherwise warn on every
** message when trying to receive what we don't have, and
** that's not helping anybody.
*/
tf_printf("%p: Previous message doesn't exist for author=%s sequence=%" PRId64 " previous=%s.\n", db, author, sequence, previous); tf_printf("%p: Previous message doesn't exist for author=%s sequence=%" PRId64 " previous=%s.\n", db, author, sequence, previous);
} }
tf_ssb_release_db_writer(ssb, db); tf_ssb_release_db_writer(ssb, db);
@ -644,44 +635,6 @@ bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_
return result; return result;
} }
typedef struct _blob_get_async_t
{
tf_ssb_t* ssb;
char id[k_blob_id_len];
tf_ssb_db_blob_get_callback_t* callback;
void* user_data;
bool out_found;
uint8_t* out_data;
size_t out_size;
} blob_get_async_t;
static void _tf_ssb_db_blob_get_async_work(tf_ssb_t* ssb, void* user_data)
{
blob_get_async_t* async = user_data;
async->out_found = tf_ssb_db_blob_get(ssb, async->id, &async->out_data, &async->out_size);
}
static void _tf_ssb_db_blob_get_async_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
blob_get_async_t* async = user_data;
async->callback(async->out_found, async->out_data, async->out_size, async->user_data);
tf_free(async->out_data);
tf_free(async);
}
void tf_ssb_db_blob_get_async(tf_ssb_t* ssb, const char* id, tf_ssb_db_blob_get_callback_t* callback, void* user_data)
{
blob_get_async_t* async = tf_malloc(sizeof(blob_get_async_t));
*async = (blob_get_async_t) {
.ssb = ssb,
.callback = callback,
.user_data = user_data,
};
snprintf(async->id, sizeof(async->id), "%s", id);
tf_ssb_run_work(ssb, _tf_ssb_db_blob_get_async_work, _tf_ssb_db_blob_get_async_after_work, async);
}
typedef struct _blob_store_work_t typedef struct _blob_store_work_t
{ {
const uint8_t* blob; const uint8_t* blob;
@ -695,10 +648,7 @@ typedef struct _blob_store_work_t
static void _tf_ssb_db_blob_store_work(tf_ssb_t* ssb, void* user_data) static void _tf_ssb_db_blob_store_work(tf_ssb_t* ssb, void* user_data)
{ {
blob_store_work_t* blob_work = user_data; blob_store_work_t* blob_work = user_data;
if (!tf_ssb_is_shutting_down(ssb)) tf_ssb_db_blob_store(ssb, blob_work->blob, blob_work->size, blob_work->id, sizeof(blob_work->id), &blob_work->is_new);
{
tf_ssb_db_blob_store(ssb, blob_work->blob, blob_work->size, blob_work->id, sizeof(blob_work->id), &blob_work->is_new);
}
} }
static void _tf_ssb_db_blob_store_after_work(tf_ssb_t* ssb, int status, void* user_data) static void _tf_ssb_db_blob_store_after_work(tf_ssb_t* ssb, int status, void* user_data)
@ -1096,8 +1046,6 @@ bool tf_ssb_db_identity_create(tf_ssb_t* ssb, const char* user, uint8_t* out_pub
if (out_private_key) if (out_private_key)
{ {
tf_ssb_id_str_to_bin(out_private_key, private); tf_ssb_id_str_to_bin(out_private_key, private);
/* HACK: tf_ssb_id_str_to_bin only produces 32 bytes even though the full private key is 32 + 32. */
tf_ssb_id_str_to_bin(out_private_key + crypto_sign_PUBLICKEYBYTES, public);
} }
return true; return true;
} }
@ -1645,13 +1593,14 @@ bool tf_ssb_db_get_account_password_hash(tf_ssb_t* ssb, const char* name, char*
return result; return result;
} }
bool tf_ssb_db_set_account_password(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password) bool tf_ssb_db_set_account_password(tf_ssb_t* ssb, const char* name, const char* password)
{ {
JSContext* context = tf_ssb_get_context(ssb);
bool result = false; bool result = false;
static const int k_salt_length = 12; static const int k_salt_length = 12;
char buffer[16]; char buffer[16];
size_t bytes = uv_random(loop, &(uv_random_t) { 0 }, buffer, sizeof(buffer), 0, NULL) == 0 ? sizeof(buffer) : 0; size_t bytes = uv_random(tf_ssb_get_loop(ssb), &(uv_random_t) { 0 }, buffer, sizeof(buffer), 0, NULL) == 0 ? sizeof(buffer) : 0;
char output[7 + 22 + 1]; char output[7 + 22 + 1];
char* salt = crypt_gensalt_rn("$2b$", k_salt_length, buffer, bytes, output, sizeof(output)); char* salt = crypt_gensalt_rn("$2b$", k_salt_length, buffer, bytes, output, sizeof(output));
char hash_output[7 + 22 + 31 + 1]; char hash_output[7 + 22 + 31 + 1];
@ -1663,6 +1612,7 @@ bool tf_ssb_db_set_account_password(uv_loop_t* loop, sqlite3* db, JSContext* con
size_t user_length = 0; size_t user_length = 0;
const char* user_string = JS_ToCStringLen(context, &user_length, user_json); const char* user_string = JS_ToCStringLen(context, &user_length, user_json);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL; sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'user:' || ?, ?)", -1, &statement, NULL) == SQLITE_OK) if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'user:' || ?, ?)", -1, &statement, NULL) == SQLITE_OK)
{ {
@ -1672,6 +1622,7 @@ bool tf_ssb_db_set_account_password(uv_loop_t* loop, sqlite3* db, JSContext* con
} }
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
tf_ssb_release_db_writer(ssb, db);
JS_FreeCString(context, user_string); JS_FreeCString(context, user_string);
JS_FreeValue(context, user_json); JS_FreeValue(context, user_json);
@ -1679,70 +1630,47 @@ bool tf_ssb_db_set_account_password(uv_loop_t* loop, sqlite3* db, JSContext* con
return result; return result;
} }
static bool _tf_ssb_db_get_global_setting_bool(sqlite3* db, const char* name, bool default_value) bool tf_ssb_db_register_account(tf_ssb_t* ssb, const char* name, const char* password)
{ {
bool result = default_value; bool result = false;
sqlite3_stmt* statement; JSContext* context = tf_ssb_get_context(ssb);
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) JSValue users_array = JS_UNDEFINED;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'auth' AND key = 'users'", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK) if (sqlite3_step(statement) == SQLITE_ROW)
{ {
if (sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_type(statement, 0) != SQLITE_NULL) users_array = JS_ParseJSON(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0), NULL);
{
result = sqlite3_column_int(statement, 0) != 0;
}
} }
sqlite3_finalize(statement); sqlite3_finalize(statement);
} }
else if (JS_IsUndefined(users_array))
{ {
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db)); users_array = JS_NewArray(context);
} }
return result; int length = tf_util_get_length(context, users_array);
} JS_SetPropertyUint32(context, users_array, length, JS_NewString(context, name));
bool tf_ssb_db_register_account(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password) JSValue json = JS_JSONStringify(context, users_array, JS_NULL, JS_NULL);
{ JS_FreeValue(context, users_array);
bool result = false; size_t value_length = 0;
JSValue users_array = JS_UNDEFINED; const char* value = JS_ToCStringLen(context, &value_length, json);
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'users', ?)", -1, &statement, NULL) == SQLITE_OK)
bool registration_allowed = _tf_ssb_db_get_global_setting_bool(db, "account_registration", true);
if (registration_allowed)
{ {
sqlite3_stmt* statement = NULL; if (sqlite3_bind_text(statement, 1, value, value_length, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'auth' AND key = 'users'", -1, &statement, NULL) == SQLITE_OK)
{ {
if (sqlite3_step(statement) == SQLITE_ROW) tf_printf("added user to properties\n");
{ result = sqlite3_step(statement) == SQLITE_DONE;
users_array = JS_ParseJSON(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0), NULL);
}
sqlite3_finalize(statement);
} }
if (JS_IsUndefined(users_array)) sqlite3_finalize(statement);
{
users_array = JS_NewArray(context);
}
int length = tf_util_get_length(context, users_array);
JS_SetPropertyUint32(context, users_array, length, JS_NewString(context, name));
JSValue json = JS_JSONStringify(context, users_array, JS_NULL, JS_NULL);
JS_FreeValue(context, users_array);
size_t value_length = 0;
const char* value = JS_ToCStringLen(context, &value_length, json);
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'users', ?)", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, value, value_length, NULL) == SQLITE_OK)
{
tf_printf("added user to properties\n");
result = sqlite3_step(statement) == SQLITE_DONE;
}
sqlite3_finalize(statement);
}
JS_FreeCString(context, value);
JS_FreeValue(context, json);
} }
JS_FreeCString(context, value);
JS_FreeValue(context, json);
tf_ssb_release_db_writer(ssb, db);
result = result && tf_ssb_db_set_account_password(loop, db, context, name, password); result = result && tf_ssb_db_set_account_password(ssb, name, password);
return result; return result;
} }
@ -1946,24 +1874,3 @@ bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id)
} }
return verified; return verified;
} }
bool tf_ssb_db_user_has_permission(tf_ssb_t* ssb, const char* id, const char* permission)
{
bool has_permission = false;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
if (sqlite3_prepare(db,
"SELECT COUNT(*) FROM properties, json_each(properties.value -> 'permissions' -> ?) AS permission WHERE properties.id = 'core' AND properties.key = 'settings' AND "
"permission.value = ?",
-1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, permission, -1, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
{
has_permission = sqlite3_column_int64(statement, 0) > 0;
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return has_permission;
}

View File

@ -55,24 +55,6 @@ bool tf_ssb_db_blob_has(tf_ssb_t* ssb, const char* id);
*/ */
bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_t* out_size); bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_t* out_size);
/**
** A function called when a blob is retrieved from the database.
** @param found Whether the blob was found.
** @param data The blob data if found.
** @param size The size of the blob data if found, in bytes.
** @param user_data The user data.
*/
typedef void(tf_ssb_db_blob_get_callback_t)(bool found, const uint8_t* data, size_t size, void* user_data);
/**
** Retrieve a blob from the database asynchronously.
** @param ssb The SSB instance.
** @param id The blob identifier.
** @param callback Callback called with the result.
** @param user_data The user data.
*/
void tf_ssb_db_blob_get_async(tf_ssb_t* ssb, const char* id, tf_ssb_db_blob_get_callback_t* callback, void* user_data);
/** /**
** A function called when a message is stored in the database. ** A function called when a message is stored in the database.
** @param id The message identifier. ** @param id The message identifier.
@ -360,25 +342,21 @@ bool tf_ssb_db_get_account_password_hash(tf_ssb_t* ssb, const char* name, char*
/** /**
** Insert or update a user's hashed password in the database. ** Insert or update a user's hashed password in the database.
** @param loop The event loop. ** @param ssb The SSB instance.
** @param db A DB writer.
** @param context A JS context.
** @param name The username. ** @param name The username.
** @param password The raw password. ** @param password The raw password.
** @return true if the hash of the password was successfully stored. ** @return true if the hash of the password was successfully stored.
*/ */
bool tf_ssb_db_set_account_password(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password); bool tf_ssb_db_set_account_password(tf_ssb_t* ssb, const char* name, const char* password);
/** /**
** Add a user account to the database. ** Add a user account to the database.
** @param loop The event loop. ** @param ssb The SSB instance.
** @param db A DB writer.
** @param context A JS context.
** @param name The username to add. ** @param name The username to add.
** @param password The user's raw password. ** @param password The user's raw password.
** @return true If the user was added successfully. ** @return true If the user was added successfully.
*/ */
bool tf_ssb_db_register_account(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password); bool tf_ssb_db_register_account(tf_ssb_t* ssb, const char* name, const char* password);
/** /**
** Get an entry from the properties table. ** Get an entry from the properties table.
@ -416,15 +394,6 @@ void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callb
*/ */
bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id); bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id);
/**
** Check if a user has a specific permission.
** @param ssb The SSB instance.
** @param id The user ID.
** @param permission The name of the permission.
** @return true If the user has the requested permission.
*/
bool tf_ssb_db_user_has_permission(tf_ssb_t* ssb, const char* id, const char* permission);
/** /**
** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use. ** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use.
** @param user_data User data registered with the authorizer. ** @param user_data User data registered with the authorizer.

View File

@ -28,8 +28,6 @@ enum
k_ssb_rpc_flag_new_request = 0x10, k_ssb_rpc_flag_new_request = 0x10,
k_ssb_blob_bytes_max = 5 * 1024 * 1024, k_ssb_blob_bytes_max = 5 * 1024 * 1024,
k_ssb_peer_exchange_expires_seconds = 60 * 60,
}; };
/** /**
@ -43,16 +41,6 @@ typedef enum _tf_ssb_change_t
k_tf_ssb_change_update, k_tf_ssb_change_update,
} tf_ssb_change_t; } tf_ssb_change_t;
/**
** The origin of a broadcast entry.
*/
typedef enum _tf_ssb_broadcast_origin_t
{
k_tf_ssb_broadcast_origin_discovery,
k_tf_ssb_broadcast_origin_room,
k_tf_ssb_broadcast_origin_peer_exchange,
} tf_ssb_broadcast_origin_t;
/** /**
** Flags describing the structure of a message. ** Flags describing the structure of a message.
*/ */
@ -156,13 +144,6 @@ tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path
*/ */
void tf_ssb_destroy(tf_ssb_t* ssb); void tf_ssb_destroy(tf_ssb_t* ssb);
/**
** Checking if the SSB instance is in the process of shutting down.
** @param ssb The SSB instance.
** @return true If the SSB instance is shutting down.
*/
bool tf_ssb_is_shutting_down(tf_ssb_t* ssb);
/** /**
** Start optional periodic work. ** Start optional periodic work.
** @param ssb The SSB instance. ** @param ssb The SSB instance.
@ -290,11 +271,9 @@ void tf_ssb_run(tf_ssb_t* ssb);
** @param author The author's public key. ** @param author The author's public key.
** @param private_key The author's private key. ** @param private_key The author's private key.
** @param message The message to sign. ** @param message The message to sign.
** @param previous_id The ID of the previous message in the feed. Optional.
** @param previous_sequence The sequence number of the previous message in the feed. Optional.
** @return The signed message. ** @return The signed message.
*/ */
JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message, const char* previous_id, int64_t previous_sequence); JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message);
/** /**
** Get the server's identity. ** Get the server's identity.
@ -311,18 +290,8 @@ bool tf_ssb_whoami(tf_ssb_t* ssb, char* out_id, size_t out_id_size);
** @param callback The callback. ** @param callback The callback.
** @param user_data User data for the callback. ** @param user_data User data for the callback.
*/ */
void tf_ssb_visit_broadcasts(tf_ssb_t* ssb, void tf_ssb_visit_broadcasts(
void (*callback)(const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data), tf_ssb_t* ssb, void (*callback)(const char* host, const struct sockaddr_in* addr, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data), void* user_data);
void* user_data);
/**
** Add a broadcast entry.
** @param ssb The SSB instance.
** @param connection The connection string to add.
** @param origin The origin of the broadcast entry.
** @param expires_seconds How long the broadcast entry should last.
*/
void tf_ssb_add_broadcast(tf_ssb_t* ssb, const char* connection, tf_ssb_broadcast_origin_t origin, int64_t expires_seconds);
/** /**
** Get the identities of all active connections. ** Get the identities of all active connections.
@ -364,13 +333,6 @@ void tf_ssb_connect_str(tf_ssb_t* ssb, const char* address);
*/ */
int tf_ssb_server_open(tf_ssb_t* ssb, int port); int tf_ssb_server_open(tf_ssb_t* ssb, int port);
/**
** Determine the port that a server is listening on.
** @param ssb The SSB instance.
** @return The port number, or 0 if not bound.
*/
int tf_ssb_server_get_port(tf_ssb_t* ssb);
/** /**
** Stop listening for SHS connections. ** Stop listening for SHS connections.
** @param ssb The SSB instance. ** @param ssb The SSB instance.
@ -776,7 +738,7 @@ JSValue tf_ssb_connection_requests_to_object(tf_ssb_connection_t* connection);
typedef void(tf_ssb_scheduled_callback_t)(tf_ssb_connection_t* connection, void* user_data); typedef void(tf_ssb_scheduled_callback_t)(tf_ssb_connection_t* connection, void* user_data);
/** /**
** Schedule work to be run when the connection is next idle. ** Schedule work to be run when the server is next idle.
** @param connection The owning connection. ** @param connection The owning connection.
** @param callback The callback to call. ** @param callback The callback to call.
** @param user_data User data to pass to the callback. ** @param user_data User data to pass to the callback.
@ -993,34 +955,6 @@ bool tf_ssb_is_room(tf_ssb_t* ssb);
*/ */
void tf_ssb_set_is_room(tf_ssb_t* ssb, bool is_room); void tf_ssb_set_is_room(tf_ssb_t* ssb, bool is_room);
/**
** Get whether the running server supports replication of messages and blobs.
** @param ssb The SSB instance.
** @return True if the server is a replicator.
*/
bool tf_ssb_is_replicator(tf_ssb_t* ssb);
/**
** Set whether the running server supports replication of messages and blobs.
** @param ssb The SSB instance.
** @param is_replicator Whether to support replication.
*/
void tf_ssb_set_is_replicator(tf_ssb_t* ssb, bool is_replicator);
/**
** Get whether the running server participates in peer exchange.
** @param ssb The SSB instance.
** @return True if the server participates in peer exchange.
*/
bool tf_ssb_is_peer_exchange(tf_ssb_t* ssb);
/**
** Set whether the running server participates in peer exchange.
** @param ssb The SSB instance.
** @param is_peer_exchange Whether to participate in peer exchange.
*/
void tf_ssb_set_is_peer_exchange(tf_ssb_t* ssb, bool is_peer_exchange);
/** /**
** Get the name of the room hosted by the running server. ** Get the name of the room hosted by the running server.
** @param ssb The SSB instance. ** @param ssb The SSB instance.
@ -1055,22 +989,4 @@ void tf_ssb_schedule_work(tf_ssb_t* ssb, int delay_ms, void (*callback)(tf_ssb_t
*/ */
bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_t payload_length, const char* signature, bool signature_is_urlb64); bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_t payload_length, const char* signature, bool signature_is_urlb64);
/**
** Adjust read backpressure. If it gets too high, TCP receive will be paused
** until it lowers.
** @param connection The connection on which to affect backpressure.
** @param delta The change in backpressure. Higher will eventually pause
** receive. Lower will resume it.
*/
void tf_ssb_connection_adjust_read_backpressure(tf_ssb_connection_t* connection, int delta);
/**
** Adjust write count. Work scheduled by tf_ssb_connection_schedule_idle will
** only start when this reaches zero.
** @param connection The connection on which to affect backpressure.
** @param delta The change in write count. Higher will pause processing
** scheduled idle work queue. Lower will resume it.
*/
void tf_ssb_connection_adjust_write_count(tf_ssb_connection_t* connection, int delta);
/** @} */ /** @} */

View File

@ -231,11 +231,7 @@ static void _tf_ssb_import_app_json(tf_ssb_t* ssb, uv_loop_t* loop, JSContext* c
void tf_ssb_import(tf_ssb_t* ssb, const char* user, const char* path) void tf_ssb_import(tf_ssb_t* ssb, const char* user, const char* path)
{ {
if (!path) if (strlen(path) > strlen(".json") && strcasecmp(path + strlen(path) - strlen(".json"), ".json") == 0)
{
return;
}
else if (strlen(path) > strlen(".json") && strcasecmp(path + strlen(path) - strlen(".json"), ".json") == 0)
{ {
_tf_ssb_import_app_json(ssb, tf_ssb_get_loop(ssb), tf_ssb_get_context(ssb), user, path); _tf_ssb_import_app_json(ssb, tf_ssb_get_loop(ssb), tf_ssb_get_context(ssb), user, path);
} }

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,6 @@
#endif #endif
static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live); static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live);
static void _tf_ssb_rpc_send_peers_exchange(tf_ssb_connection_t* connection);
static void _tf_ssb_rpc_start_delete_blobs(tf_ssb_t* ssb, int delay_ms); static void _tf_ssb_rpc_start_delete_blobs(tf_ssb_t* ssb, int delay_ms);
static int64_t _get_global_setting_int64(tf_ssb_t* ssb, const char* name, int64_t default_value) static int64_t _get_global_setting_int64(tf_ssb_t* ssb, const char* name, int64_t default_value)
@ -68,58 +67,19 @@ static void _tf_ssb_rpc_blobs_get_callback(
{ {
} }
typedef struct _blobs_get_work_t
{
int64_t request_number;
char id[k_id_base64_len];
bool found;
uint8_t* blob;
size_t size;
} blobs_get_work_t;
static void _tf_ssb_rpc_blobs_get_work(tf_ssb_connection_t* connection, void* user_data)
{
blobs_get_work_t* work = user_data;
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
work->found = tf_ssb_db_blob_get(ssb, work->id, &work->blob, &work->size);
}
static void _tf_ssb_rpc_blobs_get_after_work(tf_ssb_connection_t* connection, int status, void* user_data)
{
blobs_get_work_t* work = user_data;
if (work->found)
{
const size_t k_send_max = 8192;
for (size_t offset = 0; offset < work->size; offset += k_send_max)
{
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_binary | k_ssb_rpc_flag_stream, -work->request_number, NULL, work->blob + offset,
offset + k_send_max <= work->size ? k_send_max : (work->size - offset), NULL, NULL, NULL);
}
tf_free(work->blob);
}
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error | k_ssb_rpc_flag_stream, -work->request_number, NULL,
(const uint8_t*)(work->found ? "true" : "false"), strlen(work->found ? "true" : "false"), NULL, NULL, NULL);
tf_free(work);
}
static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{ {
if (flags & k_ssb_rpc_flag_end_error) if (flags & k_ssb_rpc_flag_end_error)
{ {
return; return;
} }
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
if (!tf_ssb_is_replicator(ssb))
{
tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, "blobs.get");
return;
}
tf_ssb_connection_add_request(connection, -request_number, "blobs.get", _tf_ssb_rpc_blobs_get_callback, NULL, NULL, NULL); tf_ssb_connection_add_request(connection, -request_number, "blobs.get", _tf_ssb_rpc_blobs_get_callback, NULL, NULL, NULL);
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
JSContext* context = tf_ssb_connection_get_context(connection); JSContext* context = tf_ssb_connection_get_context(connection);
JSValue ids = JS_GetPropertyStr(context, args, "args"); JSValue ids = JS_GetPropertyStr(context, args, "args");
int length = tf_util_get_length(context, ids); int length = tf_util_get_length(context, ids);
bool success = false;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
JSValue arg = JS_GetPropertyUint32(context, ids, i); JSValue arg = JS_GetPropertyUint32(context, ids, i);
@ -134,65 +94,39 @@ static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags
id = JS_ToCString(context, key); id = JS_ToCString(context, key);
JS_FreeValue(context, key); JS_FreeValue(context, key);
} }
uint8_t* blob = NULL;
blobs_get_work_t* work = tf_malloc(sizeof(blobs_get_work_t)); size_t size = 0;
*work = (blobs_get_work_t) { const size_t k_send_max = 8192;
.request_number = request_number, if (tf_ssb_db_blob_get(ssb, id, &blob, &size))
}; {
snprintf(work->id, sizeof(work->id), "%s", id); for (size_t offset = 0; offset < size; offset += k_send_max)
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_blobs_get_work, _tf_ssb_rpc_blobs_get_after_work, work); {
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_binary | k_ssb_rpc_flag_stream, -request_number, NULL, blob + offset,
offset + k_send_max <= size ? k_send_max : (size - offset), NULL, NULL, NULL);
}
success = true;
tf_free(blob);
}
JS_FreeCString(context, id); JS_FreeCString(context, id);
JS_FreeValue(context, arg); JS_FreeValue(context, arg);
} }
JS_FreeValue(context, ids); JS_FreeValue(context, ids);
} tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error | k_ssb_rpc_flag_stream, -request_number, NULL,
(const uint8_t*)(success ? "true" : "false"), strlen(success ? "true" : "false"), NULL, NULL, NULL);
typedef struct _blobs_has_work_t
{
int64_t request_number;
char id[k_id_base64_len];
bool found;
} blobs_has_work_t;
static void _tf_ssb_rpc_blobs_has_work(tf_ssb_connection_t* connection, void* user_data)
{
blobs_has_work_t* work = user_data;
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
work->found = tf_ssb_db_blob_has(ssb, work->id);
}
static void _tf_ssb_rpc_blobs_has_after_work(tf_ssb_connection_t* connection, int status, void* user_data)
{
blobs_has_work_t* work = user_data;
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error | k_ssb_rpc_flag_stream, -work->request_number, NULL,
(const uint8_t*)(work->found ? "true" : "false"), strlen(work->found ? "true" : "false"), NULL, NULL, NULL);
tf_free(work);
} }
static void _tf_ssb_rpc_blobs_has(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) static void _tf_ssb_rpc_blobs_has(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{ {
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
if (!tf_ssb_is_replicator(ssb))
{
tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, "blobs.has");
return;
}
JSContext* context = tf_ssb_connection_get_context(connection); JSContext* context = tf_ssb_connection_get_context(connection);
JSValue ids = JS_GetPropertyStr(context, args, "args"); JSValue ids = JS_GetPropertyStr(context, args, "args");
JSValue id = JS_GetPropertyUint32(context, ids, 0); JSValue id = JS_GetPropertyUint32(context, ids, 0);
const char* id_str = JS_ToCString(context, id); const char* id_str = JS_ToCString(context, id);
bool has = tf_ssb_db_blob_has(ssb, id_str);
blobs_has_work_t* work = tf_malloc(sizeof(blobs_has_work_t));
*work = (blobs_has_work_t) {
.request_number = request_number,
};
snprintf(work->id, sizeof(work->id), "%s", id_str);
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_blobs_has_work, _tf_ssb_rpc_blobs_has_after_work, work);
JS_FreeCString(context, id_str); JS_FreeCString(context, id_str);
JS_FreeValue(context, id); JS_FreeValue(context, id);
JS_FreeValue(context, ids); JS_FreeValue(context, ids);
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json, -request_number, NULL, (const uint8_t*)(has ? "true" : "false"), strlen(has ? "true" : "false"), NULL, NULL, NULL);
} }
static void _tf_ssb_rpc_blob_wants_added_callback(tf_ssb_t* ssb, const char* id, void* user_data) static void _tf_ssb_rpc_blob_wants_added_callback(tf_ssb_t* ssb, const char* id, void* user_data)
@ -282,13 +216,8 @@ static void _tf_ssb_rpc_request_more_blobs(tf_ssb_connection_t* connection)
static void _tf_ssb_rpc_blobs_createWants( static void _tf_ssb_rpc_blobs_createWants(
tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{ {
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
if (!tf_ssb_is_replicator(ssb))
{
tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, "blobs.createWants");
return;
}
tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection); tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection);
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
tf_ssb_add_blob_want_added_callback(ssb, _tf_ssb_rpc_blob_wants_added_callback, NULL, connection); tf_ssb_add_blob_want_added_callback(ssb, _tf_ssb_rpc_blob_wants_added_callback, NULL, connection);
blob_wants->request_number = request_number; blob_wants->request_number = request_number;
_tf_ssb_rpc_request_more_blobs(connection); _tf_ssb_rpc_request_more_blobs(connection);
@ -475,7 +404,6 @@ typedef struct _blobs_get_t
bool done; bool done;
bool storing; bool storing;
tf_ssb_t* ssb; tf_ssb_t* ssb;
tf_ssb_connection_t* connection;
uint8_t buffer[]; uint8_t buffer[];
} blobs_get_t; } blobs_get_t;
@ -483,7 +411,6 @@ static void _tf_ssb_rpc_blob_store_callback(const char* id, bool is_new, void* u
{ {
blobs_get_t* get = user_data; blobs_get_t* get = user_data;
get->storing = false; get->storing = false;
tf_ssb_connection_adjust_read_backpressure(get->connection, -1);
if (get->done) if (get->done)
{ {
tf_free(get); tf_free(get);
@ -506,7 +433,6 @@ static void _tf_ssb_rpc_connection_blobs_get_callback(
if (JS_ToBool(context, args)) if (JS_ToBool(context, args))
{ {
get->storing = true; get->storing = true;
tf_ssb_connection_adjust_read_backpressure(connection, 1);
tf_ssb_db_blob_store_async(ssb, get->buffer, get->received, _tf_ssb_rpc_blob_store_callback, get); tf_ssb_db_blob_store_async(ssb, get->buffer, get->received, _tf_ssb_rpc_blob_store_callback, get);
} }
/* TODO: Should we send the response in the callback? */ /* TODO: Should we send the response in the callback? */
@ -529,7 +455,7 @@ static void _tf_ssb_rpc_connection_blobs_get_cleanup(tf_ssb_t* ssb, void* user_d
static void _tf_ssb_rpc_connection_blobs_get(tf_ssb_connection_t* connection, const char* blob_id, size_t size) static void _tf_ssb_rpc_connection_blobs_get(tf_ssb_connection_t* connection, const char* blob_id, size_t size)
{ {
blobs_get_t* get = tf_malloc(sizeof(blobs_get_t) + size); blobs_get_t* get = tf_malloc(sizeof(blobs_get_t) + size);
*get = (blobs_get_t) { .ssb = tf_ssb_connection_get_ssb(connection), .connection = connection, .expected_size = size }; *get = (blobs_get_t) { .ssb = tf_ssb_connection_get_ssb(connection), .expected_size = size };
snprintf(get->id, sizeof(get->id), "%s", blob_id); snprintf(get->id, sizeof(get->id), "%s", blob_id);
memset(get->buffer, 0, size); memset(get->buffer, 0, size);
@ -551,45 +477,6 @@ static void _tf_ssb_rpc_connection_blobs_get(tf_ssb_connection_t* connection, co
JS_FreeValue(context, message); JS_FreeValue(context, message);
} }
typedef struct _blob_create_wants_work_t
{
tf_ssb_connection_t* connection;
char blob_id[k_blob_id_len];
bool out_result;
int64_t size;
size_t out_size;
} blob_create_wants_work_t;
static void _tf_ssb_rpc_connection_blobs_create_wants_work(tf_ssb_connection_t* connection, void* user_data)
{
blob_create_wants_work_t* work = user_data;
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(work->connection);
work->out_result = tf_ssb_db_blob_get(ssb, work->blob_id, NULL, &work->out_size);
}
static void _tf_ssb_rpc_connection_blobs_create_wants_after_work(tf_ssb_connection_t* connection, int result, void* user_data)
{
blob_create_wants_work_t* work = user_data;
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(work->connection);
tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection);
JSContext* context = tf_ssb_get_context(ssb);
if (work->out_result)
{
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, work->blob_id, JS_NewInt64(context, work->out_size));
tf_ssb_connection_rpc_send_json(work->connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
}
else if (work->size == -1LL)
{
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, work->blob_id, JS_NewInt64(context, -2));
tf_ssb_connection_rpc_send_json(work->connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
}
tf_free(work);
}
static void _tf_ssb_rpc_connection_blobs_createWants_callback( static void _tf_ssb_rpc_connection_blobs_createWants_callback(
tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{ {
@ -599,13 +486,13 @@ static void _tf_ssb_rpc_connection_blobs_createWants_callback(
return; return;
} }
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
JSContext* context = tf_ssb_connection_get_context(connection); JSContext* context = tf_ssb_connection_get_context(connection);
JSValue name = JS_GetPropertyStr(context, args, "name"); JSValue name = JS_GetPropertyStr(context, args, "name");
if (!JS_IsUndefined(name)) if (!JS_IsUndefined(name))
{ {
/* { name: "Error" } */ /* { name: "Error" } */
tf_ssb_connection_remove_request(connection, -request_number);
JS_FreeValue(context, name); JS_FreeValue(context, name);
return; return;
} }
@ -634,13 +521,21 @@ static void _tf_ssb_rpc_connection_blobs_createWants_callback(
} }
if (size < 0) if (size < 0)
{ {
blob_create_wants_work_t* work = tf_malloc(sizeof(blob_create_wants_work_t)); size_t blob_size = 0;
*work = (blob_create_wants_work_t) { if (tf_ssb_db_blob_get(ssb, blob_id, NULL, &blob_size))
.connection = connection, {
.size = size, JSValue message = JS_NewObject(context);
}; JS_SetPropertyStr(context, message, blob_id, JS_NewInt64(context, blob_size));
snprintf(work->blob_id, sizeof(work->blob_id), "%s", blob_id); tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_connection_blobs_create_wants_work, _tf_ssb_rpc_connection_blobs_create_wants_after_work, work); JS_FreeValue(context, message);
}
else if (size == -1LL)
{
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, blob_id, JS_NewInt64(context, -2));
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
}
} }
else else
{ {
@ -846,7 +741,6 @@ static void _tf_ssb_connection_send_history_stream_work(tf_ssb_connection_t* con
static void _tf_ssb_connection_send_history_stream_after_work(tf_ssb_connection_t* connection, int result, void* user_data) static void _tf_ssb_connection_send_history_stream_after_work(tf_ssb_connection_t* connection, int result, void* user_data)
{ {
tf_ssb_connection_send_history_stream_t* request = user_data; tf_ssb_connection_send_history_stream_t* request = user_data;
tf_ssb_connection_adjust_write_count(connection, -1);
if (tf_ssb_connection_is_connected(connection)) if (tf_ssb_connection_is_connected(connection))
{ {
for (int i = 0; i < request->out_messages_count; i++) for (int i = 0; i < request->out_messages_count; i++)
@ -871,19 +765,6 @@ static void _tf_ssb_connection_send_history_stream_after_work(tf_ssb_connection_
tf_free(request); tf_free(request);
} }
static void _tf_ssb_connection_send_history_stream_callback(tf_ssb_connection_t* connection, void* user_data)
{
tf_ssb_connection_adjust_write_count(connection, 1);
if (tf_ssb_connection_is_connected(connection))
{
tf_ssb_connection_run_work(connection, _tf_ssb_connection_send_history_stream_work, _tf_ssb_connection_send_history_stream_after_work, user_data);
}
else
{
_tf_ssb_connection_send_history_stream_after_work(connection, -1, user_data);
}
}
static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live) static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live)
{ {
tf_ssb_connection_send_history_stream_t* async = tf_malloc(sizeof(tf_ssb_connection_send_history_stream_t)); tf_ssb_connection_send_history_stream_t* async = tf_malloc(sizeof(tf_ssb_connection_send_history_stream_t));
@ -894,18 +775,13 @@ static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connecti
.live = live, .live = live,
}; };
snprintf(async->author, sizeof(async->author), "%s", author); snprintf(async->author, sizeof(async->author), "%s", author);
tf_ssb_connection_schedule_idle(connection, _tf_ssb_connection_send_history_stream_callback, async); tf_ssb_connection_run_work(connection, _tf_ssb_connection_send_history_stream_work, _tf_ssb_connection_send_history_stream_after_work, async);
} }
static void _tf_ssb_rpc_createHistoryStream( static void _tf_ssb_rpc_createHistoryStream(
tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{ {
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
if (!tf_ssb_is_replicator(ssb))
{
tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, "createHistoryStream");
return;
}
JSContext* context = tf_ssb_get_context(ssb); JSContext* context = tf_ssb_get_context(ssb);
JSValue arg_array = JS_GetPropertyStr(context, args, "args"); JSValue arg_array = JS_GetPropertyStr(context, args, "args");
JSValue arg = JS_GetPropertyUint32(context, arg_array, 0); JSValue arg = JS_GetPropertyUint32(context, arg_array, 0);
@ -1124,12 +1000,6 @@ static void _tf_ssb_rpc_ebt_replicate_send_messages(tf_ssb_connection_t* connect
} }
} }
static void _tf_ssb_rpc_ebt_replicate_store_callback(const char* id, bool verified, bool is_new, void* user_data)
{
tf_ssb_connection_t* connection = user_data;
tf_ssb_connection_adjust_read_backpressure(connection, -1);
}
static void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) static void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{ {
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
@ -1137,7 +1007,6 @@ static void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t f
if (_is_error(context, args)) if (_is_error(context, args))
{ {
/* TODO: Send createHistoryStream. */ /* TODO: Send createHistoryStream. */
tf_ssb_connection_remove_request(connection, -request_number);
return; return;
} }
@ -1153,8 +1022,7 @@ static void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t f
if (!JS_IsUndefined(author)) if (!JS_IsUndefined(author))
{ {
/* Looks like a message. */ /* Looks like a message. */
tf_ssb_connection_adjust_read_backpressure(connection, 1); tf_ssb_verify_strip_and_store_message(ssb, args, NULL, NULL);
tf_ssb_verify_strip_and_store_message(ssb, args, _tf_ssb_rpc_ebt_replicate_store_callback, connection);
} }
else else
{ {
@ -1209,12 +1077,6 @@ static void _tf_ssb_rpc_ebt_replicate_server(
{ {
return; return;
} }
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
if (!tf_ssb_is_replicator(ssb))
{
tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, "ebt.replicate");
return;
}
_tf_ssb_rpc_ebt_replicate(connection, flags, request_number, args, message, size, user_data); _tf_ssb_rpc_ebt_replicate(connection, flags, request_number, args, message, size, user_data);
tf_ssb_connection_add_request(connection, -request_number, "ebt.replicate", _tf_ssb_rpc_ebt_replicate, NULL, NULL, NULL); tf_ssb_connection_add_request(connection, -request_number, "ebt.replicate", _tf_ssb_rpc_ebt_replicate, NULL, NULL, NULL);
} }
@ -1224,24 +1086,21 @@ static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_chang
JSContext* context = tf_ssb_get_context(ssb); JSContext* context = tf_ssb_get_context(ssb);
if (change == k_tf_ssb_change_connect) if (change == k_tf_ssb_change_connect)
{ {
if (tf_ssb_is_replicator(ssb)) JSValue message = JS_NewObject(context);
{ JSValue name = JS_NewArray(context);
JSValue message = JS_NewObject(context); JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "blobs"));
JSValue name = JS_NewArray(context); JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "createWants"));
JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "blobs")); JS_SetPropertyStr(context, message, "name", name);
JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "createWants")); JS_SetPropertyStr(context, message, "type", JS_NewString(context, "source"));
JS_SetPropertyStr(context, message, "name", name); JS_SetPropertyStr(context, message, "args", JS_NewArray(context));
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "source")); tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), "blobs.createWants",
JS_SetPropertyStr(context, message, "args", JS_NewArray(context)); message, _tf_ssb_rpc_connection_blobs_createWants_callback, NULL, NULL);
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), "blobs.createWants", JS_FreeValue(context, message);
message, _tf_ssb_rpc_connection_blobs_createWants_callback, NULL, NULL);
JS_FreeValue(context, message);
}
if (tf_ssb_connection_is_client(connection)) if (tf_ssb_connection_is_client(connection))
{ {
JSValue message = JS_NewObject(context); message = JS_NewObject(context);
JSValue name = JS_NewArray(context); name = JS_NewArray(context);
JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "tunnel")); JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "tunnel"));
JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "isRoom")); JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "isRoom"));
JS_SetPropertyStr(context, message, "name", name); JS_SetPropertyStr(context, message, "name", name);
@ -1250,15 +1109,7 @@ static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_chang
_tf_ssb_rpc_connection_tunnel_isRoom_callback, NULL, NULL); _tf_ssb_rpc_connection_tunnel_isRoom_callback, NULL, NULL);
JS_FreeValue(context, message); JS_FreeValue(context, message);
if (tf_ssb_is_peer_exchange(ssb)) _tf_ssb_rpc_send_ebt_replicate(connection);
{
_tf_ssb_rpc_send_peers_exchange(connection);
}
if (tf_ssb_is_replicator(ssb))
{
_tf_ssb_rpc_send_ebt_replicate(connection);
}
} }
} }
else if (change == k_tf_ssb_change_remove) else if (change == k_tf_ssb_change_remove)
@ -1354,6 +1205,7 @@ static void _tf_ssb_rpc_delete_blobs_work(tf_ssb_t* ssb, void* user_data)
static void _tf_ssb_rpc_delete_blobs_after_work(tf_ssb_t* ssb, int status, void* user_data) static void _tf_ssb_rpc_delete_blobs_after_work(tf_ssb_t* ssb, int status, void* user_data)
{ {
tf_ssb_unref(ssb);
} }
static void _tf_ssb_rpc_start_delete_callback(tf_ssb_t* ssb, void* user_data) static void _tf_ssb_rpc_start_delete_callback(tf_ssb_t* ssb, void* user_data)
@ -1372,142 +1224,6 @@ void tf_ssb_rpc_start_periodic(tf_ssb_t* ssb)
_tf_ssb_rpc_start_delete_blobs(ssb, 30 * 1000); _tf_ssb_rpc_start_delete_blobs(ssb, 30 * 1000);
} }
typedef struct _peers_exchange_t
{
tf_ssb_t* ssb;
JSValue peers;
} peers_exchange_t;
static void _tf_ssb_get_peers_exhange_callback(
const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data)
{
peers_exchange_t* data = user_data;
if (origin == k_tf_ssb_broadcast_origin_peer_exchange)
{
char fullid[256] = { 0 };
tf_base64_encode(pub, k_id_bin_len, fullid, sizeof(fullid));
char* dot = strchr(fullid, '.');
if (dot)
{
*dot = '\0';
}
char connection[1024] = { 0 };
snprintf(connection, sizeof(connection), "net:%s:%d~shs:%s", host, ntohs(addr->sin_port), fullid);
JSContext* context = tf_ssb_get_context(data->ssb);
JS_SetPropertyStr(context, data->peers, connection, JS_NewInt32(context, 0));
}
}
static JSValue _tf_ssb_get_peers_exchange(tf_ssb_t* ssb)
{
JSContext* context = tf_ssb_get_context(ssb);
JSValue peers = JS_NewObject(context);
tf_ssb_visit_broadcasts(ssb, _tf_ssb_get_peers_exhange_callback, &(peers_exchange_t) { .ssb = ssb, .peers = peers });
return peers;
}
static void _tf_ssb_rpc_peers_exchange_internal(
tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{
JSContext* context = tf_ssb_connection_get_context(connection);
if (_is_error(context, args))
{
return;
}
/* The peer that participated in the exchange is now a peer exchange entry, too. */
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
if (*tf_ssb_connection_get_host(connection))
{
char fullid[k_id_base64_len] = { 0 };
tf_ssb_connection_get_id(connection, fullid, sizeof(fullid));
char* dot = strchr(fullid, '.');
if (dot)
{
*dot = '\0';
}
int port = tf_ssb_connection_get_port(connection);
JSValue port_value = JS_GetPropertyStr(context, args, "port");
JS_ToInt32(context, &port, port_value);
JS_FreeValue(context, port_value);
char connection_string[1024] = { 0 };
snprintf(connection_string, sizeof(connection_string), "net:%s:%d~shs:%s", tf_ssb_connection_get_host(connection), port, fullid + 1);
tf_ssb_add_broadcast(ssb, connection_string, k_tf_ssb_broadcast_origin_peer_exchange, k_ssb_peer_exchange_expires_seconds);
}
JSValue in_peers = JS_GetPropertyStr(context, args, "peers");
JSPropertyEnum* ptab = NULL;
uint32_t plen = 0;
if (JS_GetOwnPropertyNames(context, &ptab, &plen, in_peers, JS_GPN_STRING_MASK) == 0)
{
for (uint32_t i = 0; i < plen; ++i)
{
JSValue key = JS_AtomToString(context, ptab[i].atom);
JSPropertyDescriptor desc;
JSValue key_value = JS_NULL;
if (JS_GetOwnProperty(context, &desc, args, ptab[i].atom) == 1)
{
key_value = desc.value;
JS_FreeValue(context, desc.setter);
JS_FreeValue(context, desc.getter);
}
const char* connection = JS_ToCString(context, key);
int64_t timestamp = 0;
JS_ToInt64(context, &timestamp, key_value);
/* ADD BROADCAST connection: timestamp */
JS_FreeCString(context, connection);
JS_FreeValue(context, key);
JS_FreeValue(context, key_value);
}
for (uint32_t i = 0; i < plen; ++i)
{
JS_FreeAtom(context, ptab[i].atom);
}
js_free(context, ptab);
}
JS_FreeValue(context, in_peers);
}
static void _tf_ssb_rpc_send_peers_exchange(tf_ssb_connection_t* connection)
{
int32_t request_number = tf_ssb_connection_next_request_number(connection);
JSContext* context = tf_ssb_connection_get_context(connection);
JSValue message = JS_NewObject(context);
JSValue name = JS_NewArray(context);
JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "peers"));
JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "exchange"));
JS_SetPropertyStr(context, message, "name", name);
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
JS_SetPropertyStr(context, message, "port", JS_NewInt32(context, tf_ssb_server_get_port(ssb)));
JS_SetPropertyStr(context, message, "peers", _tf_ssb_get_peers_exchange(ssb));
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_new_request, request_number, "peers.exchange", message, _tf_ssb_rpc_peers_exchange_internal, NULL, NULL);
JS_FreeValue(context, message);
}
static void _tf_ssb_rpc_peers_exchange(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
if (!tf_ssb_is_peer_exchange(ssb))
{
tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, "peers.exchange");
return;
}
_tf_ssb_rpc_peers_exchange_internal(connection, flags, request_number, args, message, size, user_data);
JSContext* context = tf_ssb_connection_get_context(connection);
JSValue out_message = JS_NewObject(context);
JS_SetPropertyStr(context, out_message, "port", JS_NewInt32(context, tf_ssb_server_get_port(ssb)));
JS_SetPropertyStr(context, out_message, "peers", _tf_ssb_get_peers_exchange(ssb));
tf_ssb_connection_rpc_send_json(connection, flags, -request_number, NULL, out_message, NULL, NULL, NULL);
JS_FreeValue(context, out_message);
}
void tf_ssb_rpc_register(tf_ssb_t* ssb) void tf_ssb_rpc_register(tf_ssb_t* ssb)
{ {
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_rpc_connections_changed_callback, NULL, NULL); tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_rpc_connections_changed_callback, NULL, NULL);
@ -1521,5 +1237,4 @@ void tf_ssb_rpc_register(tf_ssb_t* ssb)
tf_ssb_add_rpc_callback(ssb, (const char*[]) { "room", "attendants", NULL }, _tf_ssb_rpc_room_attendants, NULL, NULL); /* SOURCE */ tf_ssb_add_rpc_callback(ssb, (const char*[]) { "room", "attendants", NULL }, _tf_ssb_rpc_room_attendants, NULL, NULL); /* SOURCE */
tf_ssb_add_rpc_callback(ssb, (const char*[]) { "createHistoryStream", NULL }, _tf_ssb_rpc_createHistoryStream, NULL, NULL); /* SOURCE */ tf_ssb_add_rpc_callback(ssb, (const char*[]) { "createHistoryStream", NULL }, _tf_ssb_rpc_createHistoryStream, NULL, NULL); /* SOURCE */
tf_ssb_add_rpc_callback(ssb, (const char*[]) { "ebt", "replicate", NULL }, _tf_ssb_rpc_ebt_replicate_server, NULL, NULL); /* DUPLEX */ tf_ssb_add_rpc_callback(ssb, (const char*[]) { "ebt", "replicate", NULL }, _tf_ssb_rpc_ebt_replicate_server, NULL, NULL); /* DUPLEX */
tf_ssb_add_rpc_callback(ssb, (const char*[]) { "peers", "exchange", NULL }, _tf_ssb_rpc_peers_exchange, NULL, NULL); /* ASYNC */
} }

View File

@ -16,15 +16,6 @@
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#if !defined(_WIN32)
#include <sys/wait.h>
#endif
#if defined(_WIN32)
#define WIFEXITED(x) 1
#define WEXITSTATUS(x) (x)
#endif
void tf_ssb_test_id_conversion(const tf_test_options_t* options) void tf_ssb_test_id_conversion(const tf_test_options_t* options)
{ {
tf_printf("Testing id conversion.\n"); tf_printf("Testing id conversion.\n");
@ -201,7 +192,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post")); JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "Hello, world!")); JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "Hello, world!"));
bool stored = false; bool stored = false;
JSValue signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj, NULL, 0); JSValue signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
JS_FreeValue(context0, signed_message); JS_FreeValue(context0, signed_message);
_wait_stored(ssb0, &stored); _wait_stored(ssb0, &stored);
@ -211,7 +202,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post")); JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "First post.")); JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "First post."));
stored = false; stored = false;
signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj, NULL, 0); signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
JS_FreeValue(context0, signed_message); JS_FreeValue(context0, signed_message);
_wait_stored(ssb0, &stored); _wait_stored(ssb0, &stored);
@ -226,7 +217,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
JS_SetPropertyUint32(context0, mentions, 0, mention); JS_SetPropertyUint32(context0, mentions, 0, mention);
JS_SetPropertyStr(context0, obj, "mentions", mentions); JS_SetPropertyStr(context0, obj, "mentions", mentions);
stored = false; stored = false;
signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj, NULL, 0); signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
JS_FreeValue(context0, signed_message); JS_FreeValue(context0, signed_message);
_wait_stored(ssb0, &stored); _wait_stored(ssb0, &stored);
@ -250,51 +241,22 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
tf_printf("Waiting for connection.\n"); tf_printf("Waiting for connection.\n");
while (test.connection_count0 != 1 || test.connection_count1 != 1) while (test.connection_count0 != 1 || test.connection_count1 != 1)
{ {
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_ONCE); uv_run(&loop, UV_RUN_ONCE);
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);
} }
tf_ssb_server_close(ssb0); tf_ssb_server_close(ssb0);
tf_printf("Waiting for messages.\n"); tf_printf("Waiting for messages.\n");
while (_ssb_test_count_messages(ssb1) < 3) while (_ssb_test_count_messages(ssb1) < 3)
{ {
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_ONCE); uv_run(&loop, UV_RUN_ONCE);
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);
} }
tf_printf("Waiting for blob.\n"); tf_printf("Waiting for blob.\n");
while (!tf_ssb_db_blob_get(ssb1, blob_id, NULL, NULL)) while (!tf_ssb_db_blob_get(ssb1, blob_id, NULL, NULL))
{ {
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_ONCE); uv_run(&loop, UV_RUN_ONCE);
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);
} }
JSContext* context = tf_ssb_get_context(ssb1);
JSValue message = JS_NewObject(context);
JSValue name = JS_NewArray(context);
JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "blobs"));
JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "has"));
JS_SetPropertyStr(context, message, "name", name);
JSValue args = JS_NewArray(context);
JS_SetPropertyUint32(context, args, 0, JS_NewString(context, blob_id));
JS_SetPropertyStr(context, message, "args", args);
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "async"));
tf_ssb_connection_t* connections[4] = { 0 };
tf_ssb_get_connections(ssb1, connections, 4);
int64_t request_number = tf_ssb_connection_next_request_number(connections[0]);
tf_ssb_connection_rpc_send_json(connections[0], k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, request_number, "blobs.has", message, NULL, NULL, NULL);
JS_FreeValue(context, message);
uint8_t* b1; uint8_t* b1;
size_t s1 = 0; size_t s1 = 0;
b = tf_ssb_db_blob_get(ssb1, blob_id, &b1, &s1); b = tf_ssb_db_blob_get(ssb1, blob_id, &b1, &s1);
@ -314,7 +276,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post")); JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "Message to self.")); JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "Message to self."));
stored = false; stored = false;
signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj, NULL, 0); signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
JS_FreeValue(context0, signed_message); JS_FreeValue(context0, signed_message);
_wait_stored(ssb0, &stored); _wait_stored(ssb0, &stored);
@ -322,22 +284,14 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
while (count0 == 0) while (count0 == 0)
{ {
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_ONCE); uv_run(&loop, UV_RUN_ONCE);
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);
} }
tf_ssb_remove_message_added_callback(ssb0, _message_added, &count0); tf_ssb_remove_message_added_callback(ssb0, _message_added, &count0);
tf_printf("Waiting for message from other.\n"); tf_printf("Waiting for message from other.\n");
while (count1 == 0) while (count1 == 0)
{ {
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_ONCE); uv_run(&loop, UV_RUN_ONCE);
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);
} }
tf_ssb_remove_message_added_callback(ssb1, _message_added, &count1); tf_ssb_remove_message_added_callback(ssb1, _message_added, &count1);
tf_printf("done\n"); tf_printf("done\n");
@ -348,11 +302,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
uv_close((uv_handle_t*)&idle1, NULL); uv_close((uv_handle_t*)&idle1, NULL);
tf_printf("final run\n"); tf_printf("final run\n");
tf_ssb_set_main_thread(ssb0, true);
tf_ssb_set_main_thread(ssb1, true);
uv_run(&loop, UV_RUN_DEFAULT); uv_run(&loop, UV_RUN_DEFAULT);
tf_ssb_set_main_thread(ssb0, false);
tf_ssb_set_main_thread(ssb1, false);
tf_printf("done\n"); tf_printf("done\n");
tf_printf("destroy 0\n"); tf_printf("destroy 0\n");
@ -364,7 +314,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
uv_loop_close(&loop); uv_loop_close(&loop);
} }
static void _broadcasts_visit(const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data) static void _broadcasts_visit(const char* host, const struct sockaddr_in* addr, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data)
{ {
int* count = user_data; int* count = user_data;
(*count)++; (*count)++;
@ -599,7 +549,7 @@ void tf_ssb_test_following(const tf_test_options_t* options)
JS_SetPropertyStr(context, message, "contact", JS_NewString(context, contact)); \ JS_SetPropertyStr(context, message, "contact", JS_NewString(context, contact)); \
JS_SetPropertyStr(context, message, "following", follow ? JS_TRUE : JS_FALSE); \ JS_SetPropertyStr(context, message, "following", follow ? JS_TRUE : JS_FALSE); \
JS_SetPropertyStr(context, message, "blocking", block ? JS_TRUE : JS_FALSE); \ JS_SetPropertyStr(context, message, "blocking", block ? JS_TRUE : JS_FALSE); \
signed_message = tf_ssb_sign_message(ssb0, id, priv, message, NULL, 0); \ signed_message = tf_ssb_sign_message(ssb0, id, priv, message); \
stored = false; \ stored = false; \
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); \ tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); \
_wait_stored(ssb0, &stored); \ _wait_stored(ssb0, &stored); \
@ -658,7 +608,7 @@ void tf_ssb_test_bench(const tf_test_options_t* options)
for (int i = 0; i < k_messages; i++) for (int i = 0; i < k_messages; i++)
{ {
bool stored = false; bool stored = false;
JSValue signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj, NULL, 0); JSValue signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
JS_FreeValue(tf_ssb_get_context(ssb0), signed_message); JS_FreeValue(tf_ssb_get_context(ssb0), signed_message);
_wait_stored(ssb0, &stored); _wait_stored(ssb0, &stored);
@ -788,8 +738,7 @@ static void _break_in_a_bit(tf_ssb_t* ssb, tf_ssb_connection_t* connection, cons
uv_timer_start(&data->timer, _close_callback, 3000, 0); uv_timer_start(&data->timer, _close_callback, 3000, 0);
} }
static void _ssb_test_room_broadcasts_visit( static void _ssb_test_room_broadcasts_visit(const char* host, const struct sockaddr_in* addr, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data)
const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data)
{ {
tf_ssb_t* ssb = user_data; tf_ssb_t* ssb = user_data;
char id[k_id_base64_len] = { 0 }; char id[k_id_base64_len] = { 0 };
@ -870,114 +819,3 @@ void tf_ssb_test_go_ssb_room(const tf_test_options_t* options)
uv_loop_close(&loop); uv_loop_close(&loop);
} }
#if !TARGET_OS_IPHONE
static void _write_file(const char* path, const char* contents)
{
FILE* file = fopen(path, "w");
if (!file)
{
printf("Unable to write %s: %s.\n", path, strerror(errno));
fflush(stdout);
abort();
}
fputs(contents, file);
fclose(file);
}
#define TEST_ARGS " --ssb-port=0 --http-port=0 --https-port=0"
void tf_ssb_test_encrypt(const tf_test_options_t* options)
{
_write_file("out/test.js",
"async function main() {\n"
" let a = await ssb.createIdentity('test');\n"
" let b = await ssb.createIdentity('test');\n"
" let c = await ssb.privateMessageEncrypt('test', a, [a, b], \"{'foo': 1}\");\n"
" if (!c.endsWith('.box')) {\n"
" exit(1);\n"
" }\n"
" print(await ssb.privateMessageDecrypt('test', a, c));\n"
"}\n"
"main().catch(() => exit(2));\n");
unlink("out/testdb.sqlite");
char command[256];
snprintf(command, sizeof(command), "%s run --db-path=out/testdb.sqlite -s out/test.js" TEST_ARGS, options->exe_path);
tf_printf("%s\n", command);
int result = system(command);
(void)result;
assert(WIFEXITED(result));
printf("returned %d\n", WEXITSTATUS(result));
assert(WEXITSTATUS(result) == 0);
}
static void _count_broadcasts_callback(
const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data)
{
int* count = user_data;
(*count)++;
}
static int _count_broadcasts(tf_ssb_t* ssb)
{
int count = 0;
tf_ssb_visit_broadcasts(ssb, _count_broadcasts_callback, &count);
return count;
}
void tf_ssb_test_peer_exchange(const tf_test_options_t* options)
{
uv_loop_t loop = { 0 };
uv_loop_init(&loop);
unlink("out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
tf_ssb_set_is_room(ssb0, false);
tf_ssb_set_is_replicator(ssb0, false);
tf_ssb_set_is_peer_exchange(ssb0, true);
tf_ssb_register(tf_ssb_get_context(ssb0), ssb0);
tf_ssb_server_open(ssb0, 12347);
unlink("out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL);
tf_ssb_set_is_room(ssb1, false);
tf_ssb_set_is_replicator(ssb1, false);
tf_ssb_set_is_peer_exchange(ssb1, true);
tf_ssb_register(tf_ssb_get_context(ssb1), ssb1);
tf_ssb_server_open(ssb1, 12348);
unlink("out/test_db2.sqlite");
tf_ssb_t* ssb2 = tf_ssb_create(&loop, NULL, "file:out/test_db2.sqlite", NULL);
tf_ssb_set_is_room(ssb2, false);
tf_ssb_set_is_replicator(ssb2, false);
tf_ssb_set_is_peer_exchange(ssb2, true);
tf_ssb_register(tf_ssb_get_context(ssb2), ssb2);
tf_ssb_server_open(ssb2, 12349);
char id0[k_id_base64_len] = { 0 };
tf_ssb_whoami(ssb0, id0, sizeof(id0));
uint8_t id0bin[k_id_bin_len];
tf_ssb_id_str_to_bin(id0bin, id0);
tf_ssb_connect(ssb1, "127.0.0.1", 12347, id0bin);
tf_ssb_connect(ssb2, "127.0.0.1", 12347, id0bin);
while (_count_broadcasts(ssb0) != 2 || _count_broadcasts(ssb1) != 1 || _count_broadcasts(ssb2) != 1)
{
uv_run(&loop, UV_RUN_ONCE);
}
tf_ssb_send_close(ssb0);
tf_ssb_send_close(ssb1);
tf_ssb_send_close(ssb2);
tf_ssb_destroy(ssb0);
tf_ssb_destroy(ssb1);
tf_ssb_destroy(ssb2);
uv_run(&loop, UV_RUN_DEFAULT);
uv_loop_close(&loop);
}
#endif

View File

@ -47,16 +47,4 @@ void tf_ssb_test_bench(const tf_test_options_t* options);
*/ */
void tf_ssb_test_go_ssb_room(const tf_test_options_t* options); void tf_ssb_test_go_ssb_room(const tf_test_options_t* options);
/**
** Test encrypting a private message.
** @param options The test options.
*/
void tf_ssb_test_encrypt(const tf_test_options_t* options);
/**
** Test peer exchange.
** @param options The test options.
*/
void tf_ssb_test_peer_exchange(const tf_test_options_t* options);
/** @} */ /** @} */

View File

@ -50,9 +50,6 @@
static JSClassID _import_class_id; static JSClassID _import_class_id;
static int _count; static int _count;
static tf_android_start_service_t* s_android_start_service;
static tf_android_stop_service_t* s_android_stop_service;
extern struct backtrace_state* g_backtrace_state; extern struct backtrace_state* g_backtrace_state;
typedef struct _export_record_t export_record_t; typedef struct _export_record_t export_record_t;
@ -140,7 +137,8 @@ typedef struct _tf_task_t
float idle_percent; float idle_percent;
float thread_percent; float thread_percent;
uv_async_t run_jobs_async; uv_idle_t idle;
uv_prepare_t prepare;
uv_signal_t sig_term; uv_signal_t sig_term;
uv_signal_t sig_int; uv_signal_t sig_int;
@ -207,6 +205,7 @@ static void _tf_task_sendPromiseExportMessage(tf_task_t* from, tf_taskstub_t* to
static JSValue _tf_task_executeSource(tf_task_t* task, const char* source, const char* name); static JSValue _tf_task_executeSource(tf_task_t* task, const char* source, const char* name);
static tf_taskstub_t* _tf_task_get_stub(tf_task_t* task, taskid_t id); static tf_taskstub_t* _tf_task_get_stub(tf_task_t* task, taskid_t id);
static void _tf_task_release_export(tf_taskstub_t* stub, exportid_t exportId); static void _tf_task_release_export(tf_taskstub_t* stub, exportid_t exportId);
static void _tf_task_run_jobs_prepare(uv_prepare_t* prepare);
static void _timeout_unlink(tf_task_t* task, timeout_t* timeout); static void _timeout_unlink(tf_task_t* task, timeout_t* timeout);
static void _timeout_closed(uv_handle_t* handle); static void _timeout_closed(uv_handle_t* handle);
@ -1519,20 +1518,25 @@ static bool _tf_task_run_jobs(tf_task_t* task)
return 0; return 0;
} }
static void _tf_task_run_jobs_async(uv_async_t* async) static void _tf_task_run_jobs_idle(uv_idle_t* idle)
{ {
tf_task_t* task = async->data; tf_task_t* task = idle->data;
if (_tf_task_run_jobs(task)) if (!_tf_task_run_jobs(task))
{ {
uv_async_send(async); /* No more jobs. Don't try again as actively. */
uv_idle_stop(&task->idle);
uv_prepare_start(&task->prepare, _tf_task_run_jobs_prepare);
} }
} }
void tf_task_check_jobs(tf_task_t* task) static void _tf_task_run_jobs_prepare(uv_prepare_t* prepare)
{ {
if (JS_IsJobPending(task->_runtime)) tf_task_t* task = prepare->data;
if (_tf_task_run_jobs(task))
{ {
uv_async_send(&task->run_jobs_async); /* More jobs. We can run again immediately. */
uv_idle_start(&task->idle, _tf_task_run_jobs_idle);
uv_prepare_stop(&task->prepare);
} }
} }
@ -1629,9 +1633,13 @@ tf_task_t* tf_task_create()
uv_timer_init(&task->_loop, &task->gc_timer); uv_timer_init(&task->_loop, &task->gc_timer);
uv_timer_start(&task->gc_timer, _tf_task_gc_timer, 1000, 1000); uv_timer_start(&task->gc_timer, _tf_task_gc_timer, 1000, 1000);
uv_unref((uv_handle_t*)&task->gc_timer); uv_unref((uv_handle_t*)&task->gc_timer);
task->run_jobs_async.data = task; task->idle.data = task;
uv_async_init(&task->_loop, &task->run_jobs_async, _tf_task_run_jobs_async); uv_idle_init(&task->_loop, &task->idle);
uv_unref((uv_handle_t*)&task->run_jobs_async); uv_unref((uv_handle_t*)&task->idle);
task->prepare.data = task;
uv_prepare_init(&task->_loop, &task->prepare);
uv_unref((uv_handle_t*)&task->prepare);
uv_idle_start(&task->idle, _tf_task_run_jobs_idle);
task->sig_term.data = task; task->sig_term.data = task;
uv_signal_init(&task->_loop, &task->sig_term); uv_signal_init(&task->_loop, &task->sig_term);
uv_signal_start(&task->sig_term, _tf_task_signal_shutdown, SIGTERM); uv_signal_start(&task->sig_term, _tf_task_signal_shutdown, SIGTERM);
@ -1899,13 +1907,14 @@ void tf_task_destroy(tf_task_t* task)
{ {
uv_close((uv_handle_t*)&task->gc_timer, _tf_task_on_handle_close); uv_close((uv_handle_t*)&task->gc_timer, _tf_task_on_handle_close);
} }
uv_close((uv_handle_t*)&task->run_jobs_async, _tf_task_on_handle_close); uv_close((uv_handle_t*)&task->idle, _tf_task_on_handle_close);
uv_close((uv_handle_t*)&task->prepare, _tf_task_on_handle_close);
uv_signal_stop(&task->sig_term); uv_signal_stop(&task->sig_term);
uv_close((uv_handle_t*)&task->sig_term, _tf_task_on_handle_close); uv_close((uv_handle_t*)&task->sig_term, _tf_task_on_handle_close);
uv_signal_stop(&task->sig_int); uv_signal_stop(&task->sig_int);
uv_close((uv_handle_t*)&task->sig_int, _tf_task_on_handle_close); uv_close((uv_handle_t*)&task->sig_int, _tf_task_on_handle_close);
while (task->trace_timer.data || task->gc_timer.data || task->run_jobs_async.data || task->sig_term.data || task->sig_int.data) while (task->trace_timer.data || task->gc_timer.data || task->idle.data || task->prepare.data || task->sig_term.data || task->sig_int.data)
{ {
uv_run(&task->_loop, UV_RUN_ONCE); uv_run(&task->_loop, UV_RUN_ONCE);
} }
@ -2151,19 +2160,3 @@ static JSValue _tf_task_pokeSandbox(JSContext* context, JSValueConst this_val, i
#endif #endif
return JS_NewInt32(context, WEXITSTATUS(result)); return JS_NewInt32(context, WEXITSTATUS(result));
} }
void tf_task_set_android_service_callbacks(tf_android_start_service_t* start_service, tf_android_stop_service_t* stop_service)
{
s_android_start_service = start_service;
s_android_stop_service = stop_service;
}
tf_android_start_service_t* tf_task_get_android_start_service()
{
return s_android_start_service;
}
tf_android_stop_service_t* tf_task_get_android_stop_service()
{
return s_android_stop_service;
}

View File

@ -333,42 +333,4 @@ char* tf_task_get_debug(tf_task_t* task);
*/ */
char* tf_task_get_hitches(tf_task_t* task); char* tf_task_get_hitches(tf_task_t* task);
/**
** A callback used to start an Android service.
** @param pipe_fd A file descriptor with which to communicate with the invoking
** task.
*/
typedef void(tf_android_start_service_t)(int pipe_fd);
/**
** A callback used to stop an Android service.
*/
typedef void(tf_android_stop_service_t)();
/**
** Set Android service callbacks.
** @param start_service Start service callback.
** @param stop_service Stop service callback.
*/
void tf_task_set_android_service_callbacks(tf_android_start_service_t* start_service, tf_android_stop_service_t* stop_service);
/**
** Get the callback registered for starting an Android service.
** @return the callback.
*/
tf_android_start_service_t* tf_task_get_android_start_service();
/**
** Get the callback registered for stopping an Android service.
** @return the callback.
*/
tf_android_stop_service_t* tf_task_get_android_stop_service();
/**
** Check for JS jobs that need to be run. Generally to be called post-JS_Call
** in tf_util_report_error.
** @param task The task.
*/
void tf_task_check_jobs(tf_task_t* task);
/** @} */ /** @} */

View File

@ -122,11 +122,10 @@ static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int a
char arg1[] = "sandbox"; char arg1[] = "sandbox";
char* command_argv[] = { _executable, arg1, 0 }; char* command_argv[] = { _executable, arg1, 0 };
tf_android_start_service_t* start_service = tf_task_get_android_start_service();
JSValue result = JS_NULL; JSValue result = JS_NULL;
if (tf_task_get_one_proc(parent) || start_service) if (tf_task_get_one_proc(parent))
{ {
uv_os_sock_t fds[2] = { 0 }; uv_os_sock_t fds[2];
int pipe_result = uv_socketpair(SOCK_STREAM, 0, fds, 0, 0); int pipe_result = uv_socketpair(SOCK_STREAM, 0, fds, 0, 0);
if (pipe_result) if (pipe_result)
{ {
@ -134,7 +133,7 @@ static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int a
} }
uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream); uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream);
*pipe = (uv_pipe_t) { 0 }; memset(pipe, 0, sizeof(*pipe));
pipe_result = uv_pipe_init(tf_task_get_loop(parent), pipe, 1); pipe_result = uv_pipe_init(tf_task_get_loop(parent), pipe, 1);
if (pipe_result != 0) if (pipe_result != 0)
{ {
@ -146,16 +145,8 @@ static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int a
tf_printf("uv_pipe_open failed: %s\n", uv_strerror(pipe_result)); tf_printf("uv_pipe_open failed: %s\n", uv_strerror(pipe_result));
} }
if (start_service) uv_thread_t* thread = tf_malloc(sizeof(uv_thread_t));
{ uv_thread_create(thread, _tf_taskstub_run_sandbox_thread, (void*)(intptr_t)fds[1]);
start_service(fds[1]);
}
else
{
/* XXX: This is a leak. */
uv_thread_t* thread = tf_malloc(sizeof(uv_thread_t));
uv_thread_create(thread, _tf_taskstub_run_sandbox_thread, (void*)(intptr_t)fds[1]);
}
tf_packetstream_set_on_receive(stub->_stream, tf_task_on_receive_packet, stub); tf_packetstream_set_on_receive(stub->_stream, tf_task_on_receive_packet, stub);
tf_packetstream_start(stub->_stream); tf_packetstream_start(stub->_stream);
@ -452,15 +443,7 @@ JSValue tf_taskstub_kill(tf_taskstub_t* stub)
JSValue result = JS_UNDEFINED; JSValue result = JS_UNDEFINED;
if (!tf_task_get_one_proc(stub->_owner)) if (!tf_task_get_one_proc(stub->_owner))
{ {
tf_android_stop_service_t* stop_service = tf_task_get_android_stop_service(); uv_process_kill(&stub->_process, SIGKILL);
if (stop_service)
{
stop_service();
}
else
{
uv_process_kill(&stub->_process, SIGKILL);
}
} }
else else
{ {

View File

@ -266,47 +266,32 @@ static void _test_promise_remote_reject(const tf_test_options_t* options)
static void _test_database(const tf_test_options_t* options) static void _test_database(const tf_test_options_t* options)
{ {
_write_file("out/test.js", _write_file("out/test.js",
"async function main() {\n" "var db = new Database('testdb');\n"
" var db = new Database('testdb');\n" "if (db.get('a')) {\n"
" if (await db.get('a')) {\n" " exit(1);\n"
" exit(1);\n" "}\n"
" }\n" "db.set('a', 1);\n"
" await db.set('a', 1);\n" "if (db.get('a') != 1) {\n"
" if (await db.get('a') != 1) {\n" " exit(2);\n"
" exit(2);\n" "}\n"
" }\n" "db.set('b', 2);\n"
" await db.exchange('b', null, 1);\n" "db.set('c', 3);\n"
" await db.exchange('b', 1, 2);\n"
" if (await db.get('b') != 2) {\n"
" exit(5);\n"
" }\n"
" await db.set('c', 3);\n"
" await db.set('d', 3);\n"
" await db.remove('d', 3);\n"
" if (JSON.stringify(await db.getLike('b%')) != '{\"b\":\"2\"}') {\n"
" exit(6);\n"
" }\n"
"\n" "\n"
" var expected = ['a', 'b', 'c'];\n" "var expected = ['a', 'b', 'c'];\n"
" var have = await db.getAll();\n" "var have = db.getAll();\n"
" for (var i = 0; i < have.length; i++) {\n" "for (var i = 0; i < have.length; i++) {\n"
" var item = have[i];\n" " var item = have[i];\n"
" if (expected.indexOf(item) == -1) {\n" " if (expected.indexOf(item) == -1) {\n"
" print('Did not find ' + item + ' in db.');\n" " print('Did not find ' + item + ' in db.');\n"
" exit(3);\n" " exit(3);\n"
" } else {\n" " } else {\n"
" expected.splice(expected.indexOf(item), 1);\n" " expected.splice(expected.indexOf(item), 1);\n"
" }\n"
" }\n"
" if (expected.length) {\n"
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
" exit(4);\n"
" }\n"
" if (JSON.stringify(await databases.list('%')) != '[\"testdb\"]') {\n"
" exit(7);\n"
" }\n" " }\n"
"}\n" "}\n"
"main();"); "if (expected.length) {\n"
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
" exit(4);\n"
"}\n");
char command[256]; char command[256];
unlink("out/test_db0.sqlite"); unlink("out/test_db0.sqlite");
@ -914,8 +899,6 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "bench", tf_ssb_test_bench, false); _tf_test_run(options, "bench", tf_ssb_test_bench, false);
_tf_test_run(options, "auto", _test_auto, false); _tf_test_run(options, "auto", _test_auto, false);
_tf_test_run(options, "go-ssb-room", tf_ssb_test_go_ssb_room, true); _tf_test_run(options, "go-ssb-room", tf_ssb_test_go_ssb_room, true);
_tf_test_run(options, "encrypt", tf_ssb_test_encrypt, false);
_tf_test_run(options, "peer_exchange", tf_ssb_test_peer_exchange, false);
tf_printf("Tests completed.\n"); tf_printf("Tests completed.\n");
#endif #endif
} }

View File

@ -211,7 +211,6 @@ static JSValue _util_print(JSContext* context, JSValueConst this_val, int argc,
bool tf_util_report_error(JSContext* context, JSValue value) bool tf_util_report_error(JSContext* context, JSValue value)
{ {
bool is_error = false; bool is_error = false;
tf_task_t* task = tf_task_get(context);
if (JS_IsError(context, value)) if (JS_IsError(context, value))
{ {
const char* string = JS_ToCString(context, value); const char* string = JS_ToCString(context, value);
@ -227,11 +226,13 @@ bool tf_util_report_error(JSContext* context, JSValue value)
} }
JS_FreeValue(context, stack); JS_FreeValue(context, stack);
tf_task_t* task = tf_task_get(context);
tf_task_send_error_to_parent(task, value); tf_task_send_error_to_parent(task, value);
is_error = true; is_error = true;
} }
else if (JS_IsException(value)) else if (JS_IsException(value))
{ {
tf_task_t* task = tf_task_get(context);
if (!tf_task_send_error_to_parent(task, value)) if (!tf_task_send_error_to_parent(task, value))
{ {
JSValue exception = JS_GetException(context); JSValue exception = JS_GetException(context);
@ -240,7 +241,6 @@ bool tf_util_report_error(JSContext* context, JSValue value)
} }
is_error = true; is_error = true;
} }
tf_task_check_jobs(task);
return is_error; return is_error;
} }
@ -435,16 +435,6 @@ const char* tf_util_backtrace_string()
return tf_util_backtrace_to_string(buffer, count); return tf_util_backtrace_to_string(buffer, count);
} }
void tf_util_print_backtrace()
{
const char* bt = tf_util_backtrace_string();
if (bt)
{
tf_printf("%s\n", bt);
}
tf_free((void*)bt);
}
#if defined(__ANDROID__) #if defined(__ANDROID__)
typedef struct _android_backtrace_t typedef struct _android_backtrace_t
{ {

View File

@ -126,11 +126,6 @@ const char* tf_util_backtrace_to_string(void* const* buffer, int count);
*/ */
const char* tf_util_backtrace_string(); const char* tf_util_backtrace_string();
/**
** Print a stack backtrace of the calling thread.
*/
void tf_util_print_backtrace();
/** /**
** Convert a function pointer to its name, if possible. ** Convert a function pointer to its name, if possible.
** @return The function name or null. ** @return The function name or null.

Some files were not shown because too many files have changed in this diff Show More