Compare commits
48 Commits
latest_rel
...
8b546c7e02
Author | SHA1 | Date | |
---|---|---|---|
8b546c7e02 | |||
c0b6ff2e64 | |||
638b7cc1e5 | |||
05e54e1be0 | |||
4c3299ead0 | |||
1ef56b35ad | |||
061e79c295 | |||
5edfe732b1 | |||
a8f9b67f71 | |||
de7fbf1eb7 | |||
a51a3d7e43 | |||
433b3b1003 | |||
6703c5b584 | |||
5f729efabe | |||
b2085b3f28 | |||
2f893494b0 | |||
e26af21f63 | |||
7e1d738f8d | |||
199448e11e | |||
fdaabab807 | |||
ca4560c5c9 | |||
2478f3064d | |||
e9b8b43e7c | |||
951155f1b6 | |||
1b678175ef | |||
8eb1f40eec | |||
235887b3bf | |||
0b3d66dd48 | |||
beb9ef3754 | |||
9f6a480736 | |||
b3bac2927d | |||
ef389f2ba2 | |||
ef21dc6ae8 | |||
6e55b6b49e | |||
db115ef1bd | |||
678838dbd5 | |||
586f87625d | |||
1542370f9b | |||
1f7d5968c7 | |||
39e51f7790 | |||
052663efbe | |||
8f84ff2611 | |||
37e1c5d97b | |||
cef526bcf3 | |||
6af36cafa9 | |||
fca859d93d | |||
2178300d8d | |||
636bdcce6b |
@@ -48,7 +48,7 @@ jobs:
|
|||||||
- name: Build documentation
|
- name: Build documentation
|
||||||
run: |
|
run: |
|
||||||
mkdir -p out/html/ ~/.ssh/
|
mkdir -p out/html/ ~/.ssh/
|
||||||
make docs
|
make -j`nproc` docs
|
||||||
echo 'pildefriends ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKD3Kde5vDO0TrMBDK0IGGeNGe/XinWAZkSQ/rXxwUjt' >> ~/.ssh/known_hosts
|
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/
|
rsync -avP --delete -e "ssh -i /opt/keys/ssh.ed25519" out/html/ tfdocs@pildefriends:docs/html/
|
||||||
- name: Setup JDK
|
- name: Setup JDK
|
||||||
@@ -59,11 +59,11 @@ jobs:
|
|||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@v3
|
uses: android-actions/setup-android@v3
|
||||||
with:
|
with:
|
||||||
packages: 'tools platform-tools build-tools;34.0.0 platforms;android-34 ndk;26.3.11579264'
|
packages: 'tools platform-tools build-tools;35.0.0 platforms;android-35 ndk;27.2.12479018'
|
||||||
- name: Docker build
|
- name: Docker build
|
||||||
run: DOCKER_BUILDKIT=1 docker build .
|
run: DOCKER_BUILDKIT=1 docker build .
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist docs
|
run: ANDROID_SDK=$HOME/.android/sdk make -j`nproc` all dist
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
21
GNUmakefile
21
GNUmakefile
@@ -16,14 +16,14 @@ MAKEFLAGS += --no-builtin-rules
|
|||||||
## LD := Linker.
|
## LD := Linker.
|
||||||
## ANDROID_SDK := Path to the Android SDK.
|
## ANDROID_SDK := Path to the Android SDK.
|
||||||
|
|
||||||
VERSION_CODE := 38
|
VERSION_CODE := 40
|
||||||
VERSION_CODE_IOS := 14
|
VERSION_CODE_IOS := 15
|
||||||
VERSION_NUMBER := 0.0.32
|
VERSION_NUMBER := 0.0.33-wip
|
||||||
VERSION_NAME := This program kills fascists.
|
VERSION_NAME := This program kills fascists.
|
||||||
|
|
||||||
IPHONEOS_VERSION_MIN=14.0
|
IPHONEOS_VERSION_MIN=14.0
|
||||||
|
|
||||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500100.zip
|
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500300.zip
|
||||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
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_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||||
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
||||||
@@ -106,10 +106,10 @@ LDFLAGS += \
|
|||||||
-Wno-aggressive-loop-optimizations
|
-Wno-aggressive-loop-optimizations
|
||||||
|
|
||||||
ANDROID_MIN_SDK_VERSION := 24
|
ANDROID_MIN_SDK_VERSION := 24
|
||||||
ANDROID_TARGET_SDK_VERSION := 34
|
ANDROID_TARGET_SDK_VERSION := 35
|
||||||
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
|
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/35.0.0
|
||||||
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
|
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
|
||||||
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.3.11579264
|
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/27.2.12479018
|
||||||
|
|
||||||
ANDROID_ARMV7A_TARGETS := \
|
ANDROID_ARMV7A_TARGETS := \
|
||||||
out/androiddebug-armv7a/tildefriends \
|
out/androiddebug-armv7a/tildefriends \
|
||||||
@@ -650,6 +650,7 @@ SODIUM_SOURCES := \
|
|||||||
deps/libsodium/src/libsodium/crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \
|
deps/libsodium/src/libsodium/crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \
|
||||||
deps/libsodium/src/libsodium/crypto_core/salsa/ref/core_salsa_ref.c \
|
deps/libsodium/src/libsodium/crypto_core/salsa/ref/core_salsa_ref.c \
|
||||||
deps/libsodium/src/libsodium/crypto_core/softaes/softaes.c \
|
deps/libsodium/src/libsodium/crypto_core/softaes/softaes.c \
|
||||||
|
deps/libsodium/src/libsodium/crypto_generichash/crypto_generichash.c \
|
||||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-ref.c \
|
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-compress-ref.c \
|
||||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c \
|
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c \
|
||||||
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c \
|
deps/libsodium/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c \
|
||||||
@@ -1054,7 +1055,7 @@ out/TildeFriends.aab: out/apk/classes.dex $(filter-out %debug%, $(ANDROID_TARGET
|
|||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym
|
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86_64/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym
|
||||||
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym
|
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip --only-keep-debug out/androidrelease-x86/tildefriends -o out/aab/staging/BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym
|
||||||
@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../
|
@cd out/aab/staging; zip -u ../../../$@ BUNDLE-METADATA/com.android.tools.build.debugsymbols/arm64-v8a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/armeabi-v7a/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86_64/libtildefriends.so.sym BUNDLE-METADATA/com.android.tools.build.debugsymbols/x86/libtildefriends.so.sym; cd ../../../
|
||||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) $@
|
@$(ANDROID_BUILD_TOOLS)/apksigner sign -ks .keys/android.jks --ks-key-alias androidKey -ks-pass pass:android --min-sdk-version=$(ANDROID_MIN_SDK_VERSION) --alignment-preserved $@
|
||||||
|
|
||||||
aab: out/TildeFriends.aab ## Build an Android App Bundle.
|
aab: out/TildeFriends.aab ## Build an Android App Bundle.
|
||||||
.PHONY: aab
|
.PHONY: aab
|
||||||
@@ -1116,12 +1117,12 @@ out/apk/TildeFriends-%.fdroid.unsigned.apk:
|
|||||||
|
|
||||||
out/%.apk: out/apk/%.unsigned.apk
|
out/%.apk: out/apk/%.unsigned.apk
|
||||||
@echo "[apksigner] $(notdir $@)"
|
@echo "[apksigner] $(notdir $@)"
|
||||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $<
|
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ --alignment-preserved $<
|
||||||
|
|
||||||
out/%.zopfli.apk: out/%.apk
|
out/%.zopfli.apk: out/%.apk
|
||||||
@echo "[zopfli] $(notdir $@)"
|
@echo "[zopfli] $(notdir $@)"
|
||||||
$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli
|
$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli
|
||||||
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $@.zopfli
|
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ --alignment-preserved $@.zopfli
|
||||||
|
|
||||||
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk ## Build an Android release APK.
|
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk ## Build an Android release APK.
|
||||||
.PHONY: release-apk
|
.PHONY: release-apk
|
||||||
|
8
apps/blog/lit-all.min.js
vendored
8
apps/blog/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
apps/issues/lit-all.min.js
vendored
8
apps/issues/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
apps/journal/lit-all.min.js
vendored
8
apps/journal/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
apps/sneaker/lit-all.min.js
vendored
8
apps/sneaker/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🦀",
|
"emoji": "🦀",
|
||||||
"previous": "&Rn4Eg5ev5qhrYRnwxPB0DiEwO7VdGMDGp7tL/W7bRZo=.sha256"
|
"previous": "&DGtlnm5wWRZCgJMF8JsP6VtzNRrd4KLoERJRpFULqOY=.sha256"
|
||||||
}
|
}
|
||||||
|
8
apps/ssb/lit-all.min.js
vendored
8
apps/ssb/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -23,6 +23,8 @@ class TfElement extends LitElement {
|
|||||||
url: {type: String},
|
url: {type: String},
|
||||||
private_messages: {type: Array},
|
private_messages: {type: Array},
|
||||||
recent_reactions: {type: Array},
|
recent_reactions: {type: Array},
|
||||||
|
is_administrator: {type: Boolean},
|
||||||
|
stay_connected: {type: Boolean},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +75,10 @@ class TfElement extends LitElement {
|
|||||||
async initial_load() {
|
async initial_load() {
|
||||||
let whoami = await tfrpc.rpc.getActiveIdentity();
|
let whoami = await tfrpc.rpc.getActiveIdentity();
|
||||||
let ids = (await tfrpc.rpc.getIdentities()) || [];
|
let ids = (await tfrpc.rpc.getIdentities()) || [];
|
||||||
|
this.is_administrator = await tfrpc.rpc.isAdministrator();
|
||||||
|
this.stay_connected =
|
||||||
|
this.is_administrator &&
|
||||||
|
(await tfrpc.rpc.globalSettingsGet('stay_connected'));
|
||||||
this.url = await tfrpc.rpc.url();
|
this.url = await tfrpc.rpc.url();
|
||||||
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
|
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
|
||||||
this.guest = !this.whoami?.length;
|
this.guest = !this.whoami?.length;
|
||||||
@@ -131,8 +137,8 @@ class TfElement extends LitElement {
|
|||||||
let channel_names = [
|
let channel_names = [
|
||||||
'',
|
'',
|
||||||
'@',
|
'@',
|
||||||
'🔐',
|
|
||||||
'👍',
|
'👍',
|
||||||
|
'🔐',
|
||||||
...this.channels.map((x) => '#' + x),
|
...this.channels.map((x) => '#' + x),
|
||||||
];
|
];
|
||||||
let index = channel_names.indexOf(this.hash.substring(1));
|
let index = channel_names.indexOf(this.hash.substring(1));
|
||||||
@@ -354,7 +360,7 @@ class TfElement extends LitElement {
|
|||||||
let start = new Date();
|
let start = new Date();
|
||||||
let result = await tfrpc.rpc.query(sql, args);
|
let result = await tfrpc.rpc.query(sql, args);
|
||||||
let end = new Date();
|
let end = new Date();
|
||||||
console.log((end - start) / 1000, sql);
|
console.log((end - start) / 1000, sql.replaceAll(/\s+/g, ' ').trim());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,16 +422,6 @@ class TfElement extends LitElement {
|
|||||||
`,
|
`,
|
||||||
k_args
|
k_args
|
||||||
),
|
),
|
||||||
this.query_timed(
|
|
||||||
`
|
|
||||||
SELECT '👍' AS channel, MAX(messages.rowid) AS rowid FROM messages
|
|
||||||
JOIN json_each(?2) AS following ON messages.author = following.value
|
|
||||||
WHERE
|
|
||||||
messages.content ->> 'type' = 'vote' AND
|
|
||||||
messages.author != ?4
|
|
||||||
`,
|
|
||||||
k_args
|
|
||||||
),
|
|
||||||
])
|
])
|
||||||
).flat();
|
).flat();
|
||||||
let latest = {};
|
let latest = {};
|
||||||
@@ -605,9 +601,13 @@ class TfElement extends LitElement {
|
|||||||
.channels_latest=${this.channels_latest}
|
.channels_latest=${this.channels_latest}
|
||||||
.channels_unread=${this.channels_unread}
|
.channels_unread=${this.channels_unread}
|
||||||
@channelsetunread=${this.channel_set_unread}
|
@channelsetunread=${this.channel_set_unread}
|
||||||
|
@refresh=${this.refresh}
|
||||||
|
@toggle_stay_connected=${this.toggle_stay_connected}
|
||||||
.connections=${this.connections}
|
.connections=${this.connections}
|
||||||
.private_messages=${this.private_messages}
|
.private_messages=${this.private_messages}
|
||||||
.recent_reactions=${this.recent_reactions}
|
.recent_reactions=${this.recent_reactions}
|
||||||
|
?is_administrator=${this.is_administrator}
|
||||||
|
?stay_connected=${this.stay_connected}
|
||||||
></tf-tab-news>
|
></tf-tab-news>
|
||||||
`;
|
`;
|
||||||
} else if (this.tab === 'connections') {
|
} else if (this.tab === 'connections') {
|
||||||
@@ -658,6 +658,18 @@ class TfElement extends LitElement {
|
|||||||
tfrpc.rpc.sync();
|
tfrpc.rpc.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toggle_stay_connected() {
|
||||||
|
let stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
|
||||||
|
let new_stay_connected = !this.stay_connected;
|
||||||
|
try {
|
||||||
|
if (new_stay_connected != stay_connected) {
|
||||||
|
await tfrpc.rpc.globalSettingsSet('stay_connected', new_stay_connected);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.stay_connected = await tfrpc.rpc.globalSettingsGet('stay_connected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
@@ -680,14 +692,26 @@ class TfElement extends LitElement {
|
|||||||
class="w3-bar w3-theme-l1"
|
class="w3-bar w3-theme-l1"
|
||||||
style="position: static; top: 0; z-index: 10"
|
style="position: static; top: 0; z-index: 10"
|
||||||
>
|
>
|
||||||
<button
|
${this.is_administrator && self.tab != 'news'
|
||||||
class=${'w3-bar-item w3-button w3-circle w3-ripple' +
|
? html`
|
||||||
(this.connections?.some((x) => x.flags.one_shot) ? ' w3-spin' : '')}
|
<button
|
||||||
style="width: 1.5em; height: 1.5em; padding: 8px"
|
class=${'w3-bar-item w3-button w3-circle w3-ripple' +
|
||||||
@click=${this.refresh}
|
(this.connections?.some((x) => x.flags.one_shot)
|
||||||
>
|
? ' w3-spin'
|
||||||
↻
|
: '')}
|
||||||
</button>
|
style="width: 1.5em; height: 1.5em; padding: 8px"
|
||||||
|
@click=${this.refresh}
|
||||||
|
>
|
||||||
|
↻
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w3-bar-item w3-button w3-ripple"
|
||||||
|
@click=${this.toggle_stay_connected}
|
||||||
|
>
|
||||||
|
${this.stay_connected ? '🔗' : '⛓️💥'}
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
: undefined}
|
||||||
${Object.entries(k_tabs).map(
|
${Object.entries(k_tabs).map(
|
||||||
([k, v]) => html`
|
([k, v]) => html`
|
||||||
<button
|
<button
|
||||||
|
@@ -581,7 +581,7 @@ class TfComposeElement extends LitElement {
|
|||||||
class="w3-button w3-bar-item w3-theme-d1"
|
class="w3-button w3-bar-item w3-theme-d1"
|
||||||
@click=${() => this.set_encrypt([])}
|
@click=${() => this.set_encrypt([])}
|
||||||
>
|
>
|
||||||
🔐
|
🔐 Encrypt
|
||||||
</button>`;
|
</button>`;
|
||||||
let result = html`
|
let result = html`
|
||||||
<style>
|
<style>
|
||||||
@@ -602,7 +602,7 @@ class TfComposeElement extends LitElement {
|
|||||||
: undefined}
|
: undefined}
|
||||||
${this.render_encrypt()}
|
${this.render_encrypt()}
|
||||||
</header>
|
</header>
|
||||||
<div class="w3-container w3-padding-small">
|
<div class="w3-container" style="padding: 0 0 16px 0">
|
||||||
<div class="w3-half">
|
<div class="w3-half">
|
||||||
<span
|
<span
|
||||||
class="w3-input w3-theme-d1 w3-border"
|
class="w3-input w3-theme-d1 w3-border"
|
||||||
@@ -623,7 +623,7 @@ class TfComposeElement extends LitElement {
|
|||||||
${Object.values(draft.mentions || {}).map((x) =>
|
${Object.values(draft.mentions || {}).map((x) =>
|
||||||
self.render_mention(x)
|
self.render_mention(x)
|
||||||
)}
|
)}
|
||||||
<footer class="w3-container">
|
<footer>
|
||||||
${this.render_attach_app()} ${this.render_content_warning()}
|
${this.render_attach_app()} ${this.render_content_warning()}
|
||||||
${this.render_new_thread()}
|
${this.render_new_thread()}
|
||||||
<button
|
<button
|
||||||
|
@@ -191,12 +191,12 @@ class TfMessageElement extends LitElement {
|
|||||||
div.style.display = 'grid';
|
div.style.display = 'grid';
|
||||||
let img = document.createElement('img');
|
let img = document.createElement('img');
|
||||||
img.src = link;
|
img.src = link;
|
||||||
img.style.maxWidth = '100%';
|
img.style.maxWidth = '100vw';
|
||||||
img.style.maxHeight = '100%';
|
img.style.maxHeight = '100vh';
|
||||||
img.style.display = 'block';
|
img.style.display = 'block';
|
||||||
img.style.margin = 'auto';
|
img.style.margin = 'auto';
|
||||||
img.style.objectFit = 'contain';
|
img.style.objectFit = 'contain';
|
||||||
img.style.width = '100%';
|
img.style.width = '100vw';
|
||||||
div.appendChild(img);
|
div.appendChild(img);
|
||||||
function image_close(event) {
|
function image_close(event) {
|
||||||
document.body.removeChild(div);
|
document.body.removeChild(div);
|
||||||
@@ -686,7 +686,11 @@ class TfMessageElement extends LitElement {
|
|||||||
${x.action}
|
${x.action}
|
||||||
${x.users.map(
|
${x.users.map(
|
||||||
(y) => html`
|
(y) => html`
|
||||||
<tf-user id=${y} .users=${this.users}></tf-user>
|
<tf-user
|
||||||
|
id=${y}
|
||||||
|
.users=${this.users}
|
||||||
|
icon_only="true"
|
||||||
|
></tf-user>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -233,7 +233,19 @@ class TfNewsElement extends LitElement {
|
|||||||
<div
|
<div
|
||||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
||||||
></div>
|
></div>
|
||||||
<div style="color: #f00; padding: 8px">unread</div>
|
<button
|
||||||
|
style="color: #f00; padding: 8px"
|
||||||
|
class="w3-button"
|
||||||
|
@click=${() =>
|
||||||
|
this.dispatchEvent(
|
||||||
|
new Event('mark_all_read', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
unread
|
||||||
|
</button>
|
||||||
<div
|
<div
|
||||||
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
style="border-bottom: 1px solid #f00; flex: 1; align-self: center; height: 1px"
|
||||||
></div>
|
></div>
|
||||||
|
@@ -14,6 +14,7 @@ class TfProfileElement extends LitElement {
|
|||||||
sequence: {type: Number},
|
sequence: {type: Number},
|
||||||
following: {type: Boolean},
|
following: {type: Boolean},
|
||||||
blocking: {type: Boolean},
|
blocking: {type: Boolean},
|
||||||
|
show_followed: {type: Boolean},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,12 +179,12 @@ class TfProfileElement extends LitElement {
|
|||||||
div.style.display = 'grid';
|
div.style.display = 'grid';
|
||||||
let img = document.createElement('img');
|
let img = document.createElement('img');
|
||||||
img.src = link;
|
img.src = link;
|
||||||
img.style.maxWidth = '100%';
|
img.style.maxWidth = '100vw';
|
||||||
img.style.maxHeight = '100%';
|
img.style.maxHeight = '100vh';
|
||||||
img.style.display = 'block';
|
img.style.display = 'block';
|
||||||
img.style.margin = 'auto';
|
img.style.margin = 'auto';
|
||||||
img.style.objectFit = 'contain';
|
img.style.objectFit = 'contain';
|
||||||
img.style.width = '100%';
|
img.style.width = '100vw';
|
||||||
div.appendChild(img);
|
div.appendChild(img);
|
||||||
function image_close(event) {
|
function image_close(event) {
|
||||||
document.body.removeChild(div);
|
document.body.removeChild(div);
|
||||||
@@ -202,11 +203,7 @@ class TfProfileElement extends LitElement {
|
|||||||
|
|
||||||
toggle_account_list(event) {
|
toggle_account_list(event) {
|
||||||
let content = event.srcElement.nextElementSibling;
|
let content = event.srcElement.nextElementSibling;
|
||||||
if (content.classList.toggle('w3-hide')) {
|
this.show_followed = !this.show_followed;
|
||||||
event.srcElement.innerText = 'Show Followed Accounts';
|
|
||||||
} else {
|
|
||||||
event.srcElement.innerText = 'Hide Followed Accounts';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load_follows() {
|
async load_follows() {
|
||||||
@@ -214,12 +211,13 @@ class TfProfileElement extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="w3-container">
|
<div class="w3-container">
|
||||||
<button
|
<button
|
||||||
class="w3-button w3-block w3-theme-d1"
|
class="w3-button w3-block w3-theme-d1 followed_accounts"
|
||||||
@click=${this.toggle_account_list}
|
@click=${this.toggle_account_list}
|
||||||
>
|
>
|
||||||
Show Followed Accounts
|
${this.show_followed ? 'Hide' : 'Show'} Followed Accounts
|
||||||
|
(${Object.keys(accounts).length})
|
||||||
</button>
|
</button>
|
||||||
<div class="w3-hide w3-card">
|
<div class=${'w3-card' + (this.show_followed ? '' : ' w3-hide')}>
|
||||||
<ul class="w3-ul w3-theme-d4 w3-border-theme">
|
<ul class="w3-ul w3-theme-d4 w3-border-theme">
|
||||||
${Object.keys(accounts).map(
|
${Object.keys(accounts).map(
|
||||||
(x) => html`
|
(x) => html`
|
||||||
@@ -329,7 +327,7 @@ class TfProfileElement extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
<div style="display: flex; flex-direction: row; gap: 1em">
|
<div style="display: flex; flex-direction: row; gap: 1em">
|
||||||
${edit_profile}
|
${edit_profile}
|
||||||
<div style="flex: 1 0 50%">
|
<div style="flex: 1 0 50%; contain: layout; overflow: auto; word-wrap: normal; word-break: normal">
|
||||||
${
|
${
|
||||||
image
|
image
|
||||||
? html`<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>`
|
? html`<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>`
|
||||||
|
@@ -108,6 +108,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
async fetch_messages(start_time, end_time) {
|
async fetch_messages(start_time, end_time) {
|
||||||
this.time_loading = [start_time, end_time];
|
this.time_loading = [start_time, end_time];
|
||||||
let result;
|
let result;
|
||||||
|
const k_max_results = 64;
|
||||||
if (this.hash == '#@') {
|
if (this.hash == '#@') {
|
||||||
result = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
@@ -118,7 +119,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
WHERE
|
WHERE
|
||||||
messages.author != ?1 AND
|
messages.author != ?1 AND
|
||||||
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
(?3 IS NULL OR messages.timestamp >= ?3) AND messages.timestamp < ?4
|
||||||
ORDER BY timestamp DESC limit 20)
|
ORDER BY timestamp DESC limit ?5)
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM mentions
|
FROM mentions
|
||||||
JOIN messages_refs ON mentions.id = messages_refs.ref
|
JOIN messages_refs ON mentions.id = messages_refs.ref
|
||||||
@@ -131,6 +132,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
JSON.stringify(this.following),
|
JSON.stringify(this.following),
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
|
k_max_results,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (this.hash.startsWith('#@')) {
|
} else if (this.hash.startsWith('#@')) {
|
||||||
@@ -140,7 +142,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
selected AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
|
WHERE messages.author = ?1 AND (?2 IS NULL OR messages.timestamp >= 2) AND messages.timestamp < ?3
|
||||||
ORDER BY sequence DESC LIMIT 20
|
ORDER BY sequence DESC LIMIT ?4
|
||||||
)
|
)
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM selected
|
FROM selected
|
||||||
@@ -149,7 +151,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
UNION
|
UNION
|
||||||
SELECT TRUE AS is_primary, * FROM selected
|
SELECT TRUE AS is_primary, * FROM selected
|
||||||
`,
|
`,
|
||||||
[this.hash.substring(1), start_time, end_time]
|
[this.hash.substring(1), start_time, end_time, k_max_results]
|
||||||
);
|
);
|
||||||
} else if (this.hash.startsWith('#%')) {
|
} else if (this.hash.startsWith('#%')) {
|
||||||
result = await tfrpc.rpc.query(
|
result = await tfrpc.rpc.query(
|
||||||
@@ -174,23 +176,24 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM messages
|
FROM messages
|
||||||
JOIN json_each(?) AS following ON messages.author = following.value
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
WHERE messages.content ->> 'channel' = ?4
|
WHERE messages.content ->> 'channel' = ?4 AND messages.content ->> 'type' != 'vote'
|
||||||
UNION
|
UNION
|
||||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM messages_refs
|
FROM messages_refs
|
||||||
JOIN messages ON messages.id = messages_refs.message
|
JOIN messages ON messages.id = messages_refs.message
|
||||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||||
WHERE messages_refs.ref = '#' || ?4
|
WHERE messages_refs.ref = '#' || ?4 AND messages.content ->> 'type' != 'vote'
|
||||||
)
|
)
|
||||||
SELECT TRUE AS is_primary, all_news.* FROM all_news
|
SELECT TRUE AS is_primary, all_news.* FROM all_news
|
||||||
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
WHERE (?2 IS NULL OR all_news.timestamp >= ?2) AND all_news.timestamp < ?3
|
||||||
ORDER BY all_news.timestamp DESC LIMIT 20
|
ORDER BY all_news.timestamp DESC LIMIT ?5
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
JSON.stringify(this.following),
|
JSON.stringify(this.following),
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
this.hash.substring(2),
|
this.hash.substring(2),
|
||||||
|
k_max_results,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
let t1 = new Date();
|
let t1 = new Date();
|
||||||
@@ -208,9 +211,14 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
WHERE
|
WHERE
|
||||||
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
||||||
json(messages.content) LIKE '"%'
|
json(messages.content) LIKE '"%'
|
||||||
ORDER BY messages.rowid DESC LIMIT 20
|
ORDER BY messages.rowid DESC LIMIT ?4
|
||||||
`,
|
`,
|
||||||
[JSON.stringify(this.private_messages), start_time, end_time]
|
[
|
||||||
|
JSON.stringify(this.private_messages),
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
k_max_results,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
result = (await this.decrypt(result)).filter((x) => x.decrypted);
|
||||||
} else if (this.hash == '#👍') {
|
} else if (this.hash == '#👍') {
|
||||||
@@ -222,33 +230,27 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
WHERE
|
WHERE
|
||||||
messages.content ->> 'type' = 'vote' AND
|
messages.content ->> 'type' = 'vote' AND
|
||||||
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
|
(?2 IS NULL OR messages.timestamp >= ?2) AND messages.timestamp < ?3
|
||||||
ORDER BY timestamp DESC limit 20)
|
ORDER BY timestamp DESC limit ?4)
|
||||||
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
SELECT FALSE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
FROM votes
|
FROM votes
|
||||||
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
|
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
|
||||||
UNION
|
UNION
|
||||||
SELECT TRUE AS is_primary, * FROM votes
|
SELECT TRUE AS is_primary, * FROM votes
|
||||||
`,
|
`,
|
||||||
[JSON.stringify(this.following), start_time, end_time]
|
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let t0 = new Date();
|
let t0 = new Date();
|
||||||
let initial_messages = await tfrpc.rpc.query(
|
let initial_messages = await tfrpc.rpc.query(
|
||||||
`
|
`
|
||||||
WITH
|
SELECT TRUE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||||
all_news AS (
|
FROM messages
|
||||||
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
JOIN json_each(?) AS following ON messages.author = following.value
|
||||||
FROM messages
|
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
||||||
JOIN json_each(?) AS following ON messages.author = following.value
|
messages.content ->> 'type' != 'vote'
|
||||||
),
|
ORDER BY timestamp DESC LIMIT ?4
|
||||||
news AS (
|
|
||||||
SELECT * FROM all_news
|
|
||||||
WHERE all_news.timestamp < ?3 AND (?2 IS NULL OR all_news.timestamp >= ?2)
|
|
||||||
ORDER BY timestamp DESC LIMIT 20
|
|
||||||
)
|
|
||||||
SELECT TRUE AS is_primary, news.* FROM news
|
|
||||||
`,
|
`,
|
||||||
[JSON.stringify(this.following), start_time, end_time]
|
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
||||||
);
|
);
|
||||||
let t1 = new Date();
|
let t1 = new Date();
|
||||||
result = await this._fetch_related_messages(initial_messages);
|
result = await this._fetch_related_messages(initial_messages);
|
||||||
@@ -492,6 +494,7 @@ class TfTabNewsFeedElement extends LitElement {
|
|||||||
channel=${this.channel()}
|
channel=${this.channel()}
|
||||||
channel_unread=${this.channels_unread?.[this.channel()]}
|
channel_unread=${this.channels_unread?.[this.channel()]}
|
||||||
.recent_reactions=${this.recent_reactions}
|
.recent_reactions=${this.recent_reactions}
|
||||||
|
@mark_all_read=${this.mark_all_read}
|
||||||
></tf-news>
|
></tf-news>
|
||||||
${more}
|
${more}
|
||||||
`);
|
`);
|
||||||
|
@@ -26,6 +26,8 @@ class TfTabNewsElement extends LitElement {
|
|||||||
private_messages: {type: Array},
|
private_messages: {type: Array},
|
||||||
recent_reactions: {type: Array},
|
recent_reactions: {type: Array},
|
||||||
peer_exchange: {type: Boolean},
|
peer_exchange: {type: Boolean},
|
||||||
|
is_administrator: {type: Boolean},
|
||||||
|
stay_connected: {type: Boolean},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,11 +156,8 @@ class TfTabNewsElement extends LitElement {
|
|||||||
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
compare_follows() {
|
compare_follows(a, b) {
|
||||||
const now = new Date().valueOf();
|
return b[1].ts > a[1].ts ? 1 : b[1].ts < a[1].ts ? -1 : 0;
|
||||||
return function (a, b) {
|
|
||||||
return (b[1].ts > now ? -1 : b[1].ts) - (a[1].ts > now ? -1 : a[1].ts);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suggested_follows() {
|
suggested_follows() {
|
||||||
@@ -167,17 +166,15 @@ class TfTabNewsElement extends LitElement {
|
|||||||
** pinned at the top.
|
** pinned at the top.
|
||||||
*/
|
*/
|
||||||
let self = this;
|
let self = this;
|
||||||
|
let now = new Date().valueOf();
|
||||||
return Object.entries(this.users)
|
return Object.entries(this.users)
|
||||||
|
.filter((x) => x[1].ts < now)
|
||||||
.filter((x) => x[1].follow_depth > 1)
|
.filter((x) => x[1].follow_depth > 1)
|
||||||
.sort(self.compare_follows())
|
.sort(self.compare_follows)
|
||||||
.slice(0, 8)
|
.slice(0, 8)
|
||||||
.map((x) => x[0]);
|
.map((x) => x[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
|
||||||
tfrpc.rpc.sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
async enable_peer_exchange() {
|
async enable_peer_exchange() {
|
||||||
await tfrpc.rpc.globalSettingsSet('peer_exchange', true);
|
await tfrpc.rpc.globalSettingsSet('peer_exchange', true);
|
||||||
await this.check_peer_exchange();
|
await this.check_peer_exchange();
|
||||||
@@ -196,6 +193,35 @@ class TfTabNewsElement extends LitElement {
|
|||||||
>
|
>
|
||||||
×
|
×
|
||||||
</div>
|
</div>
|
||||||
|
${this.is_administrator
|
||||||
|
? html`
|
||||||
|
<button
|
||||||
|
class="w3-bar-item w3-button"
|
||||||
|
@click=${() =>
|
||||||
|
this.dispatchEvent(
|
||||||
|
new Event('refresh', {bubbles: true, composed: true})
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span style="display: inline-block; width: 1.8em">↻</span>
|
||||||
|
Sync now
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w3-bar-item w3-button w3-ripple"
|
||||||
|
@click=${() =>
|
||||||
|
this.dispatchEvent(
|
||||||
|
new Event('toggle_stay_connected', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span style="display: inline-block; width: 1.8em"
|
||||||
|
>${this.stay_connected ? '🔗' : '⛓️💥'}</span
|
||||||
|
>
|
||||||
|
${this.stay_connected ? 'Online mode' : 'Passive mode'}
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
: undefined}
|
||||||
${this.hash.startsWith('##') &&
|
${this.hash.startsWith('##') &&
|
||||||
this.channels.indexOf(this.hash.substring(2)) == -1
|
this.channels.indexOf(this.hash.substring(2)) == -1
|
||||||
? html`
|
? html`
|
||||||
@@ -266,7 +292,10 @@ class TfTabNewsElement extends LitElement {
|
|||||||
(this.connections?.some((x) => x.flags.one_shot)
|
(this.connections?.some((x) => x.flags.one_shot)
|
||||||
? ' w3-spin'
|
? ' w3-spin'
|
||||||
: '')}
|
: '')}
|
||||||
@click=${this.refresh}
|
@click=${() =>
|
||||||
|
this.dispatchEvent(
|
||||||
|
new Event('refresh', {bubbles: true, composed: true})
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
↻ Sync now
|
↻ Sync now
|
||||||
</button>
|
</button>
|
||||||
|
@@ -7,6 +7,7 @@ class TfUserElement extends LitElement {
|
|||||||
return {
|
return {
|
||||||
id: {type: String},
|
id: {type: String},
|
||||||
fallback_name: {type: String},
|
fallback_name: {type: String},
|
||||||
|
icon_only: {type: Boolean},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -17,6 +18,7 @@ class TfUserElement extends LitElement {
|
|||||||
super();
|
super();
|
||||||
this.id = null;
|
this.id = null;
|
||||||
this.fallback_name = null;
|
this.fallback_name = null;
|
||||||
|
this.icon_only = false;
|
||||||
this.users = {};
|
this.users = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,9 +34,10 @@ class TfUserElement extends LitElement {
|
|||||||
>😎</span
|
>😎</span
|
||||||
>`;
|
>`;
|
||||||
let name = this.users?.[this.id]?.name;
|
let name = this.users?.[this.id]?.name;
|
||||||
name = html`<a target="_top" href=${'#' + this.id}
|
let name_string = name ?? this.fallback_name ?? this.id;
|
||||||
>${name ?? this.fallback_name ?? this.id}</a
|
name = this.icon_only
|
||||||
>`;
|
? undefined
|
||||||
|
: html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`;
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
let image_link = user.image;
|
let image_link = user.image;
|
||||||
@@ -48,6 +51,7 @@ class TfUserElement extends LitElement {
|
|||||||
class=${'w3-theme-l4 ' + shape}
|
class=${'w3-theme-l4 ' + shape}
|
||||||
style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover"
|
style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover"
|
||||||
src="/${image_link}/view"
|
src="/${image_link}/view"
|
||||||
|
title=${name_string + ' (' + this.id + ')'}
|
||||||
/>`;
|
/>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -50,9 +50,9 @@ function image(node, entering) {
|
|||||||
'</div>'
|
'</div>'
|
||||||
);
|
);
|
||||||
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
if (this.options.safe && potentiallyUnsafe(node.destination)) {
|
||||||
this.lit('<img src="" alt="');
|
this.lit('<img src="" title="');
|
||||||
} else {
|
} else {
|
||||||
this.lit('<img src="' + this.esc(node.destination) + '" alt="');
|
this.lit('<img src="' + this.esc(node.destination) + '" title="');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.disableTags += 1;
|
this.disableTags += 1;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "👋",
|
"emoji": "👋",
|
||||||
"previous": "&fY3YUKPuH/wqOgKPVNJu1vWEHCXf5fToL2qiVXMRmxc=.sha256"
|
"previous": "&5NkMRSgcMqCYF3xcLOBmaytkoxfV9zx4br7JladKPTs=.sha256"
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
async function main() {
|
|
||||||
await app.setDocument(utf8Decode(getFile('index.html')));
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
1
apps/welcome/gitea.svg
Normal file
1
apps/welcome/gitea.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
@@ -47,8 +47,10 @@
|
|||||||
<a
|
<a
|
||||||
class="w3-button w3-black w3-padding-large"
|
class="w3-button w3-black w3-padding-large"
|
||||||
href="https://dev.tildefriends.net/cory/tildefriends"
|
href="https://dev.tildefriends.net/cory/tildefriends"
|
||||||
><i class="fa fa-mug-hot"></i> Development</a
|
|
||||||
>
|
>
|
||||||
|
<img src="gitea.svg" style="height: 1em; margin: 0" />
|
||||||
|
Development
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
class="w3-button w3-black w3-padding-large"
|
class="w3-button w3-black w3-padding-large"
|
||||||
href="https://docs.tildefriends.net/"
|
href="https://docs.tildefriends.net/"
|
||||||
@@ -76,7 +78,8 @@
|
|||||||
<h2>First-time user checklist:</h2>
|
<h2>First-time user checklist:</h2>
|
||||||
<ol type="1" style="text-align: left">
|
<ol type="1" style="text-align: left">
|
||||||
<li>
|
<li>
|
||||||
<a href="https://dev.tildefriends.net/cory/tildefriends/releases"
|
<a
|
||||||
|
href="https://dev.tildefriends.net/cory/tildefriends/releases/latest"
|
||||||
>Download</a
|
>Download</a
|
||||||
>
|
>
|
||||||
Tilde Friends or use
|
Tilde Friends or use
|
||||||
@@ -84,7 +87,7 @@
|
|||||||
>https://www.tildefriends.net/</a
|
>https://www.tildefriends.net/</a
|
||||||
>.
|
>.
|
||||||
<div class="w3-cell-row">
|
<div class="w3-cell-row">
|
||||||
<div class="w3-container w3-cell">
|
<div class="w3-container w3-cell w3-mobile">
|
||||||
<h3>Mobile</h3>
|
<h3>Mobile</h3>
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
@@ -113,7 +116,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>Just launch the app.</p>
|
<p>Just launch the app.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w3-container w3-cell">
|
<div class="w3-container w3-cell w3-mobile">
|
||||||
<h3>Web</h3>
|
<h3>Web</h3>
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
@@ -128,8 +131,19 @@
|
|||||||
>
|
>
|
||||||
to take it for a spin right away.
|
to take it for a spin right away.
|
||||||
</p>
|
</p>
|
||||||
|
<h3>PeachCloud</h3>
|
||||||
|
<p>
|
||||||
|
Tilde Friends is also a part of 🍑☁️<a
|
||||||
|
href="https://peach-docs.commoninternet.net/"
|
||||||
|
>PeachCloud</a
|
||||||
|
>, which is available on
|
||||||
|
<a href="https://apps.yunohost.org/app/peachpub"
|
||||||
|
>YunoHost</a
|
||||||
|
>
|
||||||
|
for accessible self-hosting.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w3-container w3-cell">
|
<div class="w3-container w3-cell w3-mobile">
|
||||||
<h3>Desktop</h3>
|
<h3>Desktop</h3>
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
|
8
apps/wiki/lit-all.min.js
vendored
8
apps/wiki/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1050,6 +1050,8 @@ function save(save_to) {
|
|||||||
|
|
||||||
if (save_path != window.location.pathname) {
|
if (save_path != window.location.pathname) {
|
||||||
alert('Saved to ' + save_path + '.');
|
alert('Saved to ' + save_path + '.');
|
||||||
|
} else if (!gFiles['app.js']) {
|
||||||
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
reconnect(save_path);
|
reconnect(save_path);
|
||||||
}
|
}
|
||||||
|
@@ -25,14 +25,14 @@
|
|||||||
}:
|
}:
|
||||||
pkgs.stdenv.mkDerivation rec {
|
pkgs.stdenv.mkDerivation rec {
|
||||||
pname = "tildefriends";
|
pname = "tildefriends";
|
||||||
version = "0.0.31";
|
version = "0.0.32";
|
||||||
|
|
||||||
src = pkgs.fetchFromGitea {
|
src = pkgs.fetchFromGitea {
|
||||||
domain = "dev.tildefriends.net";
|
domain = "dev.tildefriends.net";
|
||||||
owner = "cory";
|
owner = "cory";
|
||||||
repo = "tildefriends";
|
repo = "tildefriends";
|
||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
hash = "sha256-c2ZKVNikI5jN5GQuvp7S53qqnRZniSrJMF1FUZdVNPI=";
|
hash = "sha256-Dk0NOEQIg2LeENySK0+MgpZEtfsClGq6dZL+eOOpE0U=";
|
||||||
fetchSubmodules = true;
|
fetchSubmodules = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
229
deps/codemirror_src/package-lock.json
generated
vendored
229
deps/codemirror_src/package-lock.json
generated
vendored
@@ -92,9 +92,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/language": {
|
"node_modules/@codemirror/language": {
|
||||||
"version": "6.11.1",
|
"version": "6.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz",
|
||||||
"integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==",
|
"integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
"@codemirror/view": "^6.23.0",
|
"@codemirror/view": "^6.23.0",
|
||||||
@@ -144,9 +144,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/view": {
|
"node_modules/@codemirror/view": {
|
||||||
"version": "6.37.2",
|
"version": "6.38.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.2.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz",
|
||||||
"integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==",
|
"integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.5.0",
|
"@codemirror/state": "^6.5.0",
|
||||||
"crelt": "^1.0.6",
|
"crelt": "^1.0.6",
|
||||||
@@ -155,17 +155,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.8",
|
"version": "0.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
|
||||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/set-array": "^1.2.1",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
|
||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
@@ -177,19 +173,10 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/set-array": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/source-map": {
|
"node_modules/@jridgewell/source-map": {
|
||||||
"version": "0.3.6",
|
"version": "0.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
|
||||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
"integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
@@ -197,15 +184,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
||||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.25",
|
"version": "0.3.29",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
|
||||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@@ -218,9 +205,9 @@
|
|||||||
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
},
|
},
|
||||||
"node_modules/@lezer/css": {
|
"node_modules/@lezer/css": {
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
|
||||||
"integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==",
|
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.2.0",
|
"@lezer/common": "^1.2.0",
|
||||||
"@lezer/highlight": "^1.0.0",
|
"@lezer/highlight": "^1.0.0",
|
||||||
@@ -345,9 +332,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz",
|
||||||
"integrity": "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==",
|
"integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -357,9 +344,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz",
|
||||||
"integrity": "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==",
|
"integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -369,9 +356,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz",
|
||||||
"integrity": "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==",
|
"integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -381,9 +368,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz",
|
||||||
"integrity": "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==",
|
"integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -393,9 +380,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz",
|
||||||
"integrity": "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==",
|
"integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -405,9 +392,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz",
|
||||||
"integrity": "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==",
|
"integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -417,9 +404,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz",
|
||||||
"integrity": "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==",
|
"integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -429,9 +416,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz",
|
||||||
"integrity": "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==",
|
"integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -441,9 +428,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz",
|
||||||
"integrity": "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==",
|
"integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -453,9 +440,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz",
|
||||||
"integrity": "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==",
|
"integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -465,9 +452,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz",
|
||||||
"integrity": "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==",
|
"integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -477,9 +464,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz",
|
||||||
"integrity": "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==",
|
"integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -489,9 +476,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz",
|
||||||
"integrity": "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==",
|
"integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -501,9 +488,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz",
|
||||||
"integrity": "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==",
|
"integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -513,9 +500,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz",
|
||||||
"integrity": "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==",
|
"integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -525,9 +512,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz",
|
||||||
"integrity": "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==",
|
"integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -537,9 +524,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz",
|
||||||
"integrity": "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==",
|
"integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -549,9 +536,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz",
|
||||||
"integrity": "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==",
|
"integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -561,9 +548,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz",
|
||||||
"integrity": "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==",
|
"integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -573,9 +560,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz",
|
||||||
"integrity": "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==",
|
"integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -707,9 +694,9 @@
|
|||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -746,9 +733,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.44.0",
|
"version": "4.45.1",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz",
|
||||||
"integrity": "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==",
|
"integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
},
|
},
|
||||||
@@ -760,26 +747,26 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.44.0",
|
"@rollup/rollup-android-arm-eabi": "4.45.1",
|
||||||
"@rollup/rollup-android-arm64": "4.44.0",
|
"@rollup/rollup-android-arm64": "4.45.1",
|
||||||
"@rollup/rollup-darwin-arm64": "4.44.0",
|
"@rollup/rollup-darwin-arm64": "4.45.1",
|
||||||
"@rollup/rollup-darwin-x64": "4.44.0",
|
"@rollup/rollup-darwin-x64": "4.45.1",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.44.0",
|
"@rollup/rollup-freebsd-arm64": "4.45.1",
|
||||||
"@rollup/rollup-freebsd-x64": "4.44.0",
|
"@rollup/rollup-freebsd-x64": "4.45.1",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.44.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.45.1",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.44.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.45.1",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.44.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.45.1",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.44.0",
|
"@rollup/rollup-linux-arm64-musl": "4.45.1",
|
||||||
"@rollup/rollup-linux-loongarch64-gnu": "4.44.0",
|
"@rollup/rollup-linux-loongarch64-gnu": "4.45.1",
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.44.0",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.45.1",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.44.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.45.1",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.44.0",
|
"@rollup/rollup-linux-riscv64-musl": "4.45.1",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.44.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.45.1",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.44.0",
|
"@rollup/rollup-linux-x64-gnu": "4.45.1",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.44.0",
|
"@rollup/rollup-linux-x64-musl": "4.45.1",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.44.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.45.1",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.44.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.45.1",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.44.0",
|
"@rollup/rollup-win32-x64-msvc": "4.45.1",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
8
deps/lit/lit-all.min.js
vendored
8
deps/lit/lit-all.min.js
vendored
File diff suppressed because one or more lines are too long
2
deps/lit/lit-all.min.js.map
vendored
2
deps/lit/lit-all.min.js.map
vendored
File diff suppressed because one or more lines are too long
2
deps/openssl_src
vendored
2
deps/openssl_src
vendored
Submodule deps/openssl_src updated: 636dfadc70...aea7aaf2ab
2
deps/picohttpparser
vendored
2
deps/picohttpparser
vendored
Submodule deps/picohttpparser updated: f8d0513f1a...f8326098f6
2
deps/speedscope/index.html
vendored
2
deps/speedscope/index.html
vendored
@@ -11,7 +11,7 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
<link rel="icon" type="image/x-icon" href="favicon-FOKUP5Y5.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="speedscope-VHEG2FVF.js"></script>
|
<script src="speedscope-7YPLLUY2.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
6
deps/speedscope/release.txt
vendored
6
deps/speedscope/release.txt
vendored
@@ -1,3 +1,3 @@
|
|||||||
speedscope@1.22.2
|
speedscope@1.23.0
|
||||||
Sat Feb 15 13:02:38 PST 2025
|
Sun Jul 6 20:04:28 PDT 2025
|
||||||
1c254dcb3e2b4f6d921340d20e972d9d27b788f4
|
aa9bef50789a2989746b576fff182b6f01dfce6a
|
||||||
|
File diff suppressed because one or more lines are too long
15
deps/sqlite/shell.c
vendored
15
deps/sqlite/shell.c
vendored
@@ -8027,13 +8027,14 @@ SQLITE_EXTENSION_INIT1
|
|||||||
# include <dirent.h>
|
# include <dirent.h>
|
||||||
# include <utime.h>
|
# include <utime.h>
|
||||||
# include <sys/time.h>
|
# include <sys/time.h>
|
||||||
|
# define STRUCT_STAT struct stat
|
||||||
#else
|
#else
|
||||||
# include "windows.h"
|
# include "windows.h"
|
||||||
# include <io.h>
|
# include <io.h>
|
||||||
# include <direct.h>
|
# include <direct.h>
|
||||||
/* # include "test_windirent.h" */
|
/* # include "test_windirent.h" */
|
||||||
# define dirent DIRENT
|
# define dirent DIRENT
|
||||||
# define stat _stat
|
# define STRUCT_STAT struct _stat
|
||||||
# define chmod(path,mode) fileio_chmod(path,mode)
|
# define chmod(path,mode) fileio_chmod(path,mode)
|
||||||
# define mkdir(path,mode) fileio_mkdir(path)
|
# define mkdir(path,mode) fileio_mkdir(path)
|
||||||
#endif
|
#endif
|
||||||
@@ -8224,7 +8225,7 @@ LPWSTR utf8_to_utf16(const char *z){
|
|||||||
*/
|
*/
|
||||||
static void statTimesToUtc(
|
static void statTimesToUtc(
|
||||||
const char *zPath,
|
const char *zPath,
|
||||||
struct stat *pStatBuf
|
STRUCT_STAT *pStatBuf
|
||||||
){
|
){
|
||||||
HANDLE hFindFile;
|
HANDLE hFindFile;
|
||||||
WIN32_FIND_DATAW fd;
|
WIN32_FIND_DATAW fd;
|
||||||
@@ -8252,7 +8253,7 @@ static void statTimesToUtc(
|
|||||||
*/
|
*/
|
||||||
static int fileStat(
|
static int fileStat(
|
||||||
const char *zPath,
|
const char *zPath,
|
||||||
struct stat *pStatBuf
|
STRUCT_STAT *pStatBuf
|
||||||
){
|
){
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
sqlite3_int64 sz = strlen(zPath);
|
sqlite3_int64 sz = strlen(zPath);
|
||||||
@@ -8276,7 +8277,7 @@ static int fileStat(
|
|||||||
*/
|
*/
|
||||||
static int fileLinkStat(
|
static int fileLinkStat(
|
||||||
const char *zPath,
|
const char *zPath,
|
||||||
struct stat *pStatBuf
|
STRUCT_STAT *pStatBuf
|
||||||
){
|
){
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
return fileStat(zPath, pStatBuf);
|
return fileStat(zPath, pStatBuf);
|
||||||
@@ -8309,7 +8310,7 @@ static int makeDirectory(
|
|||||||
int i = 1;
|
int i = 1;
|
||||||
|
|
||||||
while( rc==SQLITE_OK ){
|
while( rc==SQLITE_OK ){
|
||||||
struct stat sStat;
|
STRUCT_STAT sStat;
|
||||||
int rc2;
|
int rc2;
|
||||||
|
|
||||||
for(; zCopy[i]!='/' && i<nCopy; i++);
|
for(; zCopy[i]!='/' && i<nCopy; i++);
|
||||||
@@ -8359,7 +8360,7 @@ static int writeFile(
|
|||||||
** be an error though - if there is already a directory at the same
|
** be an error though - if there is already a directory at the same
|
||||||
** path and either the permissions already match or can be changed
|
** path and either the permissions already match or can be changed
|
||||||
** to do so using chmod(), it is not an error. */
|
** to do so using chmod(), it is not an error. */
|
||||||
struct stat sStat;
|
STRUCT_STAT sStat;
|
||||||
if( errno!=EEXIST
|
if( errno!=EEXIST
|
||||||
|| 0!=fileStat(zFile, &sStat)
|
|| 0!=fileStat(zFile, &sStat)
|
||||||
|| !S_ISDIR(sStat.st_mode)
|
|| !S_ISDIR(sStat.st_mode)
|
||||||
@@ -8561,7 +8562,7 @@ struct fsdir_cursor {
|
|||||||
const char *zBase;
|
const char *zBase;
|
||||||
int nBase;
|
int nBase;
|
||||||
|
|
||||||
struct stat sStat; /* Current lstat() results */
|
STRUCT_STAT sStat; /* Current lstat() results */
|
||||||
char *zPath; /* Path to current entry */
|
char *zPath; /* Path to current entry */
|
||||||
sqlite3_int64 iRowid; /* Current rowid */
|
sqlite3_int64 iRowid; /* Current rowid */
|
||||||
};
|
};
|
||||||
|
359
deps/sqlite/sqlite3.c
vendored
359
deps/sqlite/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
176
deps/sqlite/sqlite3.h
vendored
176
deps/sqlite/sqlite3.h
vendored
@@ -146,9 +146,9 @@ extern "C" {
|
|||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.50.1"
|
#define SQLITE_VERSION "3.50.3"
|
||||||
#define SQLITE_VERSION_NUMBER 3050001
|
#define SQLITE_VERSION_NUMBER 3050003
|
||||||
#define SQLITE_SOURCE_ID "2025-06-06 14:52:32 b77dc5e0f596d2140d9ac682b2893ff65d3a4140aa86067a3efebe29dc914c95"
|
#define SQLITE_SOURCE_ID "2025-07-17 13:25:10 3ce993b8657d6d9deda380a93cdd6404a8c8ba1b185b2bc423703e41ae5f2543"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
@@ -4079,7 +4079,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
|
|||||||
**
|
**
|
||||||
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
|
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
|
||||||
** database filename D with corresponding journal file J and WAL file W and
|
** database filename D with corresponding journal file J and WAL file W and
|
||||||
** with N URI parameters key/values pairs in the array P. The result from
|
** an array P of N URI Key/Value pairs. The result from
|
||||||
** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that
|
** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that
|
||||||
** is safe to pass to routines like:
|
** is safe to pass to routines like:
|
||||||
** <ul>
|
** <ul>
|
||||||
@@ -4760,7 +4760,7 @@ typedef struct sqlite3_context sqlite3_context;
|
|||||||
** METHOD: sqlite3_stmt
|
** METHOD: sqlite3_stmt
|
||||||
**
|
**
|
||||||
** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
|
** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
|
||||||
** literals may be replaced by a [parameter] that matches one of following
|
** literals may be replaced by a [parameter] that matches one of the following
|
||||||
** templates:
|
** templates:
|
||||||
**
|
**
|
||||||
** <ul>
|
** <ul>
|
||||||
@@ -4805,7 +4805,7 @@ typedef struct sqlite3_context sqlite3_context;
|
|||||||
**
|
**
|
||||||
** [[byte-order determination rules]] ^The byte-order of
|
** [[byte-order determination rules]] ^The byte-order of
|
||||||
** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF)
|
** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF)
|
||||||
** found in first character, which is removed, or in the absence of a BOM
|
** found in the first character, which is removed, or in the absence of a BOM
|
||||||
** the byte order is the native byte order of the host
|
** the byte order is the native byte order of the host
|
||||||
** machine for sqlite3_bind_text16() or the byte order specified in
|
** machine for sqlite3_bind_text16() or the byte order specified in
|
||||||
** the 6th parameter for sqlite3_bind_text64().)^
|
** the 6th parameter for sqlite3_bind_text64().)^
|
||||||
@@ -4825,7 +4825,7 @@ typedef struct sqlite3_context sqlite3_context;
|
|||||||
** or sqlite3_bind_text16() or sqlite3_bind_text64() then
|
** or sqlite3_bind_text16() or sqlite3_bind_text64() then
|
||||||
** that parameter must be the byte offset
|
** that parameter must be the byte offset
|
||||||
** where the NUL terminator would occur assuming the string were NUL
|
** where the NUL terminator would occur assuming the string were NUL
|
||||||
** terminated. If any NUL characters occurs at byte offsets less than
|
** terminated. If any NUL characters occur at byte offsets less than
|
||||||
** the value of the fourth parameter then the resulting string value will
|
** the value of the fourth parameter then the resulting string value will
|
||||||
** contain embedded NULs. The result of expressions involving strings
|
** contain embedded NULs. The result of expressions involving strings
|
||||||
** with embedded NULs is undefined.
|
** with embedded NULs is undefined.
|
||||||
@@ -5037,7 +5037,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
|
|||||||
** METHOD: sqlite3_stmt
|
** METHOD: sqlite3_stmt
|
||||||
**
|
**
|
||||||
** ^These routines provide a means to determine the database, table, and
|
** ^These routines provide a means to determine the database, table, and
|
||||||
** table column that is the origin of a particular result column in
|
** table column that is the origin of a particular result column in a
|
||||||
** [SELECT] statement.
|
** [SELECT] statement.
|
||||||
** ^The name of the database or table or column can be returned as
|
** ^The name of the database or table or column can be returned as
|
||||||
** either a UTF-8 or UTF-16 string. ^The _database_ routines return
|
** either a UTF-8 or UTF-16 string. ^The _database_ routines return
|
||||||
@@ -5606,8 +5606,8 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
|
|||||||
**
|
**
|
||||||
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
|
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
|
||||||
** all application-defined SQL functions that do not need to be
|
** all application-defined SQL functions that do not need to be
|
||||||
** used inside of triggers, view, CHECK constraints, or other elements of
|
** used inside of triggers, views, CHECK constraints, or other elements of
|
||||||
** the database schema. This flags is especially recommended for SQL
|
** the database schema. This flag is especially recommended for SQL
|
||||||
** functions that have side effects or reveal internal application state.
|
** functions that have side effects or reveal internal application state.
|
||||||
** Without this flag, an attacker might be able to modify the schema of
|
** Without this flag, an attacker might be able to modify the schema of
|
||||||
** a database file to include invocations of the function with parameters
|
** a database file to include invocations of the function with parameters
|
||||||
@@ -5638,7 +5638,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
|
|||||||
** [user-defined window functions|available here].
|
** [user-defined window functions|available here].
|
||||||
**
|
**
|
||||||
** ^(If the final parameter to sqlite3_create_function_v2() or
|
** ^(If the final parameter to sqlite3_create_function_v2() or
|
||||||
** sqlite3_create_window_function() is not NULL, then it is destructor for
|
** sqlite3_create_window_function() is not NULL, then it is the destructor for
|
||||||
** the application data pointer. The destructor is invoked when the function
|
** the application data pointer. The destructor is invoked when the function
|
||||||
** is deleted, either by being overloaded or when the database connection
|
** is deleted, either by being overloaded or when the database connection
|
||||||
** closes.)^ ^The destructor is also invoked if the call to
|
** closes.)^ ^The destructor is also invoked if the call to
|
||||||
@@ -6038,7 +6038,7 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
|
|||||||
** METHOD: sqlite3_value
|
** METHOD: sqlite3_value
|
||||||
**
|
**
|
||||||
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
|
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
|
||||||
** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
|
** object V and returns a pointer to that copy. ^The [sqlite3_value] returned
|
||||||
** is a [protected sqlite3_value] object even if the input is not.
|
** is a [protected sqlite3_value] object even if the input is not.
|
||||||
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
|
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
|
||||||
** memory allocation fails. ^If V is a [pointer value], then the result
|
** memory allocation fails. ^If V is a [pointer value], then the result
|
||||||
@@ -6076,7 +6076,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*);
|
|||||||
** allocation error occurs.
|
** allocation error occurs.
|
||||||
**
|
**
|
||||||
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
|
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
|
||||||
** determined by the N parameter on first successful call. Changing the
|
** determined by the N parameter on the first successful call. Changing the
|
||||||
** value of N in any subsequent call to sqlite3_aggregate_context() within
|
** value of N in any subsequent call to sqlite3_aggregate_context() within
|
||||||
** the same aggregate function instance will not resize the memory
|
** the same aggregate function instance will not resize the memory
|
||||||
** allocation.)^ Within the xFinal callback, it is customary to set
|
** allocation.)^ Within the xFinal callback, it is customary to set
|
||||||
@@ -6238,7 +6238,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
|
|||||||
**
|
**
|
||||||
** Security Warning: These interfaces should not be exposed in scripting
|
** Security Warning: These interfaces should not be exposed in scripting
|
||||||
** languages or in other circumstances where it might be possible for an
|
** languages or in other circumstances where it might be possible for an
|
||||||
** an attacker to invoke them. Any agent that can invoke these interfaces
|
** attacker to invoke them. Any agent that can invoke these interfaces
|
||||||
** can probably also take control of the process.
|
** can probably also take control of the process.
|
||||||
**
|
**
|
||||||
** Database connection client data is only available for SQLite
|
** Database connection client data is only available for SQLite
|
||||||
@@ -6352,7 +6352,7 @@ typedef void (*sqlite3_destructor_type)(void*);
|
|||||||
** pointed to by the 2nd parameter are taken as the application-defined
|
** pointed to by the 2nd parameter are taken as the application-defined
|
||||||
** function result. If the 3rd parameter is non-negative, then it
|
** function result. If the 3rd parameter is non-negative, then it
|
||||||
** must be the byte offset into the string where the NUL terminator would
|
** must be the byte offset into the string where the NUL terminator would
|
||||||
** appear if the string where NUL terminated. If any NUL characters occur
|
** appear if the string were NUL terminated. If any NUL characters occur
|
||||||
** in the string at a byte offset that is less than the value of the 3rd
|
** in the string at a byte offset that is less than the value of the 3rd
|
||||||
** parameter, then the resulting string will contain embedded NULs and the
|
** parameter, then the resulting string will contain embedded NULs and the
|
||||||
** result of expressions operating on strings with embedded NULs is undefined.
|
** result of expressions operating on strings with embedded NULs is undefined.
|
||||||
@@ -6410,7 +6410,7 @@ typedef void (*sqlite3_destructor_type)(void*);
|
|||||||
** string and preferably a string literal. The sqlite3_result_pointer()
|
** string and preferably a string literal. The sqlite3_result_pointer()
|
||||||
** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
|
** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
|
||||||
**
|
**
|
||||||
** If these routines are called from within the different thread
|
** If these routines are called from within a different thread
|
||||||
** than the one containing the application-defined function that received
|
** than the one containing the application-defined function that received
|
||||||
** the [sqlite3_context] pointer, the results are undefined.
|
** the [sqlite3_context] pointer, the results are undefined.
|
||||||
*/
|
*/
|
||||||
@@ -6816,7 +6816,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
|
|||||||
** METHOD: sqlite3
|
** METHOD: sqlite3
|
||||||
**
|
**
|
||||||
** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
|
** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
|
||||||
** for the N-th database on database connection D, or a NULL pointer of N is
|
** for the N-th database on database connection D, or a NULL pointer if N is
|
||||||
** out of range. An N value of 0 means the main database file. An N of 1 is
|
** out of range. An N value of 0 means the main database file. An N of 1 is
|
||||||
** the "temp" schema. Larger values of N correspond to various ATTACH-ed
|
** the "temp" schema. Larger values of N correspond to various ATTACH-ed
|
||||||
** databases.
|
** databases.
|
||||||
@@ -6911,7 +6911,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
|
|||||||
** <dd>The SQLITE_TXN_READ state means that the database is currently
|
** <dd>The SQLITE_TXN_READ state means that the database is currently
|
||||||
** in a read transaction. Content has been read from the database file
|
** in a read transaction. Content has been read from the database file
|
||||||
** but nothing in the database file has changed. The transaction state
|
** but nothing in the database file has changed. The transaction state
|
||||||
** will advanced to SQLITE_TXN_WRITE if any changes occur and there are
|
** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are
|
||||||
** no other conflicting concurrent write transactions. The transaction
|
** no other conflicting concurrent write transactions. The transaction
|
||||||
** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
|
** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
|
||||||
** [COMMIT].</dd>
|
** [COMMIT].</dd>
|
||||||
@@ -6920,7 +6920,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
|
|||||||
** <dd>The SQLITE_TXN_WRITE state means that the database is currently
|
** <dd>The SQLITE_TXN_WRITE state means that the database is currently
|
||||||
** in a write transaction. Content has been written to the database file
|
** in a write transaction. Content has been written to the database file
|
||||||
** but has not yet committed. The transaction state will change to
|
** but has not yet committed. The transaction state will change to
|
||||||
** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
|
** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_TXN_NONE 0
|
#define SQLITE_TXN_NONE 0
|
||||||
#define SQLITE_TXN_READ 1
|
#define SQLITE_TXN_READ 1
|
||||||
@@ -7201,7 +7201,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
|
|||||||
** CAPI3REF: Impose A Limit On Heap Size
|
** CAPI3REF: Impose A Limit On Heap Size
|
||||||
**
|
**
|
||||||
** These interfaces impose limits on the amount of heap memory that will be
|
** These interfaces impose limits on the amount of heap memory that will be
|
||||||
** by all database connections within a single process.
|
** used by all database connections within a single process.
|
||||||
**
|
**
|
||||||
** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
|
** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
|
||||||
** soft limit on the amount of heap memory that may be allocated by SQLite.
|
** soft limit on the amount of heap memory that may be allocated by SQLite.
|
||||||
@@ -7259,7 +7259,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
|
|||||||
** </ul>)^
|
** </ul>)^
|
||||||
**
|
**
|
||||||
** The circumstances under which SQLite will enforce the heap limits may
|
** The circumstances under which SQLite will enforce the heap limits may
|
||||||
** changes in future releases of SQLite.
|
** change in future releases of SQLite.
|
||||||
*/
|
*/
|
||||||
SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
|
SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
|
||||||
SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N);
|
SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N);
|
||||||
@@ -7374,8 +7374,8 @@ SQLITE_API int sqlite3_table_column_metadata(
|
|||||||
** ^The entry point is zProc.
|
** ^The entry point is zProc.
|
||||||
** ^(zProc may be 0, in which case SQLite will try to come up with an
|
** ^(zProc may be 0, in which case SQLite will try to come up with an
|
||||||
** entry point name on its own. It first tries "sqlite3_extension_init".
|
** entry point name on its own. It first tries "sqlite3_extension_init".
|
||||||
** If that does not work, it constructs a name "sqlite3_X_init" where the
|
** If that does not work, it constructs a name "sqlite3_X_init" where
|
||||||
** X is consists of the lower-case equivalent of all ASCII alphabetic
|
** X consists of the lower-case equivalent of all ASCII alphabetic
|
||||||
** characters in the filename from the last "/" to the first following
|
** characters in the filename from the last "/" to the first following
|
||||||
** "." and omitting any initial "lib".)^
|
** "." and omitting any initial "lib".)^
|
||||||
** ^The sqlite3_load_extension() interface returns
|
** ^The sqlite3_load_extension() interface returns
|
||||||
@@ -7446,7 +7446,7 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
|
|||||||
** ^(Even though the function prototype shows that xEntryPoint() takes
|
** ^(Even though the function prototype shows that xEntryPoint() takes
|
||||||
** no arguments and returns void, SQLite invokes xEntryPoint() with three
|
** no arguments and returns void, SQLite invokes xEntryPoint() with three
|
||||||
** arguments and expects an integer result as if the signature of the
|
** arguments and expects an integer result as if the signature of the
|
||||||
** entry point where as follows:
|
** entry point were as follows:
|
||||||
**
|
**
|
||||||
** <blockquote><pre>
|
** <blockquote><pre>
|
||||||
** int xEntryPoint(
|
** int xEntryPoint(
|
||||||
@@ -7610,7 +7610,7 @@ struct sqlite3_module {
|
|||||||
** virtual table and might not be checked again by the byte code.)^ ^(The
|
** virtual table and might not be checked again by the byte code.)^ ^(The
|
||||||
** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
|
** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
|
||||||
** is left in its default setting of false, the constraint will always be
|
** is left in its default setting of false, the constraint will always be
|
||||||
** checked separately in byte code. If the omit flag is change to true, then
|
** checked separately in byte code. If the omit flag is changed to true, then
|
||||||
** the constraint may or may not be checked in byte code. In other words,
|
** the constraint may or may not be checked in byte code. In other words,
|
||||||
** when the omit flag is true there is no guarantee that the constraint will
|
** when the omit flag is true there is no guarantee that the constraint will
|
||||||
** not be checked again using byte code.)^
|
** not be checked again using byte code.)^
|
||||||
@@ -7636,7 +7636,7 @@ struct sqlite3_module {
|
|||||||
** The xBestIndex method may optionally populate the idxFlags field with a
|
** The xBestIndex method may optionally populate the idxFlags field with a
|
||||||
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
|
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
|
||||||
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
|
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
|
||||||
** output to show the idxNum has hex instead of as decimal. Another flag is
|
** output to show the idxNum as hex instead of as decimal. Another flag is
|
||||||
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
|
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
|
||||||
** return at most one row.
|
** return at most one row.
|
||||||
**
|
**
|
||||||
@@ -7777,7 +7777,7 @@ struct sqlite3_index_info {
|
|||||||
** the implementation of the [virtual table module]. ^The fourth
|
** the implementation of the [virtual table module]. ^The fourth
|
||||||
** parameter is an arbitrary client data pointer that is passed through
|
** parameter is an arbitrary client data pointer that is passed through
|
||||||
** into the [xCreate] and [xConnect] methods of the virtual table module
|
** into the [xCreate] and [xConnect] methods of the virtual table module
|
||||||
** when a new virtual table is be being created or reinitialized.
|
** when a new virtual table is being created or reinitialized.
|
||||||
**
|
**
|
||||||
** ^The sqlite3_create_module_v2() interface has a fifth parameter which
|
** ^The sqlite3_create_module_v2() interface has a fifth parameter which
|
||||||
** is a pointer to a destructor for the pClientData. ^SQLite will
|
** is a pointer to a destructor for the pClientData. ^SQLite will
|
||||||
@@ -7942,7 +7942,7 @@ typedef struct sqlite3_blob sqlite3_blob;
|
|||||||
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
|
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
|
||||||
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
|
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
|
||||||
** the API is not misused, it is always safe to call [sqlite3_blob_close()]
|
** the API is not misused, it is always safe to call [sqlite3_blob_close()]
|
||||||
** on *ppBlob after this function it returns.
|
** on *ppBlob after this function returns.
|
||||||
**
|
**
|
||||||
** This function fails with SQLITE_ERROR if any of the following are true:
|
** This function fails with SQLITE_ERROR if any of the following are true:
|
||||||
** <ul>
|
** <ul>
|
||||||
@@ -8062,7 +8062,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
|
|||||||
**
|
**
|
||||||
** ^Returns the size in bytes of the BLOB accessible via the
|
** ^Returns the size in bytes of the BLOB accessible via the
|
||||||
** successfully opened [BLOB handle] in its only argument. ^The
|
** successfully opened [BLOB handle] in its only argument. ^The
|
||||||
** incremental blob I/O routines can only read or overwriting existing
|
** incremental blob I/O routines can only read or overwrite existing
|
||||||
** blob content; they cannot change the size of a blob.
|
** blob content; they cannot change the size of a blob.
|
||||||
**
|
**
|
||||||
** This routine only works on a [BLOB handle] which has been created
|
** This routine only works on a [BLOB handle] which has been created
|
||||||
@@ -8212,7 +8212,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
|
|||||||
** ^The sqlite3_mutex_alloc() routine allocates a new
|
** ^The sqlite3_mutex_alloc() routine allocates a new
|
||||||
** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
|
** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
|
||||||
** routine returns NULL if it is unable to allocate the requested
|
** routine returns NULL if it is unable to allocate the requested
|
||||||
** mutex. The argument to sqlite3_mutex_alloc() must one of these
|
** mutex. The argument to sqlite3_mutex_alloc() must be one of these
|
||||||
** integer constants:
|
** integer constants:
|
||||||
**
|
**
|
||||||
** <ul>
|
** <ul>
|
||||||
@@ -8445,7 +8445,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
|
|||||||
** CAPI3REF: Retrieve the mutex for a database connection
|
** CAPI3REF: Retrieve the mutex for a database connection
|
||||||
** METHOD: sqlite3
|
** METHOD: sqlite3
|
||||||
**
|
**
|
||||||
** ^This interface returns a pointer the [sqlite3_mutex] object that
|
** ^This interface returns a pointer to the [sqlite3_mutex] object that
|
||||||
** serializes access to the [database connection] given in the argument
|
** serializes access to the [database connection] given in the argument
|
||||||
** when the [threading mode] is Serialized.
|
** when the [threading mode] is Serialized.
|
||||||
** ^If the [threading mode] is Single-thread or Multi-thread then this
|
** ^If the [threading mode] is Single-thread or Multi-thread then this
|
||||||
@@ -8568,7 +8568,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
|||||||
** CAPI3REF: SQL Keyword Checking
|
** CAPI3REF: SQL Keyword Checking
|
||||||
**
|
**
|
||||||
** These routines provide access to the set of SQL language keywords
|
** These routines provide access to the set of SQL language keywords
|
||||||
** recognized by SQLite. Applications can uses these routines to determine
|
** recognized by SQLite. Applications can use these routines to determine
|
||||||
** whether or not a specific identifier needs to be escaped (for example,
|
** whether or not a specific identifier needs to be escaped (for example,
|
||||||
** by enclosing in double-quotes) so as not to confuse the parser.
|
** by enclosing in double-quotes) so as not to confuse the parser.
|
||||||
**
|
**
|
||||||
@@ -8736,7 +8736,7 @@ SQLITE_API void sqlite3_str_reset(sqlite3_str*);
|
|||||||
** content of the dynamic string under construction in X. The value
|
** content of the dynamic string under construction in X. The value
|
||||||
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
|
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
|
||||||
** and might be freed or altered by any subsequent method on the same
|
** and might be freed or altered by any subsequent method on the same
|
||||||
** [sqlite3_str] object. Applications must not used the pointer returned
|
** [sqlite3_str] object. Applications must not use the pointer returned by
|
||||||
** [sqlite3_str_value(X)] after any subsequent method call on the same
|
** [sqlite3_str_value(X)] after any subsequent method call on the same
|
||||||
** object. ^Applications may change the content of the string returned
|
** object. ^Applications may change the content of the string returned
|
||||||
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
|
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
|
||||||
@@ -8822,7 +8822,7 @@ SQLITE_API int sqlite3_status64(
|
|||||||
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
|
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
|
||||||
** buffer and where forced to overflow to [sqlite3_malloc()]. The
|
** buffer and where forced to overflow to [sqlite3_malloc()]. The
|
||||||
** returned value includes allocations that overflowed because they
|
** returned value includes allocations that overflowed because they
|
||||||
** where too large (they were larger than the "sz" parameter to
|
** were too large (they were larger than the "sz" parameter to
|
||||||
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
|
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
|
||||||
** no space was left in the page cache.</dd>)^
|
** no space was left in the page cache.</dd>)^
|
||||||
**
|
**
|
||||||
@@ -8906,28 +8906,29 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
|||||||
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
|
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
|
||||||
** <dd>This parameter returns the number of malloc attempts that were
|
** <dd>This parameter returns the number of malloc attempts that were
|
||||||
** satisfied using lookaside memory. Only the high-water value is meaningful;
|
** satisfied using lookaside memory. Only the high-water value is meaningful;
|
||||||
** the current value is always zero.)^
|
** the current value is always zero.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]]
|
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]]
|
||||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
|
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
|
||||||
** <dd>This parameter returns the number malloc attempts that might have
|
** <dd>This parameter returns the number of malloc attempts that might have
|
||||||
** been satisfied using lookaside memory but failed due to the amount of
|
** been satisfied using lookaside memory but failed due to the amount of
|
||||||
** memory requested being larger than the lookaside slot size.
|
** memory requested being larger than the lookaside slot size.
|
||||||
** Only the high-water value is meaningful;
|
** Only the high-water value is meaningful;
|
||||||
** the current value is always zero.)^
|
** the current value is always zero.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]]
|
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]]
|
||||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
|
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
|
||||||
** <dd>This parameter returns the number malloc attempts that might have
|
** <dd>This parameter returns the number of malloc attempts that might have
|
||||||
** been satisfied using lookaside memory but failed due to all lookaside
|
** been satisfied using lookaside memory but failed due to all lookaside
|
||||||
** memory already being in use.
|
** memory already being in use.
|
||||||
** Only the high-water value is meaningful;
|
** Only the high-water value is meaningful;
|
||||||
** the current value is always zero.)^
|
** the current value is always zero.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
|
** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
|
||||||
** <dd>This parameter returns the approximate number of bytes of heap
|
** <dd>This parameter returns the approximate number of bytes of heap
|
||||||
** memory used by all pager caches associated with the database connection.)^
|
** memory used by all pager caches associated with the database connection.)^
|
||||||
** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0.
|
** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0.
|
||||||
|
** </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]]
|
** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]]
|
||||||
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt>
|
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt>
|
||||||
@@ -8936,10 +8937,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
|||||||
** memory used by that pager cache is divided evenly between the attached
|
** memory used by that pager cache is divided evenly between the attached
|
||||||
** connections.)^ In other words, if none of the pager caches associated
|
** connections.)^ In other words, if none of the pager caches associated
|
||||||
** with the database connection are shared, this request returns the same
|
** with the database connection are shared, this request returns the same
|
||||||
** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are
|
** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are
|
||||||
** shared, the value returned by this call will be smaller than that returned
|
** shared, the value returned by this call will be smaller than that returned
|
||||||
** by DBSTATUS_CACHE_USED. ^The highwater mark associated with
|
** by DBSTATUS_CACHE_USED. ^The highwater mark associated with
|
||||||
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.
|
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt>
|
** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt>
|
||||||
** <dd>This parameter returns the approximate number of bytes of heap
|
** <dd>This parameter returns the approximate number of bytes of heap
|
||||||
@@ -8949,6 +8950,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
|||||||
** schema memory is shared with other database connections due to
|
** schema memory is shared with other database connections due to
|
||||||
** [shared cache mode] being enabled.
|
** [shared cache mode] being enabled.
|
||||||
** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0.
|
** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0.
|
||||||
|
** </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt>
|
** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt>
|
||||||
** <dd>This parameter returns the approximate number of bytes of heap
|
** <dd>This parameter returns the approximate number of bytes of heap
|
||||||
@@ -8985,7 +8987,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
|||||||
** been written to disk in the middle of a transaction due to the page
|
** been written to disk in the middle of a transaction due to the page
|
||||||
** cache overflowing. Transactions are more efficient if they are written
|
** cache overflowing. Transactions are more efficient if they are written
|
||||||
** to disk all at once. When pages spill mid-transaction, that introduces
|
** to disk all at once. When pages spill mid-transaction, that introduces
|
||||||
** additional overhead. This parameter can be used help identify
|
** additional overhead. This parameter can be used to help identify
|
||||||
** inefficiencies that can be resolved by increasing the cache size.
|
** inefficiencies that can be resolved by increasing the cache size.
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
@@ -9056,13 +9058,13 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||||||
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
|
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
|
||||||
** <dd>^This is the number of sort operations that have occurred.
|
** <dd>^This is the number of sort operations that have occurred.
|
||||||
** A non-zero value in this counter may indicate an opportunity to
|
** A non-zero value in this counter may indicate an opportunity to
|
||||||
** improvement performance through careful use of indices.</dd>
|
** improve performance through careful use of indices.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt>
|
** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt>
|
||||||
** <dd>^This is the number of rows inserted into transient indices that
|
** <dd>^This is the number of rows inserted into transient indices that
|
||||||
** were created automatically in order to help joins run faster.
|
** were created automatically in order to help joins run faster.
|
||||||
** A non-zero value in this counter may indicate an opportunity to
|
** A non-zero value in this counter may indicate an opportunity to
|
||||||
** improvement performance by adding permanent indices that do not
|
** improve performance by adding permanent indices that do not
|
||||||
** need to be reinitialized each time the statement is run.</dd>
|
** need to be reinitialized each time the statement is run.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
|
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
|
||||||
@@ -9071,19 +9073,19 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||||||
** to 2147483647. The number of virtual machine operations can be
|
** to 2147483647. The number of virtual machine operations can be
|
||||||
** used as a proxy for the total work done by the prepared statement.
|
** used as a proxy for the total work done by the prepared statement.
|
||||||
** If the number of virtual machine operations exceeds 2147483647
|
** If the number of virtual machine operations exceeds 2147483647
|
||||||
** then the value returned by this statement status code is undefined.
|
** then the value returned by this statement status code is undefined.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
|
** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
|
||||||
** <dd>^This is the number of times that the prepare statement has been
|
** <dd>^This is the number of times that the prepare statement has been
|
||||||
** automatically regenerated due to schema changes or changes to
|
** automatically regenerated due to schema changes or changes to
|
||||||
** [bound parameters] that might affect the query plan.
|
** [bound parameters] that might affect the query plan.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
|
** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
|
||||||
** <dd>^This is the number of times that the prepared statement has
|
** <dd>^This is the number of times that the prepared statement has
|
||||||
** been run. A single "run" for the purposes of this counter is one
|
** been run. A single "run" for the purposes of this counter is one
|
||||||
** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()].
|
** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()].
|
||||||
** The counter is incremented on the first [sqlite3_step()] call of each
|
** The counter is incremented on the first [sqlite3_step()] call of each
|
||||||
** cycle.
|
** cycle.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
|
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
|
||||||
** [[SQLITE_STMTSTATUS_FILTER HIT]]
|
** [[SQLITE_STMTSTATUS_FILTER HIT]]
|
||||||
@@ -9093,7 +9095,7 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||||||
** step was bypassed because a Bloom filter returned not-found. The
|
** step was bypassed because a Bloom filter returned not-found. The
|
||||||
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
|
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
|
||||||
** times that the Bloom filter returned a find, and thus the join step
|
** times that the Bloom filter returned a find, and thus the join step
|
||||||
** had to be processed as normal.
|
** had to be processed as normal.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
|
** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
|
||||||
** <dd>^This is the approximate number of bytes of heap memory
|
** <dd>^This is the approximate number of bytes of heap memory
|
||||||
@@ -9198,9 +9200,9 @@ struct sqlite3_pcache_page {
|
|||||||
** SQLite will typically create one cache instance for each open database file,
|
** SQLite will typically create one cache instance for each open database file,
|
||||||
** though this is not guaranteed. ^The
|
** though this is not guaranteed. ^The
|
||||||
** first parameter, szPage, is the size in bytes of the pages that must
|
** first parameter, szPage, is the size in bytes of the pages that must
|
||||||
** be allocated by the cache. ^szPage will always a power of two. ^The
|
** be allocated by the cache. ^szPage will always be a power of two. ^The
|
||||||
** second parameter szExtra is a number of bytes of extra storage
|
** second parameter szExtra is a number of bytes of extra storage
|
||||||
** associated with each page cache entry. ^The szExtra parameter will
|
** associated with each page cache entry. ^The szExtra parameter will be
|
||||||
** a number less than 250. SQLite will use the
|
** a number less than 250. SQLite will use the
|
||||||
** extra szExtra bytes on each page to store metadata about the underlying
|
** extra szExtra bytes on each page to store metadata about the underlying
|
||||||
** database page on disk. The value passed into szExtra depends
|
** database page on disk. The value passed into szExtra depends
|
||||||
@@ -9208,17 +9210,17 @@ struct sqlite3_pcache_page {
|
|||||||
** ^The third argument to xCreate(), bPurgeable, is true if the cache being
|
** ^The third argument to xCreate(), bPurgeable, is true if the cache being
|
||||||
** created will be used to cache database pages of a file stored on disk, or
|
** created will be used to cache database pages of a file stored on disk, or
|
||||||
** false if it is used for an in-memory database. The cache implementation
|
** false if it is used for an in-memory database. The cache implementation
|
||||||
** does not have to do anything special based with the value of bPurgeable;
|
** does not have to do anything special based upon the value of bPurgeable;
|
||||||
** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will
|
** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will
|
||||||
** never invoke xUnpin() except to deliberately delete a page.
|
** never invoke xUnpin() except to deliberately delete a page.
|
||||||
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
|
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
|
||||||
** false will always have the "discard" flag set to true.
|
** false will always have the "discard" flag set to true.
|
||||||
** ^Hence, a cache created with bPurgeable false will
|
** ^Hence, a cache created with bPurgeable set to false will
|
||||||
** never contain any unpinned pages.
|
** never contain any unpinned pages.
|
||||||
**
|
**
|
||||||
** [[the xCachesize() page cache method]]
|
** [[the xCachesize() page cache method]]
|
||||||
** ^(The xCachesize() method may be called at any time by SQLite to set the
|
** ^(The xCachesize() method may be called at any time by SQLite to set the
|
||||||
** suggested maximum cache-size (number of pages stored by) the cache
|
** suggested maximum cache-size (number of pages stored) for the cache
|
||||||
** instance passed as the first argument. This is the value configured using
|
** instance passed as the first argument. This is the value configured using
|
||||||
** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable
|
** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable
|
||||||
** parameter, the implementation is not required to do anything with this
|
** parameter, the implementation is not required to do anything with this
|
||||||
@@ -9245,12 +9247,12 @@ struct sqlite3_pcache_page {
|
|||||||
** implementation must return a pointer to the page buffer with its content
|
** implementation must return a pointer to the page buffer with its content
|
||||||
** intact. If the requested page is not already in the cache, then the
|
** intact. If the requested page is not already in the cache, then the
|
||||||
** cache implementation should use the value of the createFlag
|
** cache implementation should use the value of the createFlag
|
||||||
** parameter to help it determined what action to take:
|
** parameter to help it determine what action to take:
|
||||||
**
|
**
|
||||||
** <table border=1 width=85% align=center>
|
** <table border=1 width=85% align=center>
|
||||||
** <tr><th> createFlag <th> Behavior when page is not already in cache
|
** <tr><th> createFlag <th> Behavior when page is not already in cache
|
||||||
** <tr><td> 0 <td> Do not allocate a new page. Return NULL.
|
** <tr><td> 0 <td> Do not allocate a new page. Return NULL.
|
||||||
** <tr><td> 1 <td> Allocate a new page if it easy and convenient to do so.
|
** <tr><td> 1 <td> Allocate a new page if it is easy and convenient to do so.
|
||||||
** Otherwise return NULL.
|
** Otherwise return NULL.
|
||||||
** <tr><td> 2 <td> Make every effort to allocate a new page. Only return
|
** <tr><td> 2 <td> Make every effort to allocate a new page. Only return
|
||||||
** NULL if allocating a new page is effectively impossible.
|
** NULL if allocating a new page is effectively impossible.
|
||||||
@@ -9267,7 +9269,7 @@ struct sqlite3_pcache_page {
|
|||||||
** as its second argument. If the third parameter, discard, is non-zero,
|
** as its second argument. If the third parameter, discard, is non-zero,
|
||||||
** then the page must be evicted from the cache.
|
** then the page must be evicted from the cache.
|
||||||
** ^If the discard parameter is
|
** ^If the discard parameter is
|
||||||
** zero, then the page may be discarded or retained at the discretion of
|
** zero, then the page may be discarded or retained at the discretion of the
|
||||||
** page cache implementation. ^The page cache implementation
|
** page cache implementation. ^The page cache implementation
|
||||||
** may choose to evict unpinned pages at any time.
|
** may choose to evict unpinned pages at any time.
|
||||||
**
|
**
|
||||||
@@ -9285,7 +9287,7 @@ struct sqlite3_pcache_page {
|
|||||||
** When SQLite calls the xTruncate() method, the cache must discard all
|
** When SQLite calls the xTruncate() method, the cache must discard all
|
||||||
** existing cache entries with page numbers (keys) greater than or equal
|
** existing cache entries with page numbers (keys) greater than or equal
|
||||||
** to the value of the iLimit parameter passed to xTruncate(). If any
|
** to the value of the iLimit parameter passed to xTruncate(). If any
|
||||||
** of these pages are pinned, they are implicitly unpinned, meaning that
|
** of these pages are pinned, they become implicitly unpinned, meaning that
|
||||||
** they can be safely discarded.
|
** they can be safely discarded.
|
||||||
**
|
**
|
||||||
** [[the xDestroy() page cache method]]
|
** [[the xDestroy() page cache method]]
|
||||||
@@ -9465,7 +9467,7 @@ typedef struct sqlite3_backup sqlite3_backup;
|
|||||||
** external process or via a database connection other than the one being
|
** external process or via a database connection other than the one being
|
||||||
** used by the backup operation, then the backup will be automatically
|
** used by the backup operation, then the backup will be automatically
|
||||||
** restarted by the next call to sqlite3_backup_step(). ^If the source
|
** restarted by the next call to sqlite3_backup_step(). ^If the source
|
||||||
** database is modified by the using the same database connection as is used
|
** database is modified by using the same database connection as is used
|
||||||
** by the backup operation, then the backup database is automatically
|
** by the backup operation, then the backup database is automatically
|
||||||
** updated at the same time.
|
** updated at the same time.
|
||||||
**
|
**
|
||||||
@@ -9482,7 +9484,7 @@ typedef struct sqlite3_backup sqlite3_backup;
|
|||||||
** and may not be used following a call to sqlite3_backup_finish().
|
** and may not be used following a call to sqlite3_backup_finish().
|
||||||
**
|
**
|
||||||
** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
|
** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
|
||||||
** sqlite3_backup_step() errors occurred, regardless or whether or not
|
** sqlite3_backup_step() errors occurred, regardless of whether or not
|
||||||
** sqlite3_backup_step() completed.
|
** sqlite3_backup_step() completed.
|
||||||
** ^If an out-of-memory condition or IO error occurred during any prior
|
** ^If an out-of-memory condition or IO error occurred during any prior
|
||||||
** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
|
** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
|
||||||
@@ -9584,7 +9586,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
|||||||
** application receives an SQLITE_LOCKED error, it may call the
|
** application receives an SQLITE_LOCKED error, it may call the
|
||||||
** sqlite3_unlock_notify() method with the blocked connection handle as
|
** sqlite3_unlock_notify() method with the blocked connection handle as
|
||||||
** the first argument to register for a callback that will be invoked
|
** the first argument to register for a callback that will be invoked
|
||||||
** when the blocking connections current transaction is concluded. ^The
|
** when the blocking connection's current transaction is concluded. ^The
|
||||||
** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
|
** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
|
||||||
** call that concludes the blocking connection's transaction.
|
** call that concludes the blocking connection's transaction.
|
||||||
**
|
**
|
||||||
@@ -9604,7 +9606,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
|||||||
** blocked connection already has a registered unlock-notify callback,
|
** blocked connection already has a registered unlock-notify callback,
|
||||||
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
||||||
** called with a NULL pointer as its second argument, then any existing
|
** called with a NULL pointer as its second argument, then any existing
|
||||||
** unlock-notify callback is canceled. ^The blocked connections
|
** unlock-notify callback is canceled. ^The blocked connection's
|
||||||
** unlock-notify callback may also be canceled by closing the blocked
|
** unlock-notify callback may also be canceled by closing the blocked
|
||||||
** connection using [sqlite3_close()].
|
** connection using [sqlite3_close()].
|
||||||
**
|
**
|
||||||
@@ -10002,7 +10004,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
|||||||
** support constraints. In this configuration (which is the default) if
|
** support constraints. In this configuration (which is the default) if
|
||||||
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
|
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
|
||||||
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
|
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
|
||||||
** specified as part of the users SQL statement, regardless of the actual
|
** specified as part of the user's SQL statement, regardless of the actual
|
||||||
** ON CONFLICT mode specified.
|
** ON CONFLICT mode specified.
|
||||||
**
|
**
|
||||||
** If X is non-zero, then the virtual table implementation guarantees
|
** If X is non-zero, then the virtual table implementation guarantees
|
||||||
@@ -10036,7 +10038,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
|||||||
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
||||||
** <dd>Calls of the form
|
** <dd>Calls of the form
|
||||||
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
|
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
|
||||||
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
|
** [xConnect] or [xCreate] methods of a [virtual table] implementation
|
||||||
** identify that virtual table as being safe to use from within triggers
|
** identify that virtual table as being safe to use from within triggers
|
||||||
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
|
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
|
||||||
** virtual table can do no serious harm even if it is controlled by a
|
** virtual table can do no serious harm even if it is controlled by a
|
||||||
@@ -10204,7 +10206,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
|||||||
** </table>
|
** </table>
|
||||||
**
|
**
|
||||||
** ^For the purposes of comparing virtual table output values to see if the
|
** ^For the purposes of comparing virtual table output values to see if the
|
||||||
** values are same value for sorting purposes, two NULL values are considered
|
** values are the same value for sorting purposes, two NULL values are considered
|
||||||
** to be the same. In other words, the comparison operator is "IS"
|
** to be the same. In other words, the comparison operator is "IS"
|
||||||
** (or "IS NOT DISTINCT FROM") and not "==".
|
** (or "IS NOT DISTINCT FROM") and not "==".
|
||||||
**
|
**
|
||||||
@@ -10214,7 +10216,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
|||||||
**
|
**
|
||||||
** ^A virtual table implementation is always free to return rows in any order
|
** ^A virtual table implementation is always free to return rows in any order
|
||||||
** it wants, as long as the "orderByConsumed" flag is not set. ^When the
|
** it wants, as long as the "orderByConsumed" flag is not set. ^When the
|
||||||
** the "orderByConsumed" flag is unset, the query planner will add extra
|
** "orderByConsumed" flag is unset, the query planner will add extra
|
||||||
** [bytecode] to ensure that the final results returned by the SQL query are
|
** [bytecode] to ensure that the final results returned by the SQL query are
|
||||||
** ordered correctly. The use of the "orderByConsumed" flag and the
|
** ordered correctly. The use of the "orderByConsumed" flag and the
|
||||||
** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful
|
** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful
|
||||||
@@ -10311,7 +10313,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
|
|||||||
** sqlite3_vtab_in_next(X,P) should be one of the parameters to the
|
** sqlite3_vtab_in_next(X,P) should be one of the parameters to the
|
||||||
** xFilter method which invokes these routines, and specifically
|
** xFilter method which invokes these routines, and specifically
|
||||||
** a parameter that was previously selected for all-at-once IN constraint
|
** a parameter that was previously selected for all-at-once IN constraint
|
||||||
** processing use the [sqlite3_vtab_in()] interface in the
|
** processing using the [sqlite3_vtab_in()] interface in the
|
||||||
** [xBestIndex|xBestIndex method]. ^(If the X parameter is not
|
** [xBestIndex|xBestIndex method]. ^(If the X parameter is not
|
||||||
** an xFilter argument that was selected for all-at-once IN constraint
|
** an xFilter argument that was selected for all-at-once IN constraint
|
||||||
** processing, then these routines return [SQLITE_ERROR].)^
|
** processing, then these routines return [SQLITE_ERROR].)^
|
||||||
@@ -10366,7 +10368,7 @@ SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut);
|
|||||||
** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V)
|
** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V)
|
||||||
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
|
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
|
||||||
** constraint is not available. ^The sqlite3_vtab_rhs_value() interface
|
** constraint is not available. ^The sqlite3_vtab_rhs_value() interface
|
||||||
** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if
|
** can return a result code other than SQLITE_OK or SQLITE_NOTFOUND if
|
||||||
** something goes wrong.
|
** something goes wrong.
|
||||||
**
|
**
|
||||||
** The sqlite3_vtab_rhs_value() interface is usually only successful if
|
** The sqlite3_vtab_rhs_value() interface is usually only successful if
|
||||||
@@ -10394,8 +10396,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||||||
** KEYWORDS: {conflict resolution mode}
|
** KEYWORDS: {conflict resolution mode}
|
||||||
**
|
**
|
||||||
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
||||||
** inform a [virtual table] implementation what the [ON CONFLICT] mode
|
** inform a [virtual table] implementation of the [ON CONFLICT] mode
|
||||||
** is for the SQL statement being evaluated.
|
** for the SQL statement being evaluated.
|
||||||
**
|
**
|
||||||
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
||||||
** return value from the [sqlite3_set_authorizer()] callback and that
|
** return value from the [sqlite3_set_authorizer()] callback and that
|
||||||
@@ -10435,39 +10437,39 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||||||
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
|
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
|
||||||
** <dd>^The "double" variable pointed to by the V parameter will be set to the
|
** <dd>^The "double" variable pointed to by the V parameter will be set to the
|
||||||
** query planner's estimate for the average number of rows output from each
|
** query planner's estimate for the average number of rows output from each
|
||||||
** iteration of the X-th loop. If the query planner's estimates was accurate,
|
** iteration of the X-th loop. If the query planner's estimate was accurate,
|
||||||
** then this value will approximate the quotient NVISIT/NLOOP and the
|
** then this value will approximate the quotient NVISIT/NLOOP and the
|
||||||
** product of this value for all prior loops with the same SELECTID will
|
** product of this value for all prior loops with the same SELECTID will
|
||||||
** be the NLOOP value for the current loop.
|
** be the NLOOP value for the current loop.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
|
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
|
||||||
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
||||||
** to a zero-terminated UTF-8 string containing the name of the index or table
|
** to a zero-terminated UTF-8 string containing the name of the index or table
|
||||||
** used for the X-th loop.
|
** used for the X-th loop.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
|
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
|
||||||
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
||||||
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
|
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
|
||||||
** description for the X-th loop.
|
** description for the X-th loop.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
|
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
|
||||||
** <dd>^The "int" variable pointed to by the V parameter will be set to the
|
** <dd>^The "int" variable pointed to by the V parameter will be set to the
|
||||||
** id for the X-th query plan element. The id value is unique within the
|
** id for the X-th query plan element. The id value is unique within the
|
||||||
** statement. The select-id is the same value as is output in the first
|
** statement. The select-id is the same value as is output in the first
|
||||||
** column of an [EXPLAIN QUERY PLAN] query.
|
** column of an [EXPLAIN QUERY PLAN] query.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt>
|
** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt>
|
||||||
** <dd>The "int" variable pointed to by the V parameter will be set to the
|
** <dd>The "int" variable pointed to by the V parameter will be set to the
|
||||||
** the id of the parent of the current query element, if applicable, or
|
** id of the parent of the current query element, if applicable, or
|
||||||
** to zero if the query element has no parent. This is the same value as
|
** to zero if the query element has no parent. This is the same value as
|
||||||
** returned in the second column of an [EXPLAIN QUERY PLAN] query.
|
** returned in the second column of an [EXPLAIN QUERY PLAN] query.</dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt>
|
** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt>
|
||||||
** <dd>The sqlite3_int64 output value is set to the number of cycles,
|
** <dd>The sqlite3_int64 output value is set to the number of cycles,
|
||||||
** according to the processor time-stamp counter, that elapsed while the
|
** according to the processor time-stamp counter, that elapsed while the
|
||||||
** query element was being processed. This value is not available for
|
** query element was being processed. This value is not available for
|
||||||
** all query elements - if it is unavailable the output variable is
|
** all query elements - if it is unavailable the output variable is
|
||||||
** set to -1.
|
** set to -1.</dd>
|
||||||
** </dl>
|
** </dl>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_SCANSTAT_NLOOP 0
|
#define SQLITE_SCANSTAT_NLOOP 0
|
||||||
@@ -10508,8 +10510,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||||||
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
|
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
|
||||||
**
|
**
|
||||||
** Parameter "idx" identifies the specific query element to retrieve statistics
|
** Parameter "idx" identifies the specific query element to retrieve statistics
|
||||||
** for. Query elements are numbered starting from zero. A value of -1 may be
|
** for. Query elements are numbered starting from zero. A value of -1 may
|
||||||
** to query for statistics regarding the entire query. ^If idx is out of range
|
** retrieve statistics for the entire query. ^If idx is out of range
|
||||||
** - less than -1 or greater than or equal to the total number of query
|
** - less than -1 or greater than or equal to the total number of query
|
||||||
** elements used to implement the statement - a non-zero value is returned and
|
** elements used to implement the statement - a non-zero value is returned and
|
||||||
** the variable that pOut points to is unchanged.
|
** the variable that pOut points to is unchanged.
|
||||||
@@ -10552,7 +10554,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
|
|||||||
** METHOD: sqlite3
|
** METHOD: sqlite3
|
||||||
**
|
**
|
||||||
** ^If a write-transaction is open on [database connection] D when the
|
** ^If a write-transaction is open on [database connection] D when the
|
||||||
** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
|
** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty
|
||||||
** pages in the pager-cache that are not currently in use are written out
|
** pages in the pager-cache that are not currently in use are written out
|
||||||
** to disk. A dirty page may be in use if a database cursor created by an
|
** to disk. A dirty page may be in use if a database cursor created by an
|
||||||
** active SQL statement is reading from it, or if it is page 1 of a database
|
** active SQL statement is reading from it, or if it is page 1 of a database
|
||||||
@@ -10666,8 +10668,8 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
|
|||||||
** triggers; and so forth.
|
** triggers; and so forth.
|
||||||
**
|
**
|
||||||
** When the [sqlite3_blob_write()] API is used to update a blob column,
|
** When the [sqlite3_blob_write()] API is used to update a blob column,
|
||||||
** the pre-update hook is invoked with SQLITE_DELETE. This is because the
|
** the pre-update hook is invoked with SQLITE_DELETE, because
|
||||||
** in this case the new values are not available. In this case, when a
|
** the new values are not yet available. In this case, when a
|
||||||
** callback made with op==SQLITE_DELETE is actually a write using the
|
** callback made with op==SQLITE_DELETE is actually a write using the
|
||||||
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
|
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
|
||||||
** the index of the column being written. In other cases, where the
|
** the index of the column being written. In other cases, where the
|
||||||
@@ -10920,7 +10922,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
|||||||
** For an ordinary on-disk database file, the serialization is just a
|
** For an ordinary on-disk database file, the serialization is just a
|
||||||
** copy of the disk file. For an in-memory database or a "TEMP" database,
|
** copy of the disk file. For an in-memory database or a "TEMP" database,
|
||||||
** the serialization is the same sequence of bytes which would be written
|
** the serialization is the same sequence of bytes which would be written
|
||||||
** to disk if that database where backed up to disk.
|
** to disk if that database were backed up to disk.
|
||||||
**
|
**
|
||||||
** The usual case is that sqlite3_serialize() copies the serialization of
|
** The usual case is that sqlite3_serialize() copies the serialization of
|
||||||
** the database into memory obtained from [sqlite3_malloc64()] and returns
|
** the database into memory obtained from [sqlite3_malloc64()] and returns
|
||||||
@@ -10929,7 +10931,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
|||||||
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
|
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
|
||||||
** are made, and the sqlite3_serialize() function will return a pointer
|
** are made, and the sqlite3_serialize() function will return a pointer
|
||||||
** to the contiguous memory representation of the database that SQLite
|
** to the contiguous memory representation of the database that SQLite
|
||||||
** is currently using for that database, or NULL if the no such contiguous
|
** is currently using for that database, or NULL if no such contiguous
|
||||||
** memory representation of the database exists. A contiguous memory
|
** memory representation of the database exists. A contiguous memory
|
||||||
** representation of the database will usually only exist if there has
|
** representation of the database will usually only exist if there has
|
||||||
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
|
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
|
||||||
@@ -11000,7 +11002,7 @@ SQLITE_API unsigned char *sqlite3_serialize(
|
|||||||
** database is currently in a read transaction or is involved in a backup
|
** database is currently in a read transaction or is involved in a backup
|
||||||
** operation.
|
** operation.
|
||||||
**
|
**
|
||||||
** It is not possible to deserialized into the TEMP database. If the
|
** It is not possible to deserialize into the TEMP database. If the
|
||||||
** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the
|
** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the
|
||||||
** function returns SQLITE_ERROR.
|
** function returns SQLITE_ERROR.
|
||||||
**
|
**
|
||||||
@@ -11022,7 +11024,7 @@ SQLITE_API int sqlite3_deserialize(
|
|||||||
sqlite3 *db, /* The database connection */
|
sqlite3 *db, /* The database connection */
|
||||||
const char *zSchema, /* Which DB to reopen with the deserialization */
|
const char *zSchema, /* Which DB to reopen with the deserialization */
|
||||||
unsigned char *pData, /* The serialized database content */
|
unsigned char *pData, /* The serialized database content */
|
||||||
sqlite3_int64 szDb, /* Number bytes in the deserialization */
|
sqlite3_int64 szDb, /* Number of bytes in the deserialization */
|
||||||
sqlite3_int64 szBuf, /* Total size of buffer pData[] */
|
sqlite3_int64 szBuf, /* Total size of buffer pData[] */
|
||||||
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
|
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
|
||||||
);
|
);
|
||||||
@@ -11030,7 +11032,7 @@ SQLITE_API int sqlite3_deserialize(
|
|||||||
/*
|
/*
|
||||||
** CAPI3REF: Flags for sqlite3_deserialize()
|
** CAPI3REF: Flags for sqlite3_deserialize()
|
||||||
**
|
**
|
||||||
** The following are allowed values for 6th argument (the F argument) to
|
** The following are allowed values for the 6th argument (the F argument) to
|
||||||
** the [sqlite3_deserialize(D,S,P,N,M,F)] interface.
|
** the [sqlite3_deserialize(D,S,P,N,M,F)] interface.
|
||||||
**
|
**
|
||||||
** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
|
** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
|
||||||
|
@@ -61,6 +61,7 @@ options:
|
|||||||
autologin (default: false): Whether mobile autologin is supported.
|
autologin (default: false): Whether mobile autologin is supported.
|
||||||
broadcast (default: true): Send network discovery broadcasts.
|
broadcast (default: true): Send network discovery broadcasts.
|
||||||
discovery (default: true): Receive network discovery broadcasts.
|
discovery (default: true): Receive network discovery broadcasts.
|
||||||
|
stay_connected (default: false): Whether to attempt to keep several peer connections open.
|
||||||
-o, --one-proc Run everything in one process (unsafely!).
|
-o, --one-proc Run everything in one process (unsafely!).
|
||||||
-z, --zip path Zip archive from which to load files.
|
-z, --zip path Zip archive from which to load files.
|
||||||
-v, --verbose Log raw messages.
|
-v, --verbose Log raw messages.
|
||||||
|
8
flake.lock
generated
8
flake.lock
generated
@@ -20,16 +20,16 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748037224,
|
"lastModified": 1750622754,
|
||||||
"narHash": "sha256-92vihpZr6dwEMV6g98M5kHZIttrWahb9iRPBm1atcPk=",
|
"narHash": "sha256-kMhs+YzV4vPGfuTpD3mwzibWUE6jotw5Al2wczI0Pv8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "f09dede81861f3a83f7f06641ead34f02f37597f",
|
"rev": "c7ab75210cb8cb16ddd8f290755d9558edde7ee1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-24.11",
|
"ref": "nixos-25.05",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
description = "Tilde Friends is a platform for making, running, and sharing web applications.";
|
description = "Tilde Friends is a platform for making, running, and sharing web applications.";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
2
metadata/en-US/changelogs/39.txt
Normal file
2
metadata/en-US/changelogs/39.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
* Updating Android SDK+target versions.
|
||||||
|
* Minor UI improvements.
|
6
package-lock.json
generated
6
package-lock.json
generated
@@ -11,9 +11,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.6.1",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||||
"integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
|
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.unprompted.tildefriends"
|
package="com.unprompted.tildefriends"
|
||||||
android:versionCode="38"
|
android:versionCode="40"
|
||||||
android:versionName="0.0.32">
|
android:versionName="0.0.33-wip">
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<application
|
<application
|
||||||
|
@@ -110,9 +110,9 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
server_thread.start();
|
server_thread.start();
|
||||||
|
|
||||||
web_view.getSettings().setJavaScriptEnabled(true);
|
web_view.getSettings().setJavaScriptEnabled(true);
|
||||||
web_view.getSettings().setDatabaseEnabled(true);
|
|
||||||
web_view.getSettings().setDomStorageEnabled(true);
|
web_view.getSettings().setDomStorageEnabled(true);
|
||||||
|
|
||||||
|
set_database_enabled();
|
||||||
set_database_path();
|
set_database_path();
|
||||||
|
|
||||||
web_view.setDownloadListener(new DownloadListener() {
|
web_view.setDownloadListener(new DownloadListener() {
|
||||||
@@ -451,4 +451,10 @@ public class TildeFriendsActivity extends Activity {
|
|||||||
web_view.getSettings().setDatabasePath(getDatabasePath("webview").getPath());
|
web_view.getSettings().setDatabasePath(getDatabasePath("webview").getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private void set_database_enabled()
|
||||||
|
{
|
||||||
|
web_view.getSettings().setDatabaseEnabled(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,9 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:background="#000">
|
||||||
<com.unprompted.tildefriends.TildeFriendsWebView
|
<com.unprompted.tildefriends.TildeFriendsWebView
|
||||||
android:id="@+id/web"
|
android:id="@+id/web"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
252
src/httpd.app.c
Normal file
252
src/httpd.app.c
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
#include "picohttpparser.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
|
#include <alloca.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _app_blob_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
bool found;
|
||||||
|
bool not_modified;
|
||||||
|
bool use_handler;
|
||||||
|
bool use_static;
|
||||||
|
void* data;
|
||||||
|
size_t size;
|
||||||
|
char app_blob_id[k_blob_id_len];
|
||||||
|
const char* file;
|
||||||
|
tf_httpd_user_app_t* user_app;
|
||||||
|
char etag[256];
|
||||||
|
} app_blob_t;
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_blob_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
app_blob_t* data = user_data;
|
||||||
|
tf_http_request_t* request = data->request;
|
||||||
|
if (request->path[0] == '/' && request->path[1] == '~')
|
||||||
|
{
|
||||||
|
const char* last_slash = strchr(request->path + 1, '/');
|
||||||
|
if (last_slash)
|
||||||
|
{
|
||||||
|
last_slash = strchr(last_slash + 1, '/');
|
||||||
|
}
|
||||||
|
data->user_app = last_slash ? tf_httpd_parse_user_app_from_path(request->path, last_slash) : NULL;
|
||||||
|
if (data->user_app)
|
||||||
|
{
|
||||||
|
size_t path_length = strlen("path:") + strlen(data->user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(path_length);
|
||||||
|
snprintf(app_path, path_length, "path:%s", data->user_app->app);
|
||||||
|
const char* value = tf_ssb_db_get_property(ssb, data->user_app->user, app_path);
|
||||||
|
tf_string_set(data->app_blob_id, sizeof(data->app_blob_id), value);
|
||||||
|
tf_free(app_path);
|
||||||
|
tf_free((void*)value);
|
||||||
|
data->file = last_slash + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (request->path[0] == '/' && request->path[1] == '&')
|
||||||
|
{
|
||||||
|
const char* end = strstr(request->path, ".sha256/");
|
||||||
|
if (end)
|
||||||
|
{
|
||||||
|
snprintf(data->app_blob_id, sizeof(data->app_blob_id), "%.*s", (int)(end + strlen(".sha256") - request->path - 1), request->path + 1);
|
||||||
|
data->file = end + strlen(".sha256/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* app_blob = NULL;
|
||||||
|
size_t app_blob_size = 0;
|
||||||
|
if (*data->app_blob_id && tf_ssb_db_blob_get(ssb, data->app_blob_id, (uint8_t**)&app_blob, &app_blob_size))
|
||||||
|
{
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
JSValue app_object = JS_ParseJSON(context, app_blob, app_blob_size, NULL);
|
||||||
|
JSValue files = JS_GetPropertyStr(context, app_object, "files");
|
||||||
|
JSValue blob_id = JS_GetPropertyStr(context, files, data->file);
|
||||||
|
if (JS_IsUndefined(blob_id))
|
||||||
|
{
|
||||||
|
blob_id = JS_GetPropertyStr(context, files, "handler.js");
|
||||||
|
if (!JS_IsUndefined(blob_id))
|
||||||
|
{
|
||||||
|
data->use_handler = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* blob_id_str = JS_ToCString(context, blob_id);
|
||||||
|
if (blob_id_str)
|
||||||
|
{
|
||||||
|
snprintf(data->etag, sizeof(data->etag), "\"%s\"", blob_id_str);
|
||||||
|
const char* match = tf_http_request_get_header(data->request, "if-none-match");
|
||||||
|
if (match && strcmp(match, data->etag) == 0)
|
||||||
|
{
|
||||||
|
data->not_modified = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data->found = tf_ssb_db_blob_get(ssb, blob_id_str, (uint8_t**)&data->data, &data->size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, blob_id_str);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, blob_id);
|
||||||
|
JS_FreeValue(context, files);
|
||||||
|
JS_FreeValue(context, app_object);
|
||||||
|
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
tf_free(app_blob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_call_app_handler(tf_ssb_t* ssb, tf_http_request_t* request, const char* app_blob_id, const char* path, const char* package_owner, const char* app)
|
||||||
|
{
|
||||||
|
JSContext* context = tf_ssb_get_context(ssb);
|
||||||
|
JSValue global = JS_GetGlobalObject(context);
|
||||||
|
JSValue exports = JS_GetPropertyStr(context, global, "exports");
|
||||||
|
JSValue call_app_handler = JS_GetPropertyStr(context, exports, "callAppHandler");
|
||||||
|
|
||||||
|
JSValue response = tf_httpd_make_response_object(context, request);
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
JSValue handler_blob_id = JS_NewString(context, app_blob_id);
|
||||||
|
JSValue path_value = JS_NewString(context, path);
|
||||||
|
JSValue package_owner_value = JS_NewString(context, package_owner);
|
||||||
|
JSValue app_value = JS_NewString(context, app);
|
||||||
|
JSValue query_value = request->query ? JS_NewString(context, request->query) : JS_UNDEFINED;
|
||||||
|
|
||||||
|
JSValue headers = JS_NewObject(context);
|
||||||
|
for (int i = 0; i < request->headers_count; i++)
|
||||||
|
{
|
||||||
|
char name[256] = "";
|
||||||
|
snprintf(name, sizeof(name), "%.*s", (int)request->headers[i].name_len, request->headers[i].name);
|
||||||
|
JS_SetPropertyStr(context, headers, name, JS_NewStringLen(context, request->headers[i].value, request->headers[i].value_len));
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue args[] = {
|
||||||
|
response,
|
||||||
|
handler_blob_id,
|
||||||
|
path_value,
|
||||||
|
query_value,
|
||||||
|
headers,
|
||||||
|
package_owner_value,
|
||||||
|
app_value,
|
||||||
|
};
|
||||||
|
|
||||||
|
JSValue result = JS_Call(context, call_app_handler, JS_NULL, tf_countof(args), args);
|
||||||
|
tf_util_report_error(context, result);
|
||||||
|
JS_FreeValue(context, result);
|
||||||
|
|
||||||
|
JS_FreeValue(context, headers);
|
||||||
|
JS_FreeValue(context, query_value);
|
||||||
|
JS_FreeValue(context, app_value);
|
||||||
|
JS_FreeValue(context, package_owner_value);
|
||||||
|
JS_FreeValue(context, handler_blob_id);
|
||||||
|
JS_FreeValue(context, path_value);
|
||||||
|
JS_FreeValue(context, response);
|
||||||
|
JS_FreeValue(context, call_app_handler);
|
||||||
|
JS_FreeValue(context, exports);
|
||||||
|
JS_FreeValue(context, global);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_blob_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
app_blob_t* data = user_data;
|
||||||
|
if (data->not_modified)
|
||||||
|
{
|
||||||
|
tf_http_respond(data->request, 304, NULL, 0, NULL, 0);
|
||||||
|
}
|
||||||
|
else if (data->use_static)
|
||||||
|
{
|
||||||
|
tf_httpd_endpoint_static(data->request);
|
||||||
|
}
|
||||||
|
else if (data->use_handler)
|
||||||
|
{
|
||||||
|
_httpd_call_app_handler(ssb, data->request, data->app_blob_id, data->file, data->user_app->user, data->user_app->app);
|
||||||
|
}
|
||||||
|
else if (data->found)
|
||||||
|
{
|
||||||
|
const char* mime_type = tf_httpd_ext_to_content_type(strrchr(data->request->path, '.'), false);
|
||||||
|
if (!mime_type)
|
||||||
|
{
|
||||||
|
mime_type = tf_httpd_magic_bytes_to_content_type(data->data, data->size);
|
||||||
|
}
|
||||||
|
const char* headers[] = {
|
||||||
|
"Access-Control-Allow-Origin",
|
||||||
|
"*",
|
||||||
|
"Content-Security-Policy",
|
||||||
|
"sandbox allow-downloads allow-top-navigation-by-user-activation",
|
||||||
|
"Content-Type",
|
||||||
|
mime_type ? mime_type : "application/binary",
|
||||||
|
"etag",
|
||||||
|
data->etag,
|
||||||
|
};
|
||||||
|
tf_http_respond(data->request, 200, headers, tf_countof(headers) / 2, data->data, data->size);
|
||||||
|
}
|
||||||
|
tf_free(data->user_app);
|
||||||
|
tf_free(data->data);
|
||||||
|
tf_http_request_unref(data->request);
|
||||||
|
tf_free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_app(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
app_blob_t* data = tf_malloc(sizeof(app_blob_t));
|
||||||
|
*data = (app_blob_t) { .request = request };
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_app_blob_work, _httpd_endpoint_app_blob_after_work, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_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 = tf_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);
|
||||||
|
}
|
91
src/httpd.delete.c
Normal file
91
src/httpd.delete.c
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
typedef struct _delete_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
const char* session;
|
||||||
|
int response;
|
||||||
|
} delete_t;
|
||||||
|
|
||||||
|
static void _httpd_endpoint_delete_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
delete_t* delete = user_data;
|
||||||
|
tf_http_request_t* request = delete->request;
|
||||||
|
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, delete->session);
|
||||||
|
JSValue user = JS_GetPropertyStr(context, jwt, "name");
|
||||||
|
const char* user_string = JS_ToCString(context, user);
|
||||||
|
if (user_string && tf_httpd_is_name_valid(user_string))
|
||||||
|
{
|
||||||
|
tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/delete");
|
||||||
|
if (user_app)
|
||||||
|
{
|
||||||
|
if (strcmp(user_string, user_app->user) == 0 || (strcmp(user_app->user, "core") == 0 && tf_ssb_db_user_has_permission(ssb, NULL, user_string, "administration")))
|
||||||
|
{
|
||||||
|
size_t path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(path_length);
|
||||||
|
snprintf(app_path, path_length, "path:%s", user_app->app);
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
changed = tf_ssb_db_remove_value_from_array_property(ssb, user_string, "apps", user_app->app) || changed;
|
||||||
|
changed = tf_ssb_db_remove_property(ssb, user_string, app_path) || changed;
|
||||||
|
delete->response = changed ? 200 : 404;
|
||||||
|
tf_free(app_path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete->response = 401;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete->response = 404;
|
||||||
|
}
|
||||||
|
tf_free(user_app);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete->response = 401;
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeCString(context, user_string);
|
||||||
|
JS_FreeValue(context, user);
|
||||||
|
JS_FreeValue(context, jwt);
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_delete_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
delete_t* delete = user_data;
|
||||||
|
const char* k_payload = tf_http_status_text(delete->response ? delete->response : 404);
|
||||||
|
tf_http_respond(delete->request, delete->response ? delete->response : 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
tf_http_request_unref(delete->request);
|
||||||
|
tf_free((void*)delete->session);
|
||||||
|
tf_free(delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_delete(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
delete_t* delete = tf_malloc(sizeof(delete_t));
|
||||||
|
*delete = (delete_t) {
|
||||||
|
.request = request,
|
||||||
|
.session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session"),
|
||||||
|
};
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_delete_work, _httpd_endpoint_delete_after_work, delete);
|
||||||
|
}
|
233
src/httpd.index.c
Normal file
233
src/httpd.index.c
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "file.js.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
|
#include <alloca.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _index_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
bool found;
|
||||||
|
bool not_modified;
|
||||||
|
bool use_handler;
|
||||||
|
bool use_static;
|
||||||
|
void* data;
|
||||||
|
size_t size;
|
||||||
|
char app_blob_id[k_blob_id_len];
|
||||||
|
const char* file;
|
||||||
|
tf_httpd_user_app_t* user_app;
|
||||||
|
char etag[256];
|
||||||
|
} index_t;
|
||||||
|
|
||||||
|
static bool _has_property(JSContext* context, JSValue object, const char* name)
|
||||||
|
{
|
||||||
|
JSAtom atom = JS_NewAtom(context, name);
|
||||||
|
bool result = JS_HasProperty(context, object, atom) > 0;
|
||||||
|
JS_FreeAtom(context, atom);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_index_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
index_t* data = user_data;
|
||||||
|
data->use_static = true;
|
||||||
|
tf_httpd_user_app_t* user_app = data->user_app;
|
||||||
|
|
||||||
|
size_t app_path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(app_path_length);
|
||||||
|
snprintf(app_path, app_path_length, "path:%s", user_app->app);
|
||||||
|
const char* app_blob_id = tf_ssb_db_get_property(ssb, user_app->user, app_path);
|
||||||
|
tf_free(app_path);
|
||||||
|
|
||||||
|
uint8_t* app_blob = NULL;
|
||||||
|
size_t app_blob_size = 0;
|
||||||
|
|
||||||
|
if (tf_ssb_db_blob_get(ssb, app_blob_id, &app_blob, &app_blob_size))
|
||||||
|
{
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
JSValue app = JS_ParseJSON(context, (const char*)app_blob, app_blob_size, NULL);
|
||||||
|
JSValue files = JS_GetPropertyStr(context, app, "files");
|
||||||
|
|
||||||
|
if (!_has_property(context, files, "app.js"))
|
||||||
|
{
|
||||||
|
JSValue index = JS_GetPropertyStr(context, files, "index.html");
|
||||||
|
if (JS_IsString(index))
|
||||||
|
{
|
||||||
|
const char* index_string = JS_ToCString(context, index);
|
||||||
|
tf_ssb_db_blob_get(ssb, index_string, (uint8_t**)&data->data, &data->size);
|
||||||
|
JS_FreeCString(context, index_string);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeValue(context, files);
|
||||||
|
JS_FreeValue(context, app);
|
||||||
|
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
|
||||||
|
tf_free(app_blob);
|
||||||
|
}
|
||||||
|
tf_free((void*)app_blob_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* _replace(const char* original, size_t size, const char* find, const char* replace, size_t* out_size)
|
||||||
|
{
|
||||||
|
char* pos = strstr(original, find);
|
||||||
|
if (!pos)
|
||||||
|
{
|
||||||
|
return tf_strdup(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t replace_length = strlen(replace);
|
||||||
|
size_t find_length = strlen(find);
|
||||||
|
size_t new_size = size + replace_length - find_length;
|
||||||
|
char* buffer = tf_malloc(new_size);
|
||||||
|
memcpy(buffer, original, pos - original);
|
||||||
|
memcpy(buffer + (pos - original), replace, replace_length);
|
||||||
|
memcpy(buffer + (pos - original) + replace_length, pos + find_length, size - (pos - original) - find_length);
|
||||||
|
*out_size = new_size;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* _append_raw(char* document, size_t* current_size, const char* data, size_t size)
|
||||||
|
{
|
||||||
|
document = tf_resize_vec(document, *current_size + size);
|
||||||
|
memcpy(document + *current_size, data, size);
|
||||||
|
document[*current_size + size] = '\0';
|
||||||
|
*current_size += size;
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* _append_encoded(char* document, const char* data, size_t size, size_t* out_size)
|
||||||
|
{
|
||||||
|
size_t current_size = strlen(document);
|
||||||
|
int accum = 0;
|
||||||
|
for (int i = 0; (size_t)i < size; i++)
|
||||||
|
{
|
||||||
|
switch (data[i])
|
||||||
|
{
|
||||||
|
case '"':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, """, strlen("""));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, "'", strlen("'"));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, "<", strlen("<"));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, ">", strlen(">"));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
case '&':
|
||||||
|
if (i > accum)
|
||||||
|
{
|
||||||
|
document = _append_raw(document, ¤t_size, data + accum, i - accum);
|
||||||
|
}
|
||||||
|
document = _append_raw(document, ¤t_size, "&", strlen("&"));
|
||||||
|
accum = i + 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*out_size = current_size;
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_index_file_read(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
|
||||||
|
{
|
||||||
|
index_t* state = user_data;
|
||||||
|
if (result > 0)
|
||||||
|
{
|
||||||
|
char* replacement = tf_strdup("<iframe srcdoc=\"");
|
||||||
|
size_t replacement_size = 0;
|
||||||
|
replacement = _append_encoded(replacement, state->data, state->size, &replacement_size);
|
||||||
|
_append_raw(replacement, &replacement_size, "\"", 1);
|
||||||
|
|
||||||
|
size_t size = 0;
|
||||||
|
char* document = _replace(data, result, "<iframe", replacement, &size);
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Type",
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
};
|
||||||
|
tf_http_respond(state->request, 200, headers, tf_countof(headers) / 2, document, size);
|
||||||
|
tf_free(replacement);
|
||||||
|
tf_free(document);
|
||||||
|
}
|
||||||
|
tf_free(state->data);
|
||||||
|
tf_free(state->user_app);
|
||||||
|
tf_http_request_unref(state->request);
|
||||||
|
tf_free(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_app_index_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
index_t* data = user_data;
|
||||||
|
if (data->data)
|
||||||
|
{
|
||||||
|
tf_task_t* task = data->request->user_data;
|
||||||
|
const char* root_path = tf_task_get_root_path(task);
|
||||||
|
size_t size = (root_path ? strlen(root_path) + 1 : 0) + strlen("core/index.html") + 1;
|
||||||
|
char* path = alloca(size);
|
||||||
|
snprintf(path, size, "%s%score/index.html", root_path ? root_path : "", root_path ? "/" : "");
|
||||||
|
tf_file_read(task, path, _httpd_endpoint_app_index_file_read, data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_httpd_endpoint_static(data->request);
|
||||||
|
tf_free(data->user_app);
|
||||||
|
tf_http_request_unref(data->request);
|
||||||
|
tf_free(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_app_index(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/");
|
||||||
|
if (!user_app)
|
||||||
|
{
|
||||||
|
return tf_httpd_endpoint_static(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
index_t* data = tf_malloc(sizeof(index_t));
|
||||||
|
(*data) = (index_t) { .request = request, .user_app = user_app };
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_app_index_work, _httpd_endpoint_app_index_after_work, data);
|
||||||
|
}
|
1648
src/httpd.js.c
1648
src/httpd.js.c
File diff suppressed because it is too large
Load Diff
178
src/httpd.js.h
178
src/httpd.js.h
@@ -11,6 +11,14 @@
|
|||||||
** @{
|
** @{
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "quickjs.h"
|
||||||
|
|
||||||
|
static const int64_t k_httpd_auth_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
/** A JS context. */
|
/** A JS context. */
|
||||||
typedef struct JSContext JSContext;
|
typedef struct JSContext JSContext;
|
||||||
|
|
||||||
@@ -19,6 +27,27 @@ typedef struct JSContext JSContext;
|
|||||||
*/
|
*/
|
||||||
typedef struct _tf_http_t tf_http_t;
|
typedef struct _tf_http_t tf_http_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** An HTTP request.
|
||||||
|
*/
|
||||||
|
typedef struct _tf_http_request_t tf_http_request_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** An SSB instance.
|
||||||
|
*/
|
||||||
|
typedef struct _tf_ssb_t tf_ssb_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** A user and app name.
|
||||||
|
*/
|
||||||
|
typedef struct _tf_httpd_user_app_t
|
||||||
|
{
|
||||||
|
/** The username. */
|
||||||
|
const char* user;
|
||||||
|
/** The app name. */
|
||||||
|
const char* app;
|
||||||
|
} tf_httpd_user_app_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** Register the HTTP script interface. Also registers a number of built-in
|
** Register the HTTP script interface. Also registers a number of built-in
|
||||||
** request handlers. An ongoing project is to move the JS request handlers
|
** request handlers. An ongoing project is to move the JS request handlers
|
||||||
@@ -39,4 +68,153 @@ tf_http_t* tf_httpd_create(JSContext* context);
|
|||||||
*/
|
*/
|
||||||
void tf_httpd_destroy(tf_http_t* http);
|
void tf_httpd_destroy(tf_http_t* http);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Determine a content-type from a file extension.
|
||||||
|
** @param ext The file extension.
|
||||||
|
** @param use_fallback If not found, fallback to application/binary.
|
||||||
|
** @return A MIME type or NULL.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_ext_to_content_type(const char* ext, bool use_fallback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Determine a content type from magic bytes.
|
||||||
|
** @param bytes The first bytes of a file.
|
||||||
|
** @param size The length of the bytes.
|
||||||
|
** @return A MIME type or NULL.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_magic_bytes_to_content_type(const uint8_t* bytes, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Respond with a redirect.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
** @return true if redirected.
|
||||||
|
*/
|
||||||
|
bool tf_httpd_redirect(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Parse a username and app from a path like /~user/app/.
|
||||||
|
** @param path The path.
|
||||||
|
** @param expected_suffix A suffix that is required to be on the path, and removed.
|
||||||
|
** @return The user and app. Free with tf_free().
|
||||||
|
*/
|
||||||
|
tf_httpd_user_app_t* tf_httpd_parse_user_app_from_path(const char* path, const char* expected_suffix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Decode form data into key value pairs.
|
||||||
|
** @param data The form data string.
|
||||||
|
** @param length The length of the form data string.
|
||||||
|
** @return Key values pairs terminated by NULL.
|
||||||
|
*/
|
||||||
|
const char** tf_httpd_form_data_decode(const char* data, int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Get a form data value from an array of key value pairs produced by tf_httpd_form_data_decode().
|
||||||
|
** @param form_data The form data.
|
||||||
|
** @param key The key for which to fetch the value.
|
||||||
|
** @return the value for the case-insensitive key or NULL.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_form_data_get(const char** form_data, const char* key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Validate a JWT.
|
||||||
|
** @param ssb The SSB instance.
|
||||||
|
** @param context A JS context.
|
||||||
|
** @param jwt The JWT.
|
||||||
|
** @return The JWT contents if valid.
|
||||||
|
*/
|
||||||
|
JSValue tf_httpd_authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char* jwt);
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Make a JS response object for a request.
|
||||||
|
** @param context The JS context.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
** @return The respone object.
|
||||||
|
*/
|
||||||
|
JSValue tf_httpd_make_response_object(JSContext* context, tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Check if a name meets requirements.
|
||||||
|
** @param name The name.
|
||||||
|
** @return true if the name is valid.
|
||||||
|
*/
|
||||||
|
bool tf_httpd_is_name_valid(const char* name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Make a header for the session cookie.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
** @param session_cookie The session cookie.
|
||||||
|
** @return The header.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Make a JWT for the session.
|
||||||
|
** @param context A JS context.
|
||||||
|
** @param ssb The SSB instance.
|
||||||
|
** @param name The username.
|
||||||
|
** @return The JWT.
|
||||||
|
*/
|
||||||
|
const char* tf_httpd_make_session_jwt(JSContext* context, tf_ssb_t* ssb, const char* name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Serve a static file.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_static(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** View a blob.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_view(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Save a blob or app.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_save(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Delete a blob or app.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_delete(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** App endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_app(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** App index endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_app_index(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** App WebSocket.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_app_socket(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Login endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_login(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Auto-login endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_login_auto(tf_http_request_t* request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
** Logout endpoint.
|
||||||
|
** @param request The HTTP request.
|
||||||
|
*/
|
||||||
|
void tf_httpd_endpoint_logout(tf_http_request_t* request);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
537
src/httpd.login.c
Normal file
537
src/httpd.login.c
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "file.js.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
#include "ow-crypt.h"
|
||||||
|
#include "sodium/utils.h"
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
|
#include <alloca.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _login_request_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
const char* name;
|
||||||
|
const char* error;
|
||||||
|
const char* settings;
|
||||||
|
const char* code_of_conduct;
|
||||||
|
bool have_administrator;
|
||||||
|
bool session_is_new;
|
||||||
|
|
||||||
|
char location_header[1024];
|
||||||
|
const char* set_cookie_header;
|
||||||
|
|
||||||
|
int pending;
|
||||||
|
} login_request_t;
|
||||||
|
|
||||||
|
const char* tf_httpd_make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie)
|
||||||
|
{
|
||||||
|
const char* k_pattern = "session=%s; path=/; Max-Age=%" PRId64 "; %sSameSite=Strict; HttpOnly";
|
||||||
|
int length = session_cookie ? snprintf(NULL, 0, k_pattern, session_cookie, k_httpd_auth_refresh_interval, request->is_tls ? "Secure; " : "") : 0;
|
||||||
|
char* cookie = length ? tf_malloc(length + 1) : NULL;
|
||||||
|
if (cookie)
|
||||||
|
{
|
||||||
|
snprintf(cookie, length + 1, k_pattern, session_cookie, k_httpd_auth_refresh_interval, request->is_tls ? "Secure; " : "");
|
||||||
|
}
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _login_release(login_request_t* login)
|
||||||
|
{
|
||||||
|
int ref_count = --login->pending;
|
||||||
|
if (ref_count == 0)
|
||||||
|
{
|
||||||
|
tf_free((void*)login->name);
|
||||||
|
tf_free((void*)login->code_of_conduct);
|
||||||
|
tf_free((void*)login->set_cookie_header);
|
||||||
|
tf_free(login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
|
||||||
|
{
|
||||||
|
login_request_t* login = user_data;
|
||||||
|
tf_http_request_t* request = login->request;
|
||||||
|
if (result >= 0)
|
||||||
|
{
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Type",
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
"Set-Cookie",
|
||||||
|
login->set_cookie_header ? login->set_cookie_header : "",
|
||||||
|
};
|
||||||
|
const char* replace_me = "$AUTH_DATA";
|
||||||
|
const char* auth = strstr(data, replace_me);
|
||||||
|
if (auth)
|
||||||
|
{
|
||||||
|
JSContext* context = tf_task_get_context(task);
|
||||||
|
JSValue object = JS_NewObject(context);
|
||||||
|
JS_SetPropertyStr(context, object, "session_is_new", JS_NewBool(context, login->session_is_new));
|
||||||
|
JS_SetPropertyStr(context, object, "name", login->name ? JS_NewString(context, login->name) : JS_UNDEFINED);
|
||||||
|
JS_SetPropertyStr(context, object, "error", login->error ? JS_NewString(context, login->error) : JS_UNDEFINED);
|
||||||
|
JS_SetPropertyStr(context, object, "code_of_conduct", login->code_of_conduct ? JS_NewString(context, login->code_of_conduct) : JS_UNDEFINED);
|
||||||
|
JS_SetPropertyStr(context, object, "have_administrator", JS_NewBool(context, login->have_administrator));
|
||||||
|
JSValue object_json = JS_JSONStringify(context, object, JS_NULL, JS_NULL);
|
||||||
|
size_t json_length = 0;
|
||||||
|
const char* json = JS_ToCStringLen(context, &json_length, object_json);
|
||||||
|
|
||||||
|
char* copy = tf_malloc(result + json_length);
|
||||||
|
int replace_start = (auth - (const char*)data);
|
||||||
|
int replace_end = (auth - (const char*)data) + (int)strlen(replace_me);
|
||||||
|
memcpy(copy, data, replace_start);
|
||||||
|
memcpy(copy + replace_start, json, json_length);
|
||||||
|
memcpy(copy + replace_start + json_length, ((const char*)data) + replace_end, result - replace_end);
|
||||||
|
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, copy, replace_start + json_length + (result - replace_end));
|
||||||
|
tf_free(copy);
|
||||||
|
|
||||||
|
JS_FreeCString(context, json);
|
||||||
|
JS_FreeValue(context, object_json);
|
||||||
|
JS_FreeValue(context, object);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
}
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
_login_release(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _session_is_authenticated_as_user(JSContext* context, JSValue session)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
JSValue user = JS_GetPropertyStr(context, session, "name");
|
||||||
|
const char* user_string = JS_ToCString(context, user);
|
||||||
|
result = user_string && strcmp(user_string, "guest") != 0;
|
||||||
|
JS_FreeCString(context, user_string);
|
||||||
|
JS_FreeValue(context, user);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _make_administrator_if_first(tf_ssb_t* ssb, JSContext* context, const char* account_name_copy, bool may_become_first_admin)
|
||||||
|
{
|
||||||
|
const char* settings = tf_ssb_db_get_property(ssb, "core", "settings");
|
||||||
|
JSValue settings_value = settings && *settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
|
||||||
|
if (JS_IsUndefined(settings_value))
|
||||||
|
{
|
||||||
|
settings_value = JS_NewObject(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool have_administrator = false;
|
||||||
|
JSValue permissions = JS_GetPropertyStr(context, settings_value, "permissions");
|
||||||
|
|
||||||
|
JSPropertyEnum* ptab = NULL;
|
||||||
|
uint32_t plen = 0;
|
||||||
|
JS_GetOwnPropertyNames(context, &ptab, &plen, permissions, JS_GPN_STRING_MASK);
|
||||||
|
for (int i = 0; i < (int)plen; i++)
|
||||||
|
{
|
||||||
|
JSPropertyDescriptor desc = { 0 };
|
||||||
|
if (JS_GetOwnProperty(context, &desc, permissions, ptab[i].atom) == 1)
|
||||||
|
{
|
||||||
|
int permission_length = tf_util_get_length(context, desc.value);
|
||||||
|
for (int i = 0; i < permission_length; i++)
|
||||||
|
{
|
||||||
|
JSValue entry = JS_GetPropertyUint32(context, desc.value, i);
|
||||||
|
const char* permission = JS_ToCString(context, entry);
|
||||||
|
if (permission && strcmp(permission, "administration") == 0)
|
||||||
|
{
|
||||||
|
have_administrator = true;
|
||||||
|
}
|
||||||
|
JS_FreeCString(context, permission);
|
||||||
|
JS_FreeValue(context, entry);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, desc.setter);
|
||||||
|
JS_FreeValue(context, desc.getter);
|
||||||
|
JS_FreeValue(context, desc.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < plen; ++i)
|
||||||
|
{
|
||||||
|
JS_FreeAtom(context, ptab[i].atom);
|
||||||
|
}
|
||||||
|
js_free(context, ptab);
|
||||||
|
|
||||||
|
if (!have_administrator && may_become_first_admin)
|
||||||
|
{
|
||||||
|
if (JS_IsUndefined(permissions))
|
||||||
|
{
|
||||||
|
permissions = JS_NewObject(context);
|
||||||
|
JS_SetPropertyStr(context, settings_value, "permissions", JS_DupValue(context, permissions));
|
||||||
|
}
|
||||||
|
JSValue user = JS_GetPropertyStr(context, permissions, account_name_copy);
|
||||||
|
if (JS_IsUndefined(user))
|
||||||
|
{
|
||||||
|
user = JS_NewArray(context);
|
||||||
|
JS_SetPropertyStr(context, permissions, account_name_copy, JS_DupValue(context, user));
|
||||||
|
}
|
||||||
|
JS_SetPropertyUint32(context, user, tf_util_get_length(context, user), JS_NewString(context, "administration"));
|
||||||
|
JS_FreeValue(context, user);
|
||||||
|
|
||||||
|
JSValue settings_json = JS_JSONStringify(context, settings_value, JS_NULL, JS_NULL);
|
||||||
|
const char* settings_string = JS_ToCString(context, settings_json);
|
||||||
|
tf_ssb_db_set_property(ssb, "core", "settings", settings_string);
|
||||||
|
JS_FreeCString(context, settings_string);
|
||||||
|
JS_FreeValue(context, settings_json);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeValue(context, permissions);
|
||||||
|
JS_FreeValue(context, settings_value);
|
||||||
|
tf_free((void*)settings);
|
||||||
|
return have_administrator;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _verify_password(const char* password, const char* hash)
|
||||||
|
{
|
||||||
|
char buffer[7 + 22 + 31 + 1];
|
||||||
|
const char* out_hash = crypt_rn(password, hash, buffer, sizeof(buffer));
|
||||||
|
return out_hash && strcmp(hash, out_hash) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
login_request_t* login = user_data;
|
||||||
|
tf_http_request_t* request = login->request;
|
||||||
|
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
|
||||||
|
const char** form_data = tf_httpd_form_data_decode(request->query, request->query ? strlen(request->query) : 0);
|
||||||
|
const char* account_name_copy = NULL;
|
||||||
|
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
|
||||||
|
|
||||||
|
if (_session_is_authenticated_as_user(context, jwt))
|
||||||
|
{
|
||||||
|
const char* return_url = tf_httpd_form_data_get(form_data, "return");
|
||||||
|
if (return_url)
|
||||||
|
{
|
||||||
|
tf_string_set(login->location_header, sizeof(login->location_header), return_url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
|
||||||
|
}
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* send_session = tf_strdup(session);
|
||||||
|
bool session_is_new = false;
|
||||||
|
const char* login_error = NULL;
|
||||||
|
bool may_become_first_admin = false;
|
||||||
|
if (strcmp(request->method, "POST") == 0)
|
||||||
|
{
|
||||||
|
session_is_new = true;
|
||||||
|
const char** post_form_data = tf_httpd_form_data_decode(request->body, request->content_length);
|
||||||
|
const char* submit = tf_httpd_form_data_get(post_form_data, "submit");
|
||||||
|
if (submit && strcmp(submit, "Login") == 0)
|
||||||
|
{
|
||||||
|
const char* account_name = tf_httpd_form_data_get(post_form_data, "name");
|
||||||
|
account_name_copy = tf_strdup(account_name);
|
||||||
|
const char* password = tf_httpd_form_data_get(post_form_data, "password");
|
||||||
|
const char* new_password = tf_httpd_form_data_get(post_form_data, "new_password");
|
||||||
|
const char* confirm = tf_httpd_form_data_get(post_form_data, "confirm");
|
||||||
|
const char* change = tf_httpd_form_data_get(post_form_data, "change");
|
||||||
|
const char* form_register = tf_httpd_form_data_get(post_form_data, "register");
|
||||||
|
char account_passwd[256] = { 0 };
|
||||||
|
bool have_account = tf_ssb_db_get_account_password_hash(ssb, tf_httpd_form_data_get(post_form_data, "name"), account_passwd, sizeof(account_passwd));
|
||||||
|
|
||||||
|
if (form_register && strcmp(form_register, "1") == 0)
|
||||||
|
{
|
||||||
|
bool registered = false;
|
||||||
|
if (!tf_httpd_is_name_valid(account_name))
|
||||||
|
{
|
||||||
|
login_error = "Invalid username. Usernames must contain only letters from the English alphabet and digits and must start with a letter.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!have_account && tf_httpd_is_name_valid(account_name) && password && confirm && strcmp(password, confirm) == 0)
|
||||||
|
{
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||||
|
registered = tf_ssb_db_register_account(tf_ssb_get_loop(ssb), db, context, account_name, password);
|
||||||
|
tf_ssb_release_db_writer(ssb, db);
|
||||||
|
if (registered)
|
||||||
|
{
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
send_session = tf_httpd_make_session_jwt(context, ssb, account_name);
|
||||||
|
may_become_first_admin = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!registered && !login_error)
|
||||||
|
{
|
||||||
|
login_error = "Error registering account.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (change && strcmp(change, "1") == 0)
|
||||||
|
{
|
||||||
|
bool set = false;
|
||||||
|
if (have_account && tf_httpd_is_name_valid(account_name) && new_password && confirm && strcmp(new_password, confirm) == 0 &&
|
||||||
|
_verify_password(password, account_passwd))
|
||||||
|
{
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||||
|
set = tf_ssb_db_set_account_password(tf_ssb_get_loop(ssb), db, context, account_name, new_password);
|
||||||
|
tf_ssb_release_db_writer(ssb, db);
|
||||||
|
if (set)
|
||||||
|
{
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
send_session = tf_httpd_make_session_jwt(context, ssb, account_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!set)
|
||||||
|
{
|
||||||
|
login_error = "Error changing password.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (have_account && *account_passwd && _verify_password(password, account_passwd))
|
||||||
|
{
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
send_session = tf_httpd_make_session_jwt(context, ssb, account_name);
|
||||||
|
may_become_first_admin = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
login_error = "Invalid username or password.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
send_session = tf_httpd_make_session_jwt(context, ssb, "guest");
|
||||||
|
}
|
||||||
|
tf_free(post_form_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool have_administrator = _make_administrator_if_first(ssb, context, account_name_copy, may_become_first_admin);
|
||||||
|
|
||||||
|
if (session_is_new && tf_httpd_form_data_get(form_data, "return") && !login_error)
|
||||||
|
{
|
||||||
|
const char* return_url = tf_httpd_form_data_get(form_data, "return");
|
||||||
|
if (return_url)
|
||||||
|
{
|
||||||
|
tf_string_set(login->location_header, sizeof(login->location_header), return_url);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
|
||||||
|
}
|
||||||
|
login->set_cookie_header = tf_httpd_make_set_session_cookie_header(request, send_session);
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
login->name = account_name_copy;
|
||||||
|
login->error = login_error;
|
||||||
|
login->set_cookie_header = tf_httpd_make_set_session_cookie_header(request, send_session);
|
||||||
|
tf_free((void*)send_session);
|
||||||
|
login->session_is_new = session_is_new;
|
||||||
|
login->have_administrator = have_administrator;
|
||||||
|
login->settings = tf_ssb_db_get_property(ssb, "core", "settings");
|
||||||
|
|
||||||
|
if (login->settings)
|
||||||
|
{
|
||||||
|
JSValue settings_value = JS_ParseJSON(context, login->settings, strlen(login->settings), NULL);
|
||||||
|
JSValue code_of_conduct_value = JS_GetPropertyStr(context, settings_value, "code_of_conduct");
|
||||||
|
const char* code_of_conduct = JS_ToCString(context, code_of_conduct_value);
|
||||||
|
const char* result = tf_strdup(code_of_conduct);
|
||||||
|
JS_FreeCString(context, code_of_conduct);
|
||||||
|
JS_FreeValue(context, code_of_conduct_value);
|
||||||
|
JS_FreeValue(context, settings_value);
|
||||||
|
tf_free((void*)login->settings);
|
||||||
|
login->settings = NULL;
|
||||||
|
login->code_of_conduct = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
login->pending++;
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_file_read(login->request->user_data, "core/auth.html", _httpd_endpoint_login_file_read_callback, login);
|
||||||
|
|
||||||
|
account_name_copy = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
done:
|
||||||
|
tf_free((void*)session);
|
||||||
|
tf_free(form_data);
|
||||||
|
tf_free((void*)account_name_copy);
|
||||||
|
JS_FreeValue(context, jwt);
|
||||||
|
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_login_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
login_request_t* login = user_data;
|
||||||
|
tf_http_request_t* request = login->request;
|
||||||
|
if (login->pending == 1)
|
||||||
|
{
|
||||||
|
if (*login->location_header)
|
||||||
|
{
|
||||||
|
const char* headers[] = {
|
||||||
|
"Location",
|
||||||
|
login->location_header,
|
||||||
|
"Set-Cookie",
|
||||||
|
login->set_cookie_header ? login->set_cookie_header : "",
|
||||||
|
};
|
||||||
|
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
_login_release(login);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_login(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
login_request_t* login = tf_malloc(sizeof(login_request_t));
|
||||||
|
*login = (login_request_t) {
|
||||||
|
.request = request,
|
||||||
|
};
|
||||||
|
login->pending++;
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_login_work, _httpd_endpoint_login_after_work, login);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_logout(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
const char* k_set_cookie = request->is_tls ? "session=; path=/; Secure; SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly"
|
||||||
|
: "session=; path=/; SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly";
|
||||||
|
const char* k_location_format = "/login%s%s";
|
||||||
|
int length = snprintf(NULL, 0, k_location_format, request->query ? "?" : "", request->query);
|
||||||
|
char* location = alloca(length + 1);
|
||||||
|
snprintf(location, length + 1, k_location_format, request->query ? "?" : "", request->query ? request->query : "");
|
||||||
|
const char* headers[] = {
|
||||||
|
"Set-Cookie",
|
||||||
|
k_set_cookie,
|
||||||
|
"Location",
|
||||||
|
location,
|
||||||
|
};
|
||||||
|
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 = tf_httpd_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 = tf_httpd_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_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);
|
||||||
|
}
|
181
src/httpd.save.c
Normal file
181
src/httpd.save.c
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
typedef struct _save_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
int response;
|
||||||
|
char blob_id[k_blob_id_len];
|
||||||
|
} save_t;
|
||||||
|
|
||||||
|
static void _httpd_endpoint_save_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
save_t* save = user_data;
|
||||||
|
tf_http_request_t* request = save->request;
|
||||||
|
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
|
||||||
|
|
||||||
|
JSMallocFunctions funcs = { 0 };
|
||||||
|
tf_get_js_malloc_functions(&funcs);
|
||||||
|
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||||
|
JSContext* context = JS_NewContext(runtime);
|
||||||
|
|
||||||
|
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
|
||||||
|
JSValue user = JS_GetPropertyStr(context, jwt, "name");
|
||||||
|
const char* user_string = JS_ToCString(context, user);
|
||||||
|
|
||||||
|
if (user_string && tf_httpd_is_name_valid(user_string))
|
||||||
|
{
|
||||||
|
tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/save");
|
||||||
|
if (user_app)
|
||||||
|
{
|
||||||
|
if (strcmp(user_string, user_app->user) == 0 || (strcmp(user_app->user, "core") == 0 && tf_ssb_db_user_has_permission(ssb, NULL, user_string, "administration")))
|
||||||
|
{
|
||||||
|
size_t path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(path_length);
|
||||||
|
snprintf(app_path, path_length, "path:%s", user_app->app);
|
||||||
|
|
||||||
|
const char* old_blob_id = tf_ssb_db_get_property(ssb, user_app->user, app_path);
|
||||||
|
|
||||||
|
JSValue new_app = JS_ParseJSON(context, request->body, request->content_length, NULL);
|
||||||
|
tf_util_report_error(context, new_app);
|
||||||
|
if (JS_IsObject(new_app))
|
||||||
|
{
|
||||||
|
uint8_t* old_blob = NULL;
|
||||||
|
size_t old_blob_size = 0;
|
||||||
|
if (tf_ssb_db_blob_get(ssb, old_blob_id, &old_blob, &old_blob_size))
|
||||||
|
{
|
||||||
|
JSValue old_app = JS_ParseJSON(context, (const char*)old_blob, old_blob_size, NULL);
|
||||||
|
if (JS_IsObject(old_app))
|
||||||
|
{
|
||||||
|
JSAtom previous = JS_NewAtom(context, "previous");
|
||||||
|
JS_DeleteProperty(context, old_app, previous, 0);
|
||||||
|
JS_DeleteProperty(context, new_app, previous, 0);
|
||||||
|
|
||||||
|
JSValue old_app_json = JS_JSONStringify(context, old_app, JS_NULL, JS_NULL);
|
||||||
|
JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL);
|
||||||
|
const char* old_app_str = JS_ToCString(context, old_app_json);
|
||||||
|
const char* new_app_str = JS_ToCString(context, new_app_json);
|
||||||
|
|
||||||
|
if (old_app_str && new_app_str && strcmp(old_app_str, new_app_str) == 0)
|
||||||
|
{
|
||||||
|
snprintf(save->blob_id, sizeof(save->blob_id), "/%s", old_blob_id);
|
||||||
|
save->response = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeCString(context, old_app_str);
|
||||||
|
JS_FreeCString(context, new_app_str);
|
||||||
|
JS_FreeValue(context, old_app_json);
|
||||||
|
JS_FreeValue(context, new_app_json);
|
||||||
|
JS_FreeAtom(context, previous);
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, old_app);
|
||||||
|
tf_free(old_blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!save->response)
|
||||||
|
{
|
||||||
|
if (old_blob_id)
|
||||||
|
{
|
||||||
|
JS_SetPropertyStr(context, new_app, "previous", JS_NewString(context, old_blob_id));
|
||||||
|
}
|
||||||
|
JSValue new_app_json = JS_JSONStringify(context, new_app, JS_NULL, JS_NULL);
|
||||||
|
size_t new_app_length = 0;
|
||||||
|
const char* new_app_str = JS_ToCStringLen(context, &new_app_length, new_app_json);
|
||||||
|
|
||||||
|
char blob_id[k_blob_id_len] = { 0 };
|
||||||
|
if (tf_ssb_db_blob_store(ssb, (const uint8_t*)new_app_str, new_app_length, blob_id, sizeof(blob_id), NULL) &&
|
||||||
|
tf_ssb_db_set_property(ssb, user_app->user, app_path, blob_id))
|
||||||
|
{
|
||||||
|
tf_ssb_db_add_value_to_array_property(ssb, user_app->user, "apps", user_app->app);
|
||||||
|
tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id);
|
||||||
|
save->response = 200;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_printf("Blob store or property set failed.\n");
|
||||||
|
save->response = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FreeCString(context, new_app_str);
|
||||||
|
JS_FreeValue(context, new_app_json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save->response = 400;
|
||||||
|
}
|
||||||
|
JS_FreeValue(context, new_app);
|
||||||
|
|
||||||
|
tf_free(app_path);
|
||||||
|
tf_free((void*)old_blob_id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save->response = 403;
|
||||||
|
}
|
||||||
|
tf_free(user_app);
|
||||||
|
}
|
||||||
|
else if (strcmp(request->path, "/save") == 0)
|
||||||
|
{
|
||||||
|
char blob_id[k_blob_id_len] = { 0 };
|
||||||
|
if (tf_ssb_db_blob_store(ssb, request->body, request->content_length, blob_id, sizeof(blob_id), NULL))
|
||||||
|
{
|
||||||
|
tf_string_set(save->blob_id, sizeof(save->blob_id), blob_id);
|
||||||
|
save->response = 200;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tf_printf("Blob store failed.\n");
|
||||||
|
save->response = 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save->response = 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save->response = 401;
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_free((void*)session);
|
||||||
|
JS_FreeCString(context, user_string);
|
||||||
|
JS_FreeValue(context, user);
|
||||||
|
JS_FreeValue(context, jwt);
|
||||||
|
JS_FreeContext(context);
|
||||||
|
JS_FreeRuntime(runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_save_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
save_t* save = user_data;
|
||||||
|
tf_http_request_t* request = save->request;
|
||||||
|
if (*save->blob_id)
|
||||||
|
{
|
||||||
|
char body[256] = "";
|
||||||
|
int length = snprintf(body, sizeof(body), "/%s", save->blob_id);
|
||||||
|
tf_http_respond(request, 200, NULL, 0, body, length);
|
||||||
|
}
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
tf_free(save);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_save(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
save_t* save = tf_malloc(sizeof(save_t));
|
||||||
|
*save = (save_t) {
|
||||||
|
.request = request,
|
||||||
|
};
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_save_work, _httpd_endpoint_save_after_work, save);
|
||||||
|
}
|
202
src/httpd.static.c
Normal file
202
src/httpd.static.c
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "file.js.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(_WIN32)
|
||||||
|
#include <alloca.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct _http_file_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
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 const char* _after(const char* text, const char* prefix)
|
||||||
|
{
|
||||||
|
size_t prefix_length = strlen(prefix);
|
||||||
|
if (text && strncmp(text, prefix, prefix_length) == 0)
|
||||||
|
{
|
||||||
|
return text + prefix_length;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double _time_spec_to_double(const uv_timespec_t* time_spec)
|
||||||
|
{
|
||||||
|
return (double)time_spec->tv_sec + (double)(time_spec->tv_nsec) / 1e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 || _ends_with(path, "core/tfrpc.js"))
|
||||||
|
{
|
||||||
|
const char* content_type = tf_httpd_ext_to_content_type(strrchr(path, '.'), true);
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Type",
|
||||||
|
content_type,
|
||||||
|
"etag",
|
||||||
|
file->etag,
|
||||||
|
"Access-Control-Allow-Origin",
|
||||||
|
"null",
|
||||||
|
};
|
||||||
|
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* content_type = tf_httpd_ext_to_content_type(strrchr(path, '.'), true);
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Type",
|
||||||
|
content_type,
|
||||||
|
"etag",
|
||||||
|
file->etag,
|
||||||
|
};
|
||||||
|
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
}
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
tf_free(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_static_stat(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data)
|
||||||
|
{
|
||||||
|
tf_http_request_t* request = user_data;
|
||||||
|
const char* match = tf_http_request_get_header(request, "if-none-match");
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char etag[512];
|
||||||
|
snprintf(etag, sizeof(etag), "\"%f_%zd\"", _time_spec_to_double(&stat->st_mtim), (size_t)stat->st_size);
|
||||||
|
if (match && strcmp(match, etag) == 0)
|
||||||
|
{
|
||||||
|
tf_http_respond(request, 304, NULL, 0, NULL, 0);
|
||||||
|
tf_http_request_unref(request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
http_file_t* file = tf_malloc(sizeof(http_file_t));
|
||||||
|
*file = (http_file_t) { .request = request };
|
||||||
|
static_assert(sizeof(file->etag) == sizeof(etag), "Size mismatch");
|
||||||
|
memcpy(file->etag, etag, sizeof(etag));
|
||||||
|
tf_file_read(task, path, _httpd_endpoint_static_read, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_static(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
if (request->path && strncmp(request->path, "/.well-known/", strlen("/.well-known/")) && tf_httpd_redirect(request))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* k_static_files[] = {
|
||||||
|
"index.html",
|
||||||
|
"client.js",
|
||||||
|
"tildefriends.svg",
|
||||||
|
"jszip.min.js",
|
||||||
|
"style.css",
|
||||||
|
"tfrpc.js",
|
||||||
|
"w3.css",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* k_map[][2] = {
|
||||||
|
{ "/static/", "core/" },
|
||||||
|
{ "/lit/", "deps/lit/" },
|
||||||
|
{ "/codemirror/", "deps/codemirror/" },
|
||||||
|
{ "/prettier/", "deps/prettier/" },
|
||||||
|
{ "/speedscope/", "deps/speedscope/" },
|
||||||
|
{ "/.well-known/", "data/global/.well-known/" },
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_core = false;
|
||||||
|
const char* after = NULL;
|
||||||
|
const char* file_path = NULL;
|
||||||
|
for (int i = 0; i < tf_countof(k_map) && !after; i++)
|
||||||
|
{
|
||||||
|
const char* next_after = _after(request->path, k_map[i][0]);
|
||||||
|
if (next_after)
|
||||||
|
{
|
||||||
|
after = next_after;
|
||||||
|
file_path = k_map[i][1];
|
||||||
|
is_core = after && i == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!after || !*after) && request->path[strlen(request->path) - 1] == '/')
|
||||||
|
{
|
||||||
|
after = "index.html";
|
||||||
|
if (!file_path)
|
||||||
|
{
|
||||||
|
file_path = "core/";
|
||||||
|
is_core = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!after || strstr(after, ".."))
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_core)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
for (int i = 0; i < tf_countof(k_static_files); i++)
|
||||||
|
{
|
||||||
|
if (strcmp(after, k_static_files[i]) == 0)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
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%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);
|
||||||
|
}
|
140
src/httpd.view.c
Normal file
140
src/httpd.view.c
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#include "httpd.js.h"
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
|
#include "ssb.h"
|
||||||
|
#include "task.h"
|
||||||
|
#include "util.js.h"
|
||||||
|
|
||||||
|
typedef struct _view_t
|
||||||
|
{
|
||||||
|
tf_http_request_t* request;
|
||||||
|
const char** form_data;
|
||||||
|
void* data;
|
||||||
|
size_t size;
|
||||||
|
char etag[256];
|
||||||
|
char notify_want_blob_id[k_blob_id_len];
|
||||||
|
bool not_modified;
|
||||||
|
} view_t;
|
||||||
|
|
||||||
|
static bool _is_filename_safe(const char* filename)
|
||||||
|
{
|
||||||
|
if (!filename)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (const char* p = filename; *p; p++)
|
||||||
|
{
|
||||||
|
if ((*p <= 'a' && *p >= 'z') && (*p <= 'A' && *p >= 'Z') && (*p <= '0' && *p >= '9') && *p != '.' && *p != '-' && *p != '_')
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strlen(filename) < 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_view_work(tf_ssb_t* ssb, void* user_data)
|
||||||
|
{
|
||||||
|
view_t* view = user_data;
|
||||||
|
tf_http_request_t* request = view->request;
|
||||||
|
char blob_id[k_blob_id_len] = "";
|
||||||
|
|
||||||
|
tf_httpd_user_app_t* user_app = tf_httpd_parse_user_app_from_path(request->path, "/view");
|
||||||
|
if (user_app)
|
||||||
|
{
|
||||||
|
size_t app_path_length = strlen("path:") + strlen(user_app->app) + 1;
|
||||||
|
char* app_path = tf_malloc(app_path_length);
|
||||||
|
snprintf(app_path, app_path_length, "path:%s", user_app->app);
|
||||||
|
const char* value = tf_ssb_db_get_property(ssb, user_app->user, app_path);
|
||||||
|
tf_string_set(blob_id, sizeof(blob_id), value);
|
||||||
|
tf_free(app_path);
|
||||||
|
tf_free((void*)value);
|
||||||
|
}
|
||||||
|
else if (request->path[0] == '/' && request->path[1] == '&')
|
||||||
|
{
|
||||||
|
snprintf(blob_id, sizeof(blob_id), "%.*s", (int)(strlen(request->path) - strlen("/view") - 1), request->path + 1);
|
||||||
|
}
|
||||||
|
tf_free(user_app);
|
||||||
|
|
||||||
|
if (*blob_id)
|
||||||
|
{
|
||||||
|
snprintf(view->etag, sizeof(view->etag), "\"%s\"", blob_id);
|
||||||
|
const char* if_none_match = tf_http_request_get_header(request, "if-none-match");
|
||||||
|
char match[258];
|
||||||
|
snprintf(match, sizeof(match), "\"%s\"", blob_id);
|
||||||
|
if (if_none_match && strcmp(if_none_match, match) == 0)
|
||||||
|
{
|
||||||
|
view->not_modified = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!tf_ssb_db_blob_get(ssb, blob_id, (uint8_t**)&view->data, &view->size))
|
||||||
|
{
|
||||||
|
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
|
||||||
|
tf_ssb_db_add_blob_wants(db, blob_id);
|
||||||
|
tf_ssb_release_db_writer(ssb, db);
|
||||||
|
tf_string_set(view->notify_want_blob_id, sizeof(view->notify_want_blob_id), blob_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _httpd_endpoint_view_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
|
{
|
||||||
|
view_t* view = user_data;
|
||||||
|
const char* filename = tf_httpd_form_data_get(view->form_data, "filename");
|
||||||
|
if (!_is_filename_safe(filename))
|
||||||
|
{
|
||||||
|
filename = NULL;
|
||||||
|
}
|
||||||
|
char content_disposition[512] = "";
|
||||||
|
if (filename)
|
||||||
|
{
|
||||||
|
snprintf(content_disposition, sizeof(content_disposition), "attachment; filename=%s", filename);
|
||||||
|
}
|
||||||
|
const char* headers[] = {
|
||||||
|
"Content-Security-Policy",
|
||||||
|
"sandbox allow-downloads allow-top-navigation-by-user-activation",
|
||||||
|
"Content-Type",
|
||||||
|
view->data ? tf_httpd_magic_bytes_to_content_type(view->data, view->size) : "text/plain",
|
||||||
|
"etag",
|
||||||
|
view->etag,
|
||||||
|
filename ? "Content-Disposition" : NULL,
|
||||||
|
filename ? content_disposition : NULL,
|
||||||
|
};
|
||||||
|
int count = filename ? tf_countof(headers) / 2 : (tf_countof(headers) / 2 - 1);
|
||||||
|
if (view->not_modified)
|
||||||
|
{
|
||||||
|
tf_http_respond(view->request, 304, headers, count, NULL, 0);
|
||||||
|
}
|
||||||
|
else if (view->data)
|
||||||
|
{
|
||||||
|
tf_http_respond(view->request, 200, headers, count, view->data, view->size);
|
||||||
|
tf_free(view->data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* k_payload = tf_http_status_text(404);
|
||||||
|
tf_http_respond(view->request, 404, NULL, 0, k_payload, strlen(k_payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*view->notify_want_blob_id)
|
||||||
|
{
|
||||||
|
tf_ssb_notify_blob_want_added(ssb, view->notify_want_blob_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
tf_free(view->form_data);
|
||||||
|
tf_http_request_unref(view->request);
|
||||||
|
tf_free(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tf_httpd_endpoint_view(tf_http_request_t* request)
|
||||||
|
{
|
||||||
|
tf_http_request_ref(request);
|
||||||
|
tf_task_t* task = request->user_data;
|
||||||
|
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||||
|
view_t* view = tf_malloc(sizeof(view_t));
|
||||||
|
*view = (view_t) { .request = request, .form_data = tf_httpd_form_data_decode(request->query, request->query ? strlen(request->query) : 0) };
|
||||||
|
tf_ssb_run_work(ssb, _httpd_endpoint_view_work, _httpd_endpoint_view_after_work, view);
|
||||||
|
}
|
@@ -13,13 +13,13 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.0.32</string>
|
<string>0.0.33</string>
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
<array>
|
<array>
|
||||||
<string>iPhoneOS</string>
|
<string>iPhoneOS</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>14</string>
|
<string>15</string>
|
||||||
<key>DTPlatformName</key>
|
<key>DTPlatformName</key>
|
||||||
<string>iphoneos</string>
|
<string>iphoneos</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
#include "util.js.h"
|
#include "util.js.h"
|
||||||
|
|
||||||
#include "quickjs.h"
|
#include "quickjs.h"
|
||||||
|
#include "sodium/crypto_generichash.h"
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
#include "uv.h"
|
#include "uv.h"
|
||||||
|
|
||||||
@@ -154,11 +155,11 @@ static void _tf_mem_summarize(void* ptr, size_t size, int frames_count, void* co
|
|||||||
{
|
{
|
||||||
summary_t* summary = user_data;
|
summary_t* summary = user_data;
|
||||||
tf_mem_allocation_t allocation = {
|
tf_mem_allocation_t allocation = {
|
||||||
.stack_hash = tf_util_fnv32a(frames, sizeof(void*) * frames_count, 0),
|
|
||||||
.count = 1,
|
.count = 1,
|
||||||
.size = size,
|
.size = size,
|
||||||
.frames_count = frames_count,
|
.frames_count = frames_count,
|
||||||
};
|
};
|
||||||
|
crypto_generichash((void*)&allocation.stack_hash, sizeof(allocation.stack_hash), (const void*)frames, sizeof(void*) * frames_count, NULL, 0);
|
||||||
memcpy(allocation.frames, frames, sizeof(void*) * frames_count);
|
memcpy(allocation.frames, frames, sizeof(void*) * frames_count);
|
||||||
|
|
||||||
int index = tf_util_insert_index(&allocation, summary->allocations, summary->count, sizeof(tf_mem_allocation_t), _tf_mem_hash_stack_compare);
|
int index = tf_util_insert_index(&allocation, summary->allocations, summary->count, sizeof(tf_mem_allocation_t), _tf_mem_hash_stack_compare);
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "mem.h"
|
#include "mem.h"
|
||||||
|
#include "ssb.db.h"
|
||||||
#include "ssb.h"
|
#include "ssb.h"
|
||||||
#include "util.js.h"
|
#include "util.js.h"
|
||||||
|
|
||||||
@@ -51,15 +52,19 @@ static void _tf_ssb_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connections, char* host, size_t host_size, int* port, char* key, size_t key_size)
|
static bool _tf_ssb_connections_get_next_connection(
|
||||||
|
tf_ssb_connections_t* connections, char* host, size_t host_size, int* port, char* key, size_t key_size, bool* out_stay_connected)
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
sqlite3_stmt* statement;
|
sqlite3_stmt* statement;
|
||||||
sqlite3* db = tf_ssb_acquire_db_reader(connections->ssb);
|
sqlite3* db = tf_ssb_acquire_db_reader(connections->ssb);
|
||||||
|
|
||||||
|
tf_ssb_db_get_global_setting_bool(db, "stay_connected", out_stay_connected);
|
||||||
|
|
||||||
if (sqlite3_prepare_v2(db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > ?1) ORDER BY last_attempt LIMIT 1",
|
if (sqlite3_prepare_v2(db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > ?1) ORDER BY last_attempt LIMIT 1",
|
||||||
-1, &statement, NULL) == SQLITE_OK)
|
-1, &statement, NULL) == SQLITE_OK)
|
||||||
{
|
{
|
||||||
if (sqlite3_bind_int(statement, 1, 60000) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
if (sqlite3_bind_int(statement, 1, *out_stay_connected ? 15 : 60000) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
|
||||||
{
|
{
|
||||||
tf_string_set(host, host_size, (const char*)sqlite3_column_text(statement, 0));
|
tf_string_set(host, host_size, (const char*)sqlite3_column_text(statement, 0));
|
||||||
*port = sqlite3_column_int(statement, 1);
|
*port = sqlite3_column_int(statement, 1);
|
||||||
@@ -80,6 +85,8 @@ typedef struct _tf_ssb_connections_get_next_t
|
|||||||
{
|
{
|
||||||
tf_ssb_connections_t* connections;
|
tf_ssb_connections_t* connections;
|
||||||
bool ready;
|
bool ready;
|
||||||
|
bool stay_connected;
|
||||||
|
bool full;
|
||||||
char host[256];
|
char host[256];
|
||||||
int port;
|
int port;
|
||||||
char key[k_id_base64_len];
|
char key[k_id_base64_len];
|
||||||
@@ -92,7 +99,7 @@ static void _tf_ssb_connections_get_next_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
next->ready = _tf_ssb_connections_get_next_connection(next->connections, next->host, sizeof(next->host), &next->port, next->key, sizeof(next->key));
|
next->ready = _tf_ssb_connections_get_next_connection(next->connections, next->host, sizeof(next->host), &next->port, next->key, sizeof(next->key), &next->stay_connected);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _tf_ssb_connections_get_next_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
static void _tf_ssb_connections_get_next_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||||
@@ -100,12 +107,20 @@ static void _tf_ssb_connections_get_next_after_work(tf_ssb_t* ssb, int status, v
|
|||||||
tf_ssb_connections_get_next_t* next = user_data;
|
tf_ssb_connections_get_next_t* next = user_data;
|
||||||
if (next->ready)
|
if (next->ready)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
** Might be a duplicate connection or otherwise discarded by
|
||||||
|
** tf_ssb_connect() before we otherwise set attempted, so do it
|
||||||
|
** here.
|
||||||
|
*/
|
||||||
|
tf_ssb_connections_set_attempted(next->connections, next->host, next->port, next->key);
|
||||||
|
|
||||||
uint8_t key_bin[k_id_bin_len];
|
uint8_t key_bin[k_id_bin_len];
|
||||||
if (tf_ssb_id_str_to_bin(key_bin, next->key))
|
if (tf_ssb_id_str_to_bin(key_bin, next->key))
|
||||||
{
|
{
|
||||||
tf_ssb_connect(ssb, next->host, next->port, key_bin, k_tf_ssb_connect_flag_do_not_store, NULL, NULL);
|
tf_ssb_connect(ssb, next->host, next->port, key_bin, k_tf_ssb_connect_flag_do_not_store, NULL, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
uv_timer_set_repeat(&next->connections->timer, next->stay_connected ? (next->full ? 2000 : 200) : (next->full ? 10000 : 2000));
|
||||||
tf_free(next);
|
tf_free(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +139,7 @@ static void _tf_ssb_connections_timer(uv_timer_t* timer)
|
|||||||
tf_ssb_connections_get_next_t* next = tf_malloc(sizeof(tf_ssb_connections_get_next_t));
|
tf_ssb_connections_get_next_t* next = tf_malloc(sizeof(tf_ssb_connections_get_next_t));
|
||||||
*next = (tf_ssb_connections_get_next_t) {
|
*next = (tf_ssb_connections_get_next_t) {
|
||||||
.connections = connections,
|
.connections = connections,
|
||||||
|
.full = count + 1 == tf_countof(active),
|
||||||
};
|
};
|
||||||
tf_ssb_run_work(connections->ssb, _tf_ssb_connections_get_next_work, _tf_ssb_connections_get_next_after_work, next);
|
tf_ssb_run_work(connections->ssb, _tf_ssb_connections_get_next_work, _tf_ssb_connections_get_next_after_work, next);
|
||||||
}
|
}
|
||||||
|
@@ -384,28 +384,6 @@ void tf_ssb_ebt_set_send_clock_pending(tf_ssb_ebt_t* ebt, int pending)
|
|||||||
ebt->send_clock_pending = pending;
|
ebt->send_clock_pending = pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tf_ssb_ebt_debug_clock(tf_ssb_ebt_t* ebt, JSContext* context, JSValue debug)
|
|
||||||
{
|
|
||||||
uv_mutex_lock(&ebt->mutex);
|
|
||||||
for (int i = 0; i < ebt->entries_count; i++)
|
|
||||||
{
|
|
||||||
ebt_entry_t* entry = &ebt->entries[i];
|
|
||||||
JSValue clock = JS_NewObject(context);
|
|
||||||
JSValue out = JS_NewObject(context);
|
|
||||||
JSValue in = JS_NewObject(context);
|
|
||||||
JS_SetPropertyStr(context, out, "value", JS_NewInt64(context, entry->out));
|
|
||||||
JS_SetPropertyStr(context, out, "replicate", JS_NewBool(context, entry->out_replicate));
|
|
||||||
JS_SetPropertyStr(context, out, "receive", JS_NewBool(context, entry->out_receive));
|
|
||||||
JS_SetPropertyStr(context, clock, "out", out);
|
|
||||||
JS_SetPropertyStr(context, in, "value", JS_NewInt64(context, entry->in));
|
|
||||||
JS_SetPropertyStr(context, in, "replicate", JS_NewBool(context, entry->in_replicate));
|
|
||||||
JS_SetPropertyStr(context, in, "receive", JS_NewBool(context, entry->in_receive));
|
|
||||||
JS_SetPropertyStr(context, clock, "in", in);
|
|
||||||
JS_SetPropertyStr(context, debug, entry->id, clock);
|
|
||||||
}
|
|
||||||
uv_mutex_unlock(&ebt->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tf_ssb_ebt_get_progress(tf_ssb_ebt_t* ebt, int* in_pending, int* in_total, int* out_pending, int* out_total)
|
void tf_ssb_ebt_get_progress(tf_ssb_ebt_t* ebt, int* in_pending, int* in_total, int* out_pending, int* out_total)
|
||||||
{
|
{
|
||||||
uv_mutex_lock(&ebt->mutex);
|
uv_mutex_lock(&ebt->mutex);
|
||||||
|
@@ -106,15 +106,6 @@ int tf_ssb_ebt_get_send_clock_pending(tf_ssb_ebt_t* ebt);
|
|||||||
*/
|
*/
|
||||||
void tf_ssb_ebt_set_send_clock_pending(tf_ssb_ebt_t* ebt, int pending);
|
void tf_ssb_ebt_set_send_clock_pending(tf_ssb_ebt_t* ebt, int pending);
|
||||||
|
|
||||||
/**
|
|
||||||
** Get a JSON representation of the clock state for
|
|
||||||
** debugging.
|
|
||||||
** @param ebt The EBT instance.
|
|
||||||
** @param context The JS context.
|
|
||||||
** @param debug A JS object populated with the information.
|
|
||||||
*/
|
|
||||||
void tf_ssb_ebt_debug_clock(tf_ssb_ebt_t* ebt, JSContext* context, JSValue debug);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** Get a representation of sync progress.
|
** Get a representation of sync progress.
|
||||||
** @param ebt The EBT instance.
|
** @param ebt The EBT instance.
|
||||||
|
15
src/ssb.js.c
15
src/ssb.js.c
@@ -1082,7 +1082,7 @@ static void _tf_ssb_sqlAsync_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
uv_async_send(&sql_work->async);
|
uv_async_send(&sql_work->async);
|
||||||
sqlite3_stmt* statement = NULL;
|
sqlite3_stmt* statement = NULL;
|
||||||
sql_work->result = sqlite3_prepare_v2(db, sql_work->query, -1, &statement, NULL);
|
sql_work->result = sqlite3_prepare_v2(db, sql_work->query, -1, &statement, NULL);
|
||||||
if (sql_work->result == SQLITE_OK)
|
if (sql_work->result == SQLITE_OK && statement)
|
||||||
{
|
{
|
||||||
const uint8_t* p = sql_work->binds;
|
const uint8_t* p = sql_work->binds;
|
||||||
int column = 0;
|
int column = 0;
|
||||||
@@ -1162,7 +1162,11 @@ static void _tf_ssb_sqlAsync_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sql_work->result = r;
|
sql_work->result = r;
|
||||||
if (r != SQLITE_OK && r != SQLITE_DONE)
|
if (r == SQLITE_MISUSE)
|
||||||
|
{
|
||||||
|
sql_work->error = tf_strdup(sqlite3_errstr(sql_work->result));
|
||||||
|
}
|
||||||
|
else if (r != SQLITE_OK && r != SQLITE_DONE)
|
||||||
{
|
{
|
||||||
if (sqlite3_is_interrupted(db))
|
if (sqlite3_is_interrupted(db))
|
||||||
{
|
{
|
||||||
@@ -1176,10 +1180,15 @@ static void _tf_ssb_sqlAsync_work(tf_ssb_t* ssb, void* user_data)
|
|||||||
_tf_ssb_sql_append(&sql_work->rows, &sql_work->rows_count, &(uint8_t[]) { 0 }, 1);
|
_tf_ssb_sql_append(&sql_work->rows, &sql_work->rows_count, &(uint8_t[]) { 0 }, 1);
|
||||||
sqlite3_finalize(statement);
|
sqlite3_finalize(statement);
|
||||||
}
|
}
|
||||||
else
|
else if (sql_work->result != SQLITE_OK)
|
||||||
{
|
{
|
||||||
sql_work->error = tf_strdup(sqlite3_errmsg(db));
|
sql_work->error = tf_strdup(sqlite3_errmsg(db));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sql_work->result = SQLITE_ERROR;
|
||||||
|
sql_work->error = tf_strdup("Statement not prepared");
|
||||||
|
}
|
||||||
uv_mutex_lock(&sql_work->lock);
|
uv_mutex_lock(&sql_work->lock);
|
||||||
sql_work->db = NULL;
|
sql_work->db = NULL;
|
||||||
uv_mutex_unlock(&sql_work->lock);
|
uv_mutex_unlock(&sql_work->lock);
|
||||||
|
@@ -332,8 +332,6 @@ static void _tf_ssb_rpc_tunnel_callback(tf_ssb_connection_t* connection, uint8_t
|
|||||||
|
|
||||||
JS_FreeValue(context, stack_val);
|
JS_FreeValue(context, stack_val);
|
||||||
JS_FreeValue(context, message_val);
|
JS_FreeValue(context, message_val);
|
||||||
|
|
||||||
tf_ssb_connection_close(tun->connection, buffer);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
#include "ares.h"
|
#include "ares.h"
|
||||||
#include "backtrace.h"
|
#include "backtrace.h"
|
||||||
#include "quickjs.h"
|
#include "quickjs.h"
|
||||||
|
#include "sodium/crypto_generichash.h"
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
#include "unzip.h"
|
#include "unzip.h"
|
||||||
#include "uv.h"
|
#include "uv.h"
|
||||||
@@ -1255,6 +1256,8 @@ static void _tf_task_free_promise(tf_task_t* task, promiseid_t id)
|
|||||||
JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise)
|
JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise)
|
||||||
{
|
{
|
||||||
uint32_t stack_hash = 0;
|
uint32_t stack_hash = 0;
|
||||||
|
crypto_generichash_state state;
|
||||||
|
crypto_generichash_init(&state, NULL, 0, sizeof(stack_hash));
|
||||||
if (task->_promise_stack_debug)
|
if (task->_promise_stack_debug)
|
||||||
{
|
{
|
||||||
JSValue error = JS_ThrowInternalError(task->_context, "promise callstack");
|
JSValue error = JS_ThrowInternalError(task->_context, "promise callstack");
|
||||||
@@ -1262,16 +1265,17 @@ JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise)
|
|||||||
JSValue stack_value = JS_GetPropertyStr(task->_context, exception, "stack");
|
JSValue stack_value = JS_GetPropertyStr(task->_context, exception, "stack");
|
||||||
size_t length = 0;
|
size_t length = 0;
|
||||||
const char* stack = JS_ToCStringLen(task->_context, &length, stack_value);
|
const char* stack = JS_ToCStringLen(task->_context, &length, stack_value);
|
||||||
stack_hash = tf_util_fnv32a((const void*)stack, (int)length, 0);
|
crypto_generichash_update(&state, (const void*)stack, (int)length);
|
||||||
void* buffer[31];
|
void* buffer[31];
|
||||||
int count = tf_util_backtrace(buffer, sizeof(buffer) / sizeof(*buffer));
|
int count = tf_util_backtrace(buffer, sizeof(buffer) / sizeof(*buffer));
|
||||||
stack_hash = tf_util_fnv32a((const void*)buffer, sizeof(void*) * count, stack_hash);
|
crypto_generichash_update(&state, (const void*)buffer, sizeof(void*) * count);
|
||||||
_add_promise_stack(task, stack_hash, stack, buffer, count);
|
_add_promise_stack(task, stack_hash, stack, buffer, count);
|
||||||
JS_FreeCString(task->_context, stack);
|
JS_FreeCString(task->_context, stack);
|
||||||
JS_FreeValue(task->_context, stack_value);
|
JS_FreeValue(task->_context, stack_value);
|
||||||
JS_FreeValue(task->_context, exception);
|
JS_FreeValue(task->_context, exception);
|
||||||
JS_FreeValue(task->_context, error);
|
JS_FreeValue(task->_context, error);
|
||||||
}
|
}
|
||||||
|
crypto_generichash_final(&state, (void*)&stack_hash, sizeof(stack_hash));
|
||||||
|
|
||||||
promiseid_t promise_id;
|
promiseid_t promise_id;
|
||||||
do
|
do
|
||||||
|
@@ -7,7 +7,6 @@
|
|||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
|
|
||||||
#include "backtrace.h"
|
#include "backtrace.h"
|
||||||
#include "openssl/sha.h"
|
|
||||||
#include "picohttpparser.h"
|
#include "picohttpparser.h"
|
||||||
#include "sodium/utils.h"
|
#include "sodium/utils.h"
|
||||||
#include "uv.h"
|
#include "uv.h"
|
||||||
@@ -400,6 +399,10 @@ static const setting_t k_settings[] = {
|
|||||||
{ .name = "autologin", .type = "boolean", .description = "Whether mobile autologin is supported.", .default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE != 0 } },
|
{ .name = "autologin", .type = "boolean", .description = "Whether mobile autologin is supported.", .default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE != 0 } },
|
||||||
{ .name = "broadcast", .type = "boolean", .description = "Send network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
|
{ .name = "broadcast", .type = "boolean", .description = "Send network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
|
||||||
{ .name = "discovery", .type = "boolean", .description = "Receive network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
|
{ .name = "discovery", .type = "boolean", .description = "Receive network discovery broadcasts.", .default_value = { .kind = k_kind_bool, .bool_value = true } },
|
||||||
|
{ .name = "stay_connected",
|
||||||
|
.type = "boolean",
|
||||||
|
.description = "Whether to attempt to keep several peer connections open.",
|
||||||
|
.default_value = { .kind = k_kind_bool, .bool_value = false } },
|
||||||
};
|
};
|
||||||
|
|
||||||
static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind)
|
static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind)
|
||||||
@@ -691,17 +694,6 @@ bool tf_util_is_mobile()
|
|||||||
return TF_IS_MOBILE != 0;
|
return TF_IS_MOBILE != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t tf_util_fnv32a(const void* buffer, int length, uint32_t start)
|
|
||||||
{
|
|
||||||
uint32_t result = 0x811c9dc5;
|
|
||||||
for (int i = 0; i < length; i++)
|
|
||||||
{
|
|
||||||
result ^= ((const uint8_t*)buffer)[i];
|
|
||||||
result += (result << 1) + (result << 4) + (result << 7) + (result << 8) + (result << 24);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t tf_string_set(char* buffer, size_t size, const char* string)
|
size_t tf_string_set(char* buffer, size_t size, const char* string)
|
||||||
{
|
{
|
||||||
size_t length = string ? strlen(string) : 0;
|
size_t length = string ? strlen(string) : 0;
|
||||||
|
@@ -224,15 +224,6 @@ void tf_util_document_settings(const char* line_prefix);
|
|||||||
*/
|
*/
|
||||||
bool tf_util_is_mobile();
|
bool tf_util_is_mobile();
|
||||||
|
|
||||||
/**
|
|
||||||
** Compute a 32-bit hash of a buffer.
|
|
||||||
** @param buffer The data.
|
|
||||||
** @param length The size of the buffer in bytes.
|
|
||||||
** @param start The hash seed.
|
|
||||||
** @return The computed hash.
|
|
||||||
*/
|
|
||||||
uint32_t tf_util_fnv32a(const void* buffer, int length, uint32_t start);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
** Populate a string buffer, truncating if necessary.
|
** Populate a string buffer, truncating if necessary.
|
||||||
** @param buffer The buffer.
|
** @param buffer The buffer.
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
#define VERSION_NUMBER "0.0.32"
|
#define VERSION_NUMBER "0.0.33-wip"
|
||||||
#define VERSION_NAME "This program kills fascists."
|
#define VERSION_NAME "This program kills fascists."
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
VERSION=3.3.0
|
VERSION=3.3.1
|
||||||
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js -O deps/lit/lit-all.min.js
|
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js -O deps/lit/lit-all.min.js
|
||||||
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js.map -O deps/lit/lit-all.min.js.map
|
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js.map -O deps/lit/lit-all.min.js.map
|
||||||
cp -fv deps/lit/* apps/blog/
|
cp -fv deps/lit/* apps/blog/
|
||||||
|
Reference in New Issue
Block a user