Compare commits
32 Commits
v0.0.32.1
...
latest_rel
Author | SHA1 | Date | |
---|---|---|---|
81bd54dbe6 | |||
6a1bb0d3bc | |||
705e8b553f | |||
e4729b22f2 | |||
662112551a | |||
38fe88aab8 | |||
578c51faa0 | |||
a3ccc73b81 | |||
7312f4d43a | |||
8b546c7e02 | |||
c0b6ff2e64 | |||
638b7cc1e5 | |||
05e54e1be0 | |||
4c3299ead0 | |||
1ef56b35ad | |||
061e79c295 | |||
5edfe732b1 | |||
a8f9b67f71 | |||
de7fbf1eb7 | |||
a51a3d7e43 | |||
433b3b1003 | |||
6703c5b584 | |||
5f729efabe | |||
b2085b3f28 | |||
2f893494b0 | |||
e26af21f63 | |||
7e1d738f8d | |||
199448e11e | |||
fdaabab807 | |||
ca4560c5c9 | |||
2478f3064d | |||
e9b8b43e7c |
8
Doxyfile
8
Doxyfile
@@ -342,7 +342,7 @@ OPTIMIZE_OUTPUT_SLICE = NO
|
||||
#
|
||||
# Note see also the list of default file extension mappings.
|
||||
|
||||
EXTENSION_MAPPING =
|
||||
EXTENSION_MAPPING = js=javascript
|
||||
|
||||
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
|
||||
# according to the Markdown format, which allows for more readable
|
||||
@@ -944,6 +944,11 @@ WARN_LOGFILE =
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = README.md \
|
||||
core/app.js \
|
||||
core/client.js \
|
||||
core/core.js \
|
||||
core/http.js \
|
||||
core/tfrpc.js \
|
||||
docs/ \
|
||||
src/
|
||||
|
||||
@@ -986,6 +991,7 @@ INPUT_FILE_ENCODING =
|
||||
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.h \
|
||||
*.js \
|
||||
*.md
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
|
@@ -16,14 +16,14 @@ MAKEFLAGS += --no-builtin-rules
|
||||
## LD := Linker.
|
||||
## ANDROID_SDK := Path to the Android SDK.
|
||||
|
||||
VERSION_CODE := 39
|
||||
VERSION_CODE := 40
|
||||
VERSION_CODE_IOS := 15
|
||||
VERSION_NUMBER := 0.0.32.1
|
||||
VERSION_NUMBER := 0.0.33
|
||||
VERSION_NAME := This program kills fascists.
|
||||
|
||||
IPHONEOS_VERSION_MIN=14.0
|
||||
|
||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500200.zip
|
||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500400.zip
|
||||
BUNDLETOOL_URL := https://github.com/google/bundletool/releases/download/1.17.0/bundletool-all-1.17.0.jar
|
||||
APPIMAGETOOL_URL := https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||
APPIMAGETOOL_MD5 := e989fadfc4d685fd3d6aeeb9b525d74d out/appimagetool
|
||||
|
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",
|
||||
"emoji": "🦀",
|
||||
"previous": "&Ym1vefMN4CV4UIgLuV+zu52qj58WwIScctt4v5YIHmQ=.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
@@ -692,7 +692,7 @@ class TfElement extends LitElement {
|
||||
class="w3-bar w3-theme-l1"
|
||||
style="position: static; top: 0; z-index: 10"
|
||||
>
|
||||
${this.is_administrator
|
||||
${this.is_administrator && self.tab != 'news'
|
||||
? html`
|
||||
<button
|
||||
class=${'w3-bar-item w3-button w3-circle w3-ripple' +
|
||||
|
@@ -191,12 +191,12 @@ class TfMessageElement extends LitElement {
|
||||
div.style.display = 'grid';
|
||||
let img = document.createElement('img');
|
||||
img.src = link;
|
||||
img.style.maxWidth = '100%';
|
||||
img.style.maxHeight = '100%';
|
||||
img.style.maxWidth = '100vw';
|
||||
img.style.maxHeight = '100vh';
|
||||
img.style.display = 'block';
|
||||
img.style.margin = 'auto';
|
||||
img.style.objectFit = 'contain';
|
||||
img.style.width = '100%';
|
||||
img.style.width = '100vw';
|
||||
div.appendChild(img);
|
||||
function image_close(event) {
|
||||
document.body.removeChild(div);
|
||||
|
@@ -14,6 +14,7 @@ class TfProfileElement extends LitElement {
|
||||
sequence: {type: Number},
|
||||
following: {type: Boolean},
|
||||
blocking: {type: Boolean},
|
||||
show_followed: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,12 +179,12 @@ class TfProfileElement extends LitElement {
|
||||
div.style.display = 'grid';
|
||||
let img = document.createElement('img');
|
||||
img.src = link;
|
||||
img.style.maxWidth = '100%';
|
||||
img.style.maxHeight = '100%';
|
||||
img.style.maxWidth = '100vw';
|
||||
img.style.maxHeight = '100vh';
|
||||
img.style.display = 'block';
|
||||
img.style.margin = 'auto';
|
||||
img.style.objectFit = 'contain';
|
||||
img.style.width = '100%';
|
||||
img.style.width = '100vw';
|
||||
div.appendChild(img);
|
||||
function image_close(event) {
|
||||
document.body.removeChild(div);
|
||||
@@ -202,11 +203,7 @@ class TfProfileElement extends LitElement {
|
||||
|
||||
toggle_account_list(event) {
|
||||
let content = event.srcElement.nextElementSibling;
|
||||
if (content.classList.toggle('w3-hide')) {
|
||||
event.srcElement.innerText = 'Show Followed Accounts';
|
||||
} else {
|
||||
event.srcElement.innerText = 'Hide Followed Accounts';
|
||||
}
|
||||
this.show_followed = !this.show_followed;
|
||||
}
|
||||
|
||||
async load_follows() {
|
||||
@@ -214,12 +211,13 @@ class TfProfileElement extends LitElement {
|
||||
return html`
|
||||
<div class="w3-container">
|
||||
<button
|
||||
class="w3-button w3-block w3-theme-d1"
|
||||
class="w3-button w3-block w3-theme-d1 followed_accounts"
|
||||
@click=${this.toggle_account_list}
|
||||
>
|
||||
Show Followed Accounts
|
||||
${this.show_followed ? 'Hide' : 'Show'} Followed Accounts
|
||||
(${Object.keys(accounts).length})
|
||||
</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">
|
||||
${Object.keys(accounts).map(
|
||||
(x) => html`
|
||||
@@ -329,7 +327,7 @@ class TfProfileElement extends LitElement {
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: row; gap: 1em">
|
||||
${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
|
||||
? 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) {
|
||||
this.time_loading = [start_time, end_time];
|
||||
let result;
|
||||
const k_max_results = 64;
|
||||
if (this.hash == '#@') {
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
@@ -118,7 +119,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
WHERE
|
||||
messages.author != ?1 AND
|
||||
(?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
|
||||
FROM mentions
|
||||
JOIN messages_refs ON mentions.id = messages_refs.ref
|
||||
@@ -131,6 +132,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
JSON.stringify(this.following),
|
||||
start_time,
|
||||
end_time,
|
||||
k_max_results,
|
||||
]
|
||||
);
|
||||
} 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
|
||||
FROM messages
|
||||
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
|
||||
FROM selected
|
||||
@@ -149,7 +151,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
UNION
|
||||
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('#%')) {
|
||||
result = await tfrpc.rpc.query(
|
||||
@@ -184,13 +186,14 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
)
|
||||
SELECT TRUE AS is_primary, all_news.* FROM all_news
|
||||
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),
|
||||
start_time,
|
||||
end_time,
|
||||
this.hash.substring(2),
|
||||
k_max_results,
|
||||
]
|
||||
);
|
||||
let t1 = new Date();
|
||||
@@ -208,9 +211,14 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
WHERE
|
||||
(?2 IS NULL OR (messages.timestamp >= ?2)) AND messages.timestamp < ?3 AND
|
||||
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);
|
||||
} else if (this.hash == '#👍') {
|
||||
@@ -222,14 +230,14 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
WHERE
|
||||
messages.content ->> 'type' = 'vote' AND
|
||||
(?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
|
||||
FROM votes
|
||||
JOIN messages ON messages.id = votes.content ->> '$.vote.link'
|
||||
UNION
|
||||
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 {
|
||||
let t0 = new Date();
|
||||
@@ -240,9 +248,9 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
||||
messages.content ->> 'type' != 'vote'
|
||||
ORDER BY timestamp DESC LIMIT 20
|
||||
ORDER BY timestamp DESC LIMIT ?4
|
||||
`,
|
||||
[JSON.stringify(this.following), start_time, end_time]
|
||||
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
||||
);
|
||||
let t1 = new Date();
|
||||
result = await this._fetch_related_messages(initial_messages);
|
||||
|
@@ -156,11 +156,8 @@ class TfTabNewsElement extends LitElement {
|
||||
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
|
||||
}
|
||||
|
||||
compare_follows() {
|
||||
const now = new Date().valueOf();
|
||||
return function (a, b) {
|
||||
return (b[1].ts > now ? -1 : b[1].ts) - (a[1].ts > now ? -1 : a[1].ts);
|
||||
};
|
||||
compare_follows(a, b) {
|
||||
return b[1].ts > a[1].ts ? 1 : b[1].ts < a[1].ts ? -1 : 0;
|
||||
}
|
||||
|
||||
suggested_follows() {
|
||||
@@ -169,9 +166,11 @@ class TfTabNewsElement extends LitElement {
|
||||
** pinned at the top.
|
||||
*/
|
||||
let self = this;
|
||||
let now = new Date().valueOf();
|
||||
return Object.entries(this.users)
|
||||
.filter((x) => x[1].ts < now)
|
||||
.filter((x) => x[1].follow_depth > 1)
|
||||
.sort(self.compare_follows())
|
||||
.sort(self.compare_follows)
|
||||
.slice(0, 8)
|
||||
.map((x) => x[0]);
|
||||
}
|
||||
@@ -203,7 +202,7 @@ class TfTabNewsElement extends LitElement {
|
||||
new Event('refresh', {bubbles: true, composed: true})
|
||||
)}
|
||||
>
|
||||
<span style="width: 1.5em; height: 1.5em; padding: 8px">↻</span>
|
||||
<span style="display: inline-block; width: 1.8em">↻</span>
|
||||
Sync now
|
||||
</button>
|
||||
<button
|
||||
@@ -216,7 +215,10 @@ class TfTabNewsElement extends LitElement {
|
||||
})
|
||||
)}
|
||||
>
|
||||
${this.stay_connected ? '🔗 Online mode' : '⛓️💥 Passive mode'}
|
||||
<span style="display: inline-block; width: 1.8em"
|
||||
>${this.stay_connected ? '🔗' : '⛓️💥'}</span
|
||||
>
|
||||
${this.stay_connected ? 'Online mode' : 'Passive mode'}
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "👋",
|
||||
"previous": "&3puDxDNnf6C+YXpFysYLgxFMAy54/AO9V7Xpja6qO/k=.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
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
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
|
||||
class="w3-button w3-black w3-padding-large"
|
||||
href="https://docs.tildefriends.net/"
|
||||
|
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
287
core/client.js
287
core/client.js
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfclient Tilde Friends Client JS
|
||||
* Tilde Friends client-side browser JavaScript.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \cond */
|
||||
import {LitElement, html, css, svg} from '/lit/lit-all.min.js';
|
||||
|
||||
let cm6;
|
||||
@@ -13,8 +21,9 @@ let gUnloading;
|
||||
let kErrorColor = '#dc322f';
|
||||
let kDisconnectColor = '#f00';
|
||||
let kStatusColor = '#fff';
|
||||
/** \endcond */
|
||||
|
||||
// Functions that server-side app code can call through the app object.
|
||||
/** Functions that server-side app code can call through the app object. */
|
||||
const k_api = {
|
||||
setDocument: {args: ['content'], func: api_setDocument},
|
||||
postMessage: {args: ['message'], func: api_postMessage},
|
||||
@@ -26,29 +35,14 @@ const k_api = {
|
||||
setHash: {args: ['hash'], func: api_setHash},
|
||||
};
|
||||
|
||||
// TODO(tasiaiso): this is only used once, move it down ?
|
||||
const k_global_style = css`
|
||||
a:link {
|
||||
color: #268bd2;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #6c71c4;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #859900;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: #2aa198;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Class that represents the top bar
|
||||
*/
|
||||
class TfNavigationElement extends LitElement {
|
||||
/**
|
||||
* Get Lit Html properties.
|
||||
* @return The properties.
|
||||
*/
|
||||
static get properties() {
|
||||
return {
|
||||
credentials: {type: Object},
|
||||
@@ -64,6 +58,9 @@ class TfNavigationElement extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a TfNavigationElement instance.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.permissions = {};
|
||||
@@ -76,7 +73,7 @@ class TfNavigationElement extends LitElement {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* @param event The HTML event.
|
||||
*/
|
||||
toggle_edit(event) {
|
||||
event.preventDefault();
|
||||
@@ -89,7 +86,7 @@ class TfNavigationElement extends LitElement {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} key
|
||||
* @param key The permission to reset.
|
||||
*/
|
||||
reset_permission(key) {
|
||||
send({action: 'resetPermission', permission: key});
|
||||
@@ -97,9 +94,9 @@ class TfNavigationElement extends LitElement {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} key
|
||||
* @param {*} options
|
||||
* @returns
|
||||
* @param key The spark line identifier.
|
||||
* @param options Spark line options.
|
||||
* @return A spark line HTML element.
|
||||
*/
|
||||
get_spark_line(key, options) {
|
||||
if (!this.spark_lines[key]) {
|
||||
@@ -118,29 +115,49 @@ class TfNavigationElement extends LitElement {
|
||||
return this.spark_lines[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active SSB identity for the current application.
|
||||
* @param id The identity.
|
||||
*/
|
||||
set_active_identity(id) {
|
||||
send({action: 'setActiveIdentity', identity: id});
|
||||
this.renderRoot.getElementById('id_dropdown').classList.remove('w3-show');
|
||||
}
|
||||
|
||||
create_identity(event) {
|
||||
/**
|
||||
* Create a new SSB identity.
|
||||
*/
|
||||
create_identity() {
|
||||
if (confirm('Are you sure you want to create a new identity?')) {
|
||||
send({action: 'createIdentity'});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle visibility of the ID dropdown.
|
||||
*/
|
||||
toggle_id_dropdown() {
|
||||
this.renderRoot.getElementById('id_dropdown').classList.toggle('w3-show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the current identity's SSB profile.
|
||||
*/
|
||||
edit_profile() {
|
||||
window.location.href = '/~core/ssb/#' + this.identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign out of the current Tilde Friends user.
|
||||
*/
|
||||
logout() {
|
||||
window.location.href = `/login/logout?return=${encodeURIComponent(url() + hash())}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the identity dropdown.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render_identity() {
|
||||
let self = this;
|
||||
|
||||
@@ -287,6 +304,9 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current error.
|
||||
*/
|
||||
clear_error() {
|
||||
this.status = {};
|
||||
}
|
||||
@@ -297,6 +317,23 @@ class TfNavigationElement extends LitElement {
|
||||
*/
|
||||
render() {
|
||||
let self = this;
|
||||
const k_global_style = css`
|
||||
a:link {
|
||||
color: #268bd2;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #6c71c4;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #859900;
|
||||
}
|
||||
|
||||
a:active {
|
||||
color: #2aa198;
|
||||
}
|
||||
`;
|
||||
return html`
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||
<style>
|
||||
@@ -407,6 +444,10 @@ customElements.define('tf-navigation', TfNavigationElement);
|
||||
* TODOC
|
||||
*/
|
||||
class TfFilesElement extends LitElement {
|
||||
/**
|
||||
* LitElement properties.
|
||||
* @return The properties.
|
||||
*/
|
||||
static get properties() {
|
||||
return {
|
||||
current: {type: String},
|
||||
@@ -416,6 +457,9 @@ class TfFilesElement extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a TfFilesElement instance.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.files = {};
|
||||
@@ -424,7 +468,7 @@ class TfFilesElement extends LitElement {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} file
|
||||
* @param file The file.
|
||||
*/
|
||||
file_click(file) {
|
||||
this.dispatchEvent(
|
||||
@@ -440,8 +484,8 @@ class TfFilesElement extends LitElement {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} file
|
||||
* @returns
|
||||
* @param file The file.
|
||||
* @returns Lit HTML.
|
||||
*/
|
||||
render_file(file) {
|
||||
let classes = ['file'];
|
||||
@@ -464,7 +508,7 @@ class TfFilesElement extends LitElement {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* @param event The event.
|
||||
*/
|
||||
async drop(event) {
|
||||
event.preventDefault();
|
||||
@@ -490,7 +534,7 @@ class TfFilesElement extends LitElement {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* @param event The event.
|
||||
*/
|
||||
drag_enter(event) {
|
||||
this.dropping++;
|
||||
@@ -500,7 +544,7 @@ class TfFilesElement extends LitElement {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* @param event The event.
|
||||
*/
|
||||
drag_leave(event) {
|
||||
this.dropping--;
|
||||
@@ -509,6 +553,10 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drag over event.
|
||||
* @param event The event.
|
||||
*/
|
||||
drag_over(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
@@ -565,6 +613,10 @@ customElements.define('tf-files', TfFilesElement);
|
||||
* TODOC
|
||||
*/
|
||||
class TfFilesPaneElement extends LitElement {
|
||||
/**
|
||||
* Get Lit Html properties.
|
||||
* @return The properties.
|
||||
*/
|
||||
static get properties() {
|
||||
return {
|
||||
expanded: {type: Boolean},
|
||||
@@ -573,6 +625,9 @@ class TfFilesPaneElement extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a TfFilesPaneElement instance.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.expanded = window.localStorage.getItem('files') != '0';
|
||||
@@ -581,7 +636,7 @@ class TfFilesPaneElement extends LitElement {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} expanded
|
||||
* @param expanded Whether the files pane is expanded.
|
||||
*/
|
||||
set_expanded(expanded) {
|
||||
this.expanded = expanded;
|
||||
@@ -760,10 +815,9 @@ window.addEventListener('keydown', function (event) {
|
||||
});
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} nodes
|
||||
* @param {*} callback
|
||||
* @returns
|
||||
* Make sure a set of dependencies are loaded
|
||||
* @param nodes An array of descriptions of dependencies to load.
|
||||
* @param callback Called when all dependencies are loaded.
|
||||
*/
|
||||
function ensureLoaded(nodes, callback) {
|
||||
if (!nodes.length) {
|
||||
@@ -857,23 +911,10 @@ function trace() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} name
|
||||
* @returns
|
||||
*/
|
||||
function guessMode(name) {
|
||||
return name.endsWith('.js')
|
||||
? 'javascript'
|
||||
: name.endsWith('.html')
|
||||
? 'htmlmixed'
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} name
|
||||
* @param {*} id
|
||||
* @returns
|
||||
* Load a single file.
|
||||
* @param name The name by which the file is known.
|
||||
* @param id The file's ID.
|
||||
* @return A promise resolved with the file's contents.
|
||||
*/
|
||||
function loadFile(name, id) {
|
||||
return fetch('/' + id + '/view')
|
||||
@@ -899,9 +940,9 @@ function loadFile(name, id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} path
|
||||
* @returns
|
||||
* Load files for the app.
|
||||
* @param path The app path to load.
|
||||
* @return A promise resolved when the app is laoded.
|
||||
*/
|
||||
async function load(path) {
|
||||
let response = await fetch((path || url()) + 'view');
|
||||
@@ -958,9 +999,9 @@ function explodePath() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} save_to
|
||||
* @returns
|
||||
* Save the app.
|
||||
* @param save_to An optional path to which to save the app.
|
||||
* @return A promise resoled when the app is saved.
|
||||
*/
|
||||
function save(save_to) {
|
||||
document.getElementById('save').disabled = true;
|
||||
@@ -1050,6 +1091,8 @@ function save(save_to) {
|
||||
|
||||
if (save_path != window.location.pathname) {
|
||||
alert('Saved to ' + save_path + '.');
|
||||
} else if (!gFiles['app.js']) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
reconnect(save_path);
|
||||
}
|
||||
@@ -1127,8 +1170,8 @@ function hash() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} content
|
||||
* Set the iframe document contents.
|
||||
* @param content The contents.
|
||||
*/
|
||||
function api_setDocument(content) {
|
||||
let iframe = document.getElementById('document');
|
||||
@@ -1136,8 +1179,8 @@ function api_setDocument(content) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* Send a message to the sandboxed iframe.
|
||||
* @param message The message.
|
||||
*/
|
||||
function api_postMessage(message) {
|
||||
let iframe = document.getElementById('document');
|
||||
@@ -1146,7 +1189,7 @@ function api_postMessage(message) {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} error
|
||||
* @param error The error.
|
||||
*/
|
||||
function api_error(error) {
|
||||
if (error) {
|
||||
@@ -1160,28 +1203,28 @@ function api_error(error) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} key
|
||||
* @param {*} value
|
||||
et a value in local storage.
|
||||
* @param key The key.
|
||||
* @param value The value.
|
||||
*/
|
||||
function api_localStorageSet(key, value) {
|
||||
window.localStorage.setItem('app:' + key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} key
|
||||
* @returns
|
||||
* Get a value from local storage.
|
||||
* @param key The key.
|
||||
* @return The value.
|
||||
*/
|
||||
function api_localStorageGet(key) {
|
||||
return window.localStorage.getItem('app:' + key);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} permission
|
||||
* @param {*} id
|
||||
* @returns
|
||||
* Request a permission
|
||||
* @param permission The permission to request.
|
||||
* @param id The id requeesting the permission.
|
||||
* @return A promise fulfilled if the permission was granted.
|
||||
*/
|
||||
function api_requestPermission(permission, id) {
|
||||
let outer = document.createElement('div');
|
||||
@@ -1257,8 +1300,8 @@ function api_print() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} hash
|
||||
* Set the window's location hash.
|
||||
* @param hash The new hash.
|
||||
*/
|
||||
function api_setHash(hash) {
|
||||
window.location.hash = hash;
|
||||
@@ -1266,7 +1309,7 @@ function api_setHash(hash) {
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* @param message The message.
|
||||
*/
|
||||
function _receive_websocket_message(message) {
|
||||
if (message && message.action == 'session') {
|
||||
@@ -1362,9 +1405,9 @@ function _receive_websocket_message(message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* @param {*} color
|
||||
* Set the status message.
|
||||
* @param message The message.
|
||||
* @param color The message's color.
|
||||
*/
|
||||
function setStatusMessage(message, color) {
|
||||
document.getElementsByTagName('tf-navigation')[0].status = {
|
||||
@@ -1375,8 +1418,8 @@ function setStatusMessage(message, color) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} value
|
||||
* Send a message to the app.
|
||||
* @param value The message.
|
||||
*/
|
||||
function send(value) {
|
||||
try {
|
||||
@@ -1388,49 +1431,6 @@ function send(value) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} sourceData
|
||||
* @param {*} maxWidth
|
||||
* @param {*} maxHeight
|
||||
* @param {*} callback
|
||||
*/
|
||||
function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
||||
let result = sourceData;
|
||||
let image = new Image();
|
||||
image.crossOrigin = 'anonymous';
|
||||
image.referrerPolicy = 'no-referrer';
|
||||
image.onload = function () {
|
||||
if (image.width > maxWidth || image.height > maxHeight) {
|
||||
let downScale = Math.min(
|
||||
maxWidth / image.width,
|
||||
maxHeight / image.height
|
||||
);
|
||||
let canvas = document.createElement('canvas');
|
||||
canvas.width = image.width * downScale;
|
||||
canvas.height = image.height * downScale;
|
||||
let context = canvas.getContext('2d');
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
image.width = canvas.width;
|
||||
image.height = canvas.height;
|
||||
context.drawImage(image, 0, 0, image.width, image.height);
|
||||
result = canvas.toDataURL();
|
||||
}
|
||||
callback(result);
|
||||
};
|
||||
image.src = sourceData;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} image
|
||||
*/
|
||||
function sendImage(image) {
|
||||
fixImage(image, 320, 240, function (result) {
|
||||
send({image: result});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
*/
|
||||
@@ -1459,8 +1459,8 @@ function blur() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* Handle a message.
|
||||
* @param event The message.
|
||||
*/
|
||||
function message(event) {
|
||||
if (
|
||||
@@ -1508,8 +1508,8 @@ function message(event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} path
|
||||
* Reconnect the WebSocket.
|
||||
* @param path The path to which the WebSocket should be connected.
|
||||
*/
|
||||
function reconnect(path) {
|
||||
let oldSocket = gSocket;
|
||||
@@ -1524,8 +1524,8 @@ function reconnect(path) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} path
|
||||
* Connect the WebSocket.
|
||||
* @param path The path to which to connect.
|
||||
*/
|
||||
function connectSocket(path) {
|
||||
if (!gSocket || gSocket.readyState != gSocket.OPEN) {
|
||||
@@ -1591,8 +1591,8 @@ function connectSocket(path) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} name
|
||||
* Open a file by name.
|
||||
* @param name The file to open.
|
||||
*/
|
||||
function openFile(name) {
|
||||
let newDoc =
|
||||
@@ -1639,8 +1639,8 @@ function updateFiles() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} name
|
||||
* Create a new file with the given name.
|
||||
* @param name The file's name.
|
||||
*/
|
||||
function makeNewFile(name) {
|
||||
gFiles[name] = {
|
||||
@@ -1701,10 +1701,10 @@ async function appExport() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} name
|
||||
* @param {*} file
|
||||
* @returns
|
||||
* Save a file.
|
||||
* @param name The file to svae.
|
||||
* @param file The file contents.
|
||||
* @return A promise resolved with the blob ID of the saved file.
|
||||
*/
|
||||
async function save_file_to_blob_id(name, file) {
|
||||
console.log(`Saving ${name}.`);
|
||||
@@ -1799,7 +1799,7 @@ async function appImport() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Prettify the current source file.
|
||||
*/
|
||||
async function sourcePretty() {
|
||||
let prettier = (await import('/prettier/standalone.mjs')).default;
|
||||
@@ -1827,6 +1827,9 @@ async function sourcePretty() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle visible whitespace.
|
||||
*/
|
||||
function toggleVisibleWhitespace() {
|
||||
let editor_style = document.getElementById('editor_style');
|
||||
/*
|
||||
@@ -1900,3 +1903,5 @@ window.addEventListener('load', function () {
|
||||
toggleVisibleWhitespace();
|
||||
}
|
||||
});
|
||||
|
||||
/** @} */
|
||||
|
115
core/core.js
115
core/core.js
@@ -1,12 +1,30 @@
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfcore Tilde Friends Core JS
|
||||
* Tilde Friends process management, in JavaScript.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \cond */
|
||||
import * as app from './app.js';
|
||||
import * as http from './http.js';
|
||||
|
||||
let gProcesses = {};
|
||||
let gStatsTimer = false;
|
||||
let g_handler_index = 0;
|
||||
export {invoke, getProcessBlob};
|
||||
/** \endcond */
|
||||
|
||||
/** All running processes. */
|
||||
let gProcesses = {};
|
||||
/** Whether stats are currently being sent. */
|
||||
let gStatsTimer = false;
|
||||
/** Effectively a process ID. */
|
||||
let g_handler_index = 0;
|
||||
/** Time between pings, in milliseconds. */
|
||||
const k_ping_interval = 60 * 1000;
|
||||
|
||||
/**
|
||||
* Print an error.
|
||||
* @param error The error.
|
||||
*/
|
||||
function printError(error) {
|
||||
if (error.stackTrace) {
|
||||
print(error.fileName + ':' + error.lineNumber + ': ' + error.message);
|
||||
@@ -19,6 +37,12 @@ function printError(error) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a handler.
|
||||
* @param handlers The handlers on which to invoke the callback.
|
||||
* @param argv Arguments to pass to the handlers.
|
||||
* @return A promise.
|
||||
*/
|
||||
function invoke(handlers, argv) {
|
||||
let promises = [];
|
||||
if (handlers) {
|
||||
@@ -39,6 +63,12 @@ function invoke(handlers, argv) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a named event to all registered apps.
|
||||
* @param eventName the name of the event.
|
||||
* @param argv Arguments to pass to the handlers.
|
||||
* @return A promise.
|
||||
*/
|
||||
function broadcastEvent(eventName, argv) {
|
||||
let promises = [];
|
||||
for (let process of Object.values(gProcesses)) {
|
||||
@@ -49,6 +79,11 @@ function broadcastEvent(eventName, argv) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all other instances of the same app.
|
||||
* @param message The message.
|
||||
* @return A promise.
|
||||
*/
|
||||
function broadcast(message) {
|
||||
let sender = this;
|
||||
let promises = [];
|
||||
@@ -65,6 +100,15 @@ function broadcast(message) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to all instances of the same app running as the same user.
|
||||
* @param user The user.
|
||||
* @param packageOwner The owner of the app.
|
||||
* @param packageName The name of the app.
|
||||
* @param eventName The name of the event.
|
||||
* @param argv The arguments to pass.
|
||||
* @return A promise.
|
||||
*/
|
||||
function broadcastAppEventToUser(
|
||||
user,
|
||||
packageOwner,
|
||||
@@ -87,6 +131,11 @@ function broadcastAppEventToUser(
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user context information for a call.
|
||||
* @param caller The calling process.
|
||||
* @param process The receiving process.
|
||||
*/
|
||||
function getUser(caller, process) {
|
||||
return {
|
||||
key: process.key,
|
||||
@@ -97,38 +146,26 @@ function getUser(caller, process) {
|
||||
};
|
||||
}
|
||||
|
||||
async function getApps(user, process) {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
if (user && user !== process.credentials.session.name && user !== 'core') {
|
||||
return {};
|
||||
} else if (!user) {
|
||||
user = process.credentials.session.name;
|
||||
}
|
||||
}
|
||||
if (user) {
|
||||
let db = new Database(user);
|
||||
try {
|
||||
let names = JSON.parse(await db.get('apps'));
|
||||
let result = {};
|
||||
for (let name of names) {
|
||||
result[name] = await db.get('path:' + name);
|
||||
}
|
||||
return result;
|
||||
} catch {}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message.
|
||||
* @param from The calling process.
|
||||
* @param to The receiving process.
|
||||
* @param message The message.
|
||||
* @return A promise.
|
||||
*/
|
||||
function postMessageInternal(from, to, message) {
|
||||
if (to.eventHandlers['message']) {
|
||||
return invoke(to.eventHandlers['message'], [getUser(from, from), message]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a process for an app blob.
|
||||
* @param blobId The blob identifier.
|
||||
* @param key A unique key for the invocation.
|
||||
* @param options Other options.
|
||||
* @return The process.
|
||||
*/
|
||||
async function getProcessBlob(blobId, key, options) {
|
||||
let process = gProcesses[key];
|
||||
if (!process && !(options && 'create' in options && !options.create)) {
|
||||
@@ -220,7 +257,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
let settings = await loadSettings();
|
||||
return settings?.permissions?.[user] ?? [];
|
||||
},
|
||||
apps: (user) => getApps(user, process),
|
||||
getSockets: getSockets,
|
||||
permissionTest: async function (permission) {
|
||||
let user = process?.credentials?.session?.name;
|
||||
@@ -679,6 +715,10 @@ ssb.addEventListener('connections', function () {
|
||||
broadcastEvent('onConnectionsChanged', []);
|
||||
});
|
||||
|
||||
/**
|
||||
* Load settings from the database.
|
||||
* @return The settings as a key value pairs object.
|
||||
*/
|
||||
async function loadSettings() {
|
||||
let data = {};
|
||||
try {
|
||||
@@ -697,6 +737,9 @@ async function loadSettings() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send periodic stats to all clients.
|
||||
*/
|
||||
function sendStats() {
|
||||
let apps = Object.values(gProcesses)
|
||||
.filter((process) => process.app)
|
||||
@@ -712,6 +755,16 @@ function sendStats() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke an app's handler.js.
|
||||
* @param response The response object.
|
||||
* @param app_blob_id The app's blob identifier.
|
||||
* @param path The request path.
|
||||
* @param query The request query string.
|
||||
* @param headers The request headers.
|
||||
* @param package_owner The app's owner.
|
||||
* @param package_name The app's name.
|
||||
*/
|
||||
exports.callAppHandler = async function callAppHandler(
|
||||
response,
|
||||
app_blob_id,
|
||||
@@ -777,4 +830,4 @@ exports.callAppHandler = async function callAppHandler(
|
||||
response.end(answer?.data);
|
||||
};
|
||||
|
||||
export {invoke, getProcessBlob};
|
||||
/** @} */
|
||||
|
@@ -1,6 +1,17 @@
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfrpc Tilde Friends RPC.
|
||||
* Tilde Friends RPC.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Whether this module is being run in a web browser. */
|
||||
const k_is_browser = get_is_browser();
|
||||
/** Registered methods. */
|
||||
let g_api = {};
|
||||
/** The next method identifier. */
|
||||
let g_next_id = 1;
|
||||
/** Identifiers of pending calls. */
|
||||
let g_calls = {};
|
||||
|
||||
/**
|
||||
@@ -15,16 +26,30 @@ function get_is_browser() {
|
||||
}
|
||||
}
|
||||
|
||||
/** \cond */
|
||||
if (k_is_browser) {
|
||||
print = console.log;
|
||||
}
|
||||
|
||||
if (k_is_browser) {
|
||||
window.addEventListener('message', function (event) {
|
||||
call_rpc(event.data);
|
||||
});
|
||||
} else {
|
||||
core.register('message', function (message) {
|
||||
call_rpc(message?.message);
|
||||
});
|
||||
}
|
||||
|
||||
export let rpc = new Proxy({}, {get: make_rpc});
|
||||
/** \endcond */
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} target
|
||||
* @param {*} prop
|
||||
* @param {*} receiver
|
||||
* @returns
|
||||
* Make a function to invoke a remote procedure.
|
||||
* @param target The target.
|
||||
* @param prop The name of the function.
|
||||
* @param receiver The receiver.
|
||||
* @return A function.
|
||||
*/
|
||||
function make_rpc(target, prop, receiver) {
|
||||
return function () {
|
||||
@@ -55,8 +80,8 @@ function make_rpc(target, prop, receiver) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} response
|
||||
* Send a response.
|
||||
* @param response The response.
|
||||
*/
|
||||
function send(response) {
|
||||
if (k_is_browser) {
|
||||
@@ -67,8 +92,8 @@ function send(response) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* Invoke a remote procedure.
|
||||
* @param message An object describing the call.
|
||||
*/
|
||||
function call_rpc(message) {
|
||||
if (message && message.message === 'tfrpc') {
|
||||
@@ -112,22 +137,12 @@ function call_rpc(message) {
|
||||
}
|
||||
}
|
||||
|
||||
if (k_is_browser) {
|
||||
window.addEventListener('message', function (event) {
|
||||
call_rpc(event.data);
|
||||
});
|
||||
} else {
|
||||
core.register('message', function (message) {
|
||||
call_rpc(message?.message);
|
||||
});
|
||||
}
|
||||
|
||||
export let rpc = new Proxy({}, {get: make_rpc});
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} method
|
||||
* Register a function that to be called remotely.
|
||||
* @param method The method.
|
||||
*/
|
||||
export function register(method) {
|
||||
g_api[method.name] = method;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
331
deps/codemirror_src/package-lock.json
generated
vendored
331
deps/codemirror_src/package-lock.json
generated
vendored
@@ -22,6 +22,7 @@
|
||||
"version": "6.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
||||
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -33,6 +34,7 @@
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
|
||||
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
@@ -44,6 +46,7 @@
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@@ -56,6 +59,7 @@
|
||||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
||||
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/lang-css": "^6.0.0",
|
||||
@@ -72,6 +76,7 @@
|
||||
"version": "6.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
||||
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
@@ -86,6 +91,7 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
|
||||
"integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/json": "^1.0.0"
|
||||
@@ -95,6 +101,7 @@
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz",
|
||||
"integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
@@ -108,6 +115,7 @@
|
||||
"version": "6.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
||||
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.35.0",
|
||||
@@ -118,6 +126,7 @@
|
||||
"version": "6.5.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
@@ -128,6 +137,7 @@
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
@@ -136,6 +146,7 @@
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -144,9 +155,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.0.tgz",
|
||||
"integrity": "sha512-yvSchUwHOdupXkd7xJ0ob36jdsSR/I+/C+VbY0ffBiL5NiSTEBDfB1ZGWbbIlDd5xgdUkody+lukAdOxYrOBeg==",
|
||||
"version": "6.38.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz",
|
||||
"integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
@@ -155,17 +167,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||
"version": "0.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
|
||||
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
@@ -173,40 +182,35 @@
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"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,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
|
||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
||||
"version": "0.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz",
|
||||
"integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||
"dev": true
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
||||
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"version": "0.3.29",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
|
||||
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
@@ -215,12 +219,14 @@
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lezer/css": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz",
|
||||
"integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
|
||||
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
@@ -231,6 +237,7 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
@@ -239,6 +246,7 @@
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
|
||||
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
@@ -249,6 +257,7 @@
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz",
|
||||
"integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.1.3",
|
||||
@@ -259,6 +268,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
@@ -269,6 +279,7 @@
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
@@ -276,12 +287,14 @@
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "15.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
|
||||
"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@types/resolve": "1.20.2",
|
||||
@@ -306,6 +319,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
|
||||
"integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"serialize-javascript": "^6.0.1",
|
||||
"smob": "^1.0.0",
|
||||
@@ -327,6 +341,7 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz",
|
||||
"integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
@@ -345,240 +360,260 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz",
|
||||
"integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.0.tgz",
|
||||
"integrity": "sha512-9f3nSTFI2ivfxc7/tHBHcJ8pRnp8ROrELvsVprlQPVvcZ+j5zztYd+PTJGpyIOAdTvNwNrpCXswKSeoQcyGjMQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz",
|
||||
"integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.0.tgz",
|
||||
"integrity": "sha512-tFZSEhqJ8Yrpe50TzOdeoYi72gi/jsnT7y8Qrozf3cNu28WX+s6I3XzEPUAqoaT9SAS8Xz9AzGTFlxxCH/w20w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz",
|
||||
"integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.0.tgz",
|
||||
"integrity": "sha512-+DikIIs+p6yU2hF51UaWG8BnHbq90X0QIOt5zqSKSZxY+G3qqdLih214e9InJal21af2PuuxkDectetGfbVPJw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz",
|
||||
"integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.0.tgz",
|
||||
"integrity": "sha512-5a+NofhdEB/WimSlFMskbFQn1vqz1FWryYpA99trmZGO6qEmiS0IsX6w4B3d91U878Q2ZQdiaFF1gxX4P147og==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz",
|
||||
"integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.0.tgz",
|
||||
"integrity": "sha512-igr/RlKPS3OCy4jD3XBmAmo3UAcNZkJSubRsw1JeM8bAbwf15k/3eMZXD91bnjheijJiOJcga3kfCLKjV8IXNg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz",
|
||||
"integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.0.tgz",
|
||||
"integrity": "sha512-MdigWzPSHlQzB1xZ+MdFDWTAH+kcn7UxjEBoOKuaso7z1DRlnAnrknB1mTtNOQ+GdPI8xgExAGwHeqQjntR0Cg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz",
|
||||
"integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.0.tgz",
|
||||
"integrity": "sha512-dmZseE0ZwA/4yy1+BwFrDqFTjjNg24GO9xSrb1weVbt6AFkhp5pz1gVS7IMtfIvoWy8yp6q/zN0bKnefRUImvQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz",
|
||||
"integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.0.tgz",
|
||||
"integrity": "sha512-fzhfn6p9Cfm3W8UrWKIa4l7Wfjs/KGdgaswMBBE3KY3Ta43jg2XsPrAtfezHpsRk0Nx+TFuS3hZk/To2N5kFPQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz",
|
||||
"integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-vVDD+iPDPmJQ5nAQ5Tifq3ywdv60FartglFI8VOCK+hcU9aoG0qlQTsDJP97O5yiTaTqlneZWoARMcVC5nyUoQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz",
|
||||
"integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.0.tgz",
|
||||
"integrity": "sha512-0d0jx08fzDHCzXqrtCMEEyxKU0SvJrWmUjUDE2/KDQ2UDJql0tfiwYvEx1oHELClKO8CNdE+AGJj+RqXscZpdQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz",
|
||||
"integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-XBYu9oW9eKJadWn8M7hkTZsD4yG+RrsTrVEgyKwb4L72cpJjRbRboTG9Lg9fec8MxJp/cfTHAocg4mnismQR8A==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz",
|
||||
"integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==",
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-wJaRvcT17PoOK6Ggcfo3nouFlybHvARBS4jzT0PC/lg17fIJHcDS2fZz3sD+iA4nRlho2zE6OGbU0HvwATdokQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz",
|
||||
"integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-GZ5bkMFteAGkcmh8x0Ok4LSa+L62Ez0tMsHPX6JtR0wl4Xc3bQcrFHDiR5DGLEDFtGrXih4Nd/UDaFqs968/wA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz",
|
||||
"integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.0.tgz",
|
||||
"integrity": "sha512-7CjPw6FflFsVOUfWOrVrREiV3IYXG4RzZ1ZQUaT3BtSK8YXN6x286o+sruPZJESIaPebYuFowmg54ZdrkVBYog==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz",
|
||||
"integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-nmvnl0ZiuysltcB/cKjUh40Rx4FbSyueERDsl2FLvLYr6pCgSsvGr3SocUT84svSpmloS7f1DRWqtRha74Gi1w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz",
|
||||
"integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.0.tgz",
|
||||
"integrity": "sha512-Cv+moII5C8RM6gZbR3cb21o6rquVDZrN2o81maROg1LFzBz2dZUwIQSxFA8GtGZ/F2KtsqQ2z3eFPBb6akvQNg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz",
|
||||
"integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.0.tgz",
|
||||
"integrity": "sha512-PHcMG8DZTM9RCIjp8QIfN0VYtX0TtBPnWOTRurFhoCDoi9zptUZL2k7pCs+5rgut7JAiUsYy+huyhVKPcmxoog==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz",
|
||||
"integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.0.tgz",
|
||||
"integrity": "sha512-1SI/Rd47e8aQJeFWMDg16ET+fjvCcD/CzeaRmIEPmb05hx+3cCcwIF4ebUag4yTt/D1peE+Mgp0+Po3M358cAA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz",
|
||||
"integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.0.tgz",
|
||||
"integrity": "sha512-JwOCYxmumFDfDhx4kNyz6kTVK3gWzBIvVdMNzQMRDubcoGRDniOOmo6DDNP42qwZx3Bp9/6vWJ+kNzNqXoHmeA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz",
|
||||
"integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.0.tgz",
|
||||
"integrity": "sha512-IPMIfrfkG1GaEXi+JSsQEx8x9b4b+hRZXO7KYc2pKio3zO2/VDXDs6B9Ts/nnO+25Fk1tdAVtUn60HKKPPzDig==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -587,18 +622,21 @@
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -610,12 +648,14 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/codemirror": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
@@ -630,17 +670,20 @@
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -648,13 +691,15 @@
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@@ -667,6 +712,7 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -675,6 +721,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
@@ -686,6 +733,7 @@
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
@@ -699,17 +747,20 @@
|
||||
"node_modules/is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
|
||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -722,6 +773,7 @@
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
@@ -730,6 +782,7 @@
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
@@ -746,9 +799,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.44.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz",
|
||||
"integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==",
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.0.tgz",
|
||||
"integrity": "sha512-ONmkT3Ud3IfW15nl7l4qAZko5/2iZ5ALVBDh02ZSZ5IGVLJSYkRcRa3iB58VyEIyoofs9m2xdVrm+lTi97+3pw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -760,26 +814,26 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.44.1",
|
||||
"@rollup/rollup-android-arm64": "4.44.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.44.1",
|
||||
"@rollup/rollup-darwin-x64": "4.44.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.44.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.44.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.44.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.44.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.44.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.44.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.44.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.44.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.44.1",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.44.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.44.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.44.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.44.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.44.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.44.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.44.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.46.0",
|
||||
"@rollup/rollup-android-arm64": "4.46.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.46.0",
|
||||
"@rollup/rollup-darwin-x64": "4.46.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.46.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.46.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.46.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.46.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.46.0",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.46.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.46.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.46.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.46.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.46.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.46.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -801,13 +855,15 @@
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
|
||||
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
@@ -816,13 +872,15 @@
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz",
|
||||
"integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -832,6 +890,7 @@
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
@@ -840,12 +899,14 @@
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
|
||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -858,6 +919,7 @@
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
|
||||
"integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.14.0",
|
||||
@@ -874,7 +936,8 @@
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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/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">
|
||||
</head>
|
||||
<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
|
||||
Sat Feb 15 13:02:38 PST 2025
|
||||
1c254dcb3e2b4f6d921340d20e972d9d27b788f4
|
||||
speedscope@1.23.0
|
||||
Sun Jul 6 20:04:28 PDT 2025
|
||||
aa9bef50789a2989746b576fff182b6f01dfce6a
|
||||
|
File diff suppressed because one or more lines are too long
203
deps/sqlite/sqlite3.c
vendored
203
deps/sqlite/sqlite3.c
vendored
@@ -1,6 +1,6 @@
|
||||
/******************************************************************************
|
||||
** This file is an amalgamation of many separate C source files from SQLite
|
||||
** version 3.50.2. By combining all the individual C code files into this
|
||||
** version 3.50.4. By combining all the individual C code files into this
|
||||
** single large file, the entire code can be compiled as a single translation
|
||||
** unit. This allows many compilers to do optimizations that would not be
|
||||
** possible if the files were compiled separately. Performance improvements
|
||||
@@ -18,7 +18,7 @@
|
||||
** separate file. This file contains only code for the core SQLite library.
|
||||
**
|
||||
** The content in this amalgamation comes from Fossil check-in
|
||||
** 2af157d77fb1304a74176eaee7fbc7c7e932 with changes in files:
|
||||
** 4d8adfb30e03f9cf27f800a2c1ba3c48fb4c with changes in files:
|
||||
**
|
||||
**
|
||||
*/
|
||||
@@ -465,9 +465,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.50.2"
|
||||
#define SQLITE_VERSION_NUMBER 3050002
|
||||
#define SQLITE_SOURCE_ID "2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079"
|
||||
#define SQLITE_VERSION "3.50.4"
|
||||
#define SQLITE_VERSION_NUMBER 3050004
|
||||
#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@@ -9377,13 +9377,13 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
||||
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
|
||||
** <dd>^This is the number of sort operations that have occurred.
|
||||
** 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>
|
||||
** <dd>^This is the number of rows inserted into transient indices that
|
||||
** were created automatically in order to help joins run faster.
|
||||
** 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>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
|
||||
@@ -9392,19 +9392,19 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
||||
** to 2147483647. The number of virtual machine operations can be
|
||||
** used as a proxy for the total work done by the prepared statement.
|
||||
** 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>
|
||||
** <dd>^This is the number of times that the prepare statement has been
|
||||
** 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>
|
||||
** <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
|
||||
** 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
|
||||
** cycle.
|
||||
** cycle.</dd>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
|
||||
** [[SQLITE_STMTSTATUS_FILTER HIT]]
|
||||
@@ -9414,7 +9414,7 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
||||
** step was bypassed because a Bloom filter returned not-found. The
|
||||
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
|
||||
** 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>
|
||||
** <dd>^This is the approximate number of bytes of heap memory
|
||||
@@ -9519,9 +9519,9 @@ struct sqlite3_pcache_page {
|
||||
** SQLite will typically create one cache instance for each open database file,
|
||||
** though this is not guaranteed. ^The
|
||||
** 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
|
||||
** 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
|
||||
** extra szExtra bytes on each page to store metadata about the underlying
|
||||
** database page on disk. The value passed into szExtra depends
|
||||
@@ -9529,17 +9529,17 @@ struct sqlite3_pcache_page {
|
||||
** ^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
|
||||
** 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
|
||||
** never invoke xUnpin() except to deliberately delete a page.
|
||||
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
|
||||
** 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.
|
||||
**
|
||||
** [[the xCachesize() page cache method]]
|
||||
** ^(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
|
||||
** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable
|
||||
** parameter, the implementation is not required to do anything with this
|
||||
@@ -9566,12 +9566,12 @@ struct sqlite3_pcache_page {
|
||||
** 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
|
||||
** 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>
|
||||
** <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> 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.
|
||||
** <tr><td> 2 <td> Make every effort to allocate a new page. Only return
|
||||
** NULL if allocating a new page is effectively impossible.
|
||||
@@ -9588,7 +9588,7 @@ struct sqlite3_pcache_page {
|
||||
** as its second argument. If the third parameter, discard, is non-zero,
|
||||
** then the page must be evicted from the cache.
|
||||
** ^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
|
||||
** may choose to evict unpinned pages at any time.
|
||||
**
|
||||
@@ -9606,7 +9606,7 @@ struct sqlite3_pcache_page {
|
||||
** When SQLite calls the xTruncate() method, the cache must discard all
|
||||
** existing cache entries with page numbers (keys) greater than or equal
|
||||
** 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.
|
||||
**
|
||||
** [[the xDestroy() page cache method]]
|
||||
@@ -9905,7 +9905,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
||||
** application receives an SQLITE_LOCKED error, it may call the
|
||||
** sqlite3_unlock_notify() method with the blocked connection handle as
|
||||
** 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]
|
||||
** call that concludes the blocking connection's transaction.
|
||||
**
|
||||
@@ -9925,7 +9925,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
||||
** blocked connection already has a registered unlock-notify callback,
|
||||
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
||||
** 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
|
||||
** connection using [sqlite3_close()].
|
||||
**
|
||||
@@ -10323,7 +10323,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
||||
** support constraints. In this configuration (which is the default) if
|
||||
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
|
||||
** 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.
|
||||
**
|
||||
** If X is non-zero, then the virtual table implementation guarantees
|
||||
@@ -10357,7 +10357,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
||||
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
||||
** <dd>Calls of the form
|
||||
** [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
|
||||
** 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
|
||||
@@ -10525,7 +10525,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
||||
** </table>
|
||||
**
|
||||
** ^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"
|
||||
** (or "IS NOT DISTINCT FROM") and not "==".
|
||||
**
|
||||
@@ -10535,7 +10535,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
|
||||
** 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
|
||||
** ordered correctly. The use of the "orderByConsumed" flag and the
|
||||
** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful
|
||||
@@ -10632,7 +10632,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
|
||||
** xFilter method which invokes these routines, and specifically
|
||||
** 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
|
||||
** an xFilter argument that was selected for all-at-once IN constraint
|
||||
** processing, then these routines return [SQLITE_ERROR].)^
|
||||
@@ -10687,7 +10687,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)
|
||||
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
|
||||
** 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.
|
||||
**
|
||||
** The sqlite3_vtab_rhs_value() interface is usually only successful if
|
||||
@@ -10715,8 +10715,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
||||
** KEYWORDS: {conflict resolution mode}
|
||||
**
|
||||
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
||||
** inform a [virtual table] implementation what the [ON CONFLICT] mode
|
||||
** is for the SQL statement being evaluated.
|
||||
** inform a [virtual table] implementation of the [ON CONFLICT] mode
|
||||
** for the SQL statement being evaluated.
|
||||
**
|
||||
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
||||
** return value from the [sqlite3_set_authorizer()] callback and that
|
||||
@@ -10756,39 +10756,39 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
||||
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
|
||||
** <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
|
||||
** 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
|
||||
** 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>
|
||||
** <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
|
||||
** used for the X-th loop.
|
||||
** used for the X-th loop.</dd>
|
||||
**
|
||||
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
|
||||
** <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]
|
||||
** description for the X-th loop.
|
||||
** description for the X-th loop.</dd>
|
||||
**
|
||||
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
|
||||
** <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
|
||||
** 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>
|
||||
** <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
|
||||
** 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>
|
||||
** <dd>The sqlite3_int64 output value is set to the number of cycles,
|
||||
** according to the processor time-stamp counter, that elapsed while the
|
||||
** query element was being processed. This value is not available for
|
||||
** all query elements - if it is unavailable the output variable is
|
||||
** set to -1.
|
||||
** set to -1.</dd>
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_SCANSTAT_NLOOP 0
|
||||
@@ -10829,8 +10829,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
||||
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
|
||||
**
|
||||
** Parameter "idx" identifies the specific query element to retrieve statistics
|
||||
** for. Query elements are numbered starting from zero. A value of -1 may be
|
||||
** to query for statistics regarding the entire query. ^If idx is out of range
|
||||
** for. Query elements are numbered starting from zero. A value of -1 may
|
||||
** 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
|
||||
** elements used to implement the statement - a non-zero value is returned and
|
||||
** the variable that pOut points to is unchanged.
|
||||
@@ -10987,8 +10987,8 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
|
||||
** triggers; and so forth.
|
||||
**
|
||||
** 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
|
||||
** in this case the new values are not available. In this case, when a
|
||||
** the pre-update hook is invoked with SQLITE_DELETE, because
|
||||
** the new values are not yet available. In this case, when a
|
||||
** callback made with op==SQLITE_DELETE is actually a write using the
|
||||
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
|
||||
** the index of the column being written. In other cases, where the
|
||||
@@ -11241,7 +11241,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
|
||||
** 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
|
||||
** 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 database into memory obtained from [sqlite3_malloc64()] and returns
|
||||
@@ -11250,7 +11250,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
||||
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
|
||||
** are made, and the sqlite3_serialize() function will return a pointer
|
||||
** 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
|
||||
** representation of the database will usually only exist if there has
|
||||
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
|
||||
@@ -11321,7 +11321,7 @@ SQLITE_API unsigned char *sqlite3_serialize(
|
||||
** database is currently in a read transaction or is involved in a backup
|
||||
** 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
|
||||
** function returns SQLITE_ERROR.
|
||||
**
|
||||
@@ -11343,7 +11343,7 @@ SQLITE_API int sqlite3_deserialize(
|
||||
sqlite3 *db, /* The database connection */
|
||||
const char *zSchema, /* Which DB to reopen with the deserialization */
|
||||
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[] */
|
||||
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
|
||||
);
|
||||
@@ -11351,7 +11351,7 @@ SQLITE_API int 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 SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
|
||||
@@ -19168,7 +19168,6 @@ struct Index {
|
||||
unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
|
||||
unsigned bNoQuery:1; /* Do not use this index to optimize queries */
|
||||
unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
|
||||
unsigned bIdxRowid:1; /* One or more of the index keys is the ROWID */
|
||||
unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
|
||||
unsigned bHasExpr:1; /* Index contains an expression, either a literal
|
||||
** expression, or a reference to a VIRTUAL column */
|
||||
@@ -19441,6 +19440,7 @@ struct Expr {
|
||||
Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL
|
||||
** for a column of an index on an expression */
|
||||
Window *pWin; /* EP_WinFunc: Window/Filter defn for a function */
|
||||
int nReg; /* TK_NULLS: Number of registers to NULL out */
|
||||
struct { /* TK_IN, TK_SELECT, and TK_EXISTS */
|
||||
int iAddr; /* Subroutine entry address */
|
||||
int regReturn; /* Register used to hold return address */
|
||||
@@ -21475,6 +21475,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Table*, Column*, int)
|
||||
SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int);
|
||||
SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int);
|
||||
SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int);
|
||||
SQLITE_PRIVATE void sqlite3ExprNullRegisterRange(Parse*, int, int);
|
||||
SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
|
||||
SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int);
|
||||
SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8);
|
||||
@@ -111488,7 +111489,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){
|
||||
return pLeft;
|
||||
}else{
|
||||
u32 f = pLeft->flags | pRight->flags;
|
||||
if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse))==EP_IsFalse
|
||||
if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse|EP_HasFunc))==EP_IsFalse
|
||||
&& !IN_RENAME_OBJECT
|
||||
){
|
||||
sqlite3ExprDeferredDelete(pParse, pLeft);
|
||||
@@ -115242,6 +115243,12 @@ expr_code_doover:
|
||||
sqlite3VdbeLoadString(v, target, pExpr->u.zToken);
|
||||
return target;
|
||||
}
|
||||
case TK_NULLS: {
|
||||
/* Set a range of registers to NULL. pExpr->y.nReg registers starting
|
||||
** with target */
|
||||
sqlite3VdbeAddOp3(v, OP_Null, 0, target, target + pExpr->y.nReg - 1);
|
||||
return target;
|
||||
}
|
||||
default: {
|
||||
/* Make NULL the default case so that if a bug causes an illegal
|
||||
** Expr node to be passed into this function, it will be handled
|
||||
@@ -115926,6 +115933,25 @@ SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(
|
||||
return regDest;
|
||||
}
|
||||
|
||||
/*
|
||||
** Make arrangements to invoke OP_Null on a range of registers
|
||||
** during initialization.
|
||||
*/
|
||||
SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3ExprNullRegisterRange(
|
||||
Parse *pParse, /* Parsing context */
|
||||
int iReg, /* First register to set to NULL */
|
||||
int nReg /* Number of sequential registers to NULL out */
|
||||
){
|
||||
u8 okConstFactor = pParse->okConstFactor;
|
||||
Expr t;
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.op = TK_NULLS;
|
||||
t.y.nReg = nReg;
|
||||
pParse->okConstFactor = 1;
|
||||
sqlite3ExprCodeRunJustOnce(pParse, &t, iReg);
|
||||
pParse->okConstFactor = okConstFactor;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate code to evaluate an expression and store the results
|
||||
** into a register. Return the register number where the results
|
||||
@@ -127196,7 +127222,6 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
|
||||
assert( j<=0x7fff );
|
||||
if( j<0 ){
|
||||
j = pTab->iPKey;
|
||||
pIndex->bIdxRowid = 1;
|
||||
}else{
|
||||
if( pTab->aCol[j].notNull==0 ){
|
||||
pIndex->uniqNotNull = 0;
|
||||
@@ -153177,6 +153202,7 @@ SQLITE_PRIVATE int sqlite3Select(
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag);
|
||||
VdbeComment((v, "clear abort flag"));
|
||||
sqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1);
|
||||
sqlite3ExprNullRegisterRange(pParse, iAMem, pGroupBy->nExpr);
|
||||
|
||||
/* Begin a loop that will extract all source rows in GROUP BY order.
|
||||
** This might involve two separate loops with an OP_Sort in between, or
|
||||
@@ -160173,7 +160199,9 @@ static Expr *removeUnindexableInClauseTerms(
|
||||
int iField;
|
||||
assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 );
|
||||
iField = pLoop->aLTerm[i]->u.x.iField - 1;
|
||||
if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */
|
||||
if( NEVER(pOrigRhs->a[iField].pExpr==0) ){
|
||||
continue; /* Duplicate PK column */
|
||||
}
|
||||
pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
|
||||
pOrigRhs->a[iField].pExpr = 0;
|
||||
if( pRhs ) pRhs->a[pRhs->nExpr-1].u.x.iOrderByCol = iField+1;
|
||||
@@ -160270,7 +160298,7 @@ static SQLITE_NOINLINE void codeINTerm(
|
||||
return;
|
||||
}
|
||||
}
|
||||
for(i=iEq;i<pLoop->nLTerm; i++){
|
||||
for(i=iEq; i<pLoop->nLTerm; i++){
|
||||
assert( pLoop->aLTerm[i]!=0 );
|
||||
if( pLoop->aLTerm[i]->pExpr==pX ) nEq++;
|
||||
}
|
||||
@@ -160279,22 +160307,13 @@ static SQLITE_NOINLINE void codeINTerm(
|
||||
if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){
|
||||
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab);
|
||||
}else{
|
||||
Expr *pExpr = pTerm->pExpr;
|
||||
if( pExpr->iTable==0 || !ExprHasProperty(pExpr, EP_Subrtn) ){
|
||||
sqlite3 *db = pParse->db;
|
||||
pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
|
||||
if( !db->mallocFailed ){
|
||||
aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq);
|
||||
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap,&iTab);
|
||||
pExpr->iTable = iTab;
|
||||
}
|
||||
sqlite3ExprDelete(db, pX);
|
||||
}else{
|
||||
int n = sqlite3ExprVectorSize(pX->pLeft);
|
||||
aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*MAX(nEq,n));
|
||||
eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab);
|
||||
sqlite3 *db = pParse->db;
|
||||
Expr *pXMod = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
|
||||
if( !db->mallocFailed ){
|
||||
aiMap = (int*)sqlite3DbMallocZero(db, sizeof(int)*nEq);
|
||||
eType = sqlite3FindInIndex(pParse, pXMod, IN_INDEX_LOOP, 0, aiMap, &iTab);
|
||||
}
|
||||
pX = pExpr;
|
||||
sqlite3ExprDelete(db, pXMod);
|
||||
}
|
||||
|
||||
if( eType==IN_INDEX_INDEX_DESC ){
|
||||
@@ -160324,7 +160343,7 @@ static SQLITE_NOINLINE void codeINTerm(
|
||||
if( pIn ){
|
||||
int iMap = 0; /* Index in aiMap[] */
|
||||
pIn += i;
|
||||
for(i=iEq;i<pLoop->nLTerm; i++){
|
||||
for(i=iEq; i<pLoop->nLTerm; i++){
|
||||
if( pLoop->aLTerm[i]->pExpr==pX ){
|
||||
int iOut = iTarget + i - iEq;
|
||||
if( eType==IN_INDEX_ROWID ){
|
||||
@@ -167682,6 +167701,7 @@ static int whereLoopAddBtreeIndex(
|
||||
if( ExprUseXSelect(pExpr) ){
|
||||
/* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */
|
||||
int i;
|
||||
int bRedundant = 0;
|
||||
nIn = 46; assert( 46==sqlite3LogEst(25) );
|
||||
|
||||
/* The expression may actually be of the form (x, y) IN (SELECT...).
|
||||
@@ -167690,7 +167710,20 @@ static int whereLoopAddBtreeIndex(
|
||||
** for each such term. The following loop checks that pTerm is the
|
||||
** first such term in use, and sets nIn back to 0 if it is not. */
|
||||
for(i=0; i<pNew->nLTerm-1; i++){
|
||||
if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ) nIn = 0;
|
||||
if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ){
|
||||
nIn = 0;
|
||||
if( pNew->aLTerm[i]->u.x.iField == pTerm->u.x.iField ){
|
||||
/* Detect when two or more columns of an index match the same
|
||||
** column of a vector IN operater, and avoid adding the column
|
||||
** to the WhereLoop more than once. See tag-20250707-01
|
||||
** in test/rowvalue.test */
|
||||
bRedundant = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( bRedundant ){
|
||||
pNew->nLTerm--;
|
||||
continue;
|
||||
}
|
||||
}else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){
|
||||
/* "x IN (value, value, ...)" */
|
||||
@@ -167922,7 +167955,7 @@ static int whereLoopAddBtreeIndex(
|
||||
if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0
|
||||
&& pNew->u.btree.nEq<pProbe->nColumn
|
||||
&& (pNew->u.btree.nEq<pProbe->nKeyCol ||
|
||||
(pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY && !pProbe->bIdxRowid))
|
||||
pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY)
|
||||
){
|
||||
if( pNew->u.btree.nEq>3 ){
|
||||
sqlite3ProgressCheck(pParse);
|
||||
@@ -168465,6 +168498,7 @@ static int whereLoopAddBtree(
|
||||
pNew->u.btree.nEq = 0;
|
||||
pNew->u.btree.nBtm = 0;
|
||||
pNew->u.btree.nTop = 0;
|
||||
pNew->u.btree.nDistinctCol = 0;
|
||||
pNew->nSkip = 0;
|
||||
pNew->nLTerm = 0;
|
||||
pNew->iSortIdx = 0;
|
||||
@@ -169533,8 +169567,6 @@ static i8 wherePathSatisfiesOrderBy(
|
||||
obSat = obDone;
|
||||
}
|
||||
break;
|
||||
}else if( wctrlFlags & WHERE_DISTINCTBY ){
|
||||
pLoop->u.btree.nDistinctCol = 0;
|
||||
}
|
||||
iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor;
|
||||
|
||||
@@ -179897,12 +179929,21 @@ static YYACTIONTYPE yy_reduce(
|
||||
** expr1 IN ()
|
||||
** expr1 NOT IN ()
|
||||
**
|
||||
** simplify to constants 0 (false) and 1 (true), respectively,
|
||||
** regardless of the value of expr1.
|
||||
** simplify to constants 0 (false) and 1 (true), respectively.
|
||||
**
|
||||
** Except, do not apply this optimization if expr1 contains a function
|
||||
** because that function might be an aggregate (we don't know yet whether
|
||||
** it is or not) and if it is an aggregate, that could change the meaning
|
||||
** of the whole query.
|
||||
*/
|
||||
sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy590);
|
||||
yymsp[-4].minor.yy590 = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy502 ? "true" : "false");
|
||||
if( yymsp[-4].minor.yy590 ) sqlite3ExprIdToTrueFalse(yymsp[-4].minor.yy590);
|
||||
Expr *pB = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy502 ? "true" : "false");
|
||||
if( pB ) sqlite3ExprIdToTrueFalse(pB);
|
||||
if( !ExprHasProperty(yymsp[-4].minor.yy590, EP_HasFunc) ){
|
||||
sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy590);
|
||||
yymsp[-4].minor.yy590 = pB;
|
||||
}else{
|
||||
yymsp[-4].minor.yy590 = sqlite3PExpr(pParse, yymsp[-3].minor.yy502 ? TK_OR : TK_AND, pB, yymsp[-4].minor.yy590);
|
||||
}
|
||||
}else{
|
||||
Expr *pRHS = yymsp[-1].minor.yy402->a[0].pExpr;
|
||||
if( yymsp[-1].minor.yy402->nExpr==1 && sqlite3ExprIsConstant(pParse,pRHS) && yymsp[-4].minor.yy590->op!=TK_VECTOR ){
|
||||
@@ -181508,7 +181549,7 @@ static int getToken(const unsigned char **pz){
|
||||
int t; /* Token type to return */
|
||||
do {
|
||||
z += sqlite3GetToken(z, &t);
|
||||
}while( t==TK_SPACE );
|
||||
}while( t==TK_SPACE || t==TK_COMMENT );
|
||||
if( t==TK_ID
|
||||
|| t==TK_STRING
|
||||
|| t==TK_JOIN_KW
|
||||
@@ -246163,9 +246204,9 @@ static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){
|
||||
** leave an error in the Fts5Index object.
|
||||
*/
|
||||
static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){
|
||||
const int nTomb = pIter->pSeg->nPgTombstone;
|
||||
const i64 nTomb = (i64)pIter->pSeg->nPgTombstone;
|
||||
if( nTomb>0 ){
|
||||
int nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1);
|
||||
i64 nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1);
|
||||
Fts5TombstoneArray *pNew;
|
||||
pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte);
|
||||
if( pNew ){
|
||||
@@ -257266,7 +257307,7 @@ static void fts5SourceIdFunc(
|
||||
){
|
||||
assert( nArg==0 );
|
||||
UNUSED_PARAM2(nArg, apUnused);
|
||||
sqlite3_result_text(pCtx, "fts5: 2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079", -1, SQLITE_TRANSIENT);
|
||||
sqlite3_result_text(pCtx, "fts5: 2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3", -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
/*
|
||||
|
90
deps/sqlite/sqlite3.h
vendored
90
deps/sqlite/sqlite3.h
vendored
@@ -146,9 +146,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.50.2"
|
||||
#define SQLITE_VERSION_NUMBER 3050002
|
||||
#define SQLITE_SOURCE_ID "2025-06-28 14:00:48 2af157d77fb1304a74176eaee7fbc7c7e932d946bf25325e9c26c91db19e3079"
|
||||
#define SQLITE_VERSION "3.50.4"
|
||||
#define SQLITE_VERSION_NUMBER 3050004
|
||||
#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@@ -9058,13 +9058,13 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
||||
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
|
||||
** <dd>^This is the number of sort operations that have occurred.
|
||||
** 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>
|
||||
** <dd>^This is the number of rows inserted into transient indices that
|
||||
** were created automatically in order to help joins run faster.
|
||||
** 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>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
|
||||
@@ -9073,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
|
||||
** used as a proxy for the total work done by the prepared statement.
|
||||
** 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>
|
||||
** <dd>^This is the number of times that the prepare statement has been
|
||||
** 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>
|
||||
** <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
|
||||
** 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
|
||||
** cycle.
|
||||
** cycle.</dd>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
|
||||
** [[SQLITE_STMTSTATUS_FILTER HIT]]
|
||||
@@ -9095,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
|
||||
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
|
||||
** 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>
|
||||
** <dd>^This is the approximate number of bytes of heap memory
|
||||
@@ -9200,9 +9200,9 @@ struct sqlite3_pcache_page {
|
||||
** SQLite will typically create one cache instance for each open database file,
|
||||
** though this is not guaranteed. ^The
|
||||
** 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
|
||||
** 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
|
||||
** extra szExtra bytes on each page to store metadata about the underlying
|
||||
** database page on disk. The value passed into szExtra depends
|
||||
@@ -9210,17 +9210,17 @@ struct sqlite3_pcache_page {
|
||||
** ^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
|
||||
** 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
|
||||
** never invoke xUnpin() except to deliberately delete a page.
|
||||
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
|
||||
** 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.
|
||||
**
|
||||
** [[the xCachesize() page cache method]]
|
||||
** ^(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
|
||||
** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable
|
||||
** parameter, the implementation is not required to do anything with this
|
||||
@@ -9247,12 +9247,12 @@ struct sqlite3_pcache_page {
|
||||
** 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
|
||||
** 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>
|
||||
** <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> 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.
|
||||
** <tr><td> 2 <td> Make every effort to allocate a new page. Only return
|
||||
** NULL if allocating a new page is effectively impossible.
|
||||
@@ -9269,7 +9269,7 @@ struct sqlite3_pcache_page {
|
||||
** as its second argument. If the third parameter, discard, is non-zero,
|
||||
** then the page must be evicted from the cache.
|
||||
** ^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
|
||||
** may choose to evict unpinned pages at any time.
|
||||
**
|
||||
@@ -9287,7 +9287,7 @@ struct sqlite3_pcache_page {
|
||||
** When SQLite calls the xTruncate() method, the cache must discard all
|
||||
** existing cache entries with page numbers (keys) greater than or equal
|
||||
** 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.
|
||||
**
|
||||
** [[the xDestroy() page cache method]]
|
||||
@@ -9586,7 +9586,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
||||
** application receives an SQLITE_LOCKED error, it may call the
|
||||
** sqlite3_unlock_notify() method with the blocked connection handle as
|
||||
** 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]
|
||||
** call that concludes the blocking connection's transaction.
|
||||
**
|
||||
@@ -9606,7 +9606,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
||||
** blocked connection already has a registered unlock-notify callback,
|
||||
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
||||
** 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
|
||||
** connection using [sqlite3_close()].
|
||||
**
|
||||
@@ -10004,7 +10004,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
||||
** support constraints. In this configuration (which is the default) if
|
||||
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
|
||||
** 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.
|
||||
**
|
||||
** If X is non-zero, then the virtual table implementation guarantees
|
||||
@@ -10038,7 +10038,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
||||
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
||||
** <dd>Calls of the form
|
||||
** [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
|
||||
** 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
|
||||
@@ -10206,7 +10206,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
||||
** </table>
|
||||
**
|
||||
** ^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"
|
||||
** (or "IS NOT DISTINCT FROM") and not "==".
|
||||
**
|
||||
@@ -10216,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
|
||||
** 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
|
||||
** ordered correctly. The use of the "orderByConsumed" flag and the
|
||||
** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful
|
||||
@@ -10313,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
|
||||
** xFilter method which invokes these routines, and specifically
|
||||
** 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
|
||||
** an xFilter argument that was selected for all-at-once IN constraint
|
||||
** processing, then these routines return [SQLITE_ERROR].)^
|
||||
@@ -10368,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)
|
||||
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
|
||||
** 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.
|
||||
**
|
||||
** The sqlite3_vtab_rhs_value() interface is usually only successful if
|
||||
@@ -10396,8 +10396,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
||||
** KEYWORDS: {conflict resolution mode}
|
||||
**
|
||||
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
||||
** inform a [virtual table] implementation what the [ON CONFLICT] mode
|
||||
** is for the SQL statement being evaluated.
|
||||
** inform a [virtual table] implementation of the [ON CONFLICT] mode
|
||||
** for the SQL statement being evaluated.
|
||||
**
|
||||
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
||||
** return value from the [sqlite3_set_authorizer()] callback and that
|
||||
@@ -10437,39 +10437,39 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
||||
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
|
||||
** <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
|
||||
** 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
|
||||
** 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>
|
||||
** <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
|
||||
** used for the X-th loop.
|
||||
** used for the X-th loop.</dd>
|
||||
**
|
||||
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
|
||||
** <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]
|
||||
** description for the X-th loop.
|
||||
** description for the X-th loop.</dd>
|
||||
**
|
||||
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
|
||||
** <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
|
||||
** 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>
|
||||
** <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
|
||||
** 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>
|
||||
** <dd>The sqlite3_int64 output value is set to the number of cycles,
|
||||
** according to the processor time-stamp counter, that elapsed while the
|
||||
** query element was being processed. This value is not available for
|
||||
** all query elements - if it is unavailable the output variable is
|
||||
** set to -1.
|
||||
** set to -1.</dd>
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_SCANSTAT_NLOOP 0
|
||||
@@ -10510,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.
|
||||
**
|
||||
** Parameter "idx" identifies the specific query element to retrieve statistics
|
||||
** for. Query elements are numbered starting from zero. A value of -1 may be
|
||||
** to query for statistics regarding the entire query. ^If idx is out of range
|
||||
** for. Query elements are numbered starting from zero. A value of -1 may
|
||||
** 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
|
||||
** elements used to implement the statement - a non-zero value is returned and
|
||||
** the variable that pOut points to is unchanged.
|
||||
@@ -10668,8 +10668,8 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
|
||||
** triggers; and so forth.
|
||||
**
|
||||
** 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
|
||||
** in this case the new values are not available. In this case, when a
|
||||
** the pre-update hook is invoked with SQLITE_DELETE, because
|
||||
** the new values are not yet available. In this case, when a
|
||||
** callback made with op==SQLITE_DELETE is actually a write using the
|
||||
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
|
||||
** the index of the column being written. In other cases, where the
|
||||
@@ -10922,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
|
||||
** 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
|
||||
** 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 database into memory obtained from [sqlite3_malloc64()] and returns
|
||||
@@ -10931,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
|
||||
** are made, and the sqlite3_serialize() function will return a pointer
|
||||
** 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
|
||||
** representation of the database will usually only exist if there has
|
||||
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
|
||||
@@ -11002,7 +11002,7 @@ SQLITE_API unsigned char *sqlite3_serialize(
|
||||
** database is currently in a read transaction or is involved in a backup
|
||||
** 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
|
||||
** function returns SQLITE_ERROR.
|
||||
**
|
||||
@@ -11024,7 +11024,7 @@ SQLITE_API int sqlite3_deserialize(
|
||||
sqlite3 *db, /* The database connection */
|
||||
const char *zSchema, /* Which DB to reopen with the deserialization */
|
||||
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[] */
|
||||
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
|
||||
);
|
||||
@@ -11032,7 +11032,7 @@ SQLITE_API int 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 SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
|
||||
|
14
metadata/en-US/changelogs/40.txt
Normal file
14
metadata/en-US/changelogs/40.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
* Added an option to stay connected to a handful of peers.
|
||||
* Load more messages at a time.
|
||||
* Fix a set of Android not responding errors.
|
||||
* Target Android 15 (API level 35) to meet new requirements.
|
||||
* Support JS-less webapps.
|
||||
* Fix unnecessary tunnel disconnects.
|
||||
* Many small user interface tweaks.
|
||||
* Update:
|
||||
* CodeMirror
|
||||
* OpenSSL 3.5.1
|
||||
* lit 3.3.1
|
||||
* picohttpparser
|
||||
* speedscope 1.23.0
|
||||
* sqlite 3.50.4
|
1
package-lock.json
generated
1
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.unprompted.tildefriends"
|
||||
android:versionCode="39"
|
||||
android:versionName="0.0.32.1">
|
||||
android:versionCode="40"
|
||||
android:versionName="0.0.33">
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
|
@@ -50,6 +50,7 @@ public class TildeFriendsActivity extends Activity {
|
||||
TildeFriendsWebView web_view;
|
||||
String base_url;
|
||||
String port_file_path;
|
||||
Thread create_thread;
|
||||
Thread server_thread;
|
||||
ServiceConnection service_connection;
|
||||
FileObserver observer;
|
||||
@@ -67,17 +68,7 @@ public class TildeFriendsActivity extends Activity {
|
||||
public static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path, ConnectivityManager connectivity_manager);
|
||||
public static native int tf_sandbox_main(int pipe_fd);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
s_activity = this;
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||
.detectLeakedClosableObjects()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
super.onCreate(savedInstanceState);
|
||||
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
private void createThread() {
|
||||
web_view = (TildeFriendsWebView)findViewById(R.id.web);
|
||||
set_status("Extracting executable...");
|
||||
Log.w("tildefriends", String.format("getFilesDir() is %s", getFilesDir().toString()));
|
||||
@@ -109,152 +100,184 @@ public class TildeFriendsActivity extends Activity {
|
||||
});
|
||||
server_thread.start();
|
||||
|
||||
web_view.getSettings().setJavaScriptEnabled(true);
|
||||
web_view.getSettings().setDatabaseEnabled(true);
|
||||
web_view.getSettings().setDomStorageEnabled(true);
|
||||
runOnUiThread(() -> {
|
||||
web_view.getSettings().setJavaScriptEnabled(true);
|
||||
web_view.getSettings().setDomStorageEnabled(true);
|
||||
|
||||
set_database_path();
|
||||
set_database_enabled();
|
||||
set_database_path();
|
||||
|
||||
web_view.setDownloadListener(new DownloadListener() {
|
||||
public void onDownloadStart(String url, String userAgent, String content_disposition, String mime_type, long content_length) {
|
||||
Log.w("tildefriends", "Let's download: " + url + " (" + content_disposition + ")");
|
||||
String file_name = URLUtil.guessFileName(url, content_disposition, mime_type);
|
||||
if (url.startsWith("data:") && url.indexOf(',') != -1) {
|
||||
String b64 = url.substring(url.indexOf(',') + 1);
|
||||
byte[] data = Base64.decode(b64, Base64.DEFAULT);
|
||||
Log.w("tildefriends", "Downloaded " + data.length + " bytes.");
|
||||
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
try (OutputStream stream = new FileOutputStream(new File(path, file_name))) {
|
||||
stream.write(data);
|
||||
} catch (java.io.IOException e) {
|
||||
Log.w("tildefriends", "IOException: " + e.toString());
|
||||
web_view.setDownloadListener(new DownloadListener() {
|
||||
public void onDownloadStart(String url, String userAgent, String content_disposition, String mime_type, long content_length) {
|
||||
Log.w("tildefriends", "Let's download: " + url + " (" + content_disposition + ")");
|
||||
String file_name = URLUtil.guessFileName(url, content_disposition, mime_type);
|
||||
if (url.startsWith("data:") && url.indexOf(',') != -1) {
|
||||
String b64 = url.substring(url.indexOf(',') + 1);
|
||||
byte[] data = Base64.decode(b64, Base64.DEFAULT);
|
||||
Log.w("tildefriends", "Downloaded " + data.length + " bytes.");
|
||||
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
try (OutputStream stream = new FileOutputStream(new File(path, file_name))) {
|
||||
stream.write(data);
|
||||
} catch (java.io.IOException e) {
|
||||
Log.w("tildefriends", "IOException: " + e.toString());
|
||||
}
|
||||
Toast.makeText(getApplicationContext(), "Downloaded File", Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
|
||||
request.setMimeType(mime_type);
|
||||
String cookies = CookieManager.getInstance().getCookie(url);
|
||||
request.addRequestHeader("cookie", cookies);
|
||||
request.addRequestHeader("User-Agent", userAgent);
|
||||
request.setDescription("Downloading file...");
|
||||
request.setTitle(file_name);
|
||||
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, content_disposition, mime_type));
|
||||
DownloadManager dm = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
|
||||
dm.enqueue(request);
|
||||
Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
Toast.makeText(getApplicationContext(), "Downloaded File", Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
|
||||
request.setMimeType(mime_type);
|
||||
String cookies = CookieManager.getInstance().getCookie(url);
|
||||
request.addRequestHeader("cookie", cookies);
|
||||
request.addRequestHeader("User-Agent", userAgent);
|
||||
request.setDescription("Downloading file...");
|
||||
request.setTitle(file_name);
|
||||
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(url, content_disposition, mime_type));
|
||||
DownloadManager dm = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
|
||||
dm.enqueue(request);
|
||||
Toast.makeText(getApplicationContext(), "Downloading File", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
web_view.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
|
||||
new AlertDialog.Builder(view.getContext())
|
||||
.setTitle("Tilde Friends")
|
||||
.setMessage(message)
|
||||
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
web_view.setWebChromeClient(new WebChromeClient() {
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
|
||||
new AlertDialog.Builder(view.getContext())
|
||||
.setTitle("Tilde Friends")
|
||||
.setMessage(message)
|
||||
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
result.confirm();
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
|
||||
new AlertDialog.Builder(view.getContext())
|
||||
.setTitle("Tilde Friends")
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
result.confirm();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
result.cancel();
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
|
||||
EditText input = new EditText(view.getContext());
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
input.setText(defaultValue);
|
||||
|
||||
new AlertDialog.Builder(view.getContext())
|
||||
.setTitle("Tilde Friends")
|
||||
.setMessage(message)
|
||||
.setView(input)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
result.confirm(input.getText().toString());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
result.cancel();
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
** https://stackoverflow.com/questions/5907369/file-upload-in-webview
|
||||
** https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> message, WebChromeClient.FileChooserParams params) {
|
||||
upload_message = message;
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
TildeFriendsActivity.this.startActivityForResult(Intent.createChooser(intent, "File Chooser"), TildeFriendsActivity.FILECHOOSER_RESULT);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) {
|
||||
Log.d("tildefriends", consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
web_view.setWebViewClient(new WebViewClient() {
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
|
||||
{
|
||||
if (request.getUrl() != null && request.getUrl().toString().startsWith(base_url)) {
|
||||
return false;
|
||||
} else {
|
||||
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, request.getUrl()));
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
result.confirm();
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
|
||||
new AlertDialog.Builder(view.getContext())
|
||||
.setTitle("Tilde Friends")
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
result.confirm();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
result.cancel();
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
|
||||
EditText input = new EditText(view.getContext());
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
input.setText(defaultValue);
|
||||
|
||||
new AlertDialog.Builder(view.getContext())
|
||||
.setTitle("Tilde Friends")
|
||||
.setMessage(message)
|
||||
.setView(input)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
result.confirm(input.getText().toString());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
|
||||
{
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
result.cancel();
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
** https://stackoverflow.com/questions/5907369/file-upload-in-webview
|
||||
** https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> message, WebChromeClient.FileChooserParams params) {
|
||||
upload_message = message;
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
TildeFriendsActivity.this.startActivityForResult(Intent.createChooser(intent, "File Chooser"), TildeFriendsActivity.FILECHOOSER_RESULT);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) {
|
||||
Log.d("tildefriends", consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
web_view.setWebViewClient(new WebViewClient() {
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
|
||||
{
|
||||
if (request.getUrl() != null && request.getUrl().toString().startsWith(base_url)) {
|
||||
return false;
|
||||
} else {
|
||||
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, request.getUrl()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
s_activity.create_thread = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
s_activity = this;
|
||||
super.onCreate(savedInstanceState);
|
||||
StrictMode.setThreadPolicy(
|
||||
new StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyDialog()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.setVmPolicy(
|
||||
new StrictMode.VmPolicy.Builder()
|
||||
.detectLeakedClosableObjects()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
TextView refresh = (TextView)findViewById(R.id.refresh);
|
||||
refresh.setVisibility(View.GONE);
|
||||
refresh.setText("REFRESH");
|
||||
|
||||
create_thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
s_activity.createThread();
|
||||
}
|
||||
});
|
||||
create_thread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -451,4 +474,10 @@ public class TildeFriendsActivity extends Activity {
|
||||
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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:fitsSystemWindows="true"
|
||||
android:background="#000">
|
||||
<com.unprompted.tildefriends.TildeFriendsWebView
|
||||
android:id="@+id/web"
|
||||
android:layout_width="match_parent"
|
||||
|
148
src/api.js.c
148
src/api.js.c
@@ -1,11 +1,159 @@
|
||||
#include "api.js.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
#include "ssb.db.h"
|
||||
#include "ssb.h"
|
||||
#include "task.h"
|
||||
#include "util.js.h"
|
||||
|
||||
#include <quickjs.h>
|
||||
|
||||
typedef struct _app_path_pair_t
|
||||
{
|
||||
const char* app;
|
||||
const char* path;
|
||||
} app_path_pair_t;
|
||||
|
||||
typedef struct _get_apps_t
|
||||
{
|
||||
app_path_pair_t* apps;
|
||||
int count;
|
||||
JSContext* context;
|
||||
JSValue promise[2];
|
||||
char user[];
|
||||
} get_apps_t;
|
||||
|
||||
static void _tf_api_core_apps_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
get_apps_t* work = user_data;
|
||||
|
||||
JSMallocFunctions funcs = { 0 };
|
||||
tf_get_js_malloc_functions(&funcs);
|
||||
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
|
||||
JSContext* context = JS_NewContext(runtime);
|
||||
|
||||
const char* apps = tf_ssb_db_get_property(ssb, work->user, "apps");
|
||||
if (apps)
|
||||
{
|
||||
JSValue apps_array = JS_ParseJSON(context, apps, strlen(apps), NULL);
|
||||
if (JS_IsArray(context, apps_array))
|
||||
{
|
||||
int length = tf_util_get_length(context, apps_array);
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
JSValue name = JS_GetPropertyUint32(context, apps_array, i);
|
||||
const char* name_string = JS_ToCString(context, name);
|
||||
if (name_string)
|
||||
{
|
||||
work->apps = tf_resize_vec(work->apps, sizeof(app_path_pair_t) * (work->count + 1));
|
||||
work->apps[work->count].app = tf_strdup(name_string);
|
||||
size_t size = strlen("path:") + strlen(name_string) + 1;
|
||||
char* path_key = tf_malloc(size);
|
||||
snprintf(path_key, size, "path:%s", name_string);
|
||||
work->apps[work->count].path = tf_ssb_db_get_property(ssb, work->user, path_key);
|
||||
tf_free(path_key);
|
||||
work->count++;
|
||||
}
|
||||
JS_FreeCString(context, name_string);
|
||||
JS_FreeValue(context, name);
|
||||
}
|
||||
}
|
||||
JS_FreeValue(context, apps_array);
|
||||
}
|
||||
tf_free((void*)apps);
|
||||
|
||||
JS_FreeContext(context);
|
||||
JS_FreeRuntime(runtime);
|
||||
}
|
||||
|
||||
static void _tf_api_core_apps_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
get_apps_t* work = user_data;
|
||||
JSContext* context = work->context;
|
||||
JSValue result = JS_NewObject(context);
|
||||
for (int i = 0; i < work->count; i++)
|
||||
{
|
||||
JS_SetPropertyStr(context, result, work->apps[i].app, JS_NewString(context, work->apps[i].path));
|
||||
}
|
||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
JS_FreeValue(context, result);
|
||||
JS_FreeValue(context, work->promise[0]);
|
||||
JS_FreeValue(context, work->promise[1]);
|
||||
for (int i = 0; i < work->count; i++)
|
||||
{
|
||||
tf_free((void*)work->apps[i].app);
|
||||
tf_free((void*)work->apps[i].path);
|
||||
}
|
||||
tf_free(work->apps);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_api_core_apps(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
JSValue user = argv[0];
|
||||
JSValue process = data[0];
|
||||
const char* user_string = JS_IsString(user) ? JS_ToCString(context, user) : NULL;
|
||||
|
||||
if (JS_IsObject(process))
|
||||
{
|
||||
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");
|
||||
if (JS_IsObject(credentials))
|
||||
{
|
||||
JSValue session = JS_GetPropertyStr(context, credentials, "session");
|
||||
if (JS_IsObject(session))
|
||||
{
|
||||
JSValue session_name = JS_GetPropertyStr(context, session, "name");
|
||||
const char* session_name_string = JS_IsString(session_name) ? JS_ToCString(context, session_name) : NULL;
|
||||
if (user_string && session_name_string && strcmp(user_string, session_name_string) && strcmp(user_string, "core"))
|
||||
{
|
||||
JS_FreeCString(context, user_string);
|
||||
user_string = NULL;
|
||||
}
|
||||
else if (!user_string)
|
||||
{
|
||||
user_string = session_name_string;
|
||||
session_name_string = NULL;
|
||||
}
|
||||
JS_FreeCString(context, session_name_string);
|
||||
JS_FreeValue(context, session_name);
|
||||
}
|
||||
JS_FreeValue(context, session);
|
||||
}
|
||||
JS_FreeValue(context, credentials);
|
||||
}
|
||||
|
||||
if (user_string)
|
||||
{
|
||||
get_apps_t* work = tf_malloc(sizeof(get_apps_t) + strlen(user_string) + 1);
|
||||
*work = (get_apps_t) {
|
||||
.context = context,
|
||||
};
|
||||
memcpy(work->user, user_string, strlen(user_string) + 1);
|
||||
result = JS_NewPromiseCapability(context, work->promise);
|
||||
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
tf_ssb_run_work(ssb, _tf_api_core_apps_work, _tf_api_core_apps_after_work, work);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = JS_NewObject(context);
|
||||
}
|
||||
JS_FreeCString(context, user_string);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue imports = argv[0];
|
||||
JSValue process = argv[1];
|
||||
JSValue core = JS_GetPropertyStr(context, imports, "core");
|
||||
JS_SetPropertyStr(context, core, "apps", JS_NewCFunctionData(context, _tf_api_core_apps, 1, 0, 1, &process));
|
||||
JS_FreeValue(context, core);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
|
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);
|
||||
replacement = _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. */
|
||||
typedef struct JSContext JSContext;
|
||||
|
||||
@@ -19,6 +27,27 @@ typedef struct JSContext JSContext;
|
||||
*/
|
||||
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
|
||||
** 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);
|
||||
|
||||
/**
|
||||
** 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,7 +13,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.32.1</string>
|
||||
<string>0.0.33</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
|
@@ -1442,7 +1442,7 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
|
||||
int result = -1;
|
||||
tf_task_t* task = tf_task_create();
|
||||
tf_task_set_trusted(task, true);
|
||||
tf_printf("setting zip path to %s\n", args->zip);
|
||||
tf_printf("Zip path: %s\n", args->zip);
|
||||
tf_task_set_zip_path(task, args->zip);
|
||||
tf_task_set_ssb_network_key(task, args->network_key);
|
||||
tf_task_set_args(task, args->args);
|
||||
@@ -1487,7 +1487,7 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
|
||||
break;
|
||||
}
|
||||
}
|
||||
tf_printf("Using %s as the working directory.\n", cwd);
|
||||
tf_printf("Root path: %s\n", cwd);
|
||||
}
|
||||
if (!*cwd)
|
||||
{
|
||||
|
@@ -977,7 +977,6 @@ bool tf_ssb_connection_rpc_send_error_method_not_allowed(tf_ssb_connection_t* co
|
||||
{
|
||||
char buffer[1024] = "";
|
||||
snprintf(buffer, sizeof(buffer), "method '%s' is not in list of allowed methods", name);
|
||||
tf_printf("%s\n", buffer);
|
||||
return tf_ssb_connection_rpc_send_error(connection, flags, request_number, buffer);
|
||||
}
|
||||
|
||||
|
@@ -384,28 +384,6 @@ void tf_ssb_ebt_set_send_clock_pending(tf_ssb_ebt_t* ebt, int 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)
|
||||
{
|
||||
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);
|
||||
|
||||
/**
|
||||
** 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.
|
||||
** @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);
|
||||
sqlite3_stmt* 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;
|
||||
int column = 0;
|
||||
@@ -1162,7 +1162,11 @@ static void _tf_ssb_sqlAsync_work(tf_ssb_t* ssb, void* user_data)
|
||||
}
|
||||
}
|
||||
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))
|
||||
{
|
||||
@@ -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);
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
else
|
||||
else if (sql_work->result != SQLITE_OK)
|
||||
{
|
||||
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);
|
||||
sql_work->db = NULL;
|
||||
uv_mutex_unlock(&sql_work->lock);
|
||||
|
@@ -1561,7 +1561,10 @@ static void _tf_ssb_rpc_delete_blobs_work(tf_ssb_t* ssb, void* user_data)
|
||||
tf_free(ids);
|
||||
}
|
||||
delete->duration_ms = (uv_hrtime() - start_ns) / 1000000LL;
|
||||
tf_printf("Deleted %d blobs in %d ms.\n", deleted, (int)delete->duration_ms);
|
||||
if (deleted)
|
||||
{
|
||||
tf_printf("Deleted %d blobs in %d ms.\n", deleted, (int)delete->duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_delete_blobs_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
@@ -1580,7 +1583,6 @@ static void _tf_ssb_rpc_start_delete_blobs_callback(tf_ssb_t* ssb, void* user_da
|
||||
|
||||
static void _tf_ssb_rpc_start_delete_blobs(tf_ssb_t* ssb, int delay_ms)
|
||||
{
|
||||
tf_printf("will delete more blobs in %d ms\n", delay_ms);
|
||||
tf_ssb_schedule_work(ssb, delay_ms, _tf_ssb_rpc_start_delete_blobs_callback, NULL);
|
||||
}
|
||||
|
||||
@@ -1675,7 +1677,10 @@ static void _tf_ssb_rpc_delete_feeds_work(tf_ssb_t* ssb, void* user_data)
|
||||
JS_FreeRuntime(runtime);
|
||||
|
||||
delete->duration_ms = (uv_hrtime() - start_ns) / 1000000LL;
|
||||
tf_printf("Deleted %d message in %d ms.\n", delete->deleted, (int)delete->duration_ms);
|
||||
if (delete->deleted)
|
||||
{
|
||||
tf_printf("Deleted %d message in %d ms.\n", delete->deleted, (int)delete->duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_delete_feeds_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
@@ -1694,7 +1699,6 @@ static void _tf_ssb_rpc_start_delete_feeds_callback(tf_ssb_t* ssb, void* user_da
|
||||
|
||||
static void _tf_ssb_rpc_start_delete_feeds(tf_ssb_t* ssb, int delay_ms)
|
||||
{
|
||||
tf_printf("will delete more feeds in %d ms\n", delay_ms);
|
||||
tf_ssb_schedule_work(ssb, delay_ms, _tf_ssb_rpc_start_delete_feeds_callback, NULL);
|
||||
}
|
||||
|
||||
|
@@ -1,2 +1,2 @@
|
||||
#define VERSION_NUMBER "0.0.32.1"
|
||||
#define VERSION_NUMBER "0.0.33"
|
||||
#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.map -O deps/lit/lit-all.min.js.map
|
||||
cp -fv deps/lit/* apps/blog/
|
||||
|
Reference in New Issue
Block a user