Compare commits
199 Commits
Author | SHA1 | Date | |
---|---|---|---|
36370f2dea | |||
db9bf7f7bd | |||
fa7aef0c37 | |||
b135ea17f6 | |||
4b1643bc47 | |||
240a8ce9c7 | |||
8928e8722b | |||
d692734e55 | |||
50197198b4 | |||
1ee1107c93 | |||
cf90533b6c | |||
f0211f621e | |||
d9693af89b | |||
13722232fb | |||
0bcb033349 | |||
e92c439724 | |||
7f34b585d3 | |||
d7e9fd918a | |||
9899c0c5e2 | |||
c50de0b0f0 | |||
bb7d2d7ae0 | |||
862d172ca8 | |||
3671051d0e | |||
223e20cbbc | |||
9af4312561 | |||
934e40240e | |||
edb1980387 | |||
bb7b04013f | |||
26a3007268 | |||
5de2b09596 | |||
3660577a23 | |||
98b4c7cf04 | |||
427a7b8d25 | |||
67b84830cd | |||
973cd53266 | |||
1afdbe6932 | |||
942f582329 | |||
951a80389a | |||
b7ecfc9925 | |||
59e389d793 | |||
2ec047cc00 | |||
ee33f54745 | |||
7a79534ca8 | |||
a74a9fc821 | |||
e2c388b9db | |||
0f573ce09e | |||
bc70e41b7c | |||
f500e14aa3 | |||
8b47938238 | |||
8912212d8e | |||
6a346bf940 | |||
b5bdae4611 | |||
6590da5793 | |||
eb2b426ec7 | |||
4864a0411f | |||
68590cae33 | |||
abf2bbaec2 | |||
0da7e2722f | |||
60d4b06057 | |||
4c3df34950 | |||
7737e60b52 | |||
71e816bc13 | |||
c74f90ef04 | |||
26cb7e5a17 | |||
2bad6672d8 | |||
71c4011526 | |||
5e81078f59 | |||
31b78e74df | |||
2ff689aab0 | |||
0a6f0ed3f7 | |||
bfec46673d | |||
3a16614c72 | |||
9c9efb845c | |||
6192f1b94d | |||
ce451b2449 | |||
8534e16469 | |||
f6cc6f2eae | |||
0904425221 | |||
a729886522 | |||
e5dfedc7d1 | |||
f02423d084 | |||
8f4b6e83eb | |||
3029919553 | |||
ac67db0591 | |||
1e08838f5b | |||
d814f7ee77
|
|||
7edfb9d386 | |||
6928d6caba | |||
1a626875cf | |||
6eb3b64334 | |||
09f3595e93 | |||
11e89622d4 | |||
0fa8acc264 | |||
afc7c64ed8 | |||
c794c1b885 | |||
6247529799 | |||
ad3eedc1fb | |||
1cfac3cae6 | |||
478bcd5d13 | |||
6932da69b3 | |||
857f47bf55 | |||
373d742751 | |||
575622c522 | |||
a3e86ccb1d | |||
e491798ff1 | |||
15df4ac236 | |||
017a74c4e4 | |||
6e5b1127f3 | |||
95f4f88949 | |||
92858882d7 | |||
4cba95ec8c | |||
bbb2f89d85 | |||
88213038b2 | |||
cc18b41e76 | |||
f16127a238 | |||
2a6ecfaede | |||
cf4a09bf03 | |||
20e60262fd | |||
9e3928c976 | |||
95d8768545 | |||
8679d09040 | |||
4cc5c6acb3 | |||
6f9b548b1a | |||
fbff3386a9 | |||
e5899fca58 | |||
dddec489b9 | |||
b4049eaeaa | |||
b3fd724b5a | |||
aca25be86a | |||
0f8cbdac57 | |||
630219d667 | |||
fef268e434 | |||
e18dcf3a48 | |||
e019320146 | |||
65b31e14f9 | |||
9cb5cbcde9 | |||
25914ff5a7 | |||
c4af799279 | |||
3f8f0e14f4 | |||
5414b30e7f | |||
7aee897c1b | |||
4060f9cc11 | |||
5b526cbf5b | |||
f5065ff42b | |||
86b5546f5f | |||
43ae2a293b | |||
b8ddbd4255 | |||
87cdba1db8 | |||
df83187e33 | |||
eccdbf29ab | |||
28d181f8bc | |||
d386daf2ff | |||
0c1e116c1e | |||
bb0ed67827 | |||
b111d06851 | |||
79388845ea | |||
99faef2e77 | |||
21fffbfe10 | |||
64fb9f0c2a | |||
a42e0bef2c | |||
45a09006e1 | |||
240484be4c | |||
22f4d115e3 | |||
32920e0e5d | |||
f03a5918d1 | |||
dd1870b52a | |||
f0c1a8f98f | |||
0c181d7e7c | |||
e3dc0e833a | |||
6de875edea | |||
986a55173f | |||
59c8cabf02 | |||
a33b9fab07 | |||
f8a725e1e7 | |||
b3ab3af01b | |||
79f9463e56 | |||
4257b2ed51 | |||
6488ab60ec | |||
18bd3dfcf9 | |||
ec114e160d | |||
de033af18f | |||
e971c6fcb7 | |||
d3c465391c | |||
f1fa19593d | |||
60b6f9c73e | |||
55b95ddecb | |||
0800a251b2 | |||
82cf7a80eb | |||
379f3d12eb | |||
e52972d4d4 | |||
1a0ca4dec2 | |||
0bac9d8d5a | |||
a9608363c5 | |||
f1a2c5ae8e | |||
192a81ede7 | |||
916aa5abbd | |||
d4a5cc6eee | |||
d19605cc8d | |||
8d529327a4 |
@ -1,4 +1,3 @@
|
||||
.svn
|
||||
db.sqlite
|
||||
out/**/*.o
|
||||
out/**/*.d
|
||||
.git
|
||||
db.sqlite*
|
||||
out/
|
||||
|
@ -6,15 +6,51 @@ jobs:
|
||||
Build-All:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
valid_volumes: ['/opt/keys']
|
||||
image: node:23-bookworm-slim
|
||||
valid_volumes:
|
||||
- '/opt/keys'
|
||||
- '/opt/deps'
|
||||
volumes:
|
||||
- /opt/keys:/opt/keys
|
||||
- /opt/deps:/opt/deps
|
||||
steps:
|
||||
- name: check out code
|
||||
- name: Install build dependencies
|
||||
run: >
|
||||
apt update && apt install -y \
|
||||
build-essential \
|
||||
clang-19 \
|
||||
cmake \
|
||||
curl \
|
||||
docker.io \
|
||||
doxygen \
|
||||
file \
|
||||
gcc-aarch64-linux-gnu \
|
||||
git \
|
||||
graphviz \
|
||||
libgpgme11 \
|
||||
libssl-dev \
|
||||
mingw-w64 \
|
||||
rsync \
|
||||
unzip \
|
||||
zip \
|
||||
zlib1g-dev
|
||||
- name: Get code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- run: ln -s /opt/keys .keys
|
||||
- name: Setup environment
|
||||
run: |
|
||||
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-19 100
|
||||
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-19 100
|
||||
ln -s /opt/keys .keys
|
||||
ln -sf /opt/deps/ios_toolchain deps/
|
||||
ln -sf /opt/deps/macos_toolchain deps/
|
||||
- name: Build documentation
|
||||
run: |
|
||||
mkdir -p out/html/ ~/.ssh/
|
||||
make docs
|
||||
echo 'pildefriends ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKD3Kde5vDO0TrMBDK0IGGeNGe/XinWAZkSQ/rXxwUjt' >> ~/.ssh/known_hosts
|
||||
rsync -avP --delete -e "ssh -i /opt/keys/ssh.ed25519" out/html/ tfdocs@pildefriends:docs/html/
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
@ -24,14 +60,12 @@ jobs:
|
||||
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 libgpgme11 gcc-aarch64-linux-gnu
|
||||
- run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all docs
|
||||
- run: docker build .
|
||||
- uses: actions/upload-artifact@v3
|
||||
- name: Docker build
|
||||
run: DOCKER_BUILDKIT=1 docker build .
|
||||
- name: Build
|
||||
run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist docs
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: |
|
||||
out/TildeFriends-release.fdroid.apk
|
||||
out/winrelease/tildefriends.standalone.exe
|
||||
out/tildefriends-x86_64.AppImage
|
||||
out/release/tildefriends.standalone
|
||||
out/armrelease/tildefriends.standalone
|
||||
name: dist
|
||||
path: dist/*
|
||||
|
4
.gitignore
vendored
@ -1,11 +1,13 @@
|
||||
build/
|
||||
*.core
|
||||
db.*
|
||||
deps/ios_toolchain/
|
||||
deps/ios_toolchain
|
||||
deps/macos_toolchain
|
||||
deps/openssl/
|
||||
dist/
|
||||
.flatpak-builder
|
||||
.keys
|
||||
**/.DS_Store
|
||||
logs/
|
||||
**/node_modules
|
||||
out
|
||||
|
6
.gitmodules
vendored
@ -26,6 +26,6 @@
|
||||
[submodule "deps/c-ares"]
|
||||
path = deps/c-ares
|
||||
url = https://github.com/c-ares/c-ares.git
|
||||
[submodule "docs"]
|
||||
path = docs
|
||||
url = https://dev.tildefriends.net/cory/tildefriends.wiki.git
|
||||
[submodule "deps/zsign"]
|
||||
path = deps/zsign
|
||||
url = https://github.com/zhlynn/zsign.git
|
||||
|
@ -1,19 +1,16 @@
|
||||
FROM bitnami/minideb:bullseye AS build
|
||||
FROM bitnami/minideb:bookworm AS build
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
libc6-dev \
|
||||
libssl-dev \
|
||||
perl \
|
||||
make
|
||||
|
||||
COPY . /app
|
||||
RUN make -C /app -j $(nproc) release
|
||||
|
||||
FROM bitnami/minideb:bullseye
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libssl1.1
|
||||
FROM bitnami/minideb:bookworm
|
||||
|
||||
COPY --from=build /app/out/release/tildefriends /app/out/release/tildefriends
|
||||
COPY --from=build /app/apps /app/apps
|
||||
|
14
Doxyfile
@ -943,7 +943,9 @@ WARN_LOGFILE =
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = src/
|
||||
INPUT = README.md \
|
||||
docs/ \
|
||||
src/
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
@ -1110,7 +1112,7 @@ FILTER_SOURCE_PATTERNS =
|
||||
# (index.html). This can be useful if you have a project on for instance GitHub
|
||||
# and want to reuse the introduction page also for the doxygen output.
|
||||
|
||||
USE_MDFILE_AS_MAINPAGE =
|
||||
USE_MDFILE_AS_MAINPAGE = README.md
|
||||
|
||||
# The Fortran standard specifies that for fixed formatted Fortran code all
|
||||
# characters from position 72 are to be considered as comment. A common
|
||||
@ -1680,7 +1682,7 @@ DISABLE_INDEX = NO
|
||||
# The default value is: NO.
|
||||
# This tag requires that the tag GENERATE_HTML is set to YES.
|
||||
|
||||
GENERATE_TREEVIEW = NO
|
||||
GENERATE_TREEVIEW = YES
|
||||
|
||||
# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
|
||||
# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
|
||||
@ -2268,7 +2270,7 @@ GENERATE_AUTOGEN_DEF = NO
|
||||
# database with symbols found by doxygen stored in tables.
|
||||
# The default value is: NO.
|
||||
|
||||
GENERATE_SQLITE3 = NO
|
||||
#GENERATE_SQLITE3 = NO
|
||||
|
||||
# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
|
||||
# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
|
||||
@ -2276,7 +2278,7 @@ GENERATE_SQLITE3 = NO
|
||||
# The default directory is: sqlite3.
|
||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
||||
|
||||
SQLITE3_OUTPUT = sqlite3
|
||||
#SQLITE3_OUTPUT = sqlite3
|
||||
|
||||
# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db
|
||||
# database file will be recreated with each doxygen run. If set to NO, doxygen
|
||||
@ -2284,7 +2286,7 @@ SQLITE3_OUTPUT = sqlite3
|
||||
# The default value is: YES.
|
||||
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
|
||||
|
||||
SQLITE3_RECREATE_DB = YES
|
||||
#SQLITE3_RECREATE_DB = YES
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# Configuration options related to the Perl module output
|
||||
|
352
GNUmakefile
@ -16,11 +16,14 @@ MAKEFLAGS += --no-builtin-rules
|
||||
## LD := Linker.
|
||||
## ANDROID_SDK := Path to the Android SDK.
|
||||
|
||||
VERSION_CODE := 32
|
||||
VERSION_NUMBER := 0.0.27
|
||||
VERSION_CODE := 34
|
||||
VERSION_CODE_IOS := 11
|
||||
VERSION_NUMBER := 0.0.29
|
||||
VERSION_NAME := This program kills fascists.
|
||||
|
||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3480000.zip
|
||||
IPHONEOS_VERSION_MIN=14.0
|
||||
|
||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3490100.zip
|
||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
||||
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
||||
@ -33,21 +36,27 @@ UNAME_M := $(shell uname -m)
|
||||
ANDROID_SDK ?= ~/Android/Sdk
|
||||
BUNDLETOOL = out/bundletool.jar
|
||||
|
||||
HAVE_WIN := 0
|
||||
HAVE_CROSS_AARCH64 := 0
|
||||
HAVE_WIN :=
|
||||
HAVE_CROSS_AARCH64 :=
|
||||
USE_SYSTEM_SSL :=
|
||||
|
||||
export SOURCE_DATE_EPOCH=1
|
||||
export TZ=UTC
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
BUILD_TYPES := macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease
|
||||
BUILD_TYPES := debug release iosdebug iosrelease iossimdebug iossimrelease
|
||||
HAVE_ANDROID = 0
|
||||
HAVE_LINUX_IOS = 0
|
||||
HAVE_LINUX_MACOS = 0
|
||||
HAVE_WIN = 0
|
||||
else ifeq ($(UNAME_S),Linux)
|
||||
BUILD_TYPES := debug release
|
||||
HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1,0)
|
||||
HAVE_LINUX_IOS = $(if $(shell which deps/ios_toolchain/target/bin deps/ios_toolchain/target/bin/arm-apple-darwin11-clang),1,0)
|
||||
HAVE_WIN = $(if $(shell which x86_64-w64-mingw32-gcc-win32),1,0)
|
||||
HAVE_ANDROID = $(if $(shell which $(ANDROID_SDK)/platform-tools/adb),1)
|
||||
HAVE_LINUX_IOS = $(if $(shell which deps/ios_toolchain/target/bin deps/ios_toolchain/target/bin/arm-apple-darwin11-clang),1)
|
||||
HAVE_LINUX_MACOS = $(if $(shell which deps/macos_toolchain/bin/oa64-clang),1)
|
||||
HAVE_WIN = $(if $(shell which x86_64-w64-mingw32-gcc-win32),1)
|
||||
ifneq ($(UNAME_M),aarch64)
|
||||
HAVE_CROSS_AARCH64 = $(if $(shell which aarch64-linux-gnu-gcc),1,0)
|
||||
HAVE_CROSS_AARCH64 = $(if $(shell which aarch64-linux-gnu-gcc),1)
|
||||
endif
|
||||
else ifeq ($(UNAME_S),Haiku)
|
||||
BUILD_TYPES := debug release
|
||||
@ -56,6 +65,11 @@ LDFLAGS += \
|
||||
-lbsd \
|
||||
-lnetwork \
|
||||
-Wno-stringop-overflow
|
||||
USE_SYSTEM_SSL := 1
|
||||
HAVE_ANDROID = 0
|
||||
HAVE_LINUX_IOS = 0
|
||||
HAVE_LINUX_MACOS = 0
|
||||
HAVE_WIN = 0
|
||||
else ifeq ($(UNAME_S),OpenBSD)
|
||||
BUILD_TYPES := debug release
|
||||
CFLAGS += \
|
||||
@ -63,18 +77,24 @@ CFLAGS += \
|
||||
LDFLAGS += \
|
||||
-lexecinfo \
|
||||
-lc++abi
|
||||
HAVE_ANDROID := 0
|
||||
HAVE_LINUX_IOS := 0
|
||||
HAVE_ANDROID :=
|
||||
HAVE_LINUX_IOS :=
|
||||
HAVE_LINUX_MACOS :=
|
||||
USE_SYSTEM_SSL := 1
|
||||
else
|
||||
$(error Unexpected host platform $(UNAME_S).)
|
||||
endif
|
||||
|
||||
# Everything is set above.
|
||||
$(info Building Tilde Friends $(VERSION_NUMBER) android=$(if $(HAVE_ANDROID),1,0) win=$(if $(HAVE_WIN),1,0) cross_aarch64=$(if $(HAVE_CROSS_AARCH64),1,0) cross_ios=$(if $(HAVE_LINUX_IOS),1,0) cross_macos=$(if $(HAVE_LINUX_MACOS),1,0) system_ssl=$(if $(USE_SYSTEM_SSL),1,0))
|
||||
|
||||
CFLAGS += \
|
||||
-std=gnu11 \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-cast-function-type-mismatch \
|
||||
-Wno-unknown-warning-option \
|
||||
-Wno-unused-parameter \
|
||||
-MMD \
|
||||
-MP \
|
||||
-ffunction-sections \
|
||||
@ -136,12 +156,28 @@ ifeq ($(HAVE_CROSS_AARCH64),1)
|
||||
BUILD_TYPES += armdebug armrelease
|
||||
endif
|
||||
|
||||
LINUX_TARGETS := \
|
||||
HOST_TARGETS := \
|
||||
out/debug/tildefriends \
|
||||
out/release/tildefriends
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
MACOS_TARGETS := \
|
||||
out/macosdebug/tildefriends \
|
||||
out/macosrelease/tildefriends
|
||||
out/debug/tildefriends \
|
||||
out/release/tildefriends
|
||||
else ifeq ($(UNAME_S),Linux)
|
||||
ifeq ($(HAVE_LINUX_MACOS),1)
|
||||
MACOS_TARGETS := \
|
||||
out/macosdebug-arm/tildefriends \
|
||||
out/macosrelease-arm/tildefriends \
|
||||
out/macosdebug-x86_64/tildefriends \
|
||||
out/macosrelease-x86_64/tildefriends
|
||||
all: out/macosdebug/tildefriends.standalone
|
||||
all: out/macosrelease/tildefriends.standalone
|
||||
else
|
||||
MACOS_TARGETS :=
|
||||
endif
|
||||
else
|
||||
MACOS_TARGETS :=
|
||||
endif
|
||||
IOS_TARGETS := \
|
||||
out/iosdebug/tildefriends \
|
||||
out/iosrelease/tildefriends
|
||||
@ -155,6 +191,14 @@ ifeq ($(HAVE_LINUX_IOS),1)
|
||||
BUILD_TYPES += iosdebug iosrelease
|
||||
all: $(IOS_APPS)
|
||||
endif
|
||||
ifeq ($(HAVE_LINUX_MACOS),1)
|
||||
BUILD_TYPES += \
|
||||
macosdebug-arm \
|
||||
macosrelease-arm \
|
||||
macosdebug-x86_64 \
|
||||
macosrelease-x86_64
|
||||
all: $(IOS_APPS)
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
all: $(IOS_APPS) \
|
||||
out/tildefriends-iossimdebug.app/tildefriends \
|
||||
@ -169,35 +213,38 @@ DEBUG_TARGETS := \
|
||||
out/windebug/tildefriends.exe \
|
||||
out/iosdebug/tildefriends \
|
||||
out/iossimdebug/tildefriends \
|
||||
out/macosdebug/tildefriends \
|
||||
out/androiddebug/tildefriends \
|
||||
out/androiddebug-armv7a/tildefriends \
|
||||
out/androiddebug-x86_64/tildefriends \
|
||||
out/androiddebug-x86/tildefriends \
|
||||
out/armdebug/tildefriends
|
||||
out/armdebug/tildefriends \
|
||||
out/macosdebug-arm/tildefriends \
|
||||
out/macosdebug-x86_64/tildefriends
|
||||
RELEASE_TARGETS := \
|
||||
out/release/tildefriends \
|
||||
out/winrelease/tildefriends.exe \
|
||||
out/iosrelease/tildefriends \
|
||||
out/iossimrelease/tildefriends \
|
||||
out/macosrelease/tildefriends \
|
||||
out/androidrelease/tildefriends \
|
||||
out/androidrelease-armv7a/tildefriends \
|
||||
out/androidrelease-x86_64/tildefriends \
|
||||
out/androidrelease-x86/tildefriends \
|
||||
out/armrelease/tildefriends
|
||||
out/armrelease/tildefriends \
|
||||
out/macosrelease-arm/tildefriends \
|
||||
out/macosrelease-x86_64/tildefriends
|
||||
ALL_TARGETS = $(DEBUG_TARGETS) $(RELEASE_TARGETS)
|
||||
ANDROID_RELEASE_TARGETS := $(filter-out $(DEBUG_TARGETS),$(ANDROID_TARGETS))
|
||||
NONANDROID_RELEASE_TARGETS := $(filter-out $(ANDROID_ARM64_TARGETS),$(RELEASE_TARGETS))
|
||||
NONANDROID_TARGETS := $(filter-out $(ANDROID_TARGETS),$(ALL_TARGETS))
|
||||
NONMACOS_TARGETS := $(filter-out $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS),$(ALL_TARGETS))
|
||||
DEADSTRIP_TARGETS := $(filter-out $(ANDROID_TARGETS),$(NONMACOS_TARGETS))
|
||||
ifneq ($(UNAME_S),OpenBSD)
|
||||
$(NONMACOS_TARGETS): LDFLAGS += -static-libgcc
|
||||
endif
|
||||
|
||||
$(NONANDROID_TARGETS): CFLAGS += -fno-omit-frame-pointer
|
||||
$(filter-out $(WINDOWS_TARGETS),$(ALL_TARGETS)): LDFLAGS += -rdynamic
|
||||
$(filter-out $(WINDOWS_TARGETS),$(ALL_TARGETS)): LDFLAGS += \
|
||||
-rdynamic \
|
||||
-gz=zlib
|
||||
$(ANDROID_TARGETS): CFLAGS += \
|
||||
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
|
||||
-fPIC \
|
||||
@ -221,25 +268,29 @@ $(WINDOWS_TARGETS): CFLAGS += \
|
||||
-D_WIN32_WINNT=0x0A00 \
|
||||
-DWINVER=0x0A00 \
|
||||
-DNTDDI_VERSION=NTDDI_WIN10 \
|
||||
-Ideps/openssl/mingw64/usr/local/include
|
||||
-Iout/openssl/$(UNAME_S)/mingw64/usr/local/include
|
||||
$(WINDOWS_TARGETS): LDFLAGS += \
|
||||
-static \
|
||||
-lm \
|
||||
-Ldeps/openssl/mingw64/usr/local/lib
|
||||
-Lout/openssl/$(UNAME_S)/mingw64/usr/local/lib
|
||||
$(AARCH64_TARGETS): CC = aarch64-linux-gnu-gcc
|
||||
$(AARCH64_TARGETS): AS = $(CC)
|
||||
$(AARCH64_TARGETS): CFLAGS += -Ideps/openssl/Linux/aarch64/usr/local/include
|
||||
$(AARCH64_TARGETS): LDFLAGS += -Ldeps/openssl/Linux/aarch64/usr/local/lib
|
||||
$(AARCH64_TARGETS): CFLAGS += -Iout/openssl/Linux/aarch64/usr/local/include
|
||||
$(AARCH64_TARGETS): LDFLAGS += -Lout/openssl/Linux/aarch64/usr/local/lib
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
$(MACOS_TARGETS): CC = xcrun clang
|
||||
$(HOST_TARGETS): CC = xcrun clang
|
||||
$(IOS_TARGETS): IOS_SYSROOT := $(shell xcrun --sdk iphoneos --show-sdk-path)
|
||||
$(IOS_TARGETS): CC = xcrun --sdk iphoneos clang -isysroot $(IOS_SYSROOT) -arch arm64
|
||||
$(IOSSIM_TARGETS): IOSSIM_SYSROOT := $(shell xcrun --sdk iphonesimulator --show-sdk-path)
|
||||
$(IOSSIM_TARGETS): CC = xcrun --sdk iphonesimulator clang -isysroot $(IOSSIM_SYSROOT) -arch x86_64
|
||||
else ifeq ($(UNAME_S),Linux)
|
||||
$(IOS_TARGETS): CFLAGS += -isysroot deps/ios_toolchain/target/SDKs/iPhoneOS18.2.sdk -arch arm64
|
||||
$(IOS_TARGETS): CFLAGS += -isysroot deps/ios_toolchain/target/SDKs/iPhoneOS18.2.sdk -arch arm64 -DTARGET_OS_IPHONE=1
|
||||
$(IOS_TARGETS): LDFLAGS += -isysroot deps/ios_toolchain/target/SDKs/iPhoneOS18.2.sdk
|
||||
$(IOS_TARGETS): CC = PATH=$$PATH:deps/ios_toolchain/target/bin deps/ios_toolchain/target/bin/arm-apple-darwin11-clang
|
||||
$(IOS_TARGETS): CC = PATH=$$PATH:deps/ios_toolchain/target/bin LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/ios_toolchain/target/lib deps/ios_toolchain/target/bin/arm-apple-darwin11-clang
|
||||
$(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): CC = PATH=deps/macos_toolchain/bin:$$PATH LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/macos_toolchain/lib deps/macos_toolchain/bin/o64-clang
|
||||
$(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): CC = PATH=deps/macos_toolchain/bin:$$PATH LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/macos_toolchain/lib deps/macos_toolchain/bin/oa64-clang
|
||||
$(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): CC = PATH=deps/macos_toolchain/bin:$$PATH LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/macos_toolchain/lib deps/macos_toolchain/bin/o64-clang
|
||||
$(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): CC = PATH=deps/macos_toolchain/bin:$$PATH LD_LIBRARY_PATH=$$LD_LIBRARY_PATH:deps/macos_toolchain/lib deps/macos_toolchain/bin/oa64-clang
|
||||
endif
|
||||
$(ANDROID_X86_64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := x86_64-linux-android
|
||||
$(ANDROID_X86_TARGETS): ANDROID_NDK_TARGET_TRIPLE := i686-linux-android
|
||||
@ -250,30 +301,39 @@ $(ANDROID_TARGETS): AS = $(CC)
|
||||
$(ANDROID_TARGETS): CFLAGS += \
|
||||
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_MIN_SDK_VERSION) \
|
||||
-Wno-unknown-warning-option
|
||||
$(ANDROID_ARMV7A_TARGETS): CFLAGS += -Ideps/openssl/android/armeabi-v7a/usr/local/include
|
||||
$(ANDROID_ARMV7A_TARGETS): LDFLAGS += -Ldeps/openssl/android/armeabi-v7a/usr/local/lib
|
||||
$(ANDROID_ARM64_TARGETS): CFLAGS += -Ideps/openssl/android/arm64-v8a/usr/local/include
|
||||
$(ANDROID_ARM64_TARGETS): LDFLAGS += -Ldeps/openssl/android/arm64-v8a/usr/local/lib
|
||||
$(ANDROID_X86_TARGETS): CFLAGS += -Ideps/openssl/android/x86/usr/local/include
|
||||
$(ANDROID_ARMV7A_TARGETS): CFLAGS += -Iout/openssl/android/armeabi-v7a/usr/local/include
|
||||
$(ANDROID_ARMV7A_TARGETS): LDFLAGS += -Lout/openssl/android/armeabi-v7a/usr/local/lib
|
||||
$(ANDROID_ARM64_TARGETS): CFLAGS += -Iout/openssl/android/arm64-v8a/usr/local/include
|
||||
$(ANDROID_ARM64_TARGETS): LDFLAGS += -Lout/openssl/android/arm64-v8a/usr/local/lib
|
||||
$(ANDROID_X86_TARGETS): CFLAGS += -Iout/openssl/android/x86/usr/local/include
|
||||
$(ANDROID_X86_TARGETS): CFLAGS += -Wno-atomic-alignment
|
||||
$(ANDROID_X86_TARGETS): LDFLAGS += -Ldeps/openssl/android/x86/usr/local/lib
|
||||
$(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_TARGETS): LDFLAGS += -Lout/openssl/android/x86/usr/local/lib
|
||||
$(ANDROID_X86_64_TARGETS): CFLAGS += -Iout/openssl/android/x86_64/usr/local/include
|
||||
$(ANDROID_X86_64_TARGETS): LDFLAGS += -Lout/openssl/android/x86_64/usr/local/lib
|
||||
$(NONMACOS_TARGETS): CFLAGS += -Wno-cast-function-type
|
||||
$(DEADSTRIP_TARGETS): LDFLAGS += -Wl,--gc-sections
|
||||
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=9.0
|
||||
$(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=9.0
|
||||
$(MACOS_TARGETS): LDFLAGS += -Wl,-dead_strip
|
||||
$(NONMACOS_TARGETS): LDFLAGS += -Wl,--gc-sections -Wl,--as-needed
|
||||
$(IOS_TARGETS): CFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
|
||||
$(IOS_TARGETS): LDFLAGS += -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
$(IOS_TARGETS): CFLAGS += -Ideps/openssl/ios/ios64-xcrun/usr/local/include
|
||||
$(IOS_TARGETS): LDFLAGS += -Ldeps/openssl/ios/ios64-xcrun/usr/local/lib
|
||||
$(IOS_TARGETS): CFLAGS += -Iout/openssl/ios/ios64-xcrun/usr/local/include
|
||||
$(IOS_TARGETS): LDFLAGS += -Lout/openssl/ios/ios64-xcrun/usr/local/lib
|
||||
else
|
||||
$(IOS_TARGETS): CFLAGS += -Ideps/openssl/$(UNAME_S)/ios64-cross/usr/local/include
|
||||
$(IOS_TARGETS): LDFLAGS += -Ldeps/openssl/$(UNAME_S)/ios64-cross/usr/local/lib
|
||||
$(IOS_TARGETS): CFLAGS += -Iout/openssl/$(UNAME_S)/ios64-cross/usr/local/include
|
||||
$(IOS_TARGETS): LDFLAGS += -Lout/openssl/$(UNAME_S)/ios64-cross/usr/local/lib
|
||||
$(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-x86_64/usr/local/include
|
||||
$(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-arm/usr/local/include
|
||||
$(filter $(BUILD_DIR)/macosdebug-x86_64/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib
|
||||
$(filter $(BUILD_DIR)/macosdebug-arm/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-arm/usr/local/lib
|
||||
$(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-x86_64/usr/local/include
|
||||
$(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): CFLAGS += -Iout/openssl/$(UNAME_S)/macos-arm/usr/local/include
|
||||
$(filter $(BUILD_DIR)/macosrelease-x86_64/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib
|
||||
$(filter $(BUILD_DIR)/macosrelease-arm/%,$(ALL_TARGETS)): LDFLAGS += -Lout/openssl/$(UNAME_S)/macos-arm/usr/local/lib
|
||||
endif
|
||||
$(IOSSIM_TARGETS): CFLAGS += -Ideps/openssl/ios/iossimulator-xcrun/usr/local/include
|
||||
$(IOSSIM_TARGETS): LDFLAGS += -Ldeps/openssl/ios/iossimulator-xcrun/usr/local/lib
|
||||
$(LINUX_TARGETS) $(MACOS_TARGETS): CFLAGS += -Ideps/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/include
|
||||
$(LINUX_TARGETS) $(MACOS_TARGETS): LDFLAGS += -Ldeps/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib
|
||||
$(IOSSIM_TARGETS): CFLAGS += -Iout/openssl/ios/iossimulator-xcrun/usr/local/include
|
||||
$(IOSSIM_TARGETS): LDFLAGS += -Lout/openssl/ios/iossimulator-xcrun/usr/local/lib
|
||||
$(HOST_TARGETS): CFLAGS += -Iout/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/include
|
||||
$(HOST_TARGETS): LDFLAGS += -Lout/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib
|
||||
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
@ -292,13 +352,14 @@ endif
|
||||
|
||||
get_objs = \
|
||||
$(foreach build_type,$(BUILD_TYPES),$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)))))) \
|
||||
$(foreach build_type,debug release armdebug armrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
|
||||
$(foreach build_type,windebug winrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_win))))) \
|
||||
$(foreach build_type,androiddebug androidrelease androiddebug-x86 androidrelease-x86 androiddebug-x86_64 androidrelease-x86_64 androiddebug-armv7a androiddebug-armv7a,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_android))))) \
|
||||
$(foreach build_type,androiddebug androidrelease androiddebug-x86 androidrelease-x86 androiddebug-x86_64 androidrelease-x86_64 androiddebug-armv7a androidrelease-armv7a,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_android))))) \
|
||||
$(foreach build_type,androiddebug androidrelease androiddebug-x86 androidrelease-x86 androiddebug-x86_64 androidrelease-x86_64 androiddebug-armv7a androidrelease-armv7a,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
|
||||
$(foreach build_type,macosdebug macosrelease iosdebug iosrelease iossimdebug iossimrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_macos))))) \
|
||||
$(foreach build_type,iosdebug iosrelease iossimdebug iossimrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_ios))))) \
|
||||
$(foreach build_type,androiddebug-x86 androidrelease-x86,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_x86)))))
|
||||
$(foreach build_type,iosdebug iosrelease iossimdebug iossimrelease macosdebug-arm macosrelease-arm macosdebug-x86_64 macosrelease-x86_64,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_macos))))) \
|
||||
$(foreach build_type,androiddebug-x86 androidrelease-x86,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_x86))))) \
|
||||
$(if $(findstring Darwin,$(UNAME_S)),$(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_macos)))))) \
|
||||
$(if $(findstring Darwin,$(UNAME_S)),,$(foreach build_type,debug release armdebug armrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))))
|
||||
|
||||
APP_SOURCES := $(wildcard src/*.c)
|
||||
APP_SOURCES_ios := $(wildcard src/*.m)
|
||||
@ -320,10 +381,12 @@ $(APP_OBJS): CFLAGS += \
|
||||
-Ideps/valgrind \
|
||||
-Wdouble-promotion \
|
||||
-Werror
|
||||
ifneq ($(UNAME_S),Darwin)
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
$(filter-out $(BUILD_DIR)/android% $(BUILD_DIR)/macos% $(BUILD_DIR)/ios%,$(APP_OBJS)): CFLAGS += \
|
||||
$(filter-out $(BUILD_DIR)/android% $(BUILD_DIR)/ios% $(BUILD_DIR)/macos%,$(APP_OBJS)): CFLAGS += \
|
||||
-fanalyzer
|
||||
endif
|
||||
endif
|
||||
|
||||
ARES_SOURCES := \
|
||||
deps/c-ares/src/lib/ares_addrinfo2hostent.c \
|
||||
@ -756,12 +819,12 @@ $(MINIUNZIP_OBJS): CFLAGS += \
|
||||
LDFLAGS += \
|
||||
-pthread \
|
||||
-lm
|
||||
$(LINUX_TARGETS) $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS) $(AARCH64_TARGETS): LDFLAGS += \
|
||||
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS) $(AARCH64_TARGETS) $(filter-out $(HOST_TARGETS),$(MACOS_TARGETS)): LDFLAGS += \
|
||||
-lssl \
|
||||
-lcrypto
|
||||
ifneq ($(UNAME_S),Haiku)
|
||||
ifneq ($(UNAME_S),OpenBSD)
|
||||
debug release $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
$(HOST_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
-ldl
|
||||
endif
|
||||
endif
|
||||
@ -794,27 +857,15 @@ $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
|
||||
##
|
||||
## Common targets:
|
||||
##
|
||||
debug: ## Build a debug executable for the current platform.
|
||||
release: ## Build a release executable for the current platform.
|
||||
all: $(BUILD_TYPES) ## Build all targets that appear possible to build on this machine.
|
||||
debug: ## Build a debug executable for the current host platform.
|
||||
release: ## Build a release executable for the current host platform.
|
||||
armdebug: ## Cross-compile aarch64 debug on Linux.
|
||||
armrelease: ## Cross-compile aarch64 release on Linux.
|
||||
all: $(BUILD_TYPES) ## Build all targets that appear possible to build on this machine.
|
||||
unix: debug release ## Build all UNIX targets.
|
||||
win: windebug winrelease ## Build all Windows targets.
|
||||
windebug: ## Cross-compile a debug win32 executable on Linux.
|
||||
winrelease: ## Cross-compile a release win32 executable on Linux.
|
||||
.PHONY: all win unix
|
||||
|
||||
##
|
||||
## Windows targets:
|
||||
##
|
||||
windebug: ## Build a debug win32 executable.
|
||||
winrelease: ## Build a release win32 executable.
|
||||
|
||||
##
|
||||
## MacOS targets:
|
||||
##
|
||||
macosdebug: ## Build a MacOS debug executable.
|
||||
macosrelease: ## Build a MacOS release executable.
|
||||
|
||||
ALL_APP_OBJS := \
|
||||
$(APP_OBJS) \
|
||||
$(ARES_OBJS) \
|
||||
@ -870,6 +921,17 @@ src/android/AndroidManifest.xml : $(firstword $(MAKEFILE_LIST))
|
||||
-e 's/android:targetSdkVersion="[[:digit:]]*"/android:targetSdkVersion="$(ANDROID_TARGET_SDK_VERSION)"/' \
|
||||
$@
|
||||
|
||||
src/ios/Info.plist : $(firstword $(MAKEFILE_LIST))
|
||||
@echo "[ios_version] $@"
|
||||
@cat $@ | \
|
||||
tr '\n' '^' | \
|
||||
sed -r \
|
||||
-e 's@(<key>CFBundleShortVersionString</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(VERSION_NUMBER:%-wip=%)\2@' \
|
||||
-e 's@(<key>CFBundleVersion</key>\^[[:space:]]*<string>)[[:digit:]]+(</string>)@\1$(VERSION_CODE_IOS)\2@' \
|
||||
-e 's@(<key>MinimumOSVersion</key>\^[[:space:]]*<string>)[0-9.]*(</string>)@\1$(IPHONEOS_VERSION_MIN)\2@' | \
|
||||
tr '^' '\n' > \
|
||||
$@.tmp && mv $@.tmp $@ || rm -f $@.tmp
|
||||
|
||||
##
|
||||
## Android targets:
|
||||
##
|
||||
@ -887,29 +949,30 @@ out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
|
||||
@echo "[aapt2] $@"
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/layout/activity_main.xml
|
||||
|
||||
out/res/drawable_icon.xml.flat: src/android/res/drawable/icon.xml
|
||||
out/res/drawable_%.xml.flat: src/android/res/drawable/%.xml
|
||||
@mkdir -p $(dir $@)
|
||||
@echo "[aapt2] $@"
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/drawable/icon.xml
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ $(<)
|
||||
|
||||
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 out/res/drawable_logo.xml.flat src/android/AndroidManifest.xml
|
||||
@echo [aapt2 link] res.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 \
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon.xml.flat out/res/drawable_logo.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
|
||||
out/apk/res.fdroid.apk out/gen_fdroid/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat out/res/drawable_icon_fdroid.xml.flat out/res/drawable_logo.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 \
|
||||
@sed -e 's@drawable/icon@drawable/icon_fdroid@' src/android/AndroidManifest.xml > out/apk/AndroidManifest.fdroid.xml
|
||||
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat out/res/drawable_icon_fdroid.xml.flat out/res/drawable_logo.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 \
|
||||
--manifest out/apk/AndroidManifest.fdroid.xml \
|
||||
-o out/apk/res.fdroid.apk \
|
||||
--java out/gen_fdroid/
|
||||
|
||||
@ -926,11 +989,12 @@ out/apk/classes.dex: $(CLASS_FILES)
|
||||
@$(ANDROID_BUILD_TOOLS)/d8 --lib $(ANDROID_PLATFORM)/android.jar --output $(dir $@) out/classes/com/unprompted/tildefriends/*.class
|
||||
|
||||
PACKAGE_DIRS := \
|
||||
apps/ \
|
||||
core/ \
|
||||
deps/codemirror/ \
|
||||
deps/prettier/ \
|
||||
deps/lit/
|
||||
apps \
|
||||
core \
|
||||
deps/codemirror \
|
||||
deps/prettier \
|
||||
deps/lit \
|
||||
deps/speedscope
|
||||
|
||||
RAW_FILES := $(sort $(filter-out apps/blog% apps/issues% apps/welcome% apps/journal% %.map, $(shell find $(PACKAGE_DIRS) -type f -not -name '.*')))
|
||||
|
||||
@ -960,6 +1024,7 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
||||
--manifest src/android/AndroidManifest.xml \
|
||||
-R out/res/layout_activity_main.xml.flat \
|
||||
-R out/res/drawable_icon.xml.flat \
|
||||
-R out/res/drawable_logo.xml.flat \
|
||||
--auto-add-overlay
|
||||
@unzip out/aab/temporary.apk -d out/aab/staging/
|
||||
@mkdir -p out/aab/staging/root/deps
|
||||
@ -986,7 +1051,7 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
||||
@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
|
||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@
|
||||
|
||||
aab: out/TildeFriends.aab ## Build an Android App Bundle.
|
||||
.PHONY: aab
|
||||
@ -1092,12 +1157,24 @@ out/data.zip: $(RAW_FILES)
|
||||
@echo [zip] $@
|
||||
@zip -u $@ -q -9 $(RAW_FILES)
|
||||
|
||||
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip
|
||||
out/zsign_build/zsign: $(wildcard deps/zsign/*.cpp deps/zsign/*.h deps/zsign/*.txt deps/zsign/common/*)
|
||||
@+echo [cmake] $@
|
||||
@cmake -B out/zsign_build deps/zsign
|
||||
@cmake --build out/zsign_build -- COLOR=0 VERBOSE=0 MAKESILENT=-s
|
||||
|
||||
ifeq ($(HAVE_LINUX_IOS),1)
|
||||
ZSIGN_DEP = out/zsign_build/zsign
|
||||
else
|
||||
ZSIGN_DEP =
|
||||
endif
|
||||
|
||||
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip $(ZSIGN_DEP)
|
||||
@mkdir -p $(dir $@)
|
||||
@cp -v $< $@
|
||||
@cp -v $(filter-out out/zsign%,$<) $@
|
||||
@cp -v out/data.zip $(@D)/
|
||||
ifeq ($(HAVE_LINUX_IOS),1)
|
||||
@zsign -q -k .keys/apple.p12 -f -m src/ios/embedded.mobileprovision $(realpath $(dir $@))
|
||||
@mkdir -p $(realpath $(dir $@))/_CodeSignature
|
||||
@out/zsign_build/zsign -q -k .keys/apple.p12 -f -m src/ios/embedded.mobileprovision $(realpath $(dir $@))
|
||||
endif
|
||||
.SECONDARY:
|
||||
out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
|
||||
@ -1135,26 +1212,34 @@ iossimdebuggo: out/tildefriends-iossimdebug.app/tildefriends ## Build, install,
|
||||
xcrun simctl launch booted com.unprompted.tildefriends
|
||||
.PHONY: iossimdebuggo
|
||||
|
||||
ANDROID_DEPS := deps/openssl/android/arm64-v8a/usr/local/lib/libssl.a
|
||||
ANDROID_DEPS := out/openssl/android/arm64-v8a/usr/local/lib/libssl.a
|
||||
$(ANDROID_DEPS):
|
||||
+@ANDROID_NDK_ROOT=$(ANDROID_NDK) tools/ssl-android
|
||||
+@export ANDROID_NDK_ROOT=$(ANDROID_NDK)
|
||||
+@export BUILD_PLATFORM=android
|
||||
+@export TOOLCHAIN=$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64
|
||||
+@PATH="$$TOOLCHAIN/x86_64-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=x86_64 SSL_TARGET=android-x86_64 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
|
||||
+@PATH="$$TOOLCHAIN/i686-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=x86 SSL_TARGET=android-x86 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
|
||||
+@PATH="$$TOOLCHAIN/arm-linux-androideabi/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=armeabi-v7a SSL_TARGET=android-arm OPTIONS="--target=armv7a-linux-androideabi -Wl,--fix-cortex-a8 -D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
|
||||
+@PATH="$$TOOLCHAIN/aarch64-linux-android/bin:$$TOOLCHAIN/bin:$$PATH" BUILD_TARGET=arm64-v8a SSL_TARGET=android-arm64 OPTIONS="-D__ANDROID_API__=$(ANDROID_MIN_SDK_VERSION) -Wno-macro-redefined" tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/android%,$(APP_OBJS)): | $(ANDROID_DEPS)
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
LOCAL_DEPS := deps/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a
|
||||
ifneq ($(USE_SYSTEM_SSL),1)
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@/usr/bin/env bash tools/ssl-local
|
||||
+@tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/debug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/release/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_CROSS_AARCH64),1)
|
||||
LOCAL_DEPS := deps/openssl/$(UNAME_S)/aarch64/usr/local/lib/libssl.a
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/aarch64/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@OPTIONS="--cross-compile-prefix=aarch64-linux-gnu-" BUILD_TARGET=aarch64 tools/ssl-local
|
||||
+@OPTIONS="--cross-compile-prefix=aarch64-linux-gnu-" BUILD_TARGET=aarch64 SSL_TARGET=linux-aarch64 tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/armdebug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/armrelease/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_LINUX_IOS),1)
|
||||
LOCAL_DEPS := deps/openssl/$(UNAME_S)/ios64-cross/usr/local/lib/libssl.a
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/ios64-cross/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@PATH=deps/ios_toolchain/target/bin:$$PATH \
|
||||
BUILD_TARGET=ios64-cross \
|
||||
@ -1163,33 +1248,63 @@ $(LOCAL_DEPS):
|
||||
CROSS_TOP=../../deps/ios_toolchain/target \
|
||||
CROSS_SDK=iPhoneOS18.2.sdk \
|
||||
CC=clang \
|
||||
OPTIONS=-miphoneos-version-min=9.0 \
|
||||
OPTIONS=-miphoneos-version-min=$(IPHONEOS_VERSION_MIN) \
|
||||
tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_LINUX_MACOS),1)
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/macos-arm/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@PATH=../../deps/macos_toolchain/bin:$$PATH \
|
||||
BUILD_TARGET=macos-arm \
|
||||
SSL_TARGET=darwin64-arm64 \
|
||||
CC=../../deps/macos_toolchain/bin/oa64-clang \
|
||||
RANLIB=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ranlib \
|
||||
AR=../../deps/macos_toolchain/bin/arm64-apple-darwin24-ar \
|
||||
tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/macosrelease-arm/% $(BUILD_DIR)/macosdebug-arm/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/macos-x86_64/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@PATH=../../deps/macos_toolchain/bin:$$PATH \
|
||||
BUILD_TARGET=macos-x86_64 \
|
||||
SSL_TARGET=darwin64-x86_64 \
|
||||
CC=../../deps/macos_toolchain/bin/o64-clang \
|
||||
RANLIB=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ranlib \
|
||||
AR=../../deps/macos_toolchain/bin/x86_64-apple-darwin24-ar \
|
||||
tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/macosrelease-x86_64/% $(BUILD_DIR)/macosdebug-x86_64/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
LOCAL_DEPS := deps/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a
|
||||
LOCAL_DEPS := out/openssl/$(UNAME_S)/$(UNAME_M)/usr/local/lib/libssl.a
|
||||
$(LOCAL_DEPS):
|
||||
+@tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/macosdebug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/macosrelease/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
$(filter $(BUILD_DIR)/debug/%,$(APP_OBJS)) $(filter $(BUILD_DIR)/release/%,$(APP_OBJS)): | $(LOCAL_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_WIN),1)
|
||||
WINDOWS_DEPS := deps/openssl/mingw64/usr/local/lib/libssl.a
|
||||
WINDOWS_DEPS := out/openssl/$(UNAME_S)/mingw64/usr/local/lib/libssl.a
|
||||
$(WINDOWS_DEPS):
|
||||
+@tools/ssl-mingw64
|
||||
+@BUILD_TARGET=mingw64 SSL_TARGET=mingw64 OPTIONS="--cross-compile-prefix=x86_64-w64-mingw32-" tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/win%,$(APP_OBJS)): | $(WINDOWS_DEPS)
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
IOS_DEPS := deps/openssl/ios/ios64-xcrun/usr/local/lib/libssl.a
|
||||
IOS_DEPS := out/openssl/ios/ios64-xcrun/usr/local/lib/libssl.a
|
||||
$(IOS_DEPS):
|
||||
+@tools/ssl-ios
|
||||
+@BUILD_PLATFORM=ios BUILD_TARGET=ios64-xcrun SSL_TARGET=ios64-xcrun OPTIONS="-fPIC -Wno-macro-redefined -miphoneos-version-min=$(IPHONEOS_VERSION_MIN)" tools/ssl-local
|
||||
+@BUILD_PLATFORM=ios BUILD_TARGET=iossimulator-xcrun SSL_TARGET=iossimulator-xcrun OPTIONS="-fPIC -Wno-macro-redefined" tools/ssl-local
|
||||
$(filter $(BUILD_DIR)/ios%,$(APP_OBJS)): | $(IOS_DEPS)
|
||||
endif
|
||||
|
||||
out/macos%/tildefriends: out/macos%-arm/tildefriends out/macos%-x86_64/tildefriends
|
||||
@echo [lipo] $@
|
||||
@mkdir -p $(@D)
|
||||
@deps/macos_toolchain/bin/lipo -create -output $@ $^
|
||||
|
||||
##
|
||||
## Linux package targets:
|
||||
##
|
||||
@ -1258,7 +1373,6 @@ tarball: ## Build an all-inclusive source tarball (.tar.xz).
|
||||
--exclude=deps/libsodium/test \
|
||||
--exclude=deps/libuv/docs \
|
||||
--exclude=deps/libuv/test \
|
||||
--exclude=deps/openssl \
|
||||
--exclude=deps/speedscope/*.map \
|
||||
--exclude=deps/sqlite/shell.c \
|
||||
--exclude=deps/zlib/contrib/vstudio \
|
||||
@ -1269,7 +1383,20 @@ tarball: ## Build an all-inclusive source tarball (.tar.xz).
|
||||
.PHONY: tarball
|
||||
|
||||
dist: ## Build versions of all distributables for release.
|
||||
dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefriends.standalone.exe) out/TildeFriends-release.fdroid.apk appimage tarball out/release/tildefriends.standalone $(if $(HAVE_CROSS_AARCH64), out/armrelease/tildefriends.standalone)
|
||||
dist: release-apk aab out/TildeFriends-release.fdroid.apk appimage tarball out/release/tildefriends.standalone
|
||||
ifeq ($(HAVE_LINUX_IOS),1)
|
||||
dist: iosrelease-ipa
|
||||
endif
|
||||
ifeq ($(HAVE_LINUX_MACOS),1)
|
||||
dist: out/macosrelease/tildefriends.standalone
|
||||
endif
|
||||
ifeq ($(HAVE_WIN),1)
|
||||
dist: out/winrelease/tildefriends.standalone.exe
|
||||
endif
|
||||
ifeq ($(HAVE_CROSS_AARCH64),1)
|
||||
dist: out/armrelease/tildefriends.standalone
|
||||
endif
|
||||
dist:
|
||||
@mkdir -p dist/
|
||||
@echo "[cp] tildefriends-$(VERSION_NUMBER).tar.xz"
|
||||
@cp out/tildefriends-$(VERSION_NUMBER).tar.xz dist/tildefriends-$(VERSION_NUMBER).tar.xz
|
||||
@ -1277,8 +1404,10 @@ dist: release-apk iosrelease-ipa aab $(if $(HAVE_WIN), out/winrelease/tildefrien
|
||||
@cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
|
||||
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"
|
||||
@cp out/TildeFriends-arm-release.zopfli.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk
|
||||
@echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa"
|
||||
@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
|
||||
@test $(HAVE_LINUX_IOS) && echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa"
|
||||
@test $(HAVE_LINUX_IOS) && cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
|
||||
@test $(HAVE_LINUX_MACOS) && echo "[cp] tildefriends-macos-$(VERSION_NUMBER)"
|
||||
@test $(HAVE_LINUX_MACOS) && cp out/macosrelease/tildefriends.standalone dist/tildefriends-macos-$(VERSION_NUMBER)
|
||||
@test $(HAVE_WIN) && echo "[cp] tildefriends-$(VERSION_NUMBER).exe"
|
||||
@test $(HAVE_WIN) && cp out/winrelease/tildefriends.standalone.exe dist/tildefriends-$(VERSION_NUMBER).exe
|
||||
@echo "[cp] TildeFriends-$(VERSION_NUMBER).aab"
|
||||
@ -1300,6 +1429,17 @@ dist-test: dist ## Exercise some built distributable files, making sure they wor
|
||||
@rm -rf tildefriends-$(VERSION_NUMBER)
|
||||
.PHONY: dist-test
|
||||
|
||||
dist-ios: iosrelease-app
|
||||
rm -rfv out/Payload out/tildefriends.ipa
|
||||
mkdir -p out/Payload/tildefriends.app
|
||||
cp -avR out/tildefriends-iosrelease.app/* out/Payload/tildefriends.app/
|
||||
cp src/ios/tildefriends.png out/Payload/tildefriends.app/
|
||||
xcrun -sdk iphoneos actool --compile out/Payload/tildefriends.app/ --platform iphoneos --minimum-deployment-target $(IPHONEOS_VERSION_MIN) --app-icon AppIcon src/ios/icons/Assets.xcassets src/ios/icons/*.png --output-partial-info-plist out/actool.plist
|
||||
cp src/ios/distribution.mobileprovision out/Payload/tildefriends.app/embedded.mobileprovision
|
||||
xcrun -sdk iphoneos codesign -f -s 'Apple Distribution' --entitlements src/ios/Entitlements.plist --generate-entitlement-der out/Payload/tildefriends.app
|
||||
cd out; zip -r tildefriends.ipa Payload; cd ..
|
||||
xcrun -sdk iphoneos altool --upload-app -f out/tildefriends.ipa -t ios -u $$(cat .keys/altool-user) -p $$(cat .keys/altool-password)
|
||||
|
||||
##
|
||||
## Targets for tidying up:
|
||||
##
|
||||
|
22
README.md
@ -1,18 +1,16 @@
|
||||
# Tilde Friends
|
||||
|
||||
Tilde Friends is a tool for making and sharing.
|
||||
Tilde Friends participates in the Secure Scuttlebutt decentralized social
|
||||
network while also functioning as a platform for making, sharing, and running
|
||||
web applications.
|
||||
|
||||
A public instance lives at https://www.tildefriends.net/.
|
||||
|
||||
It is both a peer-to-peer social network client, participating in Secure
|
||||
Scuttlebutt, as well as a platform for writing and running web applications.
|
||||
|
||||
## 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.
|
||||
1. Be the fanciest, best-maintained Secure Scuttlebutt client in town.
|
||||
1. Make it easy to make, share, and run all sorts of applications while
|
||||
respecting the privacy and safety of your data.
|
||||
|
||||
## Getting the Source
|
||||
|
||||
@ -40,8 +38,7 @@ dependencies in the right places.
|
||||
|
||||
### Requirements
|
||||
|
||||
On Linux only, system OpenSSL libraries (`libssl-dev`, in debian-speak) are
|
||||
assumed to be available.
|
||||
System OpenSSL libraries are assumed to be available on Haiku and OpenSSL.
|
||||
|
||||
On MacOS, Xcode's command-line tools are expected to be available.
|
||||
|
||||
@ -63,13 +60,12 @@ repository root as the current working directory. `tildefriends -h` lists
|
||||
further options.
|
||||
|
||||
The first user to create an account and log in will be granted administrative
|
||||
privileges. Further administration can be done at
|
||||
privileges. Further administration can be done in the `admin` app at
|
||||
<http://localhost:12345/~core/admin/>.
|
||||
|
||||
## Documentation
|
||||
|
||||
Docs are a work in progress:
|
||||
<https://dev.tildefriends.net/cory/tildefriends/wiki>.
|
||||
Docs live here: <https://docs.tildefriends.net/>.
|
||||
|
||||
## License
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🎛",
|
||||
"previous": "&R49FywYF8CXPhoSEydLbSCgvCddeyTiBwGuDU/gqY+M=.sha256"
|
||||
"previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256"
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ ${description.value}</textarea
|
||||
</button>
|
||||
</li>
|
||||
`;
|
||||
} else {
|
||||
} else if (description.type != 'hidden') {
|
||||
return html`
|
||||
<li class="w3-row">
|
||||
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label>
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@ -108,6 +108,10 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@ -149,9 +153,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@ -166,15 +170,24 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@ -232,4 +245,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@ -108,6 +108,10 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@ -149,9 +153,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@ -166,15 +170,24 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@ -232,4 +245,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@ -108,6 +108,10 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@ -149,9 +153,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@ -166,15 +170,24 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@ -232,4 +245,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
|
@ -1,7 +1,9 @@
|
||||
async function main() {
|
||||
let host = core.url.match(/.*\/\/(.*?)\//)[1];
|
||||
print(core.url);
|
||||
let host = core.url.match(/.*?\/\/([^:/]*)/)[1];
|
||||
let port = await ssb.port();
|
||||
let id = (await ssb.getServerIdentity()).substring(1);
|
||||
let room = `net:${host}:${ssb.port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
|
||||
let room = `net:${host}:${port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
|
||||
await app.setDocument(`
|
||||
<body style="color: #fff">
|
||||
<h1>Server</h1>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🦀",
|
||||
"previous": "&m5ZrkzoyAjv7AY6AukzVoWIyUFUL97uuDDWCtxCloAU=.sha256"
|
||||
"previous": "&wOd/+1l5wpywBlfxC1Lm0i+HhYidrgSfrn9LRX7qy2w=.sha256"
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ class TfElement extends LitElement {
|
||||
channels_latest: {type: Object},
|
||||
guest: {type: Boolean},
|
||||
url: {type: String},
|
||||
private_messages: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
@ -334,7 +335,7 @@ class TfElement extends LitElement {
|
||||
JSON.stringify(cache)
|
||||
);
|
||||
}
|
||||
return cache.latest;
|
||||
return [cache.latest, cache.messages];
|
||||
}
|
||||
|
||||
async load_channels_latest(following) {
|
||||
@ -378,9 +379,11 @@ class TfElement extends LitElement {
|
||||
start_time = new Date();
|
||||
latest_private.then(function (latest) {
|
||||
self.channels_latest = Object.assign({}, self.channels_latest, {
|
||||
'🔐': latest,
|
||||
'🔐': latest[0],
|
||||
});
|
||||
console.log('private took', (new Date() - start_time) / 1000.0);
|
||||
console.log(latest);
|
||||
self.private_messages = latest[1];
|
||||
});
|
||||
}
|
||||
|
||||
@ -513,6 +516,7 @@ class TfElement extends LitElement {
|
||||
.channels_unread=${this.channels_unread}
|
||||
@channelsetunread=${this.channel_set_unread}
|
||||
.connections=${this.connections}
|
||||
.private_messages=${this.private_messages}
|
||||
></tf-tab-news>
|
||||
`;
|
||||
} else if (this.tab === 'connections') {
|
||||
|
@ -255,10 +255,12 @@ class TfComposeElement extends LitElement {
|
||||
let self = this;
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.onchange = function (event) {
|
||||
input.addEventListener('change', function (event) {
|
||||
input.parentNode.removeChild(input);
|
||||
let file = event.target.files[0];
|
||||
self.add_file(file);
|
||||
};
|
||||
});
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
}
|
||||
|
||||
|
@ -775,7 +775,9 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
} else {
|
||||
return this.render_small_frame(
|
||||
html`<div class="w3-container"><b>type</b>: ${content.type}</div>`
|
||||
html`<div class="w3-container">
|
||||
<p><b>type</b>: ${content.type}</p>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
} else if (typeof this.message.content == 'string') {
|
||||
|
@ -62,6 +62,7 @@ class TfProfileElement extends LitElement {
|
||||
}
|
||||
|
||||
modify(change) {
|
||||
let self = this;
|
||||
tfrpc.rpc
|
||||
.appendMessage(
|
||||
this.whoami,
|
||||
@ -73,6 +74,10 @@ class TfProfileElement extends LitElement {
|
||||
change
|
||||
)
|
||||
)
|
||||
.then(function () {
|
||||
self._follow_whoami = undefined;
|
||||
self.load();
|
||||
})
|
||||
.catch(function (error) {
|
||||
alert(error?.message);
|
||||
});
|
||||
@ -134,7 +139,8 @@ class TfProfileElement extends LitElement {
|
||||
let self = this;
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.onchange = function (event) {
|
||||
input.addEventListener('change', function (event) {
|
||||
input.parentNode.removeChild(input);
|
||||
let file = event.target.files[0];
|
||||
file
|
||||
.arrayBuffer()
|
||||
@ -149,7 +155,8 @@ class TfProfileElement extends LitElement {
|
||||
.catch(function (e) {
|
||||
alert(e.message);
|
||||
});
|
||||
};
|
||||
});
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ const tf = css`
|
||||
|
||||
// prettier-ignore
|
||||
const w3 = css`
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@ -88,7 +88,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
|
||||
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
|
||||
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
|
||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
|
||||
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
|
||||
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
|
||||
@ -136,7 +136,7 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
|
||||
@media (max-width:1205px){.w3-auto{max-width:95%}}
|
||||
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
|
||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
|
||||
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
|
||||
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
|
||||
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
|
||||
@ -158,6 +158,10 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@ -199,9 +203,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@ -216,15 +220,24 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
|
@ -103,6 +103,23 @@ class TfTabConnectionsElement extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
render_progress(name, value, max) {
|
||||
if (max && value != max) {
|
||||
return html`
|
||||
<div class="w3-theme-d1 w3-small">
|
||||
<div
|
||||
class="w3-container w3-theme-l1"
|
||||
style="width: ${Math.floor(
|
||||
(100.0 * value) / max
|
||||
)}%; text-wrap: nowrap"
|
||||
>
|
||||
${name} ${value} / ${max} (${Math.round((100.0 * value) / max)}%)
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
render_broadcast(connection) {
|
||||
let self = this;
|
||||
return html`
|
||||
@ -154,6 +171,16 @@ class TfTabConnectionsElement extends LitElement {
|
||||
: undefined}
|
||||
${connection.flags.one_shot ? '🔃' : undefined}
|
||||
<tf-user id=${connection.id} .users=${this.users}></tf-user>
|
||||
${this.render_progress(
|
||||
'recv',
|
||||
connection.progress.in.total - connection.progress.in.current,
|
||||
connection.progress.in.total
|
||||
)}
|
||||
${this.render_progress(
|
||||
'send',
|
||||
connection.progress.out.total - connection.progress.out.current,
|
||||
connection.progress.out.total
|
||||
)}
|
||||
${connection.tunnel !== undefined
|
||||
? '🚇'
|
||||
: html`(${connection.host}:${connection.port})`}
|
||||
@ -206,6 +233,21 @@ class TfTabConnectionsElement extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
toggle_accordian(id) {
|
||||
let element = this.renderRoot.getElementById(id);
|
||||
element.classList.toggle('w3-hide');
|
||||
}
|
||||
|
||||
valid_connections() {
|
||||
return this.connections.filter((x) => x.tunnel === undefined);
|
||||
}
|
||||
|
||||
valid_broadcasts() {
|
||||
return this.broadcasts
|
||||
.filter((x) => x.address)
|
||||
.filter((x) => this.connections.map((c) => c.id).indexOf(x.pubkey) == -1);
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
return html`
|
||||
@ -220,27 +262,33 @@ class TfTabConnectionsElement extends LitElement {
|
||||
>
|
||||
Connect
|
||||
</button>
|
||||
<h2>Broadcasts</h2>
|
||||
<ul class="w3-ul w3-border">
|
||||
${this.broadcasts
|
||||
.filter((x) => x.address)
|
||||
.filter(
|
||||
(x) => self.connections.map((c) => c.id).indexOf(x.pubkey) == -1
|
||||
)
|
||||
.map((x) => self.render_broadcast(x))}
|
||||
<h2
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
@click=${() => self.toggle_accordian('connections')}
|
||||
>
|
||||
Connections (${this.valid_connections().length})
|
||||
</h2>
|
||||
<ul class="w3-ul w3-border" id="connections">
|
||||
${this.valid_connections().map(
|
||||
(x) => html` <li class="w3-bar">${this.render_connection(x)}</li> `
|
||||
)}
|
||||
</ul>
|
||||
<h2>Connections</h2>
|
||||
<ul class="w3-ul w3-border">
|
||||
${this.connections
|
||||
.filter((x) => x.tunnel === undefined)
|
||||
.map(
|
||||
(x) => html`
|
||||
<li class="w3-bar">${this.render_connection(x)}</li>
|
||||
`
|
||||
)}
|
||||
<h2
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
@click=${() => self.toggle_accordian('broadcasts')}
|
||||
>
|
||||
Broadcasts (${this.valid_broadcasts().length})
|
||||
</h2>
|
||||
<ul class="w3-ul w3-border w3-hide" id="broadcasts">
|
||||
${this.valid_broadcasts().map((x) => self.render_broadcast(x))}
|
||||
</ul>
|
||||
<h2>Stored Connections</h2>
|
||||
<ul class="w3-ul w3-border">
|
||||
<h2
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
@click=${() => self.toggle_accordian('stored_connections')}
|
||||
>
|
||||
Stored Connections (${this.stored_connections.length})
|
||||
</h2>
|
||||
<ul class="w3-ul w3-border w3-hide" id="stored_connections">
|
||||
${this.stored_connections.map(
|
||||
(x) => html`
|
||||
<li>
|
||||
@ -267,8 +315,13 @@ class TfTabConnectionsElement extends LitElement {
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
<h2>Local Accounts</h2>
|
||||
<div class="w3-container">
|
||||
<h2
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
@click=${() => self.toggle_accordian('local_accounts')}
|
||||
>
|
||||
Local Accounts (${this.identities.length})
|
||||
</h2>
|
||||
<div class="w3-container w3-hide" id="local_accounts">
|
||||
${this.identities.map(
|
||||
(x) =>
|
||||
html`<div
|
||||
|
@ -17,6 +17,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
loading: {type: Number},
|
||||
time_range: {type: Array},
|
||||
time_loading: {type: Array},
|
||||
private_messages: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
@ -148,13 +149,13 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
`
|
||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
FROM messages
|
||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||
JOIN json_each(?1) AS private_messages ON messages.id = private_messages.value
|
||||
WHERE
|
||||
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
||||
json(messages.content) LIKE '"%'
|
||||
ORDER BY messages.sequence DESC LIMIT 20
|
||||
`,
|
||||
[JSON.stringify(this.following), start_time, end_time]
|
||||
[JSON.stringify(this.private_messages), start_time, end_time]
|
||||
);
|
||||
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
||||
} else {
|
||||
@ -298,7 +299,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
let now = new Date().valueOf();
|
||||
let start_time = now - 24 * 60 * 60 * 1000;
|
||||
this.start_time = start_time;
|
||||
this.time_range = [this.start_time, now + 24 * 60 * 60 * 1000];
|
||||
this.time_range = [now + 24 * 60 * 60 * 1000, now + 24 * 60 * 60 * 1000];
|
||||
messages = await this.fetch_messages(null, this.time_range[1]);
|
||||
this.update_time_range_from_messages(
|
||||
messages.filter((x) => x.timestamp < this.time_range[1])
|
||||
|
@ -23,6 +23,7 @@ class TfTabNewsElement extends LitElement {
|
||||
channels_unread: {type: Object},
|
||||
channels_latest: {type: Object},
|
||||
connections: {type: Array},
|
||||
private_messages: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
@ -337,6 +338,7 @@ class TfTabNewsElement extends LitElement {
|
||||
@tf-expand=${this.on_expand}
|
||||
.channels_unread=${this.channels_unread}
|
||||
.channels_latest=${this.channels_latest}
|
||||
.private_messages=${this.private_messages}
|
||||
></tf-tab-news-feed>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,7 +21,9 @@ class TfUserElement extends LitElement {
|
||||
render() {
|
||||
let user = this.users[this.id];
|
||||
let shape =
|
||||
!user?.follow_depth || user.follow_depth >= 2 ? 'w3-circle' : 'w3-round';
|
||||
user?.follow_depth === undefined || user.follow_depth >= 2
|
||||
? 'w3-circle'
|
||||
: 'w3-round';
|
||||
let image = html`<span
|
||||
class=${'w3-theme-l4 ' + shape}
|
||||
style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em"
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "👋",
|
||||
"previous": "&7gFmLW5zSMhmxWWY1+jeRcHdullgujSqGJg94lVgr1k=.sha256"
|
||||
"previous": "&wAb7J6E35xEXpiXsQ6t1RaWTGIvlatUnyH8ipF6pVic=.sha256"
|
||||
}
|
||||
|
@ -10,17 +10,6 @@
|
||||
<link rel="stylesheet" href="brands.min.css" />
|
||||
|
||||
<style>
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
body {
|
||||
font-size: 16px;
|
||||
}
|
||||
img {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
@ -39,11 +28,14 @@
|
||||
<b>😎 Tilde Friends</b>
|
||||
</h1>
|
||||
<h1 class="w3-xxlarge w3-text-green">
|
||||
<b>Make apps and friends from the comfort of your web browser.</b>
|
||||
<b
|
||||
>the Secure Scuttlebutt decentralized social network client that's
|
||||
<i>fancy🎩</i></b
|
||||
>
|
||||
</h1>
|
||||
<p>
|
||||
Tilde Friends is a platform for building, running, and sharing web
|
||||
applications.
|
||||
In addition to participating in Secure Scuttlebutt, Tilde Friends is
|
||||
a platform for building, running, and sharing applications.
|
||||
</p>
|
||||
<p>
|
||||
Available for lots of devices:
|
||||
@ -60,7 +52,7 @@
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://www.tildefriends.net/~cory/apps/"
|
||||
href="https://www.tildefriends.net/~core/ssb/"
|
||||
><i class="fa fa-link"></i> Try It</a
|
||||
>
|
||||
<a
|
||||
@ -68,6 +60,11 @@
|
||||
href="https://dev.tildefriends.net/"
|
||||
><i class="fa fa-mug-hot"></i> Development</a
|
||||
>
|
||||
<a
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://docs.tildefriends.net/"
|
||||
><i class="fa fa-book"></i> Documentation</a
|
||||
>
|
||||
<p>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
@ -223,16 +220,15 @@
|
||||
|
||||
<!-- Technlology Section -->
|
||||
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
|
||||
<h1 class="w3-jumbo"><b>Boring Technology</b></h1>
|
||||
<h1 class="w3-jumbo"><b>Built the Old Fashioned Way</b></h1>
|
||||
<p>
|
||||
Tilde Friends is built using boring, trusted tech. Unless a better
|
||||
reason presents itself, it strives to use only simple and widely adopted
|
||||
dependencies in order to keep it easy to build for all sorts of
|
||||
platforms and maintainable for a very long time.
|
||||
Tilde Friends strives to use only simple and widely adopted dependencies
|
||||
in order to keep it easy to build for all sorts of platforms and
|
||||
maintainable for a very long time.
|
||||
</p>
|
||||
<p>
|
||||
Though of course for building Tilde Friends apps, you are free to use
|
||||
whatever fits.
|
||||
whatever fits on top.
|
||||
</p>
|
||||
|
||||
<div class="w3-row" style="margin-top: 64px">
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@ -108,6 +108,10 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@ -149,9 +153,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@ -166,15 +170,24 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@ -232,4 +245,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
|
115
core/app.js
@ -1,53 +1,26 @@
|
||||
import * as core from './core.js';
|
||||
|
||||
let g_next_id = 1;
|
||||
let g_calls = {};
|
||||
|
||||
let gSessionIndex = 0;
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
*/
|
||||
function makeSessionId() {
|
||||
return 'session_' + (gSessionIndex++).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
*/
|
||||
function App() {
|
||||
this._on_output = null;
|
||||
this._send_queue = [];
|
||||
this.calls = {};
|
||||
this._next_call_id = 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} callback
|
||||
*/
|
||||
App.prototype.readOutput = function (callback) {
|
||||
this._on_output = callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} api
|
||||
* @returns
|
||||
*/
|
||||
App.prototype.makeFunction = function (api) {
|
||||
let self = this;
|
||||
let result = function () {
|
||||
let id = g_next_id++;
|
||||
while (!id || g_calls[id]) {
|
||||
id = g_next_id++;
|
||||
let id = self._next_call_id++;
|
||||
while (!id || self.calls[id]) {
|
||||
id = self._next_call_id++;
|
||||
}
|
||||
let promise = new Promise(function (resolve, reject) {
|
||||
g_calls[id] = {resolve: resolve, reject: reject};
|
||||
self.calls[id] = {resolve: resolve, reject: reject};
|
||||
});
|
||||
let message = {
|
||||
message: 'tfrpc',
|
||||
action: 'tfrpc',
|
||||
method: api[0],
|
||||
params: [...arguments],
|
||||
id: id,
|
||||
@ -59,10 +32,6 @@ App.prototype.makeFunction = function (api) {
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
*/
|
||||
App.prototype.send = function (message) {
|
||||
if (this._send_queue) {
|
||||
if (this._on_output) {
|
||||
@ -77,13 +46,7 @@ App.prototype.send = function (message) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} request
|
||||
* @param {*} response
|
||||
* @param {*} client
|
||||
*/
|
||||
async function socket(request, response, client) {
|
||||
exports.app_socket = async function socket(request, response) {
|
||||
let process;
|
||||
let options = {};
|
||||
let credentials = await httpd.auth_query(request.headers);
|
||||
@ -103,10 +66,16 @@ async function socket(request, response, client) {
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
print('ERROR', error, event.data, event.data.length, event.opCode);
|
||||
print(
|
||||
'WebSocket error:',
|
||||
error,
|
||||
event.data,
|
||||
event.data.length,
|
||||
event.opCode
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (message.action == 'hello') {
|
||||
if (!process && message.action == 'hello') {
|
||||
let packageOwner;
|
||||
let packageName;
|
||||
let blobId;
|
||||
@ -123,7 +92,7 @@ async function socket(request, response, client) {
|
||||
if (!blobId) {
|
||||
response.send(
|
||||
JSON.stringify({
|
||||
message: 'tfrpc',
|
||||
action: 'tfrpc',
|
||||
method: 'error',
|
||||
params: [message.path + ' not found'],
|
||||
id: -1,
|
||||
@ -164,7 +133,7 @@ async function socket(request, response, client) {
|
||||
options.packageOwner = packageOwner;
|
||||
options.packageName = packageName;
|
||||
options.url = message.url;
|
||||
let sessionId = makeSessionId();
|
||||
let sessionId = 'session_' + (gSessionIndex++).toString();
|
||||
if (blobId) {
|
||||
if (message.edit_only) {
|
||||
response.send(
|
||||
@ -176,9 +145,24 @@ async function socket(request, response, client) {
|
||||
}
|
||||
}
|
||||
if (process) {
|
||||
process.app.readOutput(function (message) {
|
||||
process.client_api.tfrpc = function (message) {
|
||||
if (message.id) {
|
||||
let calls = process?.app?.calls;
|
||||
if (calls) {
|
||||
let call = calls[message.id];
|
||||
if (call) {
|
||||
if (message.error !== undefined) {
|
||||
call.reject(message.error);
|
||||
} else {
|
||||
call.resolve(message.result);
|
||||
}
|
||||
delete calls[message.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
process.app._on_output = (message) =>
|
||||
response.send(JSON.stringify(message), 0x1);
|
||||
});
|
||||
process.app.send();
|
||||
}
|
||||
|
||||
@ -207,26 +191,13 @@ async function socket(request, response, client) {
|
||||
if (process && process.timeout > 0) {
|
||||
setTimeout(ping, process.timeout);
|
||||
}
|
||||
} else if (message.action == 'resetPermission') {
|
||||
if (process) {
|
||||
process.resetPermission(message.permission);
|
||||
}
|
||||
} else if (message.action == 'setActiveIdentity') {
|
||||
process.setActiveIdentity(message.identity);
|
||||
} else if (message.action == 'createIdentity') {
|
||||
await process.createIdentity();
|
||||
} else if (message.message == 'tfrpc') {
|
||||
if (message.id && g_calls[message.id]) {
|
||||
if (message.error !== undefined) {
|
||||
g_calls[message.id].reject(message.error);
|
||||
} else {
|
||||
g_calls[message.id].resolve(message.result);
|
||||
}
|
||||
delete g_calls[message.id];
|
||||
}
|
||||
} else {
|
||||
if (process && process.eventHandlers['message']) {
|
||||
await core.invoke(process.eventHandlers['message'], [message]);
|
||||
if (process) {
|
||||
if (process.client_api[message.action]) {
|
||||
process.client_api[message.action](message);
|
||||
} else if (process.eventHandlers['message']) {
|
||||
await core.invoke(process.eventHandlers['message'], [message]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event.opCode == 0x8) {
|
||||
@ -245,6 +216,6 @@ async function socket(request, response, client) {
|
||||
};
|
||||
|
||||
response.upgrade(100, {});
|
||||
}
|
||||
};
|
||||
|
||||
export {socket, App};
|
||||
export {App};
|
||||
|
@ -1325,7 +1325,7 @@ function _receive_websocket_message(message) {
|
||||
line.append(key, message.stats[key]);
|
||||
}
|
||||
}
|
||||
} else if (message && message.message === 'tfrpc' && message.method) {
|
||||
} else if (message && message.action === 'tfrpc' && message.method) {
|
||||
let api = k_api[message.method];
|
||||
let id = message.id;
|
||||
let params = message.params;
|
||||
@ -1333,14 +1333,14 @@ function _receive_websocket_message(message) {
|
||||
Promise.resolve(api.func(...params))
|
||||
.then(function (result) {
|
||||
send({
|
||||
message: 'tfrpc',
|
||||
action: 'tfrpc',
|
||||
id: id,
|
||||
result: result,
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
send({
|
||||
message: 'tfrpc',
|
||||
action: 'tfrpc',
|
||||
id: id,
|
||||
error: error,
|
||||
});
|
||||
|
181
core/core.js
@ -1,34 +1,24 @@
|
||||
import * as app from './app.js';
|
||||
import * as form from './form.js';
|
||||
import * as http from './http.js';
|
||||
|
||||
let gProcesses = {};
|
||||
let gStatsTimer = false;
|
||||
let kPingInterval = 60 * 1000;
|
||||
let g_handler_index = 0;
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} out
|
||||
* @param {*} error
|
||||
*/
|
||||
function printError(out, error) {
|
||||
const k_ping_interval = 60 * 1000;
|
||||
|
||||
function printError(error) {
|
||||
if (error.stackTrace) {
|
||||
out.print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
|
||||
out.print(error.stackTrace);
|
||||
print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
|
||||
print(error.stackTrace);
|
||||
} else {
|
||||
for (let [k, v] of Object.entries(error)) {
|
||||
out.print(k, v);
|
||||
print(k, v);
|
||||
}
|
||||
out.print(error.toString());
|
||||
print(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} handlers
|
||||
* @param {*} argv
|
||||
* @returns
|
||||
*/
|
||||
function invoke(handlers, argv) {
|
||||
let promises = [];
|
||||
if (handlers) {
|
||||
@ -49,12 +39,6 @@ function invoke(handlers, argv) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} eventName
|
||||
* @param {*} argv
|
||||
* @returns
|
||||
*/
|
||||
function broadcastEvent(eventName, argv) {
|
||||
let promises = [];
|
||||
for (let process of Object.values(gProcesses)) {
|
||||
@ -65,11 +49,6 @@ function broadcastEvent(eventName, argv) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* @returns
|
||||
*/
|
||||
function broadcast(message) {
|
||||
let sender = this;
|
||||
let promises = [];
|
||||
@ -86,12 +65,6 @@ function broadcast(message) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {String} eventName
|
||||
* @param {*} argv
|
||||
* @returns
|
||||
*/
|
||||
function broadcastAppEventToUser(
|
||||
user,
|
||||
packageOwner,
|
||||
@ -114,12 +87,6 @@ function broadcastAppEventToUser(
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} caller
|
||||
* @param {*} process
|
||||
* @returns
|
||||
*/
|
||||
function getUser(caller, process) {
|
||||
return {
|
||||
key: process.key,
|
||||
@ -130,12 +97,6 @@ function getUser(caller, process) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} user
|
||||
* @param {*} process
|
||||
* @returns
|
||||
*/
|
||||
async function getApps(user, process) {
|
||||
if (
|
||||
process.credentials &&
|
||||
@ -162,28 +123,13 @@ async function getApps(user, process) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} from
|
||||
* @param {*} to
|
||||
* @param {*} message
|
||||
* @returns
|
||||
*/
|
||||
function postMessageInternal(from, to, message) {
|
||||
if (to.eventHandlers['message']) {
|
||||
return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} blobId
|
||||
* @param {*} key
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
async function getProcessBlob(blobId, key, options) {
|
||||
// TODO(tasiaiso): break this down ?
|
||||
let process = gProcesses[key];
|
||||
if (!process && !(options && 'create' in options && !options.create)) {
|
||||
let resolveReady;
|
||||
@ -202,7 +148,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
process.lastActive = Date.now();
|
||||
process.lastPing = null;
|
||||
process.timeout = kPingInterval;
|
||||
process.timeout = k_ping_interval;
|
||||
process.ready = new Promise(function (resolve, reject) {
|
||||
resolveReady = resolve;
|
||||
rejectReady = reject;
|
||||
@ -462,16 +408,15 @@ async function getProcessBlob(blobId, key, options) {
|
||||
if (process.app) {
|
||||
process.app.makeFunction(['error'])(error);
|
||||
} else {
|
||||
printError({print: print}, error);
|
||||
printError(error);
|
||||
}
|
||||
} catch (e) {
|
||||
printError({print: print}, error);
|
||||
printError(error);
|
||||
}
|
||||
};
|
||||
imports.ssb = Object.fromEntries(
|
||||
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
|
||||
);
|
||||
imports.ssb.port = tildefriends.ssb_port;
|
||||
imports.ssb.createIdentity = () => process.createIdentity();
|
||||
imports.ssb.addIdentity = function (id) {
|
||||
if (
|
||||
@ -651,17 +596,26 @@ async function getProcessBlob(blobId, key, options) {
|
||||
permissions: await imports.core.permissionsGranted(),
|
||||
});
|
||||
};
|
||||
process.resetPermission = async function resetPermission(permission) {
|
||||
let user = process?.credentials?.session?.name;
|
||||
await ssb.setUserPermission(
|
||||
user,
|
||||
options?.packageOwner,
|
||||
options?.packageName,
|
||||
permission,
|
||||
undefined
|
||||
);
|
||||
return process.sendPermissions();
|
||||
process.client_api = {
|
||||
createIdentity: function () {
|
||||
return process.createIdentity();
|
||||
},
|
||||
resetPermission: async function resetPermission(message) {
|
||||
let user = process?.credentials?.session?.name;
|
||||
await ssb.setUserPermission(
|
||||
user,
|
||||
options?.packageOwner,
|
||||
options?.packageName,
|
||||
message.permission,
|
||||
undefined
|
||||
);
|
||||
return process.sendPermissions();
|
||||
},
|
||||
setActiveIdentity: function setActiveIdentity(message) {
|
||||
return process.setActiveIdentity(message.identity);
|
||||
},
|
||||
};
|
||||
ssb.registerImports(imports, process);
|
||||
process.task.setImports(imports);
|
||||
process.task.activate();
|
||||
let source = await ssb.blobGet(blobId);
|
||||
@ -688,7 +642,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
printError({print: print}, e);
|
||||
printError(e);
|
||||
}
|
||||
broadcastEvent('onSessionBegin', [getUser(process, process)]);
|
||||
if (process.app) {
|
||||
@ -702,14 +656,10 @@ async function getProcessBlob(blobId, key, options) {
|
||||
sendStats();
|
||||
}
|
||||
} catch (error) {
|
||||
if (process.app) {
|
||||
if (process?.task?.onError) {
|
||||
process.task.onError(error);
|
||||
} else {
|
||||
printError({print: print}, error);
|
||||
}
|
||||
if (process?.app && process?.task?.onError) {
|
||||
process.task.onError(error);
|
||||
} else {
|
||||
printError({print: print}, error);
|
||||
printError(error);
|
||||
}
|
||||
rejectReady(error);
|
||||
}
|
||||
@ -729,9 +679,6 @@ ssb.addEventListener('connections', function () {
|
||||
broadcastEvent('onConnectionsChanged', []);
|
||||
});
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
*/
|
||||
async function loadSettings() {
|
||||
let data = {};
|
||||
try {
|
||||
@ -750,9 +697,6 @@ async function loadSettings() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
*/
|
||||
function sendStats() {
|
||||
let apps = Object.values(gProcesses)
|
||||
.filter((process) => process.app)
|
||||
@ -768,8 +712,6 @@ function sendStats() {
|
||||
}
|
||||
}
|
||||
|
||||
let g_handler_index = 0;
|
||||
|
||||
exports.callAppHandler = async function callAppHandler(
|
||||
response,
|
||||
app_blob_id,
|
||||
@ -835,59 +777,4 @@ exports.callAppHandler = async function callAppHandler(
|
||||
response.end(answer?.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
*/
|
||||
loadSettings()
|
||||
.then(function (settings) {
|
||||
if (tildefriends.https_port && settings.http_redirect) {
|
||||
httpd.set_http_redirect(settings.http_redirect);
|
||||
}
|
||||
httpd.all('/app/socket', app.socket);
|
||||
let port = httpd.start(tildefriends.http_port);
|
||||
if (tildefriends.args.out_http_port_file) {
|
||||
print('Writing the port file.');
|
||||
File.writeFile(
|
||||
tildefriends.args.out_http_port_file,
|
||||
port.toString() + '\n'
|
||||
)
|
||||
.then(function (r) {
|
||||
print(
|
||||
'Wrote the port file:',
|
||||
tildefriends.args.out_http_port_file,
|
||||
r
|
||||
);
|
||||
})
|
||||
.catch(function () {
|
||||
print('Failed to write the port file.');
|
||||
});
|
||||
}
|
||||
|
||||
if (tildefriends.https_port) {
|
||||
async function start_tls() {
|
||||
const kCertificatePath = 'data/httpd/certificate.pem';
|
||||
const kPrivateKeyPath = 'data/httpd/privatekey.pem';
|
||||
let privateKey;
|
||||
let certificate;
|
||||
try {
|
||||
privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
|
||||
certificate = utf8Decode(await File.readFile(kCertificatePath));
|
||||
} catch (e) {
|
||||
print(`TLS disabled (${e.message}).`);
|
||||
return;
|
||||
}
|
||||
let context = new TlsContext();
|
||||
context.setPrivateKey(privateKey);
|
||||
context.setCertificate(certificate);
|
||||
httpd.start(tildefriends.https_port, context);
|
||||
}
|
||||
start_tls();
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
print('Failed to load settings.');
|
||||
printError({print: print}, error);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
export {invoke, getProcessBlob};
|
||||
|
44
core/form.js
@ -1,44 +0,0 @@
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} encoded
|
||||
* @returns
|
||||
*/
|
||||
function decode(encoded) {
|
||||
let result = '';
|
||||
for (let i = 0; i < encoded.length; i++) {
|
||||
let c = encoded[i];
|
||||
if (c == '+') {
|
||||
result += ' ';
|
||||
} else if (c == '%') {
|
||||
result += String.fromCharCode(parseInt(encoded.slice(i + 1, i + 3), 16));
|
||||
i += 2;
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} encoded
|
||||
* @param {*} initial
|
||||
* @returns
|
||||
*/
|
||||
function decodeForm(encoded, initial) {
|
||||
let result = initial || {};
|
||||
if (encoded) {
|
||||
encoded = encoded.trim();
|
||||
let items = encoded.split('&');
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i];
|
||||
let equals = item.indexOf('=');
|
||||
let key = decode(item.slice(0, equals));
|
||||
let value = decode(item.slice(equals + 1));
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export {decodeForm};
|
27
core/w3.css
@ -1,4 +1,4 @@
|
||||
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
|
||||
/* W3.CSS 5.01 March 14 2025 by Jan Egil and Borge Refsnes */
|
||||
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
|
||||
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
|
||||
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
|
||||
@ -108,6 +108,10 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
|
||||
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
|
||||
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
|
||||
|
||||
.w3-grid{display:grid}.w3-grid-padding{display:grid;gap:16px}.w3-flex{display:flex}
|
||||
.w3-text-center{text-align:center}.w3-text-bold,.w3-bold{font-weight:bold}.w3-text-italic,.w3-italic{font-style:italic}
|
||||
|
||||
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
|
||||
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
|
||||
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
|
||||
@ -149,9 +153,9 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
|
||||
.w3-hover-none:hover{box-shadow:none!important}
|
||||
/* Colors */
|
||||
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-amber,.w3-hover-amber:hover,.w3-warning{color:#000!important;background-color:#ffc107!important}
|
||||
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
|
||||
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-blue,.w3-hover-blue:hover,.w3-info,.w3-primary{color:#fff!important;background-color:#2196F3!important}
|
||||
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
|
||||
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
|
||||
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
|
||||
@ -166,15 +170,24 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
|
||||
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
|
||||
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
|
||||
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-red,.w3-hover-red:hover,.w3-danger{color:#fff!important;background-color:#f44336!important}
|
||||
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
|
||||
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-yellow,.w3-hover-yellow:hover,.w3-note{color:#000!important;background-color:#ffeb3b!important}
|
||||
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
|
||||
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
|
||||
|
||||
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover,.w3-secondary{color:#000!important;background-color:#9e9e9e!important}
|
||||
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
|
||||
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
|
||||
|
||||
.w3-asphalt,.w3-hover-asphalt:hover{color:#fff!important;background-color:#343a40!important}.w3-crimson,.w3-hover-crimson:hover{color:#fff!important;background-color:#a20025!important}
|
||||
.w3-cobalt,w3-hover-cobalt:hover{color:#fff!important;background-color:#0050ef!important}
|
||||
.w3-emerald,.w3-hover-emerald:hover,.w3-success{color:#fff!important;background-color:#008a00!important}
|
||||
.w3-olive,.w3-hover-olive:hover{color:#fff!important;background-color:#6d8764!important}
|
||||
.w3-paper,.w3-hover-paper:hover{color:#000!important;background-color:#f8f9fa!important}.w3-sienna,.w3-hover-sienna:hover{color:#fff!important;background-color:#a0522d!important}
|
||||
.w3-taupe,.w3-hover-taupe:hover{color:#fff!important;background-color:#87794e!important}
|
||||
|
||||
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
|
||||
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
|
||||
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
|
||||
@ -232,4 +245,4 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
|
||||
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
|
||||
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
|
||||
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}
|
||||
|
13
default.nix
@ -1,4 +1,8 @@
|
||||
# How to upgrade to a newer version
|
||||
# - On the june and december release, you'll have to update nixpkgs to the current branch
|
||||
# Change `nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";`
|
||||
# to the latest release (see https://nixos.org/)
|
||||
# - Run `$ nix flake update`
|
||||
# - Comment `src.hash`
|
||||
# - Change `version`
|
||||
# - Run `$ nix build`
|
||||
@ -21,31 +25,32 @@
|
||||
}:
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "tildefriends";
|
||||
version = "0.0.27";
|
||||
version = "0.0.28";
|
||||
|
||||
src = pkgs.fetchFromGitea {
|
||||
domain = "dev.tildefriends.net";
|
||||
owner = "cory";
|
||||
repo = "tildefriends";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-NhoTBWYsWc206f1+M9haxHSJU6ZSGoP5nPStymAIyRk=";
|
||||
hash = "sha256-vcLJCXgIrjC37t9oavK2QMRcMJljghuzKDYxQ4nyTcE=";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
bash
|
||||
glibc
|
||||
gnumake
|
||||
openssl
|
||||
which
|
||||
];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
glibc
|
||||
openssl
|
||||
which
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
make -j $NIX_BUILD_CORES release
|
||||
make -j $NIX_BUILD_CORES release USE_SYSTEM_SSL=1
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
|
2
deps/codemirror/cm6.js
vendored
200
deps/codemirror_src/package-lock.json
generated
vendored
@ -19,9 +19,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.18.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.4.tgz",
|
||||
"integrity": "sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==",
|
||||
"version": "6.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
||||
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@ -69,9 +69,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
|
||||
"integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.3.tgz",
|
||||
"integrity": "sha512-8PR3vIWg7pSu7ur8A07pGiYHgy3hHj+mRYRCSG8q+mPIrl0F02rgpGv+DsQTHRTc30rydOsf5PZ7yjKFg2Ackw==",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
@ -115,9 +115,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/search": {
|
||||
"version": "6.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz",
|
||||
"integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==",
|
||||
"version": "6.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz",
|
||||
"integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
@ -125,9 +125,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.1.tgz",
|
||||
"integrity": "sha512-3rA9lcwciEB47ZevqvD8qgbzhM9qMb8vCcQCNmDfVRPQG4JT9mSb0Jg8H7YjKGGQcFnLN323fj9jdnG59Kx6bg==",
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
@ -144,9 +144,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.2.tgz",
|
||||
"integrity": "sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==",
|
||||
"version": "6.36.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.4.tgz",
|
||||
"integrity": "sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"style-mod": "^4.1.0",
|
||||
@ -344,9 +344,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz",
|
||||
"integrity": "sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz",
|
||||
"integrity": "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -356,9 +356,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.32.1.tgz",
|
||||
"integrity": "sha512-If3PDskT77q7zgqVqYuj7WG3WC08G1kwXGVFi9Jr8nY6eHucREHkfpX79c0ACAjLj3QIWKPJR7w4i+f5EdLH5Q==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz",
|
||||
"integrity": "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -368,9 +368,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.32.1.tgz",
|
||||
"integrity": "sha512-zCpKHioQ9KgZToFp5Wvz6zaWbMzYQ2LJHQ+QixDKq52KKrF65ueu6Af4hLlLWHjX1Wf/0G5kSJM9PySW9IrvHA==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz",
|
||||
"integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -380,9 +380,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.32.1.tgz",
|
||||
"integrity": "sha512-sFvF+t2+TyUo/ZQqUcifrJIgznx58oFZbdHS9TvHq3xhPVL9nOp+yZ6LKrO9GWTP+6DbFtoyLDbjTpR62Mbr3Q==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz",
|
||||
"integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -392,9 +392,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.32.1.tgz",
|
||||
"integrity": "sha512-NbOa+7InvMWRcY9RG+B6kKIMD/FsnQPH0MWUvDlQB1iXnF/UcKSudCXZtv4lW+C276g3w5AxPbfry5rSYvyeYA==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz",
|
||||
"integrity": "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -404,9 +404,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.32.1.tgz",
|
||||
"integrity": "sha512-JRBRmwvHPXR881j2xjry8HZ86wIPK2CcDw0EXchE1UgU0ubWp9nvlT7cZYKc6bkypBt745b4bglf3+xJ7hXWWw==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz",
|
||||
"integrity": "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -416,9 +416,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.32.1.tgz",
|
||||
"integrity": "sha512-PKvszb+9o/vVdUzCCjL0sKHukEQV39tD3fepXxYrHE3sTKrRdCydI7uldRLbjLmDA3TFDmh418XH19NOsDRH8g==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz",
|
||||
"integrity": "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -428,9 +428,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.32.1.tgz",
|
||||
"integrity": "sha512-9WHEMV6Y89eL606ReYowXuGF1Yb2vwfKWKdD1A5h+OYnPZSJvxbEjxTRKPgi7tkP2DSnW0YLab1ooy+i/FQp/Q==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz",
|
||||
"integrity": "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -440,9 +440,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.32.1.tgz",
|
||||
"integrity": "sha512-tZWc9iEt5fGJ1CL2LRPw8OttkCBDs+D8D3oEM8mH8S1ICZCtFJhD7DZ3XMGM8kpqHvhGUTvNUYVDnmkj4BDXnw==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -452,9 +452,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.32.1.tgz",
|
||||
"integrity": "sha512-FTYc2YoTWUsBz5GTTgGkRYYJ5NGJIi/rCY4oK/I8aKowx1ToXeoVVbIE4LGAjsauvlhjfl0MYacxClLld1VrOw==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -464,9 +464,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.32.1.tgz",
|
||||
"integrity": "sha512-F51qLdOtpS6P1zJVRzYM0v6MrBNypyPEN1GfMiz0gPu9jN8ScGaEFIZQwteSsGKg799oR5EaP7+B2jHgL+d+Kw==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@ -476,9 +476,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.32.1.tgz",
|
||||
"integrity": "sha512-wO0WkfSppfX4YFm5KhdCCpnpGbtgQNj/tgvYzrVYFKDpven8w2N6Gg5nB6w+wAMO3AIfSTWeTjfVe+uZ23zAlg==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -488,9 +488,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.32.1.tgz",
|
||||
"integrity": "sha512-iWswS9cIXfJO1MFYtI/4jjlrGb/V58oMu4dYJIKnR5UIwbkzR0PJ09O0PDZT0oJ3LYWXBSWahNf/Mjo6i1E5/g==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@ -500,9 +500,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.32.1.tgz",
|
||||
"integrity": "sha512-RKt8NI9tebzmEthMnfVgG3i/XeECkMPS+ibVZjZ6mNekpbbUmkNWuIN2yHsb/mBPyZke4nlI4YqIdFPgKuoyQQ==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -512,9 +512,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.1.tgz",
|
||||
"integrity": "sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz",
|
||||
"integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -524,9 +524,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.32.1.tgz",
|
||||
"integrity": "sha512-BLoiyHDOWoS3uccNSADMza6V6vCNiphi94tQlVIL5de+r6r/CCQuNnerf+1g2mnk2b6edp5dk0nhdZ7aEjOBsA==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz",
|
||||
"integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -536,9 +536,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.32.1.tgz",
|
||||
"integrity": "sha512-w2l3UnlgYTNNU+Z6wOR8YdaioqfEnwPjIsJ66KxKAf0p+AuL2FHeTX6qvM+p/Ue3XPBVNyVSfCrfZiQh7vZHLQ==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -548,9 +548,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.32.1.tgz",
|
||||
"integrity": "sha512-Am9H+TGLomPGkBnaPWie4F3x+yQ2rr4Bk2jpwy+iV+Gel9jLAu/KqT8k3X4jxFPW6Zf8OMnehyutsd+eHoq1WQ==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -560,9 +560,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.32.1.tgz",
|
||||
"integrity": "sha512-ar80GhdZb4DgmW3myIS9nRFYcpJRSME8iqWgzH2i44u+IdrzmiXVxeFnExQ5v4JYUSpg94bWjevMG8JHf1Da5Q==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz",
|
||||
"integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -582,9 +582,9 @@
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@ -733,9 +733,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.1.tgz",
|
||||
"integrity": "sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==",
|
||||
"version": "4.34.9",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.9.tgz",
|
||||
"integrity": "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.6"
|
||||
},
|
||||
@ -747,25 +747,25 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.32.1",
|
||||
"@rollup/rollup-android-arm64": "4.32.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.32.1",
|
||||
"@rollup/rollup-darwin-x64": "4.32.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.32.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.32.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.32.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.32.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.32.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.32.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.32.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.32.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.32.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.32.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.32.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.32.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.32.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.32.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.32.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.34.9",
|
||||
"@rollup/rollup-android-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
"@rollup/rollup-freebsd-arm64": "4.34.9",
|
||||
"@rollup/rollup-freebsd-x64": "4.34.9",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.34.9",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.34.9",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.34.9",
|
||||
"@rollup/rollup-linux-x64-musl": "4.34.9",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.34.9",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.34.9",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.34.9",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@ -840,9 +840,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.37.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
|
||||
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
|
||||
"version": "5.39.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
||||
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
|
2
deps/libbacktrace
vendored
2
deps/openssl_src
vendored
2
deps/speedscope/index.html
vendored
@ -11,7 +11,7 @@
|
||||
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
||||
</head>
|
||||
<body>
|
||||
<script src="speedscope-CAEVGCWN.js"></script>
|
||||
<script src="speedscope-VHEG2FVF.js"></script>
|
||||
|
||||
|
||||
|
||||
|
6
deps/speedscope/release.txt
vendored
@ -1,3 +1,3 @@
|
||||
speedscope@1.22.0
|
||||
Thu Jan 16 16:49:47 PST 2025
|
||||
bd7b44a0a7d63375ee6ea0a0d1b96e65a456642f
|
||||
speedscope@1.22.2
|
||||
Sat Feb 15 13:02:38 PST 2025
|
||||
1c254dcb3e2b4f6d921340d20e972d9d27b788f4
|
||||
|
24
deps/sqlite/shell.c
vendored
@ -357,6 +357,11 @@ void sqlite3_fsetmode(FILE *stream, int mode);
|
||||
** use O_U8TEXT when writing to the Windows console (or anything
|
||||
** else for which _isatty() returns true) and to use O_BINARY or O_TEXT
|
||||
** for all other output channels.
|
||||
**
|
||||
** The SQLITE_USE_W32_FOR_CONSOLE_IO macro is also available. If
|
||||
** defined, it forces the use of Win32 APIs for all console I/O, both
|
||||
** input and output. This is necessary for some non-Microsoft run-times
|
||||
** that implement stdio differently from Microsoft/Visual-Studio.
|
||||
*/
|
||||
#if defined(SQLITE_U8TEXT_ONLY)
|
||||
# define UseWtextForOutput(fd) 1
|
||||
@ -459,10 +464,10 @@ char *sqlite3_fgets(char *buf, int sz, FILE *in){
|
||||
*/
|
||||
wchar_t *b1 = sqlite3_malloc( sz*sizeof(wchar_t) );
|
||||
if( b1==0 ) return 0;
|
||||
#ifndef SQLITE_USE_STDIO_FOR_CONSOLE
|
||||
#ifdef SQLITE_USE_W32_FOR_CONSOLE_IO
|
||||
DWORD nRead = 0;
|
||||
if( IsConsole(in)
|
||||
&& ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), b1, sz, &nRead, 0)
|
||||
&& ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), b1, sz-1, &nRead, 0)
|
||||
){
|
||||
b1[nRead] = 0;
|
||||
}else
|
||||
@ -537,7 +542,7 @@ int sqlite3_fputs(const char *z, FILE *out){
|
||||
sz = MultiByteToWideChar(CP_UTF8, 0, z, sz, b1, sz);
|
||||
b1[sz] = 0;
|
||||
|
||||
#ifndef SQLITE_STDIO_FOR_CONSOLE
|
||||
#ifdef SQLITE_USE_W32_FOR_CONSOLE_IO
|
||||
DWORD nWr = 0;
|
||||
if( IsConsole(out)
|
||||
&& WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),b1,sz,&nWr,0)
|
||||
@ -547,8 +552,9 @@ int sqlite3_fputs(const char *z, FILE *out){
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
/* For non-console I/O, or if SQLITE_USE_STDIO_FOR_CONSOLE is defined
|
||||
** then write using the standard library. */
|
||||
/* As long as SQLITE_USE_W32_FOR_CONSOLE_IO is not defined, or for
|
||||
** non-console I/O even if that macro is defined, write using the
|
||||
** standard library. */
|
||||
_setmode(_fileno(out), _O_U8TEXT);
|
||||
if( UseBinaryWText(out) ){
|
||||
piecemealOutput(b1, sz, out);
|
||||
@ -5314,7 +5320,7 @@ static u8* fromBase64( char *pIn, int ncIn, u8 *pOut ){
|
||||
deliberate_fall_through; /* FALLTHRU */
|
||||
case 1:
|
||||
pOut[0] = (qv>>16) & 0xff;
|
||||
deliberate_fall_through; /* FALLTHRU */
|
||||
break;
|
||||
}
|
||||
pOut += nbo;
|
||||
}
|
||||
@ -17199,7 +17205,7 @@ static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){
|
||||
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
|
||||
sqlite3_vfs *pRoot = pInfo->pRootVfs;
|
||||
vfstraceOnOff(pInfo, VTR_DLCLOSE);
|
||||
vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName);
|
||||
vfstrace_printf(pInfo, "%s.xDlClose()\n", pInfo->zVfsName);
|
||||
pRoot->xDlClose(pRoot, pHandle);
|
||||
}
|
||||
|
||||
@ -28885,6 +28891,9 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
const char *zName;
|
||||
int op;
|
||||
} aDbConfig[] = {
|
||||
{ "attach_create", SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE },
|
||||
{ "attach_write", SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE },
|
||||
{ "comments", SQLITE_DBCONFIG_ENABLE_COMMENTS },
|
||||
{ "defensive", SQLITE_DBCONFIG_DEFENSIVE },
|
||||
{ "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL },
|
||||
{ "dqs_dml", SQLITE_DBCONFIG_DQS_DML },
|
||||
@ -31635,6 +31644,7 @@ static int do_meta_command(char *zLine, ShellState *p){
|
||||
{ 0x04000000, 1, "NullUnusedCols" },
|
||||
{ 0x08000000, 1, "OnePass" },
|
||||
{ 0x10000000, 1, "OrderBySubq" },
|
||||
{ 0x20000000, 1, "StarQuery" },
|
||||
{ 0xffffffff, 0, "All" },
|
||||
};
|
||||
unsigned int curOpt;
|
||||
|
4064
deps/sqlite/sqlite3.c
vendored
153
deps/sqlite/sqlite3.h
vendored
@ -146,9 +146,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.48.0"
|
||||
#define SQLITE_VERSION_NUMBER 3048000
|
||||
#define SQLITE_SOURCE_ID "2025-01-14 11:05:00 d2fe6b05f38d9d7cd78c5d252e99ac59f1aea071d669830c1ffe4e8966e84010"
|
||||
#define SQLITE_VERSION "3.49.1"
|
||||
#define SQLITE_VERSION_NUMBER 3049001
|
||||
#define SQLITE_SOURCE_ID "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@ -2211,7 +2211,15 @@ struct sqlite3_mem_methods {
|
||||
** CAPI3REF: Database Connection Configuration Options
|
||||
**
|
||||
** These constants are the available integer configuration options that
|
||||
** can be passed as the second argument to the [sqlite3_db_config()] interface.
|
||||
** can be passed as the second parameter to the [sqlite3_db_config()] interface.
|
||||
**
|
||||
** The [sqlite3_db_config()] interface is a var-args functions. It takes a
|
||||
** variable number of parameters, though always at least two. The number of
|
||||
** parameters passed into sqlite3_db_config() depends on which of these
|
||||
** constants is given as the second parameter. This documentation page
|
||||
** refers to parameters beyond the second as "arguments". Thus, when this
|
||||
** page says "the N-th argument" it means "the N-th parameter past the
|
||||
** configuration option" or "the (N+2)-th parameter to sqlite3_db_config()".
|
||||
**
|
||||
** New configuration options may be added in future releases of SQLite.
|
||||
** Existing configuration options might be discontinued. Applications
|
||||
@ -2223,8 +2231,14 @@ struct sqlite3_mem_methods {
|
||||
** <dl>
|
||||
** [[SQLITE_DBCONFIG_LOOKASIDE]]
|
||||
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
|
||||
** <dd> ^This option takes three additional arguments that determine the
|
||||
** [lookaside memory allocator] configuration for the [database connection].
|
||||
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
|
||||
** configuration of the lookaside memory allocator within a database
|
||||
** connection.
|
||||
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
|
||||
** in the [DBCONFIG arguments|usual format].
|
||||
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
|
||||
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
|
||||
** should have a total of five parameters.
|
||||
** ^The first argument (the third parameter to [sqlite3_db_config()] is a
|
||||
** pointer to a memory buffer to use for lookaside memory.
|
||||
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
|
||||
@ -2247,7 +2261,8 @@ struct sqlite3_mem_methods {
|
||||
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
|
||||
** <dd> ^This option is used to enable or disable the enforcement of
|
||||
** [foreign key constraints]. There should be two additional arguments.
|
||||
** [foreign key constraints]. This is the same setting that is
|
||||
** enabled or disabled by the [PRAGMA foreign_keys] statement.
|
||||
** The first argument is an integer which is 0 to disable FK enforcement,
|
||||
** positive to enable FK enforcement or negative to leave FK enforcement
|
||||
** unchanged. The second parameter is a pointer to an integer into which
|
||||
@ -2269,13 +2284,13 @@ struct sqlite3_mem_methods {
|
||||
** <p>Originally this option disabled all triggers. ^(However, since
|
||||
** SQLite version 3.35.0, TEMP triggers are still allowed even if
|
||||
** this option is off. So, in other words, this option now only disables
|
||||
** triggers in the main database schema or in the schemas of ATTACH-ed
|
||||
** triggers in the main database schema or in the schemas of [ATTACH]-ed
|
||||
** databases.)^ </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
|
||||
** <dd> ^This option is used to enable or disable [CREATE VIEW | views].
|
||||
** There should be two additional arguments.
|
||||
** There must be two additional arguments.
|
||||
** The first argument is an integer which is 0 to disable views,
|
||||
** positive to enable views or negative to leave the setting unchanged.
|
||||
** The second parameter is a pointer to an integer into which
|
||||
@ -2294,7 +2309,7 @@ struct sqlite3_mem_methods {
|
||||
** <dd> ^This option is used to enable or disable the
|
||||
** [fts3_tokenizer()] function which is part of the
|
||||
** [FTS3] full-text search engine extension.
|
||||
** There should be two additional arguments.
|
||||
** There must be two additional arguments.
|
||||
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
|
||||
** positive to enable fts3_tokenizer() or negative to leave the setting
|
||||
** unchanged.
|
||||
@ -2309,7 +2324,7 @@ struct sqlite3_mem_methods {
|
||||
** interface independently of the [load_extension()] SQL function.
|
||||
** The [sqlite3_enable_load_extension()] API enables or disables both the
|
||||
** C-API [sqlite3_load_extension()] and the SQL function [load_extension()].
|
||||
** There should be two additional arguments.
|
||||
** There must be two additional arguments.
|
||||
** When the first argument to this interface is 1, then only the C-API is
|
||||
** enabled and the SQL function remains disabled. If the first argument to
|
||||
** this interface is 0, then both the C-API and the SQL function are disabled.
|
||||
@ -2323,23 +2338,30 @@ struct sqlite3_mem_methods {
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
|
||||
** <dd> ^This option is used to change the name of the "main" database
|
||||
** schema. ^The sole argument is a pointer to a constant UTF8 string
|
||||
** which will become the new schema name in place of "main". ^SQLite
|
||||
** does not make a copy of the new main schema name string, so the application
|
||||
** must ensure that the argument passed into this DBCONFIG option is unchanged
|
||||
** until after the database connection closes.
|
||||
** schema. This option does not follow the
|
||||
** [DBCONFIG arguments|usual SQLITE_DBCONFIG argument format].
|
||||
** This option takes exactly one additional argument so that the
|
||||
** [sqlite3_db_config()] call has a total of three parameters. The
|
||||
** extra argument must be a pointer to a constant UTF8 string which
|
||||
** will become the new schema name in place of "main". ^SQLite does
|
||||
** not make a copy of the new main schema name string, so the application
|
||||
** must ensure that the argument passed into SQLITE_DBCONFIG MAINDBNAME
|
||||
** is unchanged until after the database connection closes.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]]
|
||||
** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt>
|
||||
** <dd> Usually, when a database in wal mode is closed or detached from a
|
||||
** database handle, SQLite checks if this will mean that there are now no
|
||||
** connections at all to the database. If so, it performs a checkpoint
|
||||
** operation before closing the connection. This option may be used to
|
||||
** override this behavior. The first parameter passed to this operation
|
||||
** is an integer - positive to disable checkpoints-on-close, or zero (the
|
||||
** default) to enable them, and negative to leave the setting unchanged.
|
||||
** The second parameter is a pointer to an integer
|
||||
** <dd> Usually, when a database in [WAL mode] is closed or detached from a
|
||||
** database handle, SQLite checks if if there are other connections to the
|
||||
** same database, and if there are no other database connection (if the
|
||||
** connection being closed is the last open connection to the database),
|
||||
** then SQLite performs a [checkpoint] before closing the connection and
|
||||
** deletes the WAL file. The SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE option can
|
||||
** be used to override that behavior. The first argument passed to this
|
||||
** operation (the third parameter to [sqlite3_db_config()]) is an integer
|
||||
** which is positive to disable checkpoints-on-close, or zero (the default)
|
||||
** to enable them, and negative to leave the setting unchanged.
|
||||
** The second argument (the fourth parameter) is a pointer to an integer
|
||||
** into which is written 0 or 1 to indicate whether checkpoints-on-close
|
||||
** have been disabled - 0 if they are not disabled, 1 if they are.
|
||||
** </dd>
|
||||
@ -2500,7 +2522,7 @@ struct sqlite3_mem_methods {
|
||||
** statistics. For statistics to be collected, the flag must be set on
|
||||
** the database handle both when the SQL statement is prepared and when it
|
||||
** is stepped. The flag is set (collection of statistics is enabled)
|
||||
** by default. This option takes two arguments: an integer and a pointer to
|
||||
** by default. <p>This option takes two arguments: an integer and a pointer to
|
||||
** an integer.. The first argument is 1, 0, or -1 to enable, disable, or
|
||||
** leave unchanged the statement scanstatus option. If the second argument
|
||||
** is not NULL, then the value of the statement scanstatus setting after
|
||||
@ -2514,7 +2536,7 @@ struct sqlite3_mem_methods {
|
||||
** in which tables and indexes are scanned so that the scans start at the end
|
||||
** and work toward the beginning rather than starting at the beginning and
|
||||
** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the
|
||||
** same as setting [PRAGMA reverse_unordered_selects]. This option takes
|
||||
** same as setting [PRAGMA reverse_unordered_selects]. <p>This option takes
|
||||
** two arguments which are an integer and a pointer to an integer. The first
|
||||
** argument is 1, 0, or -1 to enable, disable, or leave unchanged the
|
||||
** reverse scan order flag, respectively. If the second argument is not NULL,
|
||||
@ -2523,7 +2545,76 @@ struct sqlite3_mem_methods {
|
||||
** first argument.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]]
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE option enables or disables
|
||||
** the ability of the [ATTACH DATABASE] SQL command to create a new database
|
||||
** file if the database filed named in the ATTACH command does not already
|
||||
** exist. This ability of ATTACH to create a new database is enabled by
|
||||
** default. Applications can disable or reenable the ability for ATTACH to
|
||||
** create new database files using this DBCONFIG option.<p>
|
||||
** This option takes two arguments which are an integer and a pointer
|
||||
** to an integer. The first argument is 1, 0, or -1 to enable, disable, or
|
||||
** leave unchanged the attach-create flag, respectively. If the second
|
||||
** argument is not NULL, then 0 or 1 is written into the integer that the
|
||||
** second argument points to depending on if the attach-create flag is set
|
||||
** after processing the first argument.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE]]
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the
|
||||
** ability of the [ATTACH DATABASE] SQL command to open a database for writing.
|
||||
** This capability is enabled by default. Applications can disable or
|
||||
** reenable this capability using the current DBCONFIG option. If the
|
||||
** the this capability is disabled, the [ATTACH] command will still work,
|
||||
** but the database will be opened read-only. If this option is disabled,
|
||||
** then the ability to create a new database using [ATTACH] is also disabled,
|
||||
** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]
|
||||
** option.<p>
|
||||
** This option takes two arguments which are an integer and a pointer
|
||||
** to an integer. The first argument is 1, 0, or -1 to enable, disable, or
|
||||
** leave unchanged the ability to ATTACH another database for writing,
|
||||
** respectively. If the second argument is not NULL, then 0 or 1 is written
|
||||
** into the integer to which the second argument points, depending on whether
|
||||
** the ability to ATTACH a read/write database is enabled or disabled
|
||||
** after processing the first argument.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_ENABLE_COMMENTS]]
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_COMMENTS</dt>
|
||||
** <dd>The SQLITE_DBCONFIG_ENABLE_COMMENTS option enables or disables the
|
||||
** ability to include comments in SQL text. Comments are enabled by default.
|
||||
** An application can disable or reenable comments in SQL text using this
|
||||
** DBCONFIG option.<p>
|
||||
** This option takes two arguments which are an integer and a pointer
|
||||
** to an integer. The first argument is 1, 0, or -1 to enable, disable, or
|
||||
** leave unchanged the ability to use comments in SQL text,
|
||||
** respectively. If the second argument is not NULL, then 0 or 1 is written
|
||||
** into the integer that the second argument points to depending on if
|
||||
** comments are allowed in SQL text after processing the first argument.
|
||||
** </dd>
|
||||
**
|
||||
** </dl>
|
||||
**
|
||||
** [[DBCONFIG arguments]] <h3>Arguments To SQLITE_DBCONFIG Options</h3>
|
||||
**
|
||||
** <p>Most of the SQLITE_DBCONFIG options take two arguments, so that the
|
||||
** overall call to [sqlite3_db_config()] has a total of four parameters.
|
||||
** The first argument (the third parameter to sqlite3_db_config()) is a integer.
|
||||
** The second argument is a pointer to an integer. If the first argument is 1,
|
||||
** then the option becomes enabled. If the first integer argument is 0, then the
|
||||
** option is disabled. If the first argument is -1, then the option setting
|
||||
** is unchanged. The second argument, the pointer to an integer, may be NULL.
|
||||
** If the second argument is not NULL, then a value of 0 or 1 is written into
|
||||
** the integer to which the second argument points, depending on whether the
|
||||
** setting is disabled or enabled after applying any changes specified by
|
||||
** the first argument.
|
||||
**
|
||||
** <p>While most SQLITE_DBCONFIG options use the argument format
|
||||
** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME]
|
||||
** and [SQLITE_DBCONFIG_LOOKASIDE] options are different. See the
|
||||
** documentation of those exceptional options for details.
|
||||
*/
|
||||
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
|
||||
#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */
|
||||
@ -2545,7 +2636,10 @@ struct sqlite3_mem_methods {
|
||||
#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */
|
||||
#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */
|
||||
#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */
|
||||
#define SQLITE_DBCONFIG_MAX 1019 /* Largest DBCONFIG */
|
||||
#define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */
|
||||
#define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */
|
||||
#define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */
|
||||
#define SQLITE_DBCONFIG_MAX 1022 /* Largest DBCONFIG */
|
||||
|
||||
/*
|
||||
** CAPI3REF: Enable Or Disable Extended Result Codes
|
||||
@ -10748,8 +10842,9 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
||||
/*
|
||||
** CAPI3REF: Serialize a database
|
||||
**
|
||||
** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory
|
||||
** that is a serialization of the S database on [database connection] D.
|
||||
** The sqlite3_serialize(D,S,P,F) interface returns a pointer to
|
||||
** memory that is a serialization of the S database on
|
||||
** [database connection] D. If S is a NULL pointer, the main database is used.
|
||||
** If P is not a NULL pointer, then the size of the database in bytes
|
||||
** is written into *P.
|
||||
**
|
||||
|
1
deps/zsign
vendored
Submodule
1
docs
63
docs/app_development_cheat_sheet.md
Normal file
@ -0,0 +1,63 @@
|
||||
# App Development 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)
|
166
docs/app_development_guide.md
Normal file
@ -0,0 +1,166 @@
|
||||
# App Development Guide
|
||||
|
||||
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.
|
||||
|
||||
## Example 1: Hello, world!
|
||||
|
||||
Of course we must start with a classic.
|
||||
|
||||
### app.js
|
||||
|
||||
```js
|
||||
app.setDocument('<h1 style="color: #fff">Hello, world!</h1>');
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
<h1 style="color: #fff">Hello, world!</h1>
|
||||
|
||||
### Explanation
|
||||
|
||||
At a glance, this might seem mundane, but for it to work:
|
||||
|
||||
- the server starts a real process for your app and loads your code into it
|
||||
- your code runs
|
||||
- `app.setDocument()` sends a message back to the server
|
||||
- the server interprets the message and redirects it to the browser
|
||||
- `core/client.js` in the browser receives the message and puts your HTML into an iframe
|
||||
- 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.
|
||||
|
||||
## Example 2: Hit Counter
|
||||
|
||||
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.
|
||||
|
||||
### app.js
|
||||
|
||||
```js
|
||||
async function main() {
|
||||
let db = await shared_database('visitors');
|
||||
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();
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
<h1 style="color: #fff">Welcome, visitor #1!</h1>
|
||||
|
||||
### Explanation
|
||||
|
||||
Just as pure browser apps have access to `localStorage`, Tilde Friends apps have access to key-value storage on the server.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Example 3: Files
|
||||
|
||||
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.
|
||||
|
||||
### app.js
|
||||
|
||||
```js
|
||||
async function main() {
|
||||
let html = utf8Decode(await getFile('index.html'));
|
||||
app.setDocument(html);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
### index.html
|
||||
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="script.js"></script>
|
||||
</head>
|
||||
<body style="color: #fff">
|
||||
<h1>File Test</h1>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### script.js
|
||||
|
||||
```js
|
||||
window.addEventListener('load', function() {
|
||||
document.body.appendChild(document.createTextNode('Hello, world');
|
||||
});
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
<h1>File Test</h1><p>Hello, world!</p>
|
||||
|
||||
### Explanation
|
||||
|
||||
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.
|
||||
|
||||
## Example 4: Remote Procedure Call
|
||||
|
||||
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.
|
||||
|
||||
### app.js
|
||||
|
||||
```js
|
||||
import * as tf from '/tfrpc.js';
|
||||
|
||||
function sum() {
|
||||
let s = 0;
|
||||
for (let x of arguments) {
|
||||
s += x;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
tf.register(sum);
|
||||
|
||||
async function main() {
|
||||
app.setDocument(utf8Decode(await getFile('index.html')));
|
||||
}
|
||||
main();
|
||||
```
|
||||
|
||||
### index.html
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<h1 id="result">Calculating...</h1>
|
||||
</body>
|
||||
<script type="module" src="script.js"></script>
|
||||
</html>
|
||||
```
|
||||
|
||||
### script.js
|
||||
|
||||
```js
|
||||
import * as tf from '/static/tfrpc.js';
|
||||
|
||||
window.addEventListener('load', async function () {
|
||||
document.getElementById('result').innerText = await tf.rpc.sum(1, 2, 3);
|
||||
});
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
<h1>6</h1>
|
||||
|
||||
### Explanation
|
||||
|
||||
Here the browser makes an asynchronous call to the server to do some basic math and update its DOM with the result.
|
||||
|
||||
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.
|
||||
|
||||
## Conclusion
|
||||
|
||||
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.
|
||||
|
||||
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.
|
15
docs/inspiration.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Inspiration
|
||||
|
||||
This is an ever-growing list of software that is similar to what Tilde Friends tries to be but as far as I can tell don't quite fit the same niche.
|
||||
|
||||
- Secure Scuttlebutt Clients
|
||||
- [Manyverse](https://www.manyver.se/)
|
||||
- [Patchwork](https://github.com/ssbc/patchwork)
|
||||
- [Patchfox](https://patchfox.org/#/)
|
||||
- [Habitat](https://gitlab.com/quickdudley/habitat)
|
||||
- [Āhau](https://gitlab.com/ahau/ahau/)
|
||||
- [erlbutt](https://github.com/cmoid/erlbutt/)
|
||||
- Web Application Platforms
|
||||
- [Glitch](https://glitch.com/)
|
||||
- [Val Town](https://www.val.town/)
|
||||
- [Clace](https://clace.io/)
|
24
docs/release_checklist.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Release Checklist
|
||||
|
||||
- make sure ci is passing
|
||||
- run the tests
|
||||
- format + prettier
|
||||
- update metadata/en-US/changelogs
|
||||
- git tag v1.2.3
|
||||
- git tag -f latest_release
|
||||
- push
|
||||
- make a release on gitea
|
||||
- upload the artifacts
|
||||
- upload the AppImage and zsyncmake
|
||||
- upload to Google
|
||||
- upload to Apple with dist-ios on macos
|
||||
- nix
|
||||
- june and december: update release version
|
||||
- run `nix flake update`
|
||||
- comment out the hash in default.nix
|
||||
- update the version
|
||||
- run `nix-build`
|
||||
- update the hash
|
||||
- bump the versions in GNUmakefile for the next release
|
||||
- make
|
||||
- commit
|
18
docs/upgrading.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Upgrading
|
||||
|
||||
Tilde Friends can be upgraded simply by running a new executable against an
|
||||
existing database.
|
||||
|
||||
Tilde Friends writes all data to a `db.sqlite` file, either in
|
||||
`~/.local/share/tildefriends/` or in the working directory where it is run,
|
||||
depending on the platform and whether each one already exists. Run with
|
||||
`tildefriends run -d DB_PATH` to specify the path to the database explicitly.
|
||||
|
||||
This file can be copied and moved across machines as needed like any [sqlite3
|
||||
database](https://www.sqlite.org/onefile.html).
|
||||
|
||||
Schema changes and compatibility breaks have been rare, by design. In general,
|
||||
upgrading is not expected to require any manual intervention and likely does
|
||||
not involve any automatic migration, either. Downgrading is not well-supported
|
||||
but will probably just work excepting rare changes that will be called out in
|
||||
the changelog.
|
64
docs/vision.md
Normal file
@ -0,0 +1,64 @@
|
||||
# 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.
|
14
flake.lock
generated
@ -5,11 +5,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -20,16 +20,16 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1717281328,
|
||||
"narHash": "sha256-evZPzpf59oNcDUXxh2GHcxHkTEG4fjae2ytWP85jXRo=",
|
||||
"lastModified": 1739758141,
|
||||
"narHash": "sha256-uq6A2L7o1/tR6VfmYhZWoVAwb3gTy7j4Jx30MIrH0rE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b3b2b28c1daa04fe2ae47c21bb76fd226eac4ca1",
|
||||
"rev": "c618e28f70257593de75a7044438efc1c1fc0791",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"ref": "nixos-24.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
description = "Tilde Friends is a platform for making, running, and sharing web applications.";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
|
15
metadata/en-US/changelogs/33.txt
Normal file
@ -0,0 +1,15 @@
|
||||
* Allow specifying all global settings from the command-line (CLI usage changed).
|
||||
* Replication improvements.
|
||||
* An iOS build is on TestFlight.
|
||||
* macOS targets are debug and release like everywhere else.
|
||||
* Running from a subdirectory is fine.
|
||||
* Patchwork compatibility improvements.
|
||||
* Invite fixes.
|
||||
* Follow/block UI fixes.
|
||||
* Mobile automatically logs in.
|
||||
* Updates:
|
||||
* CodeMirror
|
||||
* OpenSSL 3.4.1
|
||||
* libbacktrace
|
||||
* speedscope 1.22.2
|
||||
* sqlite 3.49.1
|
11
metadata/en-US/changelogs/34.txt
Normal file
@ -0,0 +1,11 @@
|
||||
* The connections tab now shows replication progress.
|
||||
* Replication performance and thoroughness improvements.
|
||||
* Bind only to localhost on mobile, configurable.
|
||||
* Request blobs referenced by referenced blobs, and improve performance of that
|
||||
query.
|
||||
* Fix file upload on iOS.
|
||||
* Add rough back/forward/refresh buttons on iOS.
|
||||
* Other crash fixes and performance improvements.
|
||||
* Updates:
|
||||
* CodeMirror
|
||||
* w3.css
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 101 KiB |
6
package-lock.json
generated
@ -11,9 +11,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
|
||||
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.unprompted.tildefriends"
|
||||
android:versionCode="32"
|
||||
android:versionName="0.0.27">
|
||||
android:versionCode="34"
|
||||
android:versionName="0.0.29">
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
|
@ -421,7 +421,7 @@ public class TildeFriendsActivity extends Activity {
|
||||
base_url = "http://127.0.0.1:" + String.valueOf(port) + "/";
|
||||
runOnUiThread(() -> {
|
||||
hide_status();
|
||||
web_view.loadUrl(base_url);
|
||||
web_view.loadUrl(base_url + "login/auto");
|
||||
});
|
||||
observer = null;
|
||||
} else {
|
||||
|
@ -1,55 +1,16 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="65dp"
|
||||
android:height="65dp"
|
||||
android:viewportWidth="61"
|
||||
android:viewportHeight="65">
|
||||
<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:strokeWidth=".712717"
|
||||
android:fillColor="#0af"
|
||||
android:fillAlpha="1"/>
|
||||
<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:fillColor="#000000"/>
|
||||
<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:fillColor="#fcea2b"/>
|
||||
<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: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:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<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:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<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:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<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:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M40.974,27.8716s1.309,-2.7346 2.6974,0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:gravity="center"
|
||||
android:width="256dp"
|
||||
android:height="256dp">
|
||||
<shape>
|
||||
<solid
|
||||
android:color="#0af"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:drawable="@drawable/logo"
|
||||
android:width="65dp"
|
||||
android:height="65dp"
|
||||
android:gravity="center"/>
|
||||
</layer-list>
|
||||
|
16
src/android/res/drawable/icon_fdroid.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:gravity="center"
|
||||
android:width="256dp"
|
||||
android:height="256dp">
|
||||
<shape>
|
||||
<solid
|
||||
android:color="#0fa"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:drawable="@drawable/logo"
|
||||
android:width="65dp"
|
||||
android:height="65dp"
|
||||
android:gravity="center"/>
|
||||
</layer-list>
|
51
src/android/res/drawable/logo.xml
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="65dp"
|
||||
android:height="65dp"
|
||||
android:viewportWidth="65"
|
||||
android:viewportHeight="65">
|
||||
<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:fillColor="#000000"/>
|
||||
<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:fillColor="#fcea2b"/>
|
||||
<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: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:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<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:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<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:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<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:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M40.974,27.8716s1.309,-2.7346 2.6974,0"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
19
src/api.js.c
Normal file
@ -0,0 +1,19 @@
|
||||
#include "api.js.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <quickjs.h>
|
||||
|
||||
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
void tf_api_register(JSContext* context)
|
||||
{
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue ssb = JS_GetPropertyStr(context, global, "ssb");
|
||||
JS_SetPropertyStr(context, ssb, "registerImports", JS_NewCFunction(context, _tf_api_register_imports, "registerImports", 2));
|
||||
JS_FreeValue(context, ssb);
|
||||
JS_FreeValue(context, global);
|
||||
}
|
18
src/api.js.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
** \defgroup api_js JS API
|
||||
** Functions that are ultimately exposed to apps.
|
||||
** @{
|
||||
*/
|
||||
|
||||
/** A JS context. */
|
||||
typedef struct JSContext JSContext;
|
||||
|
||||
/**
|
||||
** Register JS API functions.
|
||||
** @param context The JS context.
|
||||
*/
|
||||
void tf_api_register(JSContext* context);
|
||||
|
||||
/** @} */
|
198
src/file.js.c
@ -18,20 +18,6 @@
|
||||
|
||||
static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_read_file_zip(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_stat(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_stat_zip(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
|
||||
static double _time_spec_to_double(const uv_timespec_t* time_spec);
|
||||
static void _file_on_stat_complete(uv_fs_t* request);
|
||||
|
||||
typedef struct file_stat_t
|
||||
{
|
||||
void* _task;
|
||||
JSContext* _context;
|
||||
promiseid_t _promise;
|
||||
uv_fs_t _request;
|
||||
} file_stat_t;
|
||||
|
||||
typedef struct fs_req_t
|
||||
{
|
||||
@ -45,12 +31,10 @@ void tf_file_register(JSContext* context)
|
||||
{
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue file = JS_NewObject(context);
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
tf_task_t* task = JS_GetContextOpaque(context);
|
||||
const char* zip = tf_task_get_zip_path(task);
|
||||
JS_SetPropertyStr(context, global, "File", file);
|
||||
JS_SetPropertyStr(context, file, "readFile", JS_NewCFunction(context, zip ? _file_read_file_zip : _file_read_file, "readFile", 1));
|
||||
JS_SetPropertyStr(context, file, "writeFile", JS_NewCFunction(context, _file_write_file, "writeFile", 2));
|
||||
JS_SetPropertyStr(context, file, "stat", JS_NewCFunction(context, zip ? _file_stat_zip : _file_stat, "stat", 1));
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
|
||||
@ -65,105 +49,6 @@ static void _file_async_close_callback(uv_fs_t* req)
|
||||
tf_free(req);
|
||||
}
|
||||
|
||||
static void _file_write_write_callback(uv_fs_t* req)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
fs_req_t* fsreq = (fs_req_t*)req;
|
||||
tf_task_t* task = req->loop->data;
|
||||
JSContext* context = tf_task_get_context(task);
|
||||
promiseid_t promise = (promiseid_t)(intptr_t)req->data;
|
||||
if (req->result >= 0)
|
||||
{
|
||||
tf_task_resolve_promise(task, promise, JS_NewInt64(context, req->result));
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to write %s: %s", req->path, uv_strerror(req->result)));
|
||||
}
|
||||
int result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_free(fsreq);
|
||||
}
|
||||
}
|
||||
|
||||
static void _file_write_open_callback(uv_fs_t* req)
|
||||
{
|
||||
fs_req_t* fsreq = (fs_req_t*)req;
|
||||
const char* path = tf_strdup(req->path);
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_task_t* task = req->loop->data;
|
||||
JSContext* context = tf_task_get_context(task);
|
||||
promiseid_t promise = (promiseid_t)(intptr_t)req->data;
|
||||
if (req->result >= 0)
|
||||
{
|
||||
uv_buf_t buf = { .base = fsreq->buffer, .len = fsreq->size };
|
||||
fsreq->file = req->result;
|
||||
int result = uv_fs_write(req->loop, req, fsreq->file, &buf, 1, 0, _file_write_write_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to write %s: %s", path, uv_strerror(result)));
|
||||
result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
uv_fs_req_cleanup(req);
|
||||
tf_free(fsreq);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
|
||||
tf_free(req);
|
||||
}
|
||||
tf_free((void*)path);
|
||||
}
|
||||
|
||||
static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* file_name = JS_ToCString(context, argv[0]);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buffer = tf_util_try_get_array_buffer(context, &size, argv[1]);
|
||||
bool is_array_buffer = false;
|
||||
if (buffer)
|
||||
{
|
||||
is_array_buffer = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = (uint8_t*)JS_ToCStringLen(context, &size, argv[1]);
|
||||
}
|
||||
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
fs_req_t* req = tf_malloc(sizeof(fs_req_t) + size);
|
||||
*req = (fs_req_t)
|
||||
{
|
||||
.fs =
|
||||
{
|
||||
.data = (void*)(intptr_t)promise,
|
||||
},
|
||||
.size = size,
|
||||
};
|
||||
memcpy(req->buffer, buffer, size);
|
||||
if (!is_array_buffer)
|
||||
{
|
||||
JS_FreeCString(context, (const char*)buffer);
|
||||
}
|
||||
|
||||
int result = uv_fs_open(tf_task_get_loop(task), &req->fs, file_name, UV_FS_O_CREAT | UV_FS_O_WRONLY, 0644, _file_write_open_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for write: %s", file_name, uv_strerror(result)));
|
||||
}
|
||||
JS_FreeCString(context, file_name);
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
static void _file_read_read_callback(uv_fs_t* req)
|
||||
{
|
||||
fs_req_t* fsreq = (fs_req_t*)req;
|
||||
@ -224,8 +109,9 @@ static void _file_read_open_callback(uv_fs_t* req)
|
||||
|
||||
static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
tf_task_t* task = JS_GetContextOpaque(context);
|
||||
const char* file_name = JS_ToCString(context, argv[0]);
|
||||
const char* actual = tf_task_get_path_with_root(task, file_name);
|
||||
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
@ -239,7 +125,7 @@ static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int ar
|
||||
.size = k_file_read_max,
|
||||
};
|
||||
memset(req + 1, 0, k_file_read_max);
|
||||
int result = uv_fs_open(tf_task_get_loop(task), &req->fs, file_name, UV_FS_O_RDONLY, 0, _file_read_open_callback);
|
||||
int result = uv_fs_open(tf_task_get_loop(task), &req->fs, actual, UV_FS_O_RDONLY, 0, _file_read_open_callback);
|
||||
if (result < 0)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for read: %s", file_name, uv_strerror(result)));
|
||||
@ -247,6 +133,7 @@ static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int ar
|
||||
tf_free(req);
|
||||
}
|
||||
JS_FreeCString(context, file_name);
|
||||
tf_free((char*)actual);
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
@ -365,81 +252,6 @@ static JSValue _file_read_file_zip(JSContext* context, JSValueConst this_val, in
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
static JSValue _file_stat(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* path = JS_ToCString(context, argv[0]);
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
|
||||
file_stat_t* data = tf_malloc(sizeof(file_stat_t));
|
||||
data->_task = task;
|
||||
data->_promise = promise;
|
||||
data->_request.data = data;
|
||||
data->_context = context;
|
||||
|
||||
int result = uv_fs_stat(tf_task_get_loop(task), &data->_request, path, _file_on_stat_complete);
|
||||
if (result)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_NewInt32(context, result));
|
||||
uv_fs_req_cleanup(&data->_request);
|
||||
tf_free(data);
|
||||
}
|
||||
JS_FreeCString(context, path);
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
static JSValue _file_stat_zip(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
promiseid_t promise = -1;
|
||||
JSValue promise_value = tf_task_allocate_promise(task, &promise);
|
||||
|
||||
file_stat_t* data = tf_malloc(sizeof(file_stat_t));
|
||||
data->_task = task;
|
||||
data->_promise = promise;
|
||||
data->_request.data = data;
|
||||
data->_context = context;
|
||||
|
||||
/* Ignore the requested path and stat the zip itself. */
|
||||
int result = uv_fs_stat(tf_task_get_loop(task), &data->_request, tf_task_get_zip_path(task), _file_on_stat_complete);
|
||||
if (result)
|
||||
{
|
||||
tf_task_reject_promise(task, promise, JS_NewInt32(context, result));
|
||||
uv_fs_req_cleanup(&data->_request);
|
||||
tf_free(data);
|
||||
}
|
||||
return promise_value;
|
||||
}
|
||||
|
||||
static double _time_spec_to_double(const uv_timespec_t* time_spec)
|
||||
{
|
||||
return time_spec->tv_sec + (double)(time_spec->tv_nsec) / 1e9;
|
||||
}
|
||||
|
||||
static void _file_on_stat_complete(uv_fs_t* request)
|
||||
{
|
||||
file_stat_t* data = (file_stat_t*)(request->data);
|
||||
JSContext* context = data->_context;
|
||||
|
||||
if (request->result)
|
||||
{
|
||||
tf_task_reject_promise(data->_task, data->_promise, JS_NewInt32(context, request->result));
|
||||
}
|
||||
else
|
||||
{
|
||||
JSValue result = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, result, "mtime", JS_NewFloat64(context, _time_spec_to_double(&request->statbuf.st_mtim)));
|
||||
JS_SetPropertyStr(context, result, "ctime", JS_NewFloat64(context, _time_spec_to_double(&request->statbuf.st_ctim)));
|
||||
JS_SetPropertyStr(context, result, "atime", JS_NewFloat64(context, _time_spec_to_double(&request->statbuf.st_atim)));
|
||||
JS_SetPropertyStr(context, result, "size", JS_NewFloat64(context, request->statbuf.st_size));
|
||||
tf_task_resolve_promise(data->_task, data->_promise, result);
|
||||
JS_FreeValue(context, result);
|
||||
}
|
||||
uv_fs_req_cleanup(request);
|
||||
tf_free(data);
|
||||
}
|
||||
|
||||
typedef struct _stat_t
|
||||
{
|
||||
uv_fs_t request;
|
||||
|
70
src/http.c
@ -84,6 +84,7 @@ typedef struct _tf_http_listener_t
|
||||
typedef struct _tf_http_t
|
||||
{
|
||||
bool is_shutting_down;
|
||||
bool is_in_destroy;
|
||||
|
||||
tf_http_listener_t** listeners;
|
||||
int listeners_count;
|
||||
@ -395,7 +396,7 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
|
||||
|
||||
if (fin)
|
||||
{
|
||||
if (connection->request->on_message)
|
||||
if (connection->request && connection->request->on_message)
|
||||
{
|
||||
tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "websocket");
|
||||
connection->request->on_message(connection->request, connection->fragment_length ? connection->fragment_op_code : op_code,
|
||||
@ -697,7 +698,7 @@ static void _http_on_connection(uv_stream_t* stream, int status)
|
||||
http->connections[http->connections_count++] = connection;
|
||||
}
|
||||
|
||||
int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data)
|
||||
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data)
|
||||
{
|
||||
tf_http_listener_t* listener = tf_malloc(sizeof(tf_http_listener_t));
|
||||
*listener = (tf_http_listener_t) {
|
||||
@ -715,25 +716,29 @@ int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cle
|
||||
|
||||
if (r == 0)
|
||||
{
|
||||
bool use_ipv6 = !tf_util_is_mobile();
|
||||
|
||||
#if defined(__HAIKU__)
|
||||
/*
|
||||
** Binding to IPv6 here fails with an odd error, and the socket
|
||||
** becomes unusable. Since we probably want localhost only
|
||||
** on this single-user OS, let's just assume IPv4.
|
||||
*/
|
||||
struct sockaddr_in addr = {
|
||||
use_ipv6 = false;
|
||||
#endif
|
||||
|
||||
struct sockaddr_in addr4 = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr = { .s_addr = INADDR_ANY },
|
||||
.sin_addr = { .s_addr = local_only ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY) },
|
||||
.sin_port = ntohs(port),
|
||||
};
|
||||
#else
|
||||
struct sockaddr_in6 addr = {
|
||||
struct sockaddr_in6 addr6 = {
|
||||
.sin6_family = AF_INET6,
|
||||
.sin6_addr = IN6ADDR_ANY_INIT,
|
||||
.sin6_addr = local_only ? (struct in6_addr)IN6ADDR_LOOPBACK_INIT : (struct in6_addr)IN6ADDR_ANY_INIT,
|
||||
.sin6_port = ntohs(port),
|
||||
};
|
||||
#endif
|
||||
r = uv_tcp_bind(&listener->tcp, (struct sockaddr*)&addr, 0);
|
||||
struct sockaddr* addr = use_ipv6 ? (struct sockaddr*)&addr6 : (struct sockaddr*)&addr4;
|
||||
r = uv_tcp_bind(&listener->tcp, addr, 0);
|
||||
if (r)
|
||||
{
|
||||
tf_printf("%s:%d: uv_tcp_bind: %s\n", __FILE__, __LINE__, uv_strerror(r));
|
||||
@ -786,7 +791,13 @@ static void _http_free_listener_on_close(uv_handle_t* handle)
|
||||
|
||||
void tf_http_destroy(tf_http_t* http)
|
||||
{
|
||||
if (http->is_in_destroy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
http->is_shutting_down = true;
|
||||
http->is_in_destroy = true;
|
||||
|
||||
for (int i = 0; i < http->connections_count; i++)
|
||||
{
|
||||
@ -845,6 +856,10 @@ void tf_http_destroy(tf_http_t* http)
|
||||
|
||||
tf_free(http);
|
||||
}
|
||||
else
|
||||
{
|
||||
http->is_in_destroy = false;
|
||||
}
|
||||
}
|
||||
|
||||
const char* tf_http_status_text(int status)
|
||||
@ -963,9 +978,42 @@ static void _http_write(tf_http_connection_t* connection, const void* data, size
|
||||
}
|
||||
}
|
||||
|
||||
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size)
|
||||
void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size)
|
||||
{
|
||||
_http_write(request->connection, data, size);
|
||||
uint8_t* copy = tf_malloc(size + 16);
|
||||
bool fin = true;
|
||||
size_t header = 1;
|
||||
copy[0] = (fin ? (1 << 7) : 0) | (op_code & 0xf);
|
||||
if (size < 126)
|
||||
{
|
||||
copy[1] = size;
|
||||
header += 1;
|
||||
}
|
||||
else if (size < (1 << 16))
|
||||
{
|
||||
copy[1] = 126;
|
||||
copy[2] = (size >> 8) & 0xff;
|
||||
copy[3] = (size >> 0) & 0xff;
|
||||
header += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t high = ((uint64_t)size >> 32) & 0xffffffff;
|
||||
uint32_t low = (size >> 0) & 0xffffffff;
|
||||
copy[1] = 127;
|
||||
copy[2] = (high >> 24) & 0xff;
|
||||
copy[3] = (high >> 16) & 0xff;
|
||||
copy[4] = (high >> 8) & 0xff;
|
||||
copy[5] = (high >> 0) & 0xff;
|
||||
copy[6] = (low >> 24) & 0xff;
|
||||
copy[7] = (low >> 16) & 0xff;
|
||||
copy[8] = (low >> 8) & 0xff;
|
||||
copy[9] = (low >> 0) & 0xff;
|
||||
header += 9;
|
||||
}
|
||||
memcpy(copy + header, data, size);
|
||||
_http_write(request->connection, copy, header + size);
|
||||
tf_free(copy);
|
||||
}
|
||||
|
||||
void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length)
|
||||
|
@ -116,12 +116,13 @@ void tf_http_set_trace(tf_http_t* http, tf_trace_t* trace);
|
||||
** times to listen on multiple ports.
|
||||
** @param http The HTTP instance.
|
||||
** @param port The port on which to listen, or 0 to assign a free port.
|
||||
** @param local_only Only access connections on localhost, otherwise any address.
|
||||
** @param tls An optional TLS context to use for HTTPS requests.
|
||||
** @param cleanup A function called when the HTTP instance is being cleaned up.
|
||||
** @param user_data User data passed to the cleanup callback.
|
||||
** @return The port number on which the HTTP instance is now listening.
|
||||
*/
|
||||
int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data);
|
||||
int tf_http_listen(tf_http_t* http, int port, bool local_only, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data);
|
||||
|
||||
/**
|
||||
** Add an HTTP request handler.
|
||||
@ -209,10 +210,11 @@ const char* tf_http_get_cookie(const char* cookie_header, const char* name);
|
||||
** Send a websocket message.
|
||||
** @param request The HTTP request which was previously updated to a websocket
|
||||
** session with tf_http_request_websocket_upgrade().
|
||||
** @param op_code Websocket op code.
|
||||
** @param data The message data.
|
||||
** @param size The size of data.
|
||||
*/
|
||||
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size);
|
||||
void tf_http_request_websocket_send(tf_http_request_t* request, int op_code, const void* data, size_t size);
|
||||
|
||||
/**
|
||||
** Upgrade an HTTP request to a websocket session.
|
||||
|
470
src/httpd.js.c
@ -5,8 +5,10 @@
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
#include "ssb.db.h"
|
||||
#include "ssb.ebt.h"
|
||||
#include "ssb.h"
|
||||
#include "task.h"
|
||||
#include "tls.h"
|
||||
#include "tlscontext.js.h"
|
||||
#include "trace.h"
|
||||
#include "util.js.h"
|
||||
@ -151,44 +153,9 @@ static JSValue _httpd_response_send(JSContext* context, JSValueConst this_val, i
|
||||
tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id);
|
||||
int opcode = 0x1;
|
||||
JS_ToInt32(context, &opcode, argv[1]);
|
||||
uint64_t length = 0;
|
||||
size_t length_size = 0;
|
||||
const char* message = JS_ToCStringLen(context, &length_size, argv[0]);
|
||||
length = length_size;
|
||||
uint8_t* copy = tf_malloc(length + 16);
|
||||
bool fin = true;
|
||||
size_t header = 1;
|
||||
copy[0] = (fin ? (1 << 7) : 0) | (opcode & 0xf);
|
||||
if (length < 126)
|
||||
{
|
||||
copy[1] = length;
|
||||
header += 1;
|
||||
}
|
||||
else if (length < (1 << 16))
|
||||
{
|
||||
copy[1] = 126;
|
||||
copy[2] = (length >> 8) & 0xff;
|
||||
copy[3] = (length >> 0) & 0xff;
|
||||
header += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t high = (length >> 32) & 0xffffffff;
|
||||
uint32_t low = (length >> 0) & 0xffffffff;
|
||||
copy[1] = 127;
|
||||
copy[2] = (high >> 24) & 0xff;
|
||||
copy[3] = (high >> 16) & 0xff;
|
||||
copy[4] = (high >> 8) & 0xff;
|
||||
copy[5] = (high >> 0) & 0xff;
|
||||
copy[6] = (low >> 24) & 0xff;
|
||||
copy[7] = (low >> 16) & 0xff;
|
||||
copy[8] = (low >> 8) & 0xff;
|
||||
copy[9] = (low >> 0) & 0xff;
|
||||
header += 9;
|
||||
}
|
||||
memcpy(copy + header, message, length);
|
||||
tf_http_request_send(request, copy, header + length);
|
||||
tf_free(copy);
|
||||
size_t length = 0;
|
||||
const char* message = JS_ToCStringLen(context, &length, argv[0]);
|
||||
tf_http_request_websocket_send(request, opcode, message, length);
|
||||
JS_FreeCString(context, message);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
@ -233,46 +200,6 @@ static JSValue _httpd_make_response_object(JSContext* context, tf_http_request_t
|
||||
return response_object;
|
||||
}
|
||||
|
||||
static void _httpd_callback_internal(tf_http_request_t* request, bool is_websocket)
|
||||
{
|
||||
http_handler_data_t* data = request->user_data;
|
||||
JSContext* context = data->context;
|
||||
JSValue request_object = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, request_object, "method", JS_NewString(context, request->method));
|
||||
JS_SetPropertyStr(context, request_object, "uri", JS_NewString(context, request->path));
|
||||
JSValue headers = JS_NewObject(context);
|
||||
for (int i = 0; i < request->headers_count; i++)
|
||||
{
|
||||
JS_SetPropertyStr(context, headers, request->headers[i].name, JS_NewString(context, request->headers[i].value));
|
||||
}
|
||||
JS_SetPropertyStr(context, request_object, "headers", headers);
|
||||
if (request->query)
|
||||
{
|
||||
JS_SetPropertyStr(context, request_object, "query", JS_NewString(context, request->query));
|
||||
}
|
||||
if (request->body)
|
||||
{
|
||||
JS_SetPropertyStr(context, request_object, "body", tf_util_new_uint8_array(context, request->body, request->content_length));
|
||||
}
|
||||
|
||||
JSValue client = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, client, "tls", request->is_tls ? JS_TRUE : JS_FALSE);
|
||||
JS_SetPropertyStr(context, request_object, "client", client);
|
||||
|
||||
JSValue response_object = _httpd_make_response_object(context, request);
|
||||
/* The ref is owned by the JS object and will be released by the finalizer. */
|
||||
tf_http_request_ref(request);
|
||||
JSValue args[] = {
|
||||
request_object,
|
||||
response_object,
|
||||
};
|
||||
JSValue response = JS_Call(context, data->callback, JS_UNDEFINED, 2, args);
|
||||
tf_util_report_error(context, response);
|
||||
JS_FreeValue(context, request_object);
|
||||
JS_FreeValue(context, response);
|
||||
JS_FreeValue(context, response_object);
|
||||
}
|
||||
|
||||
static bool _httpd_redirect(tf_http_request_t* request)
|
||||
{
|
||||
if (request->is_tls)
|
||||
@ -292,16 +219,6 @@ static bool _httpd_redirect(tf_http_request_t* request)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _httpd_callback(tf_http_request_t* request)
|
||||
{
|
||||
if (_httpd_redirect(request))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_httpd_callback_internal(request, false);
|
||||
}
|
||||
|
||||
static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_http_request_t* request = JS_GetOpaque(this_val, _httpd_request_class_id);
|
||||
@ -388,71 +305,19 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static void _httpd_cleanup_callback(void* user_data)
|
||||
{
|
||||
http_handler_data_t* data = user_data;
|
||||
JS_FreeValue(data->context, data->callback);
|
||||
tf_free(data);
|
||||
}
|
||||
|
||||
static JSValue _httpd_endpoint_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id);
|
||||
const char* pattern = JS_ToCString(context, argv[0]);
|
||||
http_handler_data_t* data = tf_malloc(sizeof(http_handler_data_t));
|
||||
*data = (http_handler_data_t) { .context = context, .callback = JS_DupValue(context, argv[1]) };
|
||||
tf_http_add_handler(http, pattern, _httpd_callback, _httpd_cleanup_callback, data);
|
||||
JS_FreeCString(context, pattern);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
typedef struct _httpd_listener_t
|
||||
{
|
||||
JSContext* context;
|
||||
JSValue tls;
|
||||
tf_tls_context_t* tls;
|
||||
} httpd_listener_t;
|
||||
|
||||
static void _httpd_listener_cleanup(void* user_data)
|
||||
{
|
||||
httpd_listener_t* listener = user_data;
|
||||
JS_FreeValue(listener->context, listener->tls);
|
||||
tf_free(listener);
|
||||
}
|
||||
|
||||
static JSValue _httpd_endpoint_start(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id);
|
||||
int port = 0;
|
||||
JS_ToInt32(context, &port, argv[0]);
|
||||
|
||||
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
|
||||
*listener = (httpd_listener_t) { .context = context, .tls = JS_DupValue(context, argv[1]) };
|
||||
tf_tls_context_t* tls = tf_tls_context_get(listener->tls);
|
||||
int assigned_port = tf_http_listen(http, port, tls, _httpd_listener_cleanup, listener);
|
||||
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http%s://127.0.0.1:%d/" RESET ".\n", tls ? "s" : "", assigned_port);
|
||||
return JS_NewInt32(context, assigned_port);
|
||||
}
|
||||
|
||||
static void _httpd_free_user_data(void* user_data)
|
||||
{
|
||||
tf_free(user_data);
|
||||
}
|
||||
|
||||
static JSValue _httpd_set_http_redirect(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_http_t* http = JS_GetOpaque(this_val, _httpd_class_id);
|
||||
http_user_data_t* user_data = tf_http_get_user_data(http);
|
||||
if (!user_data)
|
||||
if (listener->tls)
|
||||
{
|
||||
user_data = tf_malloc(sizeof(http_user_data_t));
|
||||
memset(user_data, 0, sizeof(http_user_data_t));
|
||||
tf_http_set_user_data(http, user_data, _httpd_free_user_data);
|
||||
tf_tls_context_destroy(listener->tls);
|
||||
}
|
||||
|
||||
const char* redirect = JS_ToCString(context, argv[0]);
|
||||
snprintf(user_data->redirect, sizeof(user_data->redirect), "%s", redirect ? redirect : "");
|
||||
JS_FreeCString(context, redirect);
|
||||
return JS_UNDEFINED;
|
||||
tf_free(listener);
|
||||
}
|
||||
|
||||
typedef struct _auth_query_work_t
|
||||
@ -705,6 +570,50 @@ static void _httpd_endpoint_trace(tf_http_request_t* request)
|
||||
tf_free(json);
|
||||
}
|
||||
|
||||
static void _httpd_endpoint_ebt(tf_http_request_t* request)
|
||||
{
|
||||
if (_httpd_redirect(request))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tf_task_t* task = request->user_data;
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
|
||||
JSValue object = JS_NewObject(context);
|
||||
|
||||
tf_ssb_connection_t* connections[256];
|
||||
int connection_count = tf_ssb_get_connections(ssb, connections, tf_countof(connections));
|
||||
for (int i = 0; i < connection_count; i++)
|
||||
{
|
||||
char id[k_id_base64_len];
|
||||
tf_ssb_connection_get_id(connections[i], id, sizeof(id));
|
||||
|
||||
char key[256];
|
||||
JSValue clock = JS_NewObject(context);
|
||||
snprintf(key, sizeof(key), "%d:%s", i, id);
|
||||
|
||||
tf_ssb_ebt_t* ebt = tf_ssb_connection_get_ebt(connections[i]);
|
||||
tf_ssb_ebt_debug_clock(ebt, context, clock);
|
||||
JS_SetPropertyStr(context, object, key, clock);
|
||||
}
|
||||
|
||||
JSValue json_value = JS_JSONStringify(context, object, JS_NULL, JS_NewInt32(context, 2));
|
||||
const char* json = JS_ToCString(context, json_value);
|
||||
JS_FreeValue(context, json_value);
|
||||
|
||||
const char* headers[] = {
|
||||
"Content-Type",
|
||||
"application/json; charset=utf-8",
|
||||
"Access-Control-Allow-Origin",
|
||||
"*",
|
||||
};
|
||||
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, json, json ? strlen(json) : 0);
|
||||
JS_FreeCString(context, json);
|
||||
JS_FreeValue(context, object);
|
||||
}
|
||||
|
||||
static void _httpd_endpoint_mem(tf_http_request_t* request)
|
||||
{
|
||||
if (_httpd_redirect(request))
|
||||
@ -783,13 +692,24 @@ typedef struct _http_file_t
|
||||
char etag[512];
|
||||
} http_file_t;
|
||||
|
||||
static bool _ends_with(const char* a, const char* suffix)
|
||||
{
|
||||
if (!a || !suffix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
size_t alen = strlen(a);
|
||||
size_t suffixlen = strlen(suffix);
|
||||
return alen >= suffixlen && strcmp(a + alen - suffixlen, suffix) == 0;
|
||||
}
|
||||
|
||||
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;
|
||||
tf_http_request_t* request = file->request;
|
||||
if (result >= 0)
|
||||
{
|
||||
if (strcmp(path, "core/tfrpc.js") == 0)
|
||||
if (strcmp(path, "core/tfrpc.js") == 0 || _ends_with(path, "core/tfrpc.js"))
|
||||
{
|
||||
const char* content_type = _ext_to_content_type(strrchr(path, '.'), true);
|
||||
const char* headers[] = {
|
||||
@ -931,9 +851,10 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
|
||||
}
|
||||
|
||||
tf_task_t* task = request->user_data;
|
||||
size_t size = strlen(file_path) + strlen(after) + 1;
|
||||
const char* root_path = tf_task_get_root_path(task);
|
||||
size_t size = (root_path ? strlen(root_path) + 1 : 0) + strlen(file_path) + strlen(after) + 1;
|
||||
char* path = alloca(size);
|
||||
snprintf(path, size, "%s%s", file_path, after);
|
||||
snprintf(path, size, "%s%s%s%s", root_path ? root_path : "", root_path ? "/" : "", file_path, after);
|
||||
tf_http_request_ref(request);
|
||||
tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
|
||||
}
|
||||
@ -2255,6 +2176,176 @@ static void _httpd_endpoint_logout(tf_http_request_t* request)
|
||||
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
|
||||
}
|
||||
|
||||
typedef struct _auto_login_t
|
||||
{
|
||||
tf_http_request_t* request;
|
||||
bool autologin;
|
||||
const char* users;
|
||||
} auto_login_t;
|
||||
|
||||
static void _httpd_auto_login_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
auto_login_t* request = user_data;
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
tf_ssb_db_get_global_setting_bool(db, "autologin", &request->autologin);
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
if (request->autologin)
|
||||
{
|
||||
request->users = tf_ssb_db_get_property(ssb, "auth", "users");
|
||||
if (request->users && strcmp(request->users, "[]") == 0)
|
||||
{
|
||||
tf_free((void*)request->users);
|
||||
request->users = NULL;
|
||||
}
|
||||
|
||||
if (!request->users)
|
||||
{
|
||||
JSMallocFunctions funcs = { 0 };
|
||||
tf_get_js_malloc_functions(&funcs);
|
||||
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||
JSContext* context = JS_NewContext(runtime);
|
||||
static const char* k_account_name = "mobile";
|
||||
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||
bool registered = tf_ssb_db_register_account(tf_ssb_get_loop(ssb), db, context, k_account_name, k_account_name);
|
||||
tf_ssb_release_db_writer(ssb, db);
|
||||
if (registered)
|
||||
{
|
||||
_make_administrator_if_first(ssb, context, k_account_name, true);
|
||||
}
|
||||
JS_FreeContext(context);
|
||||
JS_FreeRuntime(runtime);
|
||||
|
||||
request->users = tf_ssb_db_get_property(ssb, "auth", "users");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _httpd_auto_login_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
auto_login_t* work = user_data;
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
const char* session_token = NULL;
|
||||
if (!work->autologin)
|
||||
{
|
||||
const char* k_payload = tf_http_status_text(404);
|
||||
tf_http_respond(work->request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (work->users)
|
||||
{
|
||||
JSValue json = JS_ParseJSON(context, work->users, strlen(work->users), NULL);
|
||||
JSValue user = JS_GetPropertyUint32(context, json, 0);
|
||||
const char* user_string = JS_ToCString(context, user);
|
||||
session_token = _make_session_jwt(context, ssb, user_string);
|
||||
JS_FreeCString(context, user_string);
|
||||
JS_FreeValue(context, user);
|
||||
JS_FreeValue(context, json);
|
||||
}
|
||||
if (session_token)
|
||||
{
|
||||
const char* cookie = _make_set_session_cookie_header(work->request, session_token);
|
||||
tf_free((void*)session_token);
|
||||
const char* headers[] = {
|
||||
"Set-Cookie",
|
||||
cookie,
|
||||
"Location",
|
||||
"/",
|
||||
};
|
||||
tf_http_respond(work->request, 303, headers, tf_countof(headers) / 2, NULL, 0);
|
||||
tf_free((void*)cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* headers[] = {
|
||||
"Location",
|
||||
"/",
|
||||
};
|
||||
tf_http_respond(work->request, 303, headers, tf_countof(headers) / 2, NULL, 0);
|
||||
}
|
||||
}
|
||||
tf_http_request_unref(work->request);
|
||||
tf_free((void*)work->users);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static void _httpd_endpoint_login_auto(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);
|
||||
|
||||
auto_login_t* work = tf_malloc(sizeof(auto_login_t));
|
||||
*work = (auto_login_t) { .request = request };
|
||||
tf_ssb_run_work(ssb, _httpd_auto_login_work, _httpd_auto_login_after_work, work);
|
||||
}
|
||||
|
||||
static void _httpd_endpoint_app_socket(tf_http_request_t* request)
|
||||
{
|
||||
tf_task_t* task = request->user_data;
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue exports = JS_GetPropertyStr(context, global, "exports");
|
||||
JSValue app_socket = JS_GetPropertyStr(context, exports, "app_socket");
|
||||
|
||||
JSValue request_object = JS_NewObject(context);
|
||||
JSValue headers = JS_NewObject(context);
|
||||
for (int i = 0; i < request->headers_count; i++)
|
||||
{
|
||||
JS_SetPropertyStr(context, headers, request->headers[i].name, JS_NewString(context, request->headers[i].value));
|
||||
}
|
||||
JS_SetPropertyStr(context, request_object, "headers", headers);
|
||||
|
||||
JSValue response = _httpd_make_response_object(context, request);
|
||||
tf_http_request_ref(request);
|
||||
|
||||
JSValue args[] = {
|
||||
request_object,
|
||||
response,
|
||||
};
|
||||
|
||||
JSValue result = JS_Call(context, app_socket, JS_NULL, tf_countof(args), args);
|
||||
tf_util_report_error(context, result);
|
||||
JS_FreeValue(context, result);
|
||||
|
||||
for (int i = 0; i < tf_countof(args); i++)
|
||||
{
|
||||
JS_FreeValue(context, args[i]);
|
||||
}
|
||||
|
||||
JS_FreeValue(context, app_socket);
|
||||
JS_FreeValue(context, exports);
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
|
||||
static void _httpd_free_user_data(void* user_data)
|
||||
{
|
||||
tf_free(user_data);
|
||||
}
|
||||
|
||||
static const char* _httpd_read_file(tf_task_t* task, const char* path)
|
||||
{
|
||||
const char* actual = tf_task_get_path_with_root(task, path);
|
||||
const size_t k_max_read = 8 * 1024 * 1024;
|
||||
char* result = NULL;
|
||||
char* buffer = tf_malloc(k_max_read);
|
||||
FILE* file = fopen(actual, "rb");
|
||||
if (file)
|
||||
{
|
||||
size_t size = fread(buffer, 1, k_max_read, file);
|
||||
result = tf_malloc(size + 1);
|
||||
memcpy(result, buffer, size);
|
||||
result[size] = '\0';
|
||||
fclose(file);
|
||||
}
|
||||
tf_free(buffer);
|
||||
tf_free((char*)actual);
|
||||
return result;
|
||||
}
|
||||
|
||||
void tf_httpd_register(JSContext* context)
|
||||
{
|
||||
JS_NewClassID(&_httpd_class_id);
|
||||
@ -2275,15 +2366,48 @@ void tf_httpd_register(JSContext* context)
|
||||
{
|
||||
fprintf(stderr, "Failed to register Request.\n");
|
||||
}
|
||||
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue httpd = JS_NewObjectClass(context, _httpd_class_id);
|
||||
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
uv_loop_t* loop = tf_task_get_loop(task);
|
||||
tf_http_t* http = tf_http_create(loop);
|
||||
tf_http_set_trace(http, tf_task_get_trace(task));
|
||||
JS_SetOpaque(httpd, http);
|
||||
|
||||
int64_t http_port = 0;
|
||||
int64_t https_port = 0;
|
||||
char out_http_port_file[512] = "";
|
||||
bool local_only = false;
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port);
|
||||
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port);
|
||||
tf_ssb_db_get_global_setting_string(db, "out_http_port_file", out_http_port_file, sizeof(out_http_port_file));
|
||||
tf_ssb_db_get_global_setting_bool(db, "http_local_only", &local_only);
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
if (https_port)
|
||||
{
|
||||
http_user_data_t* user_data = tf_http_get_user_data(http);
|
||||
if (!user_data)
|
||||
{
|
||||
user_data = tf_malloc(sizeof(http_user_data_t));
|
||||
memset(user_data, 0, sizeof(http_user_data_t));
|
||||
tf_http_set_user_data(http, user_data, _httpd_free_user_data);
|
||||
}
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
tf_ssb_db_get_global_setting_string(db, "http_redirect", user_data->redirect, sizeof(user_data->redirect));
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
/* Workaround. */
|
||||
if (strcmp(user_data->redirect, "0") == 0)
|
||||
{
|
||||
*user_data->redirect = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
tf_http_add_handler(http, "/", _httpd_endpoint_root, NULL, task);
|
||||
tf_http_add_handler(http, "/codemirror/*", _httpd_endpoint_static, NULL, task);
|
||||
tf_http_add_handler(http, "/lit/*", _httpd_endpoint_static, NULL, task);
|
||||
@ -2308,14 +2432,60 @@ void tf_httpd_register(JSContext* context)
|
||||
tf_http_add_handler(http, "/hitches", _httpd_endpoint_hitches, NULL, task);
|
||||
tf_http_add_handler(http, "/mem", _httpd_endpoint_mem, NULL, task);
|
||||
tf_http_add_handler(http, "/trace", _httpd_endpoint_trace, NULL, task);
|
||||
tf_http_add_handler(http, "/ebt", _httpd_endpoint_ebt, NULL, task);
|
||||
|
||||
tf_http_add_handler(http, "/login/logout", _httpd_endpoint_logout, NULL, task);
|
||||
tf_http_add_handler(http, "/login/auto", _httpd_endpoint_login_auto, NULL, task);
|
||||
tf_http_add_handler(http, "/login", _httpd_endpoint_login, NULL, task);
|
||||
|
||||
JS_SetPropertyStr(context, httpd, "all", JS_NewCFunction(context, _httpd_endpoint_all, "all", 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));
|
||||
tf_http_add_handler(http, "/app/socket", _httpd_endpoint_app_socket, NULL, task);
|
||||
|
||||
JS_SetPropertyStr(context, httpd, "auth_query", JS_NewCFunction(context, _httpd_auth_query, "auth_query", 1));
|
||||
JS_SetPropertyStr(context, global, "httpd", httpd);
|
||||
JS_FreeValue(context, global);
|
||||
|
||||
if (http_port > 0 || *out_http_port_file)
|
||||
{
|
||||
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
|
||||
*listener = (httpd_listener_t) { 0 };
|
||||
int assigned_port = tf_http_listen(http, http_port, local_only, NULL, _httpd_listener_cleanup, listener);
|
||||
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http://127.0.0.1:%d/" RESET ".\n", assigned_port);
|
||||
|
||||
if (*out_http_port_file)
|
||||
{
|
||||
const char* actual_http_port_file = tf_task_get_path_with_root(task, out_http_port_file);
|
||||
FILE* file = fopen(actual_http_port_file, "wb");
|
||||
if (file)
|
||||
{
|
||||
fprintf(file, "%d", assigned_port);
|
||||
fclose(file);
|
||||
tf_printf("Wrote the port file: %s.\n", out_http_port_file);
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("Failed to open %s for write: %s.\n", out_http_port_file, strerror(errno));
|
||||
}
|
||||
tf_free((char*)actual_http_port_file);
|
||||
}
|
||||
|
||||
if (https_port)
|
||||
{
|
||||
const char* k_certificate = "data/httpd/certificate.pem";
|
||||
const char* k_private_key = "data/httpd/privatekey.pem";
|
||||
const char* certificate = _httpd_read_file(task, k_certificate);
|
||||
const char* private_key = _httpd_read_file(task, k_private_key);
|
||||
if (certificate && private_key)
|
||||
{
|
||||
tf_tls_context_t* tls = tf_tls_context_create();
|
||||
tf_tls_context_set_certificate(tls, certificate);
|
||||
tf_tls_context_set_private_key(tls, private_key);
|
||||
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
|
||||
*listener = (httpd_listener_t) { .tls = tls };
|
||||
int assigned_port = tf_http_listen(http, https_port, local_only, tls, _httpd_listener_cleanup, listener);
|
||||
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "https://127.0.0.1:%d/" RESET ".\n", assigned_port);
|
||||
}
|
||||
tf_free((char*)certificate);
|
||||
tf_free((char*)private_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
\mainpage Tilde Friends Source Documentation
|
56
src/ios.m
@ -4,34 +4,84 @@
|
||||
#import <WebKit/WKWebView.h>
|
||||
#import <WebKit/WKWebViewConfiguration.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <libgen.h>
|
||||
#include <string.h>
|
||||
|
||||
void tf_run_thread_start(const char* zip_path);
|
||||
|
||||
@interface ViewController : UIViewController <WKUIDelegate, WKNavigationDelegate>
|
||||
@interface ViewController : UINavigationController <WKUIDelegate, WKNavigationDelegate>
|
||||
@property (strong, nonatomic) WKWebView* web_view;
|
||||
@property bool initial_load_complete;
|
||||
@end
|
||||
|
||||
static void _start_initial_load(WKWebView* web_view)
|
||||
{
|
||||
[web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:12345/"]]];
|
||||
[web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:12345/login/auto"]]];
|
||||
}
|
||||
|
||||
@implementation ViewController : UIViewController
|
||||
@implementation ViewController : UINavigationController
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
[self setToolbarHidden:false animated:false];
|
||||
self.toolbar.items = @[
|
||||
[[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStylePlain target:self action:@selector(goBack)],
|
||||
[[UIBarButtonItem alloc] initWithTitle:@"Forward" style:UIBarButtonItemStylePlain target:self action:@selector(goForward)],
|
||||
[[UIBarButtonItem alloc] initWithTitle:@"Refresh" style:UIBarButtonItemStylePlain target:self action:@selector(reload)]
|
||||
];
|
||||
|
||||
WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
|
||||
self.web_view = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
|
||||
self.web_view.UIDelegate = self;
|
||||
self.web_view.navigationDelegate = self;
|
||||
self.web_view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||
self.web_view.translatesAutoresizingMaskIntoConstraints = false;
|
||||
[self.view addSubview:self.web_view];
|
||||
|
||||
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0]];
|
||||
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:-75]];
|
||||
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view
|
||||
attribute:NSLayoutAttributeLeft
|
||||
multiplier:1.0
|
||||
constant:0]];
|
||||
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view
|
||||
attribute:NSLayoutAttributeRight
|
||||
multiplier:1.0
|
||||
constant:0]];
|
||||
|
||||
_start_initial_load(self.web_view);
|
||||
}
|
||||
|
||||
- (void)goBack
|
||||
{
|
||||
if (self.web_view.canGoBack)
|
||||
{
|
||||
[self.web_view goBack];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)goForward
|
||||
{
|
||||
if (self.web_view.canGoForward)
|
||||
{
|
||||
[self.web_view goForward];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
[self.web_view reload];
|
||||
}
|
||||
|
||||
- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
|
||||
{
|
||||
self.initial_load_complete = true;
|
||||
|
@ -7,6 +7,6 @@
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>EDVXQ27EB5</string>
|
||||
<key>get-task-allow</key>
|
||||
<true/>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -2,34 +2,86 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>tildefriends</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Tilde Friends</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>tildefriends</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.unprompted.tildefriends</string>
|
||||
<key>CFBundleResourceSpecification</key>
|
||||
<string>ResourceRules.plist</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>tildefriends</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.29</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>11</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Tilde Friends</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>tildefriends.png</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>14.0</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict>
|
||||
<key>UIImageName</key>
|
||||
<string>tildefriends.png</string>
|
||||
</dict>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>24D70</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>22C146</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>18.2</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>22C146</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>iphoneos18.2</string>
|
||||
<key>DTXcode</key>
|
||||
<string>1620</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>16C5032a</string>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~iphone</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>AppIcon60x60</string>
|
||||
</array>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
BIN
src/ios/distribution.mobileprovision
Normal file
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/100.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/1024.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/114.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/120.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/144.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/152.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/167.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/180.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/20.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/29.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/40.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/50.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/57.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/58.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/60.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/72.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/76.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/80.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
src/ios/icons/Assets.xcassets/AppIcon.appiconset/87.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
@ -0,0 +1 @@
|
||||
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
|
BIN
src/ios/icons/appstore.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
src/ios/icons/playstore.png
Normal file
After Width: | Height: | Size: 56 KiB |
135
src/main.c
@ -808,8 +808,8 @@ static int _tf_command_create_invite(const char* file, int argc, char* argv[])
|
||||
const char* default_db_path = _get_db_path();
|
||||
const char* db_path = default_db_path;
|
||||
const char* identity = NULL;
|
||||
int use_count = 0;
|
||||
int expires = 0;
|
||||
int use_count = 1;
|
||||
int expires = 3600;
|
||||
bool show_usage = false;
|
||||
const char* host = NULL;
|
||||
int port = 0;
|
||||
@ -868,8 +868,8 @@ static int _tf_command_create_invite(const char* file, int argc, char* argv[])
|
||||
tf_printf(" -i, --identity identity Account from which to get latest sequence number.\n");
|
||||
tf_printf(" -a, --address address Address to which the recipient will connect.\n");
|
||||
tf_printf(" -p, --port port Port to which the recipient will connect.\n");
|
||||
tf_printf(" -u, --use_count count Number of times this invite may be used.\n");
|
||||
tf_printf(" -e, --expires seconds How long this invite is valid in seconds (-1 for indefinitely).\n");
|
||||
tf_printf(" -u, --use_count count Number of times this invite may be used (default: 1).\n");
|
||||
tf_printf(" -e, --expires seconds How long this invite is valid in seconds (-1 for indefinitely, default: 1 hour).\n");
|
||||
tf_printf(" -h, --help Show this usage information.\n");
|
||||
tf_free((void*)default_db_path);
|
||||
return EXIT_FAILURE;
|
||||
@ -1057,7 +1057,7 @@ static int _tf_command_get_profile(const char* file, int argc, char* argv[])
|
||||
sqlite3_close(db);
|
||||
tf_free((void*)profile);
|
||||
tf_free((void*)default_db_path);
|
||||
return profile != NULL;
|
||||
return profile != NULL ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
static int _tf_command_get_contacts(const char* file, int argc, char* argv[])
|
||||
@ -1217,9 +1217,6 @@ typedef struct tf_run_args_t
|
||||
{
|
||||
const char* script;
|
||||
const char* network_key;
|
||||
int ssb_port;
|
||||
int http_port;
|
||||
int https_port;
|
||||
const char* db_path;
|
||||
int count;
|
||||
const char* args;
|
||||
@ -1244,9 +1241,6 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
|
||||
tf_printf("setting zip path to %s\n", args->zip);
|
||||
tf_task_set_zip_path(task, args->zip);
|
||||
tf_task_set_ssb_network_key(task, args->network_key);
|
||||
tf_task_set_ssb_port(task, args->ssb_port ? args->ssb_port + index : 0);
|
||||
tf_task_set_http_port(task, args->http_port ? args->http_port + index : 0);
|
||||
tf_task_set_https_port(task, args->https_port ? args->https_port + index : 0);
|
||||
tf_task_set_args(task, args->args);
|
||||
tf_task_set_one_proc(task, args->one_proc);
|
||||
const char* db_path = args->db_path;
|
||||
@ -1256,23 +1250,93 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
|
||||
snprintf(db_path_buffer, sizeof(db_path_buffer), "%s.%d", args->db_path, index);
|
||||
db_path = db_path_buffer;
|
||||
}
|
||||
|
||||
char* cwd = NULL;
|
||||
if (!args->zip)
|
||||
{
|
||||
size_t cwd_size = 1;
|
||||
uv_cwd((char[1]) { 0 }, &cwd_size);
|
||||
cwd = alloca(cwd_size);
|
||||
if (uv_cwd(cwd, &cwd_size) == 0)
|
||||
{
|
||||
size_t test_path_size = cwd_size + strlen("/core/core.js");
|
||||
char* test_path = alloca(test_path_size);
|
||||
|
||||
uv_loop_t* loop = tf_task_get_loop(task);
|
||||
uv_fs_t req = { 0 };
|
||||
while (true)
|
||||
{
|
||||
snprintf(test_path, test_path_size, "%s/core/core.js", cwd);
|
||||
int r = uv_fs_access(loop, &req, test_path, 0000, NULL);
|
||||
uv_fs_req_cleanup(&req);
|
||||
if (r != UV_ENOENT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
char* slash = strrchr(cwd, '/');
|
||||
if (slash)
|
||||
{
|
||||
*slash = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
tf_printf("Using %s as the working directory.\n", cwd);
|
||||
}
|
||||
if (!*cwd)
|
||||
{
|
||||
cwd = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
tf_task_set_db_path(task, db_path);
|
||||
tf_task_activate(task);
|
||||
tf_ssb_set_verbose(tf_task_get_ssb(task), args->verbose);
|
||||
tf_ssb_start_periodic(tf_task_get_ssb(task));
|
||||
if (args->http_port || args->https_port)
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
int64_t http_port = 0;
|
||||
int64_t https_port = 0;
|
||||
char out_http_port_file[512] = "";
|
||||
tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port);
|
||||
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port);
|
||||
tf_ssb_db_get_global_setting_string(db, "out_http_port_file", out_http_port_file, sizeof(out_http_port_file));
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
if (http_port || https_port || *out_http_port_file)
|
||||
{
|
||||
if (args->zip)
|
||||
{
|
||||
tf_ssb_import_from_zip(tf_task_get_ssb(task), args->zip, "core", "apps");
|
||||
}
|
||||
else if (cwd)
|
||||
{
|
||||
size_t apps_path_size = strlen(cwd) + strlen("/apps") + 1;
|
||||
char* apps_path = alloca(apps_path_size);
|
||||
snprintf(apps_path, apps_path_size, "%s/apps", cwd);
|
||||
tf_ssb_import(tf_task_get_ssb(task), "core", apps_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
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))
|
||||
const char* script = args->script;
|
||||
if (!script && cwd)
|
||||
{
|
||||
size_t script_size = (cwd ? strlen(cwd) : 0) + strlen("/core/core.js") + 1;
|
||||
char* script_buffer = alloca(script_size);
|
||||
snprintf(script_buffer, script_size, "%s/core/core.js", cwd);
|
||||
script = script_buffer;
|
||||
}
|
||||
else if (!script)
|
||||
{
|
||||
script = "core/core.js";
|
||||
}
|
||||
tf_task_set_root_path(task, cwd);
|
||||
if (tf_task_execute(task, script))
|
||||
{
|
||||
tf_task_run(task);
|
||||
result = 0;
|
||||
@ -1356,10 +1420,6 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
|
||||
const char* default_db_path = _get_db_path();
|
||||
tf_run_args_t args = {
|
||||
.count = 1,
|
||||
.script = "core/core.js",
|
||||
.http_port = 12345,
|
||||
.https_port = 12346,
|
||||
.ssb_port = 8008,
|
||||
.db_path = default_db_path,
|
||||
};
|
||||
bool show_usage = false;
|
||||
@ -1376,10 +1436,7 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
|
||||
{
|
||||
static const struct option k_options[] = {
|
||||
{ "script", required_argument, NULL, 's' },
|
||||
{ "ssb-port", required_argument, NULL, 'b' },
|
||||
{ "ssb-network-key", required_argument, NULL, 'k' },
|
||||
{ "http-port", required_argument, NULL, 'p' },
|
||||
{ "https-port", required_argument, NULL, 'q' },
|
||||
{ "db-path", required_argument, NULL, 'd' },
|
||||
{ "count", required_argument, NULL, 'n' },
|
||||
{ "args", required_argument, NULL, 'a' },
|
||||
@ -1388,7 +1445,7 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
|
||||
{ "verbose", no_argument, NULL, 'v' },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
};
|
||||
int c = getopt_long(argc, argv, "s:b:k:p:q:d:n:a:oz:vh", k_options, NULL);
|
||||
int c = getopt_long(argc, argv, "s:k:d:n:a:oz:vh", k_options, NULL);
|
||||
if (c == -1)
|
||||
{
|
||||
break;
|
||||
@ -1407,15 +1464,6 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
|
||||
case 'k':
|
||||
args.network_key = optarg;
|
||||
break;
|
||||
case 'b':
|
||||
args.ssb_port = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
args.http_port = atoi(optarg);
|
||||
break;
|
||||
case 'q':
|
||||
args.https_port = atoi(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
args.db_path = optarg;
|
||||
break;
|
||||
@ -1437,18 +1485,22 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = optind; i < argc; i++)
|
||||
{
|
||||
tf_printf("Unexpected argument: %s\n", argv[i]);
|
||||
show_usage = true;
|
||||
}
|
||||
|
||||
if (show_usage)
|
||||
{
|
||||
tf_printf("\n%s run [options]\n\n", file);
|
||||
tf_printf("options\n");
|
||||
tf_printf(" -s, --script script Script to run (default: core/core.js).\n");
|
||||
tf_printf(" -b, --ssb-port port Port on which to run SSB (default: 8008, 0 disables).\n");
|
||||
tf_printf(" -p, --http-port port Port on which to run Tilde Friends web server (default: 12345).\n");
|
||||
tf_printf(" -q, --https-port port Port on which to run secure Tilde Friends web server (default: 12346).\n");
|
||||
tf_printf(" -d, --db-path path SQLite database path (default: %s).\n", default_db_path);
|
||||
tf_printf(" -k, --ssb-network-key key SSB network key to use.\n");
|
||||
tf_printf(" -n, --count count Number of instances to run.\n");
|
||||
tf_printf(" -a, --args args Arguments of the format key=value,foo=bar,verbose=true.\n");
|
||||
tf_printf(" -a, --args args Arguments of the format key=value,foo=bar,verbose=true (note: these are persisted to the database).\n");
|
||||
tf_util_document_settings(" ");
|
||||
tf_printf(" -o, --one-proc Run everything in one process (unsafely!).\n");
|
||||
tf_printf(" -z, --zip path Zip archive from which to load files.\n");
|
||||
tf_printf(" -v, --verbose Log raw messages.\n");
|
||||
@ -1684,18 +1736,15 @@ static jint _tf_server_main(JNIEnv* env, jobject this_object, jstring files_dir,
|
||||
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);
|
||||
size_t args_length = strlen(out_port_file) + strlen("--args=http_port=0,out_http_port_file=") + 1;
|
||||
char* args_buffer = alloca(args_length);
|
||||
snprintf(args_buffer, args_length, "--args=http_port=0,out_http_port_file=%s", out_port_file);
|
||||
|
||||
const char* args[] = {
|
||||
"run",
|
||||
"-z",
|
||||
apk,
|
||||
"-a",
|
||||
port_file_arg,
|
||||
"-p",
|
||||
"0",
|
||||
args_buffer,
|
||||
};
|
||||
|
||||
tf_task_set_android_service_callbacks(_tf_service_start, _tf_service_stop);
|
||||
@ -1787,10 +1836,6 @@ void tf_run_thread_start(const char* zip_path)
|
||||
tf_run_thread_data_t* data = tf_malloc(sizeof(tf_run_thread_data_t));
|
||||
tf_run_args_t args = {
|
||||
.count = 1,
|
||||
.script = "core/core.js",
|
||||
.http_port = 12345,
|
||||
.https_port = 12346,
|
||||
.ssb_port = 8008,
|
||||
.db_path = "db.sqlite",
|
||||
.one_proc = true,
|
||||
.zip = zip_path,
|
||||
|
@ -22,8 +22,6 @@ static int64_t s_tls_malloc_size;
|
||||
static int64_t s_js_malloc_size;
|
||||
static int64_t s_sqlite_malloc_size;
|
||||
|
||||
extern uint32_t fnv32a(const void* buffer, int length, uint32_t start);
|
||||
|
||||
static size_t _tf_mem_round_up(size_t size)
|
||||
{
|
||||
return (size + 7) & ~7;
|
||||
@ -156,7 +154,7 @@ static void _tf_mem_summarize(void* ptr, size_t size, int frames_count, void* co
|
||||
{
|
||||
summary_t* summary = user_data;
|
||||
tf_mem_allocation_t allocation = {
|
||||
.stack_hash = fnv32a(frames, sizeof(void*) * frames_count, 0),
|
||||
.stack_hash = tf_util_fnv32a(frames, sizeof(void*) * frames_count, 0),
|
||||
.count = 1,
|
||||
.size = size,
|
||||
.frames_count = frames_count,
|
||||
|
103
src/ssb.c
@ -372,7 +372,7 @@ 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_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 bool _tf_ssb_parse_connect_string(const char* in_broadcast, tf_ssb_broadcast_t* out_broadcast);
|
||||
static bool _tf_ssb_parse_connect_string(tf_ssb_t* ssb, 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_update_settings(tf_ssb_t* ssb);
|
||||
static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t size);
|
||||
@ -579,6 +579,11 @@ static void _tf_ssb_connection_box_stream_send(tf_ssb_connection_t* connection,
|
||||
{
|
||||
size_t send_size = size - offset > k_send_max ? k_send_max : size - offset;
|
||||
uint8_t* message_enc = tf_malloc(send_size + 34);
|
||||
if (!message_enc)
|
||||
{
|
||||
tf_ssb_connection_close(connection, "out of memory");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t nonce1[crypto_secretbox_NONCEBYTES];
|
||||
memcpy(nonce1, connection->send_nonce, sizeof(nonce1));
|
||||
@ -746,13 +751,16 @@ static bool _tf_ssb_connection_get_request_callback(
|
||||
*out_name = request->name;
|
||||
}
|
||||
request->last_active = uv_now(connection->ssb->loop);
|
||||
if (connection->flags & k_tf_ssb_connect_flag_one_shot)
|
||||
if (tf_ssb_connection_is_connected(connection) && !tf_ssb_connection_is_closing(connection) && !tf_ssb_is_shutting_down(tf_ssb_connection_get_ssb(connection)))
|
||||
{
|
||||
uv_timer_start(&connection->activity_timer, _tf_ssb_connection_activity_timer, k_activity_timeout_ms, 0);
|
||||
}
|
||||
if (uv_timer_get_due_in(&connection->ssb->request_activity_timer) == 0)
|
||||
{
|
||||
uv_timer_start(&connection->ssb->request_activity_timer, _tf_ssb_request_activity_timer, k_rpc_active_ms, 0);
|
||||
if ((connection->flags & k_tf_ssb_connect_flag_one_shot))
|
||||
{
|
||||
uv_timer_start(&connection->activity_timer, _tf_ssb_connection_activity_timer, k_activity_timeout_ms, 0);
|
||||
}
|
||||
if (uv_timer_get_due_in(&connection->ssb->request_activity_timer) == 0)
|
||||
{
|
||||
uv_timer_start(&connection->ssb->request_activity_timer, _tf_ssb_request_activity_timer, k_rpc_active_ms, 0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -799,13 +807,16 @@ void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t requ
|
||||
connection->requests_count++;
|
||||
connection->ssb->request_count++;
|
||||
}
|
||||
if (connection->flags & k_tf_ssb_connect_flag_one_shot)
|
||||
if (tf_ssb_connection_is_connected(connection) && !tf_ssb_connection_is_closing(connection) && !tf_ssb_is_shutting_down(tf_ssb_connection_get_ssb(connection)))
|
||||
{
|
||||
uv_timer_start(&connection->activity_timer, _tf_ssb_connection_activity_timer, k_activity_timeout_ms, 0);
|
||||
}
|
||||
if (uv_timer_get_due_in(&connection->ssb->request_activity_timer) == 0)
|
||||
{
|
||||
uv_timer_start(&connection->ssb->request_activity_timer, _tf_ssb_request_activity_timer, k_rpc_active_ms, 0);
|
||||
if (connection->flags & k_tf_ssb_connect_flag_one_shot)
|
||||
{
|
||||
uv_timer_start(&connection->activity_timer, _tf_ssb_connection_activity_timer, k_activity_timeout_ms, 0);
|
||||
}
|
||||
if (uv_timer_get_due_in(&connection->ssb->request_activity_timer) == 0)
|
||||
{
|
||||
uv_timer_start(&connection->ssb->request_activity_timer, _tf_ssb_request_activity_timer, k_rpc_active_ms, 0);
|
||||
}
|
||||
}
|
||||
_tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_update, connection);
|
||||
connection->last_notified_active = now_ms;
|
||||
@ -1404,7 +1415,7 @@ tf_ssb_connection_t* tf_ssb_connection_get_tunnel(tf_ssb_connection_t* connectio
|
||||
return connection ? connection->tunnel_connection : NULL;
|
||||
}
|
||||
|
||||
static bool _tf_ssb_is_already_connected(tf_ssb_t* ssb, uint8_t* id, tf_ssb_connection_t* ignore_connection)
|
||||
static tf_ssb_connection_t* _tf_ssb_is_already_connected(tf_ssb_t* ssb, uint8_t* id, tf_ssb_connection_t* ignore_connection)
|
||||
{
|
||||
for (tf_ssb_connection_t* connection = ssb->connections; connection; connection = connection->next)
|
||||
{
|
||||
@ -1412,15 +1423,15 @@ static bool _tf_ssb_is_already_connected(tf_ssb_t* ssb, uint8_t* id, tf_ssb_conn
|
||||
{
|
||||
if (memcmp(connection->serverpub, id, k_id_bin_len) == 0)
|
||||
{
|
||||
return true;
|
||||
return connection;
|
||||
}
|
||||
else if (memcmp(ssb->pub, id, k_id_bin_len) == 0)
|
||||
{
|
||||
return true;
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _tf_ssb_connection_is_account_a_stranger_work(tf_ssb_connection_t* connection, void* user_data)
|
||||
@ -1436,19 +1447,26 @@ static void _tf_ssb_connection_is_account_a_stranger_work(tf_ssb_connection_t* c
|
||||
{
|
||||
int64_t replication_hops = 2;
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
tf_ssb_db_get_global_setting_int64(db, "replication_hops", &replication_hops);
|
||||
connection->is_stranger = !tf_ssb_db_has_invite(db, id);
|
||||
if (connection->is_stranger)
|
||||
{
|
||||
tf_ssb_db_get_global_setting_int64(db, "replication_hops", &replication_hops);
|
||||
}
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
const char** identities = tf_ssb_db_get_all_visible_identities(ssb, replication_hops);
|
||||
for (int i = 0; identities[i]; i++)
|
||||
if (connection->is_stranger)
|
||||
{
|
||||
if (strcmp(id, identities[i]) == 0)
|
||||
const char** identities = tf_ssb_db_get_all_visible_identities(ssb, replication_hops);
|
||||
for (int i = 0; identities[i]; i++)
|
||||
{
|
||||
connection->is_stranger = false;
|
||||
break;
|
||||
if (strcmp(id, identities[i]) == 0)
|
||||
{
|
||||
connection->is_stranger = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
tf_free((void*)identities);
|
||||
}
|
||||
tf_free((void*)identities);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1538,14 +1556,14 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne
|
||||
}
|
||||
uint8_t* detached_signature_A = m;
|
||||
|
||||
if (_tf_ssb_is_already_connected(connection->ssb, m + 64, connection))
|
||||
tf_ssb_connection_t* other_connection = NULL;
|
||||
while ((other_connection = _tf_ssb_is_already_connected(connection->ssb, m + 64, connection)))
|
||||
{
|
||||
char id_base64[k_id_base64_len] = { 0 };
|
||||
tf_ssb_id_bin_to_str(id_base64, sizeof(id_base64), m + 64);
|
||||
char reason[256];
|
||||
snprintf(reason, sizeof(reason), "already connected: %s\n", id_base64);
|
||||
tf_ssb_connection_close(connection, reason);
|
||||
return;
|
||||
snprintf(reason, sizeof(reason), "Replacing connection: %s\n", id_base64);
|
||||
tf_ssb_connection_close(other_connection, reason);
|
||||
}
|
||||
|
||||
memcpy(connection->serverpub, m + 64, sizeof(connection->serverpub));
|
||||
@ -1712,7 +1730,11 @@ static void _tf_ssb_connection_rpc_recv(tf_ssb_connection_t* connection, uint8_t
|
||||
bool close_connection = false;
|
||||
if (size == 0)
|
||||
{
|
||||
tf_ssb_connection_close(connection, "rpc recv zero");
|
||||
char buffer[256];
|
||||
const char* request_name = "<unknown>";
|
||||
_tf_ssb_connection_get_request_callback(connection, -request_number, NULL, NULL, &request_name);
|
||||
snprintf(buffer, sizeof(buffer), "rpc recv zero (req=%d, name=%s)", request_number, request_name);
|
||||
tf_ssb_connection_close(connection, buffer);
|
||||
return;
|
||||
}
|
||||
else if (flags & k_ssb_rpc_flag_json)
|
||||
@ -1986,7 +2008,7 @@ static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const ch
|
||||
if (!connection->is_closing)
|
||||
{
|
||||
connection->is_closing = true;
|
||||
uv_timer_start(&connection->linger_timer, _tf_ssb_connection_linger_timer, 5000, 0);
|
||||
uv_timer_start(&connection->linger_timer, _tf_ssb_connection_linger_timer, ssb->shutting_down ? 0 : 5000, 0);
|
||||
_tf_ssb_notify_connections_changed(ssb, k_tf_ssb_change_update, connection);
|
||||
}
|
||||
if (connection->connect_callback)
|
||||
@ -3322,7 +3344,7 @@ static void _tf_ssb_update_seeds_after_work(tf_ssb_t* ssb, int status, void* use
|
||||
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_connect_string(seeds->seeds[i], &broadcast))
|
||||
if (_tf_ssb_parse_connect_string(ssb, seeds->seeds[i], &broadcast))
|
||||
{
|
||||
_tf_ssb_add_broadcast(ssb, &broadcast, k_seed_expire_seconds);
|
||||
}
|
||||
@ -3431,7 +3453,7 @@ bool tf_ssb_whoami(tf_ssb_t* ssb, char* out_id, size_t out_id_size)
|
||||
return tf_ssb_id_bin_to_str(out_id, out_id_size, ssb->pub);
|
||||
}
|
||||
|
||||
static bool _tf_ssb_parse_connect_string(const char* in_broadcast, tf_ssb_broadcast_t* out_broadcast)
|
||||
static bool _tf_ssb_parse_connect_string(tf_ssb_t* ssb, const char* in_broadcast, tf_ssb_broadcast_t* out_broadcast)
|
||||
{
|
||||
char public_key_str[54] = { 0 };
|
||||
char secret_key_str[45] = { 0 };
|
||||
@ -3450,7 +3472,7 @@ static bool _tf_ssb_parse_connect_string(const char* in_broadcast, tf_ssb_broadc
|
||||
return tf_ssb_id_str_to_bin(out_broadcast->pub, public_key_str) &&
|
||||
tf_base64_decode(secret_key_str, strlen(secret_key_str), out_broadcast->invite, sizeof(out_broadcast->invite));
|
||||
}
|
||||
else if (strncmp(in_broadcast, "ws:", 3) == 0)
|
||||
else if (ssb->verbose && strncmp(in_broadcast, "ws:", 3) == 0)
|
||||
{
|
||||
tf_printf("Unsupported broadcast: %s\n", in_broadcast);
|
||||
}
|
||||
@ -3460,7 +3482,7 @@ static bool _tf_ssb_parse_connect_string(const char* in_broadcast, tf_ssb_broadc
|
||||
void tf_ssb_connect_str(tf_ssb_t* ssb, const char* address, int connect_flags, tf_ssb_connect_callback_t* callback, void* user_data)
|
||||
{
|
||||
tf_ssb_broadcast_t broadcast = { 0 };
|
||||
if (_tf_ssb_parse_connect_string(address, &broadcast))
|
||||
if (_tf_ssb_parse_connect_string(ssb, address, &broadcast))
|
||||
{
|
||||
if (memcmp(broadcast.invite, (uint8_t[crypto_sign_ed25519_SEEDBYTES]) { 0 }, crypto_sign_ed25519_SEEDBYTES) == 0)
|
||||
{
|
||||
@ -3556,7 +3578,7 @@ static void _tf_ssb_add_broadcast(tf_ssb_t* ssb, const tf_ssb_broadcast_t* broad
|
||||
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_connect_string(connection, &broadcast))
|
||||
if (_tf_ssb_parse_connect_string(ssb, connection, &broadcast))
|
||||
{
|
||||
_tf_ssb_add_broadcast(ssb, &broadcast, expires_seconds);
|
||||
}
|
||||
@ -3579,7 +3601,7 @@ static void _tf_ssb_on_broadcast_listener_recv(uv_udp_t* handle, ssize_t nread,
|
||||
while (entry)
|
||||
{
|
||||
tf_ssb_broadcast_t broadcast = { .origin = k_tf_ssb_broadcast_origin_discovery };
|
||||
if (_tf_ssb_parse_connect_string(entry, &broadcast))
|
||||
if (_tf_ssb_parse_connect_string(ssb, entry, &broadcast))
|
||||
{
|
||||
_tf_ssb_add_broadcast(ssb, &broadcast, k_udp_discovery_expires_seconds);
|
||||
}
|
||||
@ -3860,8 +3882,7 @@ JSValue tf_ssb_connection_get_object(tf_ssb_connection_t* connection)
|
||||
return connection ? connection->object : JS_UNDEFINED;
|
||||
}
|
||||
|
||||
void tf_ssb_add_message_added_callback(
|
||||
tf_ssb_t* ssb, void (*callback)(tf_ssb_t* ssb, const char* id, void* user_data), void (*cleanup)(tf_ssb_t* ssb, void* user_data), void* user_data)
|
||||
void tf_ssb_add_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_callback_t* callback, void (*cleanup)(tf_ssb_t* ssb, void* user_data), void* user_data)
|
||||
{
|
||||
tf_ssb_message_added_callback_node_t* node = tf_malloc(sizeof(tf_ssb_message_added_callback_node_t));
|
||||
*node = (tf_ssb_message_added_callback_node_t) {
|
||||
@ -3902,7 +3923,7 @@ void tf_ssb_notify_blob_stored(tf_ssb_t* ssb, const char* id)
|
||||
ssb->blobs_stored++;
|
||||
}
|
||||
|
||||
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* id, JSValue message_keys)
|
||||
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int64_t sequence, const char* id, JSValue message_keys)
|
||||
{
|
||||
tf_ssb_message_added_callback_node_t* next = NULL;
|
||||
ssb->messages_stored++;
|
||||
@ -3911,7 +3932,7 @@ void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* id, JSValue message_
|
||||
next = node->next;
|
||||
tf_trace_begin(ssb->trace, "message added callback");
|
||||
PRE_CALLBACK(ssb, node->callback);
|
||||
node->callback(ssb, id, node->user_data);
|
||||
node->callback(ssb, author, sequence, id, node->user_data);
|
||||
POST_CALLBACK(ssb, node->callback);
|
||||
tf_trace_end(ssb->trace);
|
||||
}
|
||||
@ -4559,7 +4580,7 @@ void tf_ssb_connection_adjust_read_backpressure(tf_ssb_connection_t* connection,
|
||||
void tf_ssb_connection_adjust_write_count(tf_ssb_connection_t* connection, int delta)
|
||||
{
|
||||
connection->active_write_count += delta;
|
||||
if (!connection->is_closing)
|
||||
if (!connection->is_closing && connection->active_write_count == 0)
|
||||
{
|
||||
uv_async_send(&connection->scheduled_async);
|
||||
}
|
||||
|
501
src/ssb.db.c
@ -23,6 +23,17 @@ typedef struct _message_store_t message_store_t;
|
||||
|
||||
static void _tf_ssb_db_store_message_after_work(tf_ssb_t* ssb, int status, void* user_data);
|
||||
|
||||
static int _tf_ssb_db_try_exec(sqlite3* db, const char* statement)
|
||||
{
|
||||
char* error = NULL;
|
||||
int result = sqlite3_exec(db, statement, NULL, NULL, &error);
|
||||
if (result != SQLITE_OK)
|
||||
{
|
||||
tf_printf("Error running '%s': %s.\n", statement, error ? error : sqlite3_errmsg(db));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void _tf_ssb_db_exec(sqlite3* db, const char* statement)
|
||||
{
|
||||
char* error = NULL;
|
||||
@ -60,11 +71,17 @@ static bool _tf_ssb_db_has_rows(sqlite3* db, const char* query)
|
||||
return found;
|
||||
}
|
||||
|
||||
static int _tf_ssb_db_busy_handler(void* user_data, int count)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void _tf_ssb_db_init_internal(sqlite3* db)
|
||||
{
|
||||
sqlite3_extended_result_codes(db, 1);
|
||||
_tf_ssb_db_exec(db, "PRAGMA journal_mode = WAL");
|
||||
_tf_ssb_db_exec(db, "PRAGMA synchronous = NORMAL");
|
||||
sqlite3_busy_handler(db, _tf_ssb_db_busy_handler, db);
|
||||
}
|
||||
|
||||
void tf_ssb_db_init_reader(sqlite3* db)
|
||||
@ -108,21 +125,31 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
|
||||
" flags INTEGER,"
|
||||
" UNIQUE(author, sequence)"
|
||||
")");
|
||||
if (_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_stats')"))
|
||||
{
|
||||
if (_tf_ssb_db_has_rows(db, "SELECT 1 FROM messages_stats WHERE max_sequence IS NULL LIMIT 1"))
|
||||
{
|
||||
tf_printf("Rebuilding messages_stats.\n");
|
||||
_tf_ssb_db_exec(db, "DROP TABLE messages_stats");
|
||||
}
|
||||
}
|
||||
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_stats')"))
|
||||
{
|
||||
_tf_ssb_db_exec(db, "BEGIN TRANSACTION");
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS messages_stats ("
|
||||
" author TEXT PRIMARY KEY,"
|
||||
" max_sequence INTEGER,"
|
||||
" max_timestamp READ"
|
||||
" max_sequence INTEGER NOT NULL,"
|
||||
" max_timestamp REAL NOT NULL"
|
||||
")");
|
||||
_tf_ssb_db_exec(
|
||||
db, "INSERT OR REPLACE INTO messages_stats (author, max_sequence, max_timestamp) SELECT author, MAX(sequence), MAX(timestamp) FROM messages GROUP BY author");
|
||||
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
|
||||
}
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TRIGGER IF NOT EXISTS messages_ai_stats AFTER INSERT ON messages BEGIN INSERT INTO messages_stats(author, max_sequence, max_timestamp) VALUES (new.author, "
|
||||
"new.sequence, new.timestamp) ON CONFLICT DO UPDATE SET max_sequence = MAX(max_sequence, excluded.max_sequence), max_timestamp = MAX(max_timestamp, "
|
||||
"excluded.max_timestamp); END");
|
||||
"new.sequence, new.timestamp) ON CONFLICT DO UPDATE SET max_sequence = MAX(max_sequence, new.sequence), max_timestamp = MAX(max_timestamp, "
|
||||
"new.timestamp); END");
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TRIGGER IF NOT EXISTS messages_ad_stats AFTER DELETE ON messages BEGIN UPDATE messages_stats SET max_sequence = (SELECT MAX(messages.sequence) FROM messages WHERE "
|
||||
"messages.author = old.author), max_timestamp = (SELECT MAX(messages.timestamp) FROM messages WHERE messages.author = old.author); END");
|
||||
@ -228,6 +255,7 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
|
||||
|
||||
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_refs')"))
|
||||
{
|
||||
_tf_ssb_db_exec(db, "BEGIN TRANSACTION");
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS messages_refs ("
|
||||
" message TEXT, "
|
||||
@ -242,6 +270,7 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
|
||||
"j.value LIKE '%%%.sha256' OR "
|
||||
"j.value LIKE '@%.ed25519' "
|
||||
"ON CONFLICT DO NOTHING");
|
||||
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
|
||||
tf_printf("Done.\n");
|
||||
}
|
||||
|
||||
@ -259,24 +288,94 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
|
||||
|
||||
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_refs_message_idx ON messages_refs (message)");
|
||||
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_refs_ref_idx ON messages_refs (ref)");
|
||||
|
||||
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('blobs_refs')"))
|
||||
{
|
||||
_tf_ssb_db_exec(db, "BEGIN TRANSACTION");
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TABLE IF NOT EXISTS blobs_refs ("
|
||||
" blob TEXT, "
|
||||
" ref TEXT, "
|
||||
" UNIQUE(blob, ref)"
|
||||
")");
|
||||
tf_printf("Populating blobs_refs...\n");
|
||||
_tf_ssb_db_exec(db,
|
||||
"INSERT INTO blobs_refs(blob, ref) "
|
||||
"SELECT blobs.id, j.value FROM blobs, json_tree(blobs.content) as j WHERE "
|
||||
"json_valid(blobs.content) AND j.value LIKE '&%.sha256' "
|
||||
"ON CONFLICT DO NOTHING");
|
||||
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
|
||||
tf_printf("Done.\n");
|
||||
}
|
||||
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS blobs_ai_refs");
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TRIGGER IF NOT EXISTS blobs_ai_refs AFTER INSERT ON blobs BEGIN "
|
||||
"INSERT INTO blobs_refs(blob, ref) "
|
||||
"SELECT DISTINCT new.id, j.value FROM json_tree(new.content) as j WHERE "
|
||||
"json_valid(new.content) AND j.value LIKE '&%.sha256' "
|
||||
"ON CONFLICT DO NOTHING; END");
|
||||
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS blobs_ad_refs");
|
||||
_tf_ssb_db_exec(db, "CREATE TRIGGER IF NOT EXISTS blobs_ad_refs AFTER DELETE ON blobs BEGIN DELETE FROM blobs_refs WHERE blobs_refs.blob = old.id; END");
|
||||
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blobs_refs_blob_idx ON blobs_refs (blob)");
|
||||
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blobs_refs_ref_idx ON blobs_refs (ref)");
|
||||
|
||||
_tf_ssb_db_exec(db, "DROP VIEW IF EXISTS blob_wants_view");
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE VIEW IF NOT EXISTS blob_wants_view (id, timestamp) AS "
|
||||
" WITH wanted AS ( "
|
||||
" SELECT messages_refs.ref AS id, messages.timestamp AS timestamp "
|
||||
"CREATE VIEW IF NOT EXISTS blob_wants_view (source, id, timestamp) AS "
|
||||
" WITH RECURSIVE "
|
||||
" wanted1 AS ( "
|
||||
" SELECT messages_refs.message AS source, messages_refs.ref AS id, messages.timestamp AS timestamp "
|
||||
" FROM messages_refs "
|
||||
" JOIN messages ON messages.id = messages_refs.message "
|
||||
" UNION "
|
||||
" SELECT messages_refs.ref AS id, unixepoch() * 1000 AS timestamp "
|
||||
" SELECT messages_refs.message AS source, messages_refs.ref AS id, unixepoch() * 1000 AS timestamp "
|
||||
" FROM messages_refs "
|
||||
" JOIN messages ON messages.id = messages_refs.message "
|
||||
" WHERE messages.content ->> 'type' = 'about' "
|
||||
" ), "
|
||||
" wanted(source, id, timestamp) AS ( "
|
||||
" SELECT wanted1.source AS source, wanted1.id AS id, wanted1.timestamp AS timestamp FROM wanted1 "
|
||||
" UNION "
|
||||
" SELECT wanted.source AS source, br.ref AS id, wanted.timestamp AS timestamp FROM wanted JOIN blobs_refs br ON br.blob = wanted.id "
|
||||
" ) "
|
||||
" SELECT wanted.id, wanted.timestamp FROM wanted "
|
||||
" SELECT wanted.source, wanted.id, wanted.timestamp FROM wanted "
|
||||
" LEFT OUTER JOIN blobs ON wanted.id = blobs.id "
|
||||
" WHERE blobs.id IS NULL "
|
||||
" AND LENGTH(wanted.id) = 52 "
|
||||
" AND wanted.id LIKE '&%.sha256'");
|
||||
if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('blob_wants_cache')"))
|
||||
{
|
||||
tf_printf("Populating blob_wants_cache...\n");
|
||||
_tf_ssb_db_exec(db, "BEGIN TRANSACTION");
|
||||
_tf_ssb_db_exec(db, "CREATE TABLE IF NOT EXISTS blob_wants_cache (source TEXT, id TEXT, timestamp REAL, UNIQUE(source, id))");
|
||||
_tf_ssb_db_exec(
|
||||
db, "INSERT INTO blob_wants_cache SELECT * FROM blob_wants_view WHERE true ON CONFLICT(source, id) DO UPDATE SET timestamp = MAX(timestamp, excluded.timestamp)");
|
||||
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
|
||||
tf_printf("Done.\n");
|
||||
}
|
||||
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blob_wants_cache_id_idx ON blob_wants_cache (id)");
|
||||
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blob_wants_cache_timestamp_id_idx ON blob_wants_cache (timestamp, id)");
|
||||
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TRIGGER IF NOT EXISTS messages_ai_blob_wants_cache AFTER INSERT ON messages_refs BEGIN "
|
||||
"INSERT INTO blob_wants_cache (source, id, timestamp) "
|
||||
"SELECT messages.id, new.ref, messages.timestamp FROM messages WHERE messages.id = new.message AND "
|
||||
"LENGTH(new.ref) = 52 AND new.ref LIKE '&%.sha256' "
|
||||
"ON CONFLICT (source, id) DO NOTHING; END");
|
||||
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS blobs_refs_ai_blob_wants_cache");
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TRIGGER IF NOT EXISTS blobs_refs_ai_blob_wants_cache AFTER INSERT ON blobs_refs BEGIN "
|
||||
"INSERT INTO blob_wants_cache (source, id, timestamp) "
|
||||
"SELECT messages.id, new.ref, messages.timestamp FROM messages "
|
||||
"JOIN blob_wants_cache bwc ON bwc.source = messages.id AND bwc.id = new.blob "
|
||||
"ON CONFLICT (source, id) DO NOTHING; END");
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TRIGGER IF NOT EXISTS messages_ad_blob_wants_cache AFTER DELETE ON messages BEGIN "
|
||||
"DELETE FROM blob_wants_cache WHERE blob_wants_cache.source = old.id; END");
|
||||
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS blobs_ai_blob_wants_cache");
|
||||
_tf_ssb_db_exec(db,
|
||||
"CREATE TRIGGER IF NOT EXISTS blobs_ai_blob_wants_cache AFTER INSERT ON blobs BEGIN "
|
||||
"DELETE FROM blob_wants_cache WHERE blob_wants_cache.id = new.id; END");
|
||||
|
||||
bool need_add_flags = true;
|
||||
bool need_convert_timestamp_to_real = false;
|
||||
@ -344,10 +443,9 @@ static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author,
|
||||
return exists;
|
||||
}
|
||||
|
||||
static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const char* previous, const char* author, int64_t sequence, double timestamp, const char* content,
|
||||
static int64_t _tf_ssb_db_store_message_raw(sqlite3* db, const char* id, const char* previous, const char* author, int64_t sequence, double timestamp, const char* content,
|
||||
size_t content_len, const char* signature, int flags)
|
||||
{
|
||||
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||
int64_t last_row_id = -1;
|
||||
bool id_mismatch = false;
|
||||
|
||||
@ -396,7 +494,6 @@ static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const
|
||||
*/
|
||||
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);
|
||||
return last_row_id;
|
||||
}
|
||||
|
||||
@ -469,30 +566,47 @@ typedef struct _message_store_t
|
||||
static void _tf_ssb_db_store_message_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
message_store_t* store = user_data;
|
||||
int64_t last_row_id = _tf_ssb_db_store_message_raw(
|
||||
ssb, store->id, *store->previous ? store->previous : NULL, store->author, store->sequence, store->timestamp, store->content, store->length, store->signature, store->flags);
|
||||
if (last_row_id != -1)
|
||||
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||
bool in_transaction = _tf_ssb_db_try_exec(db, "BEGIN TRANSACTION") == SQLITE_OK;
|
||||
|
||||
while (store)
|
||||
{
|
||||
store->out_stored = true;
|
||||
store->out_blob_wants = _tf_ssb_db_get_message_blob_wants(ssb, last_row_id);
|
||||
int64_t last_row_id = _tf_ssb_db_store_message_raw(db, store->id, *store->previous ? store->previous : NULL, store->author, store->sequence, store->timestamp,
|
||||
store->content, store->length, store->signature, store->flags);
|
||||
if (last_row_id != -1)
|
||||
{
|
||||
store->out_stored = true;
|
||||
store->out_blob_wants = _tf_ssb_db_get_message_blob_wants(ssb, last_row_id);
|
||||
}
|
||||
store = store->next;
|
||||
}
|
||||
|
||||
if (in_transaction)
|
||||
{
|
||||
if (_tf_ssb_db_try_exec(db, "COMMIT TRANSACTION") != SQLITE_OK)
|
||||
{
|
||||
store = user_data;
|
||||
while (store)
|
||||
{
|
||||
store->out_stored = false;
|
||||
store = store->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
tf_ssb_release_db_writer(ssb, db);
|
||||
}
|
||||
|
||||
static void _wake_up_queue(tf_ssb_t* ssb, tf_ssb_store_queue_t* queue)
|
||||
{
|
||||
if (!queue->running)
|
||||
{
|
||||
message_store_t* next = queue->head;
|
||||
if (next)
|
||||
message_store_t* stores = queue->head;
|
||||
queue->head = NULL;
|
||||
queue->tail = NULL;
|
||||
if (stores)
|
||||
{
|
||||
queue->head = next->next;
|
||||
if (queue->tail == next)
|
||||
{
|
||||
queue->tail = NULL;
|
||||
}
|
||||
next->next = NULL;
|
||||
queue->running = true;
|
||||
tf_ssb_run_work(ssb, _tf_ssb_db_store_message_work, _tf_ssb_db_store_message_after_work, next);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_db_store_message_work, _tf_ssb_db_store_message_after_work, stores);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -501,43 +615,63 @@ static void _tf_ssb_db_store_message_after_work(tf_ssb_t* ssb, int status, void*
|
||||
{
|
||||
message_store_t* store = user_data;
|
||||
tf_trace_t* trace = tf_ssb_get_trace(ssb);
|
||||
if (store->out_stored)
|
||||
|
||||
message_store_t* last_stored = NULL;
|
||||
|
||||
while (store)
|
||||
{
|
||||
if (store->out_stored)
|
||||
{
|
||||
last_stored = store;
|
||||
}
|
||||
if (store->out_blob_wants)
|
||||
{
|
||||
tf_trace_begin(trace, "notify_blob_wants_added");
|
||||
for (char* p = store->out_blob_wants; *p; p = p + strlen(p))
|
||||
{
|
||||
tf_ssb_notify_blob_want_added(ssb, p);
|
||||
}
|
||||
tf_free(store->out_blob_wants);
|
||||
tf_trace_end(trace);
|
||||
}
|
||||
|
||||
if (store->callback)
|
||||
{
|
||||
store->callback(store->id, store->out_stored, store->user_data);
|
||||
}
|
||||
store = store->next;
|
||||
}
|
||||
|
||||
if (last_stored)
|
||||
{
|
||||
tf_trace_begin(trace, "notify_message_added");
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue formatted =
|
||||
tf_ssb_format_message(context, store->previous, store->author, store->sequence, store->timestamp, "sha256", store->content, store->signature, store->flags);
|
||||
JSValue formatted = tf_ssb_format_message(context, last_stored->previous, last_stored->author, last_stored->sequence, last_stored->timestamp, "sha256",
|
||||
last_stored->content, last_stored->signature, last_stored->flags);
|
||||
JSValue message = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, message, "key", JS_NewString(context, store->id));
|
||||
JS_SetPropertyStr(context, message, "key", JS_NewString(context, last_stored->id));
|
||||
JS_SetPropertyStr(context, message, "value", formatted);
|
||||
char timestamp_string[256];
|
||||
snprintf(timestamp_string, sizeof(timestamp_string), "%f", store->timestamp);
|
||||
snprintf(timestamp_string, sizeof(timestamp_string), "%f", last_stored->timestamp);
|
||||
JS_SetPropertyStr(context, message, "timestamp", JS_NewString(context, timestamp_string));
|
||||
tf_ssb_notify_message_added(ssb, store->id, message);
|
||||
tf_ssb_notify_message_added(ssb, last_stored->author, last_stored->sequence, last_stored->id, message);
|
||||
JS_FreeValue(context, message);
|
||||
tf_trace_end(trace);
|
||||
}
|
||||
if (store->out_blob_wants)
|
||||
{
|
||||
tf_trace_begin(trace, "notify_blob_wants_added");
|
||||
for (char* p = store->out_blob_wants; *p; p = p + strlen(p))
|
||||
{
|
||||
tf_ssb_notify_blob_want_added(ssb, p);
|
||||
}
|
||||
tf_free(store->out_blob_wants);
|
||||
tf_trace_end(trace);
|
||||
}
|
||||
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
if (store->callback)
|
||||
store = user_data;
|
||||
while (store)
|
||||
{
|
||||
store->callback(store->id, store->out_stored, store->user_data);
|
||||
JS_FreeCString(context, store->content);
|
||||
message_store_t* last = store;
|
||||
store = store->next;
|
||||
tf_free(last);
|
||||
}
|
||||
JS_FreeCString(context, store->content);
|
||||
|
||||
tf_ssb_store_queue_t* queue = tf_ssb_get_store_queue(ssb);
|
||||
queue->running = false;
|
||||
_wake_up_queue(ssb, queue);
|
||||
tf_free(store);
|
||||
}
|
||||
|
||||
void tf_ssb_db_store_message(
|
||||
@ -880,27 +1014,52 @@ bool tf_ssb_db_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, i
|
||||
bool found = false;
|
||||
sqlite3_stmt* statement;
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
const char* query = "SELECT id, sequence FROM messages WHERE author = ?1 AND sequence = (SELECT MAX(sequence) FROM messages WHERE author = ?1)";
|
||||
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
|
||||
|
||||
if (out_message_id)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
||||
const char* query = "SELECT id, sequence FROM messages WHERE author = ?1 ORDER BY sequence DESC LIMIT 1";
|
||||
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
if (out_sequence)
|
||||
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
||||
{
|
||||
*out_sequence = sqlite3_column_int64(statement, 1);
|
||||
if (out_sequence)
|
||||
{
|
||||
*out_sequence = sqlite3_column_int64(statement, 1);
|
||||
}
|
||||
if (out_message_id)
|
||||
{
|
||||
strncpy(out_message_id, (const char*)sqlite3_column_text(statement, 0), out_message_id_size - 1);
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
if (out_message_id)
|
||||
{
|
||||
strncpy(out_message_id, (const char*)sqlite3_column_text(statement, 0), out_message_id_size - 1);
|
||||
}
|
||||
found = true;
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
|
||||
const char* query = "SELECT max_sequence FROM messages_stats WHERE author = ?1";
|
||||
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
||||
{
|
||||
if (out_sequence)
|
||||
{
|
||||
*out_sequence = sqlite3_column_int64(statement, 0);
|
||||
}
|
||||
found = true;
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
|
||||
}
|
||||
}
|
||||
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
return found;
|
||||
}
|
||||
@ -1004,8 +1163,8 @@ int tf_ssb_sqlite_authorizer(void* user_data, int action_code, const char* arg0,
|
||||
result = SQLITE_OK;
|
||||
break;
|
||||
case SQLITE_READ:
|
||||
result = (strcmp(arg0, "blob_wants_view") == 0 || strcmp(arg0, "json_each") == 0 || strcmp(arg0, "json_tree") == 0 || strcmp(arg0, "messages") == 0 ||
|
||||
strcmp(arg0, "messages_stats") == 0 || strcmp(arg0, "messages_fts") == 0 || strcmp(arg0, "messages_fts_idx") == 0 ||
|
||||
result = (strcmp(arg0, "blob_wants_cache") == 0 || strcmp(arg0, "blob_wants_view") == 0 || strcmp(arg0, "json_each") == 0 || strcmp(arg0, "json_tree") == 0 ||
|
||||
strcmp(arg0, "messages") == 0 || strcmp(arg0, "messages_stats") == 0 || strcmp(arg0, "messages_fts") == 0 || strcmp(arg0, "messages_fts_idx") == 0 ||
|
||||
strcmp(arg0, "messages_fts_config") == 0 || strcmp(arg0, "messages_refs") == 0 || strcmp(arg0, "messages_refs_message_idx") == 0 ||
|
||||
strcmp(arg0, "messages_refs_ref_idx") == 0 || strcmp(arg0, "sqlite_master") == 0 || false)
|
||||
? SQLITE_OK
|
||||
@ -1277,7 +1436,7 @@ static int _following_compare(const void* a, const void* b)
|
||||
|
||||
static bool _has_following_entry(const char* id, following_t** list, int count)
|
||||
{
|
||||
return count ? bsearch(id, list, count, sizeof(following_t*), _following_compare) != 0 : false;
|
||||
return count ? bsearch(id, list, count, sizeof(following_t*), _following_compare) != NULL : false;
|
||||
}
|
||||
|
||||
static bool _add_following_entry(following_t*** list, int* count, following_t* add)
|
||||
@ -1367,9 +1526,9 @@ static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, foll
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
sqlite3_stmt* statement = NULL;
|
||||
if (sqlite3_prepare(db,
|
||||
"SELECT json_extract(content, '$.contact') AS contact, json_extract(content, '$.following'), json_extract(content, '$.blocking') "
|
||||
"SELECT content ->> '$.contact' AS contact, content ->> '$.following', content ->> '$.blocking' "
|
||||
"FROM messages "
|
||||
"WHERE contact IS NOT NULL AND author = ? AND json_extract(content, '$.type') = 'contact' "
|
||||
"WHERE contact IS NOT NULL AND author = ? AND content ->> '$.type' = 'contact' "
|
||||
"ORDER BY sequence",
|
||||
-1, &statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
@ -1432,21 +1591,22 @@ static void _populate_follows_and_blocks(tf_ssb_t* ssb, following_t* entry, foll
|
||||
static void _get_following(
|
||||
tf_ssb_t* ssb, following_t* entry, following_t*** following, int* following_count, int depth, int max_depth, block_node_t* active_blocks, bool include_blocks)
|
||||
{
|
||||
int old_depth = entry->depth;
|
||||
entry->depth = tf_min(depth, entry->depth);
|
||||
if (depth < max_depth && !entry->populated && !_is_blocked_by_active_blocks(entry->id, active_blocks))
|
||||
if (depth < max_depth && depth < old_depth)
|
||||
{
|
||||
entry->populated = true;
|
||||
_populate_follows_and_blocks(ssb, entry, following, following_count, active_blocks, include_blocks);
|
||||
|
||||
if (depth < max_depth)
|
||||
if (!_is_blocked_by_active_blocks(entry->id, active_blocks))
|
||||
{
|
||||
if (!entry->populated)
|
||||
{
|
||||
entry->populated = true;
|
||||
_populate_follows_and_blocks(ssb, entry, following, following_count, active_blocks, include_blocks);
|
||||
}
|
||||
|
||||
block_node_t blocks = { .entry = entry, .parent = active_blocks };
|
||||
for (int i = 0; i < entry->following_count; i++)
|
||||
{
|
||||
if (!_has_following_entry(entry->following[i]->id, entry->blocking, entry->blocking_count))
|
||||
{
|
||||
_get_following(ssb, entry->following[i], following, following_count, depth + 1, max_depth, &blocks, include_blocks);
|
||||
}
|
||||
_get_following(ssb, entry->following[i], following, following_count, depth + 1, max_depth, &blocks, include_blocks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2126,6 +2286,51 @@ bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* ou
|
||||
return result;
|
||||
}
|
||||
|
||||
bool tf_ssb_db_set_global_setting_from_string(sqlite3* db, const char* name, char* value)
|
||||
{
|
||||
tf_setting_kind_t kind = tf_util_get_global_setting_kind(name);
|
||||
if (kind == k_kind_unknown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(db,
|
||||
"INSERT INTO properties (id, key, value) VALUES ('core', 'settings', json_object(?1, ?2)) ON CONFLICT DO UPDATE SET value = json_set(value, '$.' || ?1, ?2)", -1,
|
||||
&statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK)
|
||||
{
|
||||
bool bound = false;
|
||||
switch (kind)
|
||||
{
|
||||
case k_kind_bool:
|
||||
bound = sqlite3_bind_int(statement, 2, value && (strcmp(value, "true") == 0 || atoi(value))) == SQLITE_OK;
|
||||
break;
|
||||
case k_kind_int:
|
||||
bound = sqlite3_bind_int(statement, 2, atoi(value)) == SQLITE_OK;
|
||||
break;
|
||||
case k_kind_string:
|
||||
bound = sqlite3_bind_text(statement, 2, value, -1, NULL) == SQLITE_OK;
|
||||
break;
|
||||
case k_kind_unknown:
|
||||
break;
|
||||
}
|
||||
if (bound && sqlite3_step(statement) == SQLITE_DONE)
|
||||
{
|
||||
result = sqlite3_changes(db) != 0;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* tf_ssb_db_get_profile(sqlite3* db, const char* id)
|
||||
{
|
||||
const char* result = NULL;
|
||||
@ -2159,7 +2364,7 @@ const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id)
|
||||
if (sqlite3_prepare(db,
|
||||
"SELECT name FROM (SELECT messages.author, RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank, "
|
||||
"messages.content ->> 'name' AS name FROM messages WHERE messages.author = ? "
|
||||
"AND json_extract(messages.content, '$.type') = 'about' AND content ->> 'about' = messages.author AND name IS NOT NULL) "
|
||||
"AND messages.content ->> '$.type' = 'about' AND content ->> 'about' = messages.author AND name IS NOT NULL) "
|
||||
"WHERE author_rank = 1",
|
||||
-1, &statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
@ -2182,7 +2387,7 @@ const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id)
|
||||
static void _tf_ssb_db_invite_cleanup(sqlite3* db)
|
||||
{
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(db, "DELETE FROM invites WHERE use_count = 0 OR expires < ?", -1, &statement, NULL) == SQLITE_OK)
|
||||
if (sqlite3_prepare(db, "DELETE FROM invites WHERE use_count = 0 OR (expires > 0 AND expires < ?)", -1, &statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_bind_int64(statement, 1, (int64_t)time(NULL)) == SQLITE_OK)
|
||||
{
|
||||
@ -2190,7 +2395,13 @@ static void _tf_ssb_db_invite_cleanup(sqlite3* db)
|
||||
{
|
||||
if (sqlite3_changes(db))
|
||||
{
|
||||
tf_printf("Cleaned up %d used/expired invites.\n", sqlite3_changes(db));
|
||||
char buffer[2] = { 0 };
|
||||
size_t buffer_size = sizeof(buffer);
|
||||
bool verbose = uv_os_getenv("TF_SSB_VERBOSE", buffer, &buffer_size) == 0 && strcmp(buffer, "1") == 0;
|
||||
if (verbose)
|
||||
{
|
||||
tf_printf("Cleaned up %d used/expired invites.\n", sqlite3_changes(db));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2231,7 +2442,8 @@ bool tf_ssb_db_generate_invite(sqlite3* db, const char* id, const char* host, in
|
||||
if (sqlite3_prepare(db, "INSERT INTO invites (invite_public_key, account, use_count, expires) VALUES (?, ?, ?, ?)", -1, &statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, 1, public, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, id, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_int(statement, 3, use_count) == SQLITE_OK && sqlite3_bind_int64(statement, 4, (int64_t)time(NULL) + expires_seconds) == SQLITE_OK)
|
||||
sqlite3_bind_int(statement, 3, use_count) == SQLITE_OK &&
|
||||
sqlite3_bind_int64(statement, 4, (expires_seconds > 0 ? (int64_t)time(NULL) : 0) + expires_seconds) == SQLITE_OK)
|
||||
{
|
||||
inserted = sqlite3_step(statement) == SQLITE_DONE;
|
||||
}
|
||||
@ -2248,8 +2460,8 @@ bool tf_ssb_db_use_invite(sqlite3* db, const char* id)
|
||||
{
|
||||
bool used = false;
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(db, "UPDATE invites SET use_count = use_count - 1 WHERE invite_public_key = ? AND expires >= ? AND (use_count > 0 OR use_count = -1)", -1, &statement,
|
||||
NULL) == SQLITE_OK)
|
||||
if (sqlite3_prepare(db, "UPDATE invites SET use_count = use_count - 1 WHERE invite_public_key = ? AND (expires < 0 OR expires >= ?) AND (use_count > 0 OR use_count = -1)", -1,
|
||||
&statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, (int64_t)time(NULL)) == SQLITE_OK)
|
||||
{
|
||||
@ -2262,3 +2474,136 @@ bool tf_ssb_db_use_invite(sqlite3* db, const char* id)
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
bool tf_ssb_db_has_invite(sqlite3* db, const char* id)
|
||||
{
|
||||
bool has = false;
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(db, "SELECT COUNT(*) FROM invites WHERE invite_public_key = ? AND (expires < 0 OR expires >= ?) AND (use_count > 0 OR use_count = -1)", -1, &statement,
|
||||
NULL) == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, (int64_t)time(NULL)) == SQLITE_OK)
|
||||
{
|
||||
has = sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_int(statement, 0) > 0;
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
return has;
|
||||
}
|
||||
|
||||
static void _tf_ssb_db_get_identity_info_visit(const char* identity, void* user_data)
|
||||
{
|
||||
tf_ssb_identity_info_t* info = user_data;
|
||||
info->identity = tf_resize_vec(info->identity, (info->count + 1) * sizeof(char*));
|
||||
info->name = tf_resize_vec(info->name, (info->count + 1) * sizeof(char*));
|
||||
char buffer[k_id_base64_len];
|
||||
snprintf(buffer, sizeof(buffer), "@%s", identity);
|
||||
info->identity[info->count] = tf_strdup(buffer);
|
||||
info->name[info->count] = NULL;
|
||||
info->count++;
|
||||
}
|
||||
|
||||
tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* user, const char* package_owner, const char* package_name)
|
||||
{
|
||||
tf_ssb_identity_info_t* info = tf_malloc(sizeof(tf_ssb_identity_info_t));
|
||||
*info = (tf_ssb_identity_info_t) { 0 };
|
||||
|
||||
char id[k_id_base64_len] = "";
|
||||
if (tf_ssb_db_user_has_permission(ssb, NULL, user, "administration"))
|
||||
{
|
||||
if (tf_ssb_whoami(ssb, id, sizeof(id)))
|
||||
{
|
||||
_tf_ssb_db_get_identity_info_visit(*id == '@' ? id + 1 : id, info);
|
||||
}
|
||||
}
|
||||
tf_ssb_db_identity_visit(ssb, user, _tf_ssb_db_get_identity_info_visit, info);
|
||||
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
sqlite3_stmt* statement = NULL;
|
||||
int result = sqlite3_prepare(db,
|
||||
"SELECT author, name FROM ( "
|
||||
" SELECT "
|
||||
" messages.author, "
|
||||
" RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank, "
|
||||
" messages.content ->> 'name' AS name "
|
||||
" FROM messages "
|
||||
" JOIN identities ON messages.author = ('@' || identities.public_key) "
|
||||
" WHERE "
|
||||
" (identities.user = ? OR identities.public_key = ?) AND "
|
||||
" messages.content ->> '$.type' = 'about' AND "
|
||||
" content ->> 'about' = messages.author AND name IS NOT NULL) "
|
||||
"WHERE author_rank = 1 ",
|
||||
-1, &statement, NULL);
|
||||
if (result == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, *id == '@' ? id + 1 : id, -1, NULL) == SQLITE_OK)
|
||||
{
|
||||
int r = SQLITE_OK;
|
||||
while ((r = sqlite3_step(statement)) == SQLITE_ROW)
|
||||
{
|
||||
const char* identity = (const char*)sqlite3_column_text(statement, 0);
|
||||
const char* name = (const char*)sqlite3_column_text(statement, 1);
|
||||
for (int i = 0; i < info->count; i++)
|
||||
{
|
||||
if (!info->name[i] && strcmp(info->identity[i], identity) == 0)
|
||||
{
|
||||
info->name[i] = tf_strdup(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("prepare failed: %s.\n", sqlite3_errmsg(db));
|
||||
}
|
||||
|
||||
tf_ssb_db_identity_get_active(db, user, package_owner, package_name, info->active_identity, sizeof(info->active_identity));
|
||||
if (!*info->active_identity && info->count)
|
||||
{
|
||||
snprintf(info->active_identity, sizeof(info->active_identity), "%s", info->identity[0]);
|
||||
}
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
size_t size = sizeof(tf_ssb_identity_info_t) + sizeof(char*) * info->count * 2;
|
||||
for (int i = 0; i < info->count; i++)
|
||||
{
|
||||
size += strlen(info->identity[i]) + 1;
|
||||
size += info->name[i] ? strlen(info->name[i]) + 1 : 0;
|
||||
}
|
||||
tf_ssb_identity_info_t* copy = tf_malloc(size);
|
||||
*copy = *info;
|
||||
|
||||
copy->identity = (const char**)(copy + 1);
|
||||
copy->name = (const char**)(copy + 1) + copy->count;
|
||||
|
||||
char* p = (char*)((const char**)(copy + 1) + copy->count * 2);
|
||||
|
||||
for (int i = 0; i < info->count; i++)
|
||||
{
|
||||
size_t length = strlen(info->identity[i]);
|
||||
memcpy(p, info->identity[i], length + 1);
|
||||
copy->identity[i] = p;
|
||||
p += length + 1;
|
||||
tf_free((void*)info->identity[i]);
|
||||
|
||||
if (info->name[i])
|
||||
{
|
||||
length = strlen(info->name[i]);
|
||||
memcpy(p, info->name[i], length + 1);
|
||||
copy->name[i] = p;
|
||||
p += length + 1;
|
||||
tf_free((void*)info->name[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
copy->name[i] = NULL;
|
||||
}
|
||||
}
|
||||
tf_free(info->name);
|
||||
tf_free(info->identity);
|
||||
tf_free(info);
|
||||
return copy;
|
||||
}
|
||||
|