Compare commits
51 Commits
8b546c7e02
...
latest_rel
Author | SHA1 | Date | |
---|---|---|---|
c550f92003 | |||
ed836b3ee0 | |||
ac7a43abf4 | |||
49f19fce91 | |||
b2197eb8e9 | |||
5fbc2cae1c | |||
730abb49ce | |||
edccab054a | |||
e8210c6fdd | |||
55d69d7c13 | |||
50fb18d4ff | |||
77b1ea1fc8 | |||
61501a9b64 | |||
c1507adac5 | |||
45fb9eda1c | |||
982a61f4bf | |||
18e5b41663 | |||
910c39cbd0 | |||
9952dfd49d | |||
00f75d5382 | |||
d78828554b | |||
b84b561109 | |||
a618815500 | |||
e1f3dc6ae4 | |||
f378db6c6f | |||
7cec0f7d61 | |||
f902d0374c | |||
b5f0a0c4f7 | |||
00623cea09 | |||
ed4f1d6f2c | |||
73f4a3407f | |||
6f11318e84 | |||
e88ee91f0e | |||
3f8daf257c | |||
dc387acadc | |||
68aa41ab96 | |||
85b23437b3 | |||
c59fba817d | |||
c3415ab75c | |||
f1d0151d71 | |||
3c5c1756d1 | |||
6a6b65d1b3 | |||
81bd54dbe6 | |||
6a1bb0d3bc | |||
705e8b553f | |||
e4729b22f2 | |||
662112551a | |||
38fe88aab8 | |||
578c51faa0 | |||
a3ccc73b81 | |||
7312f4d43a |
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
|
||||
|
18
GNUmakefile
18
GNUmakefile
@@ -16,14 +16,14 @@ MAKEFLAGS += --no-builtin-rules
|
||||
## LD := Linker.
|
||||
## ANDROID_SDK := Path to the Android SDK.
|
||||
|
||||
VERSION_CODE := 40
|
||||
VERSION_CODE_IOS := 15
|
||||
VERSION_NUMBER := 0.0.33-wip
|
||||
VERSION_CODE := 42
|
||||
VERSION_CODE_IOS := 16
|
||||
VERSION_NUMBER := 0.2025.8
|
||||
VERSION_NAME := This program kills fascists.
|
||||
|
||||
IPHONEOS_VERSION_MIN=14.0
|
||||
|
||||
SQLITE_URL := https://www.sqlite.org/2025/sqlite-amalgamation-3500300.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
|
||||
@@ -253,7 +253,10 @@ $(ANDROID_TARGETS): CFLAGS += \
|
||||
-fno-asynchronous-unwind-tables \
|
||||
-funwind-tables \
|
||||
-Wno-unknown-warning-option
|
||||
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
|
||||
$(ANDROID_TARGETS): LDFLAGS += \
|
||||
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
|
||||
-Wl,-z,max-page-size=16384 \
|
||||
-fPIC
|
||||
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
|
||||
$(DEBUG_TARGETS): LDFLAGS += -Og
|
||||
$(RELEASE_TARGETS): CFLAGS += \
|
||||
@@ -1140,6 +1143,11 @@ releaseapkgo: out/TildeFriends-arm-release.apk ## Build, install, and run a rele
|
||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||
.PHONY: releaseapkgo
|
||||
|
||||
x86releaseapkgo: out/TildeFriends-x86-release.apk ## Build, install, and run an x86 release Android APK.
|
||||
@adb install -r $<
|
||||
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
|
||||
.PHONY: x86releaseapkgo
|
||||
|
||||
apklog: ## Display Android log output.
|
||||
@adb logcat *:S tildefriends
|
||||
.PHONY: apklog
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🦀",
|
||||
"previous": "&DGtlnm5wWRZCgJMF8JsP6VtzNRrd4KLoERJRpFULqOY=.sha256"
|
||||
"previous": "&Hd6CuhhnZIf13PdFJYZBUYLYZO84WdaKvWXLC29M7Ac=.sha256"
|
||||
}
|
||||
|
@@ -21,10 +21,13 @@ class TfElement extends LitElement {
|
||||
channels_latest: {type: Object},
|
||||
guest: {type: Boolean},
|
||||
url: {type: String},
|
||||
private_closed: {type: Object},
|
||||
private_messages: {type: Array},
|
||||
grouped_private_messages: {type: Object},
|
||||
recent_reactions: {type: Array},
|
||||
is_administrator: {type: Boolean},
|
||||
stay_connected: {type: Boolean},
|
||||
progress: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,6 +50,7 @@ class TfElement extends LitElement {
|
||||
this.loading_latest = 0;
|
||||
this.loading_latest_scheduled = 0;
|
||||
this.recent_reactions = [];
|
||||
this.private_closed = {};
|
||||
tfrpc.rpc.getBroadcasts().then((b) => {
|
||||
self.broadcasts = b || [];
|
||||
});
|
||||
@@ -56,6 +60,7 @@ class TfElement extends LitElement {
|
||||
tfrpc.rpc.getHash().then((hash) => self.set_hash(hash));
|
||||
tfrpc.register(function hashChanged(hash) {
|
||||
self.set_hash(hash);
|
||||
self.reset_progress();
|
||||
});
|
||||
tfrpc.register(async function notifyNewMessage(id) {
|
||||
await self.fetch_new_message(id);
|
||||
@@ -83,9 +88,22 @@ class TfElement extends LitElement {
|
||||
this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
|
||||
this.guest = !this.whoami?.length;
|
||||
this.ids = ids;
|
||||
let private_closed =
|
||||
(await tfrpc.rpc.databaseGet('private_closed')) ?? '{}';
|
||||
this.private_closed = JSON.parse(private_closed);
|
||||
await this.load_channels();
|
||||
}
|
||||
|
||||
async close_private_chat(event) {
|
||||
let update = {};
|
||||
update[event.detail.key] = true;
|
||||
this.private_closed = Object.assign(update, this.private_closed);
|
||||
await tfrpc.rpc.databaseSet(
|
||||
'private_closed',
|
||||
JSON.stringify(this.private_closed)
|
||||
);
|
||||
}
|
||||
|
||||
async load_channels() {
|
||||
let channels = await tfrpc.rpc.query(
|
||||
`
|
||||
@@ -133,12 +151,32 @@ class TfElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
visible_private() {
|
||||
if (!this.grouped_private_messages || !this.private_closed) {
|
||||
return [];
|
||||
}
|
||||
let self = this;
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.grouped_private_messages).filter(([key, value]) => {
|
||||
let channel = '🔐' + [...new Set(JSON.parse(key))].sort().join(',');
|
||||
let grouped_latest = Math.max(...value.map((x) => x.rowid));
|
||||
return (
|
||||
!self.private_closed[key] ||
|
||||
self.channels_unread[channel] === undefined ||
|
||||
grouped_latest > self.channels_unread[channel]
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
next_channel(delta) {
|
||||
let channel_names = [
|
||||
'',
|
||||
'@',
|
||||
'👍',
|
||||
'🔐',
|
||||
...Object.keys(this.visible_private())
|
||||
.sort()
|
||||
.map((x) => '🔐' + JSON.parse(x).join(',')),
|
||||
...this.channels.map((x) => '#' + x),
|
||||
];
|
||||
let index = channel_names.indexOf(this.hash.substring(1));
|
||||
@@ -364,6 +402,36 @@ class TfElement extends LitElement {
|
||||
return result;
|
||||
}
|
||||
|
||||
async group_private_messages(messages) {
|
||||
let groups = {};
|
||||
let result = await this.decrypt(
|
||||
await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT messages.rowid, messages.id, author, timestamp, json(content) AS content
|
||||
FROM messages
|
||||
JOIN json_each(?) AS ids
|
||||
WHERE messages.id = ids.value
|
||||
ORDER BY timestamp DESC
|
||||
`,
|
||||
[JSON.stringify(messages)]
|
||||
)
|
||||
);
|
||||
for (let message of result) {
|
||||
let key = JSON.stringify(
|
||||
[
|
||||
...new Set(
|
||||
message?.decrypted?.recps?.filter((x) => x != this.whoami)
|
||||
),
|
||||
].sort() ?? []
|
||||
);
|
||||
if (!groups[key]) {
|
||||
groups[key] = [];
|
||||
}
|
||||
groups[key].push(message);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
async load_channels_latest(following) {
|
||||
let start_time = new Date();
|
||||
let latest_private = this.get_latest_private(following);
|
||||
@@ -436,12 +504,15 @@ class TfElement extends LitElement {
|
||||
console.log('channels took', (new Date() - start_time) / 1000.0);
|
||||
let self = this;
|
||||
start_time = new Date();
|
||||
latest_private.then(function (latest) {
|
||||
latest_private.then(async function (latest) {
|
||||
self.channels_latest = Object.assign({}, self.channels_latest, {
|
||||
'🔐': latest[0],
|
||||
});
|
||||
console.log('private took', (new Date() - start_time) / 1000.0);
|
||||
self.private_messages = latest[1];
|
||||
self.grouped_private_messages = await self.group_private_messages(
|
||||
latest[1]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -450,7 +521,28 @@ class TfElement extends LitElement {
|
||||
this.schedule_load_latest();
|
||||
}
|
||||
|
||||
reset_progress() {
|
||||
if (this.progress === undefined) {
|
||||
this._progress_start = new Date();
|
||||
requestAnimationFrame(this.update_progress.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
update_progress() {
|
||||
if (
|
||||
!this.loading_latest &&
|
||||
!this.loading_latest_scheduled &&
|
||||
!this.shadowRoot.getElementById('tf-tab-news')?.is_loading()
|
||||
) {
|
||||
this.progress = undefined;
|
||||
return;
|
||||
}
|
||||
this.progress = (new Date() - this._progress_start).valueOf();
|
||||
requestAnimationFrame(this.update_progress.bind(this));
|
||||
}
|
||||
|
||||
schedule_load_latest() {
|
||||
this.reset_progress();
|
||||
if (!this.loading_latest) {
|
||||
this.shadowRoot.getElementById('tf-tab-news')?.load_latest();
|
||||
this.load();
|
||||
@@ -495,6 +587,7 @@ class TfElement extends LitElement {
|
||||
|
||||
async load() {
|
||||
this.loading_latest = true;
|
||||
this.reset_progress();
|
||||
try {
|
||||
let start_time = new Date();
|
||||
let whoami = this.whoami;
|
||||
@@ -603,8 +696,11 @@ class TfElement extends LitElement {
|
||||
@channelsetunread=${this.channel_set_unread}
|
||||
@refresh=${this.refresh}
|
||||
@toggle_stay_connected=${this.toggle_stay_connected}
|
||||
@loadmessages=${this.reset_progress}
|
||||
@closeprivatechat=${this.close_private_chat}
|
||||
.connections=${this.connections}
|
||||
.private_messages=${this.private_messages}
|
||||
.grouped_private_messages=${this.visible_private()}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
?is_administrator=${this.is_administrator}
|
||||
?stay_connected=${this.stay_connected}
|
||||
@@ -646,6 +742,7 @@ class TfElement extends LitElement {
|
||||
async set_tab(tab) {
|
||||
this.tab = tab;
|
||||
if (tab === 'news') {
|
||||
this.schedule_load_latest();
|
||||
await tfrpc.rpc.setHash('#');
|
||||
} else if (tab === 'connections') {
|
||||
await tfrpc.rpc.setHash('#connections');
|
||||
@@ -751,11 +848,23 @@ class TfElement extends LitElement {
|
||||
Loading...
|
||||
</div>`
|
||||
: this.render_tab();
|
||||
let progress =
|
||||
this.progress !== undefined
|
||||
? html`
|
||||
<div style="position: absolute; width: 100%" id="progress">
|
||||
<div
|
||||
class="w3-theme-l3"
|
||||
style=${`height: 4px; position: absolute; right: ${Math.cos(this.progress / 250) > 0 ? 'auto' : '0'}; width: ${50 * Math.sin(this.progress / 250) + 50}%`}
|
||||
></div>
|
||||
</div>
|
||||
`
|
||||
: undefined;
|
||||
return html`
|
||||
<div
|
||||
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
|
||||
class="w3-theme-dark"
|
||||
>
|
||||
${progress}
|
||||
<div style="flex: 0 0">${tabs}</div>
|
||||
<div style="flex: 1 1; overflow: auto; contain: layout">
|
||||
${contents}
|
||||
|
@@ -16,6 +16,7 @@ class TfComposeElement extends LitElement {
|
||||
author: {type: String},
|
||||
channel: {type: String},
|
||||
new_thread: {type: Boolean},
|
||||
recipients: {type: Array},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -91,7 +92,9 @@ class TfComposeElement extends LitElement {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
id: this.branch,
|
||||
id:
|
||||
this.branch ??
|
||||
(this.recipients ? this.recipients.join(',') : undefined),
|
||||
draft: draft,
|
||||
},
|
||||
})
|
||||
@@ -291,7 +294,7 @@ class TfComposeElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
get_values() {
|
||||
let values = Object.entries(this.users).map((x) => ({
|
||||
key: x[1].name ?? x[0],
|
||||
value: x[0],
|
||||
@@ -307,11 +310,15 @@ class TfComposeElement extends LitElement {
|
||||
values
|
||||
);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
let tribute = new Tribute({
|
||||
iframe: this.shadowRoot,
|
||||
collection: [
|
||||
{
|
||||
values: values,
|
||||
values: this.get_values(),
|
||||
selectTemplate: function (item) {
|
||||
return item
|
||||
? `[@${item.original.key}](${item.original.value})`
|
||||
@@ -330,6 +337,7 @@ class TfComposeElement extends LitElement {
|
||||
],
|
||||
});
|
||||
tribute.attach(this.renderRoot.getElementById('edit'));
|
||||
this._tribute = tribute;
|
||||
}
|
||||
|
||||
updated() {
|
||||
@@ -340,6 +348,7 @@ class TfComposeElement extends LitElement {
|
||||
preview.innerHTML = this.process_text(edit.innerText);
|
||||
this.last_updated_text = edit.innerText;
|
||||
}
|
||||
this._tribute.collection[0].values = this.get_values();
|
||||
let encrypt = this.renderRoot.getElementById('encrypt_to');
|
||||
if (encrypt) {
|
||||
let tribute = new Tribute({
|
||||
@@ -496,7 +505,17 @@ class TfComposeElement extends LitElement {
|
||||
}
|
||||
|
||||
get_draft() {
|
||||
return this.drafts[this.branch || ''] || {};
|
||||
let key =
|
||||
this.branch ||
|
||||
(this.recipients ? this.recipients.join(',') : undefined) ||
|
||||
'';
|
||||
let draft = this.drafts[key] || {};
|
||||
if (this.recipients && !draft.encrypt_to?.length) {
|
||||
draft.encrypt_to = [
|
||||
...new Set(this.recipients).union(new Set(draft.encrypt_to ?? [])),
|
||||
];
|
||||
}
|
||||
return draft;
|
||||
}
|
||||
|
||||
update_encrypt(event) {
|
||||
|
@@ -789,60 +789,45 @@ class TfMessageElement extends LitElement {
|
||||
</div>
|
||||
`);
|
||||
} else if (content.type == 'contact') {
|
||||
return this.render_frame(html`
|
||||
<div class="w3-bar">
|
||||
<div class="w3-bar-item">
|
||||
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
|
||||
is
|
||||
${content.blocking === true
|
||||
? 'blocking'
|
||||
: content.blocking === false
|
||||
? 'no longer blocking'
|
||||
: content.following === true
|
||||
? 'following'
|
||||
: content.following === false
|
||||
? 'no longer following'
|
||||
: '?'}
|
||||
<tf-user
|
||||
id=${this.message.content.contact}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
</div>
|
||||
<div class="w3-bar-item w3-right">
|
||||
<button class="w3-button w3-theme-d1" @click=${this.toggle_menu}>
|
||||
%
|
||||
</button>
|
||||
<div
|
||||
class="w3-dropdown-content w3-bar-block w3-card-4 w3-theme-l1"
|
||||
style="right: 48px"
|
||||
>
|
||||
<a
|
||||
target="_top"
|
||||
class="w3-button w3-bar-item"
|
||||
href=${'#' + encodeURIComponent(this.message?.id)}
|
||||
>View Message</a
|
||||
>
|
||||
<button
|
||||
class="w3-button w3-bar-item w3-border-bottom"
|
||||
@click=${this.copy_id}
|
||||
>
|
||||
Copy ID
|
||||
</button>
|
||||
${this.drafts[this.message?.id] === undefined
|
||||
? html`
|
||||
<button
|
||||
class="w3-button w3-bar-item"
|
||||
@click=${this.show_reply}
|
||||
>
|
||||
↩️ Reply
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
switch (this.format) {
|
||||
case 'message':
|
||||
default:
|
||||
return this.render_frame(html`
|
||||
<div class="w3-bar">
|
||||
<div class="w3-bar-item">
|
||||
<tf-user
|
||||
id=${this.message.author}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
is
|
||||
${content.blocking === true
|
||||
? 'blocking'
|
||||
: content.blocking === false
|
||||
? 'no longer blocking'
|
||||
: content.following === true
|
||||
? 'following'
|
||||
: content.following === false
|
||||
? 'no longer following'
|
||||
: '?'}
|
||||
<tf-user
|
||||
id=${this.message.content.contact}
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
</div>
|
||||
${this.render_menu()} ${this.render_votes()}
|
||||
${this.render_actions()}
|
||||
</div>
|
||||
`);
|
||||
break;
|
||||
case 'raw':
|
||||
return this.render_frame(html`
|
||||
${this.render_header()}
|
||||
<div class="w3-container">${this.render_raw()}</div>
|
||||
${this.render_votes()} ${this.render_actions()}
|
||||
</div>
|
||||
${this.render_votes()} ${this.render_actions()}
|
||||
</div>
|
||||
`);
|
||||
`);
|
||||
break;
|
||||
}
|
||||
} else if (content.type == 'post') {
|
||||
let self = this;
|
||||
let body;
|
||||
|
@@ -349,6 +349,9 @@ class TfProfileElement extends LitElement {
|
||||
${until(this.load_follows(), html`<p>Loading accounts followed...</p>`)}
|
||||
<footer class="w3-container">
|
||||
<p>
|
||||
<a class="w3-button w3-theme-d1" href=${'#🔐' + (this.id != this.whoami ? this.id : '')}>
|
||||
Open Private Chat
|
||||
</a>
|
||||
${edit}
|
||||
${follow}
|
||||
${block}
|
||||
|
@@ -43,6 +43,8 @@ const tf = css`
|
||||
border-left: 4px solid #fff;
|
||||
padding: 8px;
|
||||
padding-left: 12px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@@ -18,6 +18,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
time_range: {type: Array},
|
||||
time_loading: {type: Array},
|
||||
private_messages: {type: Array},
|
||||
grouped_private_messages: {type: Object},
|
||||
recent_reactions: {type: Array},
|
||||
};
|
||||
}
|
||||
@@ -106,6 +107,12 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
}
|
||||
|
||||
async fetch_messages(start_time, end_time) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('loadmessages', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
this.time_loading = [start_time, end_time];
|
||||
let result;
|
||||
const k_max_results = 64;
|
||||
@@ -202,7 +209,9 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
console.log(
|
||||
`load of ${result.length} rows took ${(t2 - t0) / 1000} (${(t1 - t0) / 1000} to find ${initial_messages.length} initial messages, ${(t2 - t1) / 1000} to find ${result.length} total messages) following=${this.following.length} st=${start_time} et=${end_time}`
|
||||
);
|
||||
} else if (this.hash == '#🔐') {
|
||||
} else if (this.hash.startsWith('#🔐')) {
|
||||
let ids =
|
||||
this.hash == '#🔐' ? [] : this.hash.substring('#🔐'.length).split(',');
|
||||
result = await tfrpc.rpc.query(
|
||||
`
|
||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
|
||||
@@ -214,7 +223,11 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
ORDER BY messages.rowid DESC LIMIT ?4
|
||||
`,
|
||||
[
|
||||
JSON.stringify(this.private_messages),
|
||||
JSON.stringify(
|
||||
this.grouped_private_messages?.[JSON.stringify(ids)]?.map(
|
||||
(x) => x.id
|
||||
) ?? []
|
||||
),
|
||||
start_time,
|
||||
end_time,
|
||||
k_max_results,
|
||||
@@ -373,12 +386,16 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
let self = this;
|
||||
this.loading++;
|
||||
let messages = [];
|
||||
let original_hash = this.hash;
|
||||
try {
|
||||
if (this._messages_hash !== this.hash) {
|
||||
this.messages = [];
|
||||
this._messages_hash = this.hash;
|
||||
}
|
||||
this._messages_following = this.following;
|
||||
this._messages_following = JSON.stringify(this.following);
|
||||
this._private_messages =
|
||||
JSON.stringify(this.private_messages) +
|
||||
JSON.stringify(this.grouped_private_messages);
|
||||
let now = new Date().valueOf();
|
||||
let start_time = now - 24 * 60 * 60 * 1000;
|
||||
this.start_time = start_time;
|
||||
@@ -391,7 +408,9 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
} finally {
|
||||
this.loading--;
|
||||
}
|
||||
this.messages = this.merge_messages(this.messages, messages);
|
||||
if (this.hash == original_hash) {
|
||||
this.messages = this.merge_messages(this.messages, messages);
|
||||
}
|
||||
this.time_loading = undefined;
|
||||
console.log(
|
||||
`loading ${messages.length} messages done for ${self.whoami} in ${(new Date() - start_time) / 1000}s`
|
||||
@@ -417,12 +436,42 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
close_private_chat() {
|
||||
this.mark_all_read();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('closeprivatechat', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: {
|
||||
key: JSON.stringify(
|
||||
this.hash == '#🔐'
|
||||
? []
|
||||
: this.hash.substring('#🔐'.length).split(',')
|
||||
),
|
||||
},
|
||||
})
|
||||
);
|
||||
tfrpc.rpc.setHash('#');
|
||||
}
|
||||
|
||||
render_close_chat_button() {
|
||||
if (this.hash.startsWith('#🔐')) {
|
||||
return html`
|
||||
<button class="w3-button w3-theme-d1" @click=${this.close_private_chat}>
|
||||
Close Chat
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (
|
||||
!this.messages ||
|
||||
this._messages_hash !== this.hash ||
|
||||
JSON.stringify(this._messages_following) !==
|
||||
JSON.stringify(this.following)
|
||||
this._messages_following !== JSON.stringify(this.following) ||
|
||||
this._private_messages !==
|
||||
JSON.stringify(this.private_messages) +
|
||||
JSON.stringify(this.grouped_private_messages)
|
||||
) {
|
||||
console.log(
|
||||
`loading messages for ${this.whoami} (following ${this.following.length})`
|
||||
@@ -482,6 +531,7 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
Mark All Read
|
||||
</button>`
|
||||
: undefined}
|
||||
${this.render_close_chat_button()}
|
||||
<tf-news
|
||||
id="news"
|
||||
whoami=${this.whoami}
|
||||
|
@@ -24,6 +24,7 @@ class TfTabNewsElement extends LitElement {
|
||||
channels_latest: {type: Object},
|
||||
connections: {type: Array},
|
||||
private_messages: {type: Array},
|
||||
grouped_private_messages: {type: Object},
|
||||
recent_reactions: {type: Array},
|
||||
peer_exchange: {type: Boolean},
|
||||
is_administrator: {type: Boolean},
|
||||
@@ -115,6 +116,19 @@ class TfTabNewsElement extends LitElement {
|
||||
) {
|
||||
return '✉️ ';
|
||||
}
|
||||
} else if (channel?.startsWith('🔐')) {
|
||||
let key = JSON.stringify(channel.substring('🔐'.length).split(','));
|
||||
if (this.grouped_private_messages?.[key]) {
|
||||
let grouped_latest = Math.max(
|
||||
...this.grouped_private_messages?.[key]?.map((x) => x.rowid)
|
||||
);
|
||||
if (
|
||||
this.channels_unread[channel] === undefined ||
|
||||
grouped_latest > this.channels_unread[channel]
|
||||
) {
|
||||
return '✉️ ';
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
this.channels_latest[channel] &&
|
||||
this.channels_latest[channel] > 0 &&
|
||||
@@ -180,6 +194,10 @@ class TfTabNewsElement extends LitElement {
|
||||
await this.check_peer_exchange();
|
||||
}
|
||||
|
||||
is_loading() {
|
||||
return this.shadowRoot?.getElementById('news')?.loading;
|
||||
}
|
||||
|
||||
render_sidebar() {
|
||||
return html`
|
||||
<div
|
||||
@@ -253,12 +271,29 @@ class TfTabNewsElement extends LitElement {
|
||||
style=${this.hash == '#👍' ? 'font-weight: bold' : undefined}
|
||||
>${this.unread_status('👍')}👍votes</a
|
||||
>
|
||||
<a
|
||||
href="#🔐"
|
||||
class="w3-bar-item w3-button"
|
||||
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined}
|
||||
>${this.unread_status('🔐')}🔐private</a
|
||||
>
|
||||
${Object.keys(this?.grouped_private_messages ?? [])
|
||||
?.sort()
|
||||
?.map(
|
||||
(key) => html`
|
||||
<a
|
||||
href=${'#🔐' + JSON.parse(key).join(',')}
|
||||
class="w3-bar-item w3-button"
|
||||
style=${this.hash == '#🔐' + JSON.parse(key).join(',')
|
||||
? 'font-weight: bold'
|
||||
: undefined}
|
||||
>${this.unread_status('🔐' + JSON.parse(key).join(','))}
|
||||
${(key != '[]' ? JSON.parse(key) : [this.whoami]).map(
|
||||
(id) => html`
|
||||
<tf-user
|
||||
id=${id}
|
||||
nolink="true"
|
||||
.users=${this.users}
|
||||
></tf-user>
|
||||
`
|
||||
)}</a
|
||||
>
|
||||
`
|
||||
)}
|
||||
${Object.keys(this.drafts)
|
||||
.sort()
|
||||
.map(
|
||||
@@ -414,6 +449,9 @@ class TfTabNewsElement extends LitElement {
|
||||
.drafts=${this.drafts}
|
||||
@tf-draft=${this.draft}
|
||||
.channel=${this.channel()}
|
||||
.recipients=${this.hash.startsWith('#🔐')
|
||||
? this.hash.substring('#🔐'.length).split(',')
|
||||
: undefined}
|
||||
></tf-compose>
|
||||
</div>
|
||||
${profile}
|
||||
@@ -430,6 +468,7 @@ class TfTabNewsElement extends LitElement {
|
||||
.channels_unread=${this.channels_unread}
|
||||
.channels_latest=${this.channels_latest}
|
||||
.private_messages=${this.private_messages}
|
||||
.grouped_private_messages=${this.grouped_private_messages}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
></tf-tab-news-feed>
|
||||
</div>
|
||||
|
@@ -9,6 +9,7 @@ class TfUserElement extends LitElement {
|
||||
fallback_name: {type: String},
|
||||
icon_only: {type: Boolean},
|
||||
users: {type: Object},
|
||||
nolink: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,7 +38,9 @@ class TfUserElement extends LitElement {
|
||||
let name_string = name ?? this.fallback_name ?? this.id;
|
||||
name = this.icon_only
|
||||
? undefined
|
||||
: html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`;
|
||||
: !this.nolink
|
||||
? html`<a target="_top" href=${'#' + this.id}>${name_string}</a>`
|
||||
: html`<span>${name_string}</span>`;
|
||||
|
||||
if (user) {
|
||||
let image_link = user.image;
|
||||
@@ -56,7 +59,8 @@ class TfUserElement extends LitElement {
|
||||
}
|
||||
}
|
||||
return html` <div
|
||||
style="display: inline-block; vertical-align: middle; font-weight: bold; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis"
|
||||
style=${'display: inline-block; vertical-align: middle; text-wrap: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis' +
|
||||
(this.nolink ? '' : '; font-weight: bold')}
|
||||
>
|
||||
${image} ${name}
|
||||
</div>`;
|
||||
|
37
core/app.js
37
core/app.js
@@ -1,7 +1,23 @@
|
||||
/**
|
||||
* \file
|
||||
* \defgroup tfapp Tilde Friends App JS
|
||||
* Tilde Friends server-side app wrapper.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** \cond */
|
||||
import * as core from './core.js';
|
||||
|
||||
let gSessionIndex = 0;
|
||||
export {App};
|
||||
/** \endcond */
|
||||
|
||||
/** A sequence number of apps. */
|
||||
let g_session_index = 0;
|
||||
|
||||
/**
|
||||
** App constructor.
|
||||
** @return An app instance.
|
||||
*/
|
||||
function App() {
|
||||
this._send_queue = [];
|
||||
this.calls = {};
|
||||
@@ -9,6 +25,12 @@ function App() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
** Create a function wrapper that when called invokes a function on the app
|
||||
** itself.
|
||||
** @param api The function and argument names.
|
||||
** @return A function.
|
||||
*/
|
||||
App.prototype.makeFunction = function (api) {
|
||||
let self = this;
|
||||
let result = function () {
|
||||
@@ -32,6 +54,10 @@ App.prototype.makeFunction = function (api) {
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
** Send a message to the app.
|
||||
** @param message The message to send.
|
||||
*/
|
||||
App.prototype.send = function (message) {
|
||||
if (this._send_queue) {
|
||||
if (this._on_output) {
|
||||
@@ -46,6 +72,11 @@ App.prototype.send = function (message) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
** App socket handler.
|
||||
** @param request The HTTP request of the WebSocket connection.
|
||||
** @param response The HTTP response.
|
||||
*/
|
||||
exports.app_socket = async function socket(request, response) {
|
||||
let process;
|
||||
let options = {};
|
||||
@@ -133,7 +164,7 @@ exports.app_socket = async function socket(request, response) {
|
||||
options.packageOwner = packageOwner;
|
||||
options.packageName = packageName;
|
||||
options.url = message.url;
|
||||
let sessionId = 'session_' + (gSessionIndex++).toString();
|
||||
let sessionId = 'session_' + (g_session_index++).toString();
|
||||
if (blobId) {
|
||||
if (message.edit_only) {
|
||||
response.send(
|
||||
@@ -218,4 +249,4 @@ exports.app_socket = async function socket(request, response) {
|
||||
response.upgrade(100, {});
|
||||
};
|
||||
|
||||
export {App};
|
||||
/** @} */
|
||||
|
406
core/client.js
406
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 = {};
|
||||
@@ -75,8 +72,8 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* Toggle editor visibility.
|
||||
* @param event The HTML event.
|
||||
*/
|
||||
toggle_edit(event) {
|
||||
event.preventDefault();
|
||||
@@ -88,18 +85,18 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} key
|
||||
* Remove a stored permission.
|
||||
* @param key The permission to reset.
|
||||
*/
|
||||
reset_permission(key) {
|
||||
send({action: 'resetPermission', permission: key});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} key
|
||||
* @param {*} options
|
||||
* @returns
|
||||
* Get or create a spark line.
|
||||
* @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;
|
||||
|
||||
@@ -245,8 +262,8 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the permissions popup.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render_permissions() {
|
||||
if (this.show_permissions) {
|
||||
@@ -287,16 +304,36 @@ class TfNavigationElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current error.
|
||||
*/
|
||||
clear_error() {
|
||||
this.status = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the navigation bar.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
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>
|
||||
@@ -404,9 +441,13 @@ class TfNavigationElement extends LitElement {
|
||||
customElements.define('tf-navigation', TfNavigationElement);
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* A file in the files sidebar.
|
||||
*/
|
||||
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 = {};
|
||||
@@ -423,8 +467,8 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} file
|
||||
* Select a clicked file.
|
||||
* @param file The file.
|
||||
*/
|
||||
file_click(file) {
|
||||
this.dispatchEvent(
|
||||
@@ -439,9 +483,9 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} file
|
||||
* @returns
|
||||
* Render a single file in the file list.
|
||||
* @param file The file.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render_file(file) {
|
||||
let classes = ['file'];
|
||||
@@ -463,8 +507,8 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* Create a file entry for a dropped file.
|
||||
* @param event The event.
|
||||
*/
|
||||
async drop(event) {
|
||||
event.preventDefault();
|
||||
@@ -489,8 +533,8 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* Called when a file starts being dragged over the file.
|
||||
* @param event The event.
|
||||
*/
|
||||
drag_enter(event) {
|
||||
this.dropping++;
|
||||
@@ -499,8 +543,8 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* Called when a file stops being dragged over the file.
|
||||
* @param event The event.
|
||||
*/
|
||||
drag_leave(event) {
|
||||
this.dropping--;
|
||||
@@ -509,13 +553,17 @@ class TfFilesElement extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a file is being dragged over the file.
|
||||
* @param event The event.
|
||||
*/
|
||||
drag_over(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the file.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render() {
|
||||
let self = this;
|
||||
@@ -562,9 +610,13 @@ class TfFilesElement extends LitElement {
|
||||
customElements.define('tf-files', TfFilesElement);
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* The files pane element.
|
||||
*/
|
||||
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';
|
||||
@@ -580,8 +635,8 @@ class TfFilesPaneElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} expanded
|
||||
* Set whether the files pane is expanded.
|
||||
* @param expanded Whether the files pane is expanded.
|
||||
*/
|
||||
set_expanded(expanded) {
|
||||
this.expanded = expanded;
|
||||
@@ -589,8 +644,8 @@ class TfFilesPaneElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the files pane element.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render() {
|
||||
let self = this;
|
||||
@@ -649,7 +704,7 @@ class TfFilesPaneElement extends LitElement {
|
||||
customElements.define('tf-files-pane', TfFilesPaneElement);
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* A tiny graph.
|
||||
*/
|
||||
class TfSparkLineElement extends LitElement {
|
||||
static get properties() {
|
||||
@@ -669,9 +724,9 @@ class TfSparkLineElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} key
|
||||
* @param {*} value
|
||||
* Add a data point to the graph.
|
||||
* @param key The line to which the point applies.
|
||||
* @param value The numeric value of the data point.
|
||||
*/
|
||||
append(key, value) {
|
||||
let line = null;
|
||||
@@ -698,9 +753,9 @@ class TfSparkLineElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} line
|
||||
* @returns
|
||||
* Render a single series line.
|
||||
* @param line The line data.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render_line(line) {
|
||||
if (line?.values?.length >= 2) {
|
||||
@@ -716,8 +771,8 @@ class TfSparkLineElement extends LitElement {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Render the graph.
|
||||
* @return Lit HTML.
|
||||
*/
|
||||
render() {
|
||||
let max =
|
||||
@@ -744,7 +799,9 @@ class TfSparkLineElement extends LitElement {
|
||||
|
||||
customElements.define('tf-sparkline', TfSparkLineElement);
|
||||
|
||||
// TODOC
|
||||
/**
|
||||
* A keyboard key is pressed down.
|
||||
*/
|
||||
window.addEventListener('keydown', function (event) {
|
||||
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
||||
if (editing()) {
|
||||
@@ -760,10 +817,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) {
|
||||
@@ -806,24 +862,23 @@ function ensureLoaded(nodes, callback) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Check whether the editior is currently visible.
|
||||
* @return true if the editor is visible.
|
||||
*/
|
||||
function editing() {
|
||||
return document.getElementById('editPane').style.display != 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Check whether only the editor is visible and the app is hidden.
|
||||
* @return true if the editor is visible and the app is not.
|
||||
*/
|
||||
function is_edit_only() {
|
||||
return window.location.search == '?editonly=1' || window.innerWidth < 1024;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Show the editor.
|
||||
*/
|
||||
async function edit() {
|
||||
if (editing()) {
|
||||
@@ -850,30 +905,17 @@ async function edit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Open a performance trace.
|
||||
*/
|
||||
function trace() {
|
||||
window.open(`/speedscope/#profileURL=${encodeURIComponent('/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 +941,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');
|
||||
@@ -941,7 +983,7 @@ async function load(path) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Hide the editor.
|
||||
*/
|
||||
function closeEditor() {
|
||||
window.localStorage.setItem('editing', '0');
|
||||
@@ -950,17 +992,9 @@ function closeEditor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
*/
|
||||
function explodePath() {
|
||||
return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@@ -1070,7 +1104,7 @@ function save(save_to) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to set the app icon.
|
||||
*/
|
||||
function changeIcon() {
|
||||
let value = prompt('Enter a new app icon emoji:');
|
||||
@@ -1081,7 +1115,7 @@ function changeIcon() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to delete the current app.
|
||||
*/
|
||||
function deleteApp() {
|
||||
let name = document.getElementById('name');
|
||||
@@ -1102,8 +1136,8 @@ function deleteApp() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Get the current app URL.
|
||||
* @return The app URL.
|
||||
*/
|
||||
function url() {
|
||||
let hash = window.location.href.indexOf('#');
|
||||
@@ -1121,16 +1155,16 @@ function url() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Get the window hash without the lone '#' if it is empty.
|
||||
* @return The hash.
|
||||
*/
|
||||
function hash() {
|
||||
return window.location.hash != '#' ? window.location.hash : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} content
|
||||
* Set the iframe document contents.
|
||||
* @param content The contents.
|
||||
*/
|
||||
function api_setDocument(content) {
|
||||
let iframe = document.getElementById('document');
|
||||
@@ -1138,8 +1172,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');
|
||||
@@ -1147,8 +1181,8 @@ function api_postMessage(message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} error
|
||||
* Show an error.
|
||||
* @param error The error.
|
||||
*/
|
||||
function api_error(error) {
|
||||
if (error) {
|
||||
@@ -1162,28 +1196,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');
|
||||
@@ -1252,23 +1286,23 @@ function api_requestPermission(permission, id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Log from the app to the console.
|
||||
*/
|
||||
function api_print() {
|
||||
console.log('app>', ...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} hash
|
||||
* Set the window's location hash.
|
||||
* @param hash The new hash.
|
||||
*/
|
||||
function api_setHash(hash) {
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} message
|
||||
* Process an incoming WebSocket message.
|
||||
* @param message The message.
|
||||
*/
|
||||
function _receive_websocket_message(message) {
|
||||
if (message && message.action == 'session') {
|
||||
@@ -1364,9 +1398,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 = {
|
||||
@@ -1377,8 +1411,8 @@ function setStatusMessage(message, color) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} value
|
||||
* Send a message to the app.
|
||||
* @param value The message.
|
||||
*/
|
||||
function send(value) {
|
||||
try {
|
||||
@@ -1391,57 +1425,14 @@ 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
|
||||
* Notify the app of the window hash changing.
|
||||
*/
|
||||
function hashChange() {
|
||||
send({event: 'hashChange', hash: window.location.hash});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Make sure the app is connected on window focus, and notify the app.
|
||||
*/
|
||||
function focus() {
|
||||
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
||||
@@ -1452,7 +1443,7 @@ function focus() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Notify the app of lost focus.
|
||||
*/
|
||||
function blur() {
|
||||
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
||||
@@ -1461,8 +1452,8 @@ function blur() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} event
|
||||
* Handle a message.
|
||||
* @param event The message.
|
||||
*/
|
||||
function message(event) {
|
||||
if (
|
||||
@@ -1510,8 +1501,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;
|
||||
@@ -1526,8 +1517,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) {
|
||||
@@ -1593,8 +1584,8 @@ function connectSocket(path) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} name
|
||||
* Open a file by name.
|
||||
* @param name The file to open.
|
||||
*/
|
||||
function openFile(name) {
|
||||
let newDoc =
|
||||
@@ -1619,7 +1610,7 @@ function openFile(name) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Refresh the files list.
|
||||
*/
|
||||
function updateFiles() {
|
||||
let files = document.getElementsByTagName('tf-files-pane')[0];
|
||||
@@ -1641,8 +1632,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] = {
|
||||
@@ -1652,7 +1643,7 @@ function makeNewFile(name) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to create a new file.
|
||||
*/
|
||||
function newFile() {
|
||||
let name = prompt('Name of new file:', 'file.js');
|
||||
@@ -1662,7 +1653,7 @@ function newFile() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to remove a file.
|
||||
*/
|
||||
function removeFile() {
|
||||
if (confirm('Remove ' + gCurrentFile + '?')) {
|
||||
@@ -1672,7 +1663,7 @@ function removeFile() {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Export the app to a zip file, which is downloaded by the browser.
|
||||
*/
|
||||
async function appExport() {
|
||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||
@@ -1703,10 +1694,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}.`);
|
||||
@@ -1730,7 +1721,7 @@ async function save_file_to_blob_id(name, file) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* Prompt to import an app from a zip file.
|
||||
*/
|
||||
async function appImport() {
|
||||
let JsZip = (await import('/static/jszip.min.js')).default;
|
||||
@@ -1801,7 +1792,7 @@ async function appImport() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Prettify the current source file.
|
||||
*/
|
||||
async function sourcePretty() {
|
||||
let prettier = (await import('/prettier/standalone.mjs')).default;
|
||||
@@ -1829,6 +1820,9 @@ async function sourcePretty() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle visible whitespace.
|
||||
*/
|
||||
function toggleVisibleWhitespace() {
|
||||
let editor_style = document.getElementById('editor_style');
|
||||
/*
|
||||
@@ -1854,7 +1848,9 @@ function toggleVisibleWhitespace() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODOC
|
||||
/**
|
||||
* Register event handlers and connect the WebSocket on load.
|
||||
*/
|
||||
window.addEventListener('load', function () {
|
||||
window.addEventListener('hashchange', hashChange);
|
||||
window.addEventListener('focus', focus);
|
||||
@@ -1902,3 +1898,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};
|
||||
/** @} */
|
||||
|
34
core/http.js
34
core/http.js
@@ -1,8 +1,14 @@
|
||||
/**
|
||||
* TODOC
|
||||
* TODO: document so we can improve this
|
||||
* @param {*} url
|
||||
* @returns
|
||||
* \file
|
||||
* \defgroup tfhttp Tilde Friends HTTP Client JS
|
||||
* Tilde Friends server-side HTTP client.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse a URL into protocol, host, path, and port parts.
|
||||
* @param url
|
||||
* @return An object of the URL parts.
|
||||
*/
|
||||
function parseUrl(url) {
|
||||
// XXX: Hack.
|
||||
@@ -16,9 +22,9 @@ function parseUrl(url) {
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} data
|
||||
* @returns
|
||||
* Parse an HTTP response into headers and body content.
|
||||
* @param data The response data, headers and body included.
|
||||
* @return headers and body data.
|
||||
*/
|
||||
function parseResponse(data) {
|
||||
let firstLine;
|
||||
@@ -36,15 +42,15 @@ function parseResponse(data) {
|
||||
headers[line.substring(colon)] = line.substring(colon + 1);
|
||||
}
|
||||
}
|
||||
return {body: data};
|
||||
return {headers: headers, body: data};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @param {*} url
|
||||
* @param {*} options
|
||||
* @param {*} allowed_hosts
|
||||
* @returns
|
||||
* Make an HTTP request.
|
||||
* @param url The URL.
|
||||
* @param options Request options.
|
||||
* @param allowed_hosts List of allowed hosts.
|
||||
* @return A promise resolved with the response headers and body.
|
||||
*/
|
||||
export function fetch(url, options, allowed_hosts) {
|
||||
let parsed = parseUrl(url);
|
||||
@@ -111,3 +117,5 @@ export function fetch(url, options, allowed_hosts) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
@@ -1,11 +1,22 @@
|
||||
/**
|
||||
* \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 = {};
|
||||
|
||||
/**
|
||||
* TODOC
|
||||
* @returns
|
||||
* Check if being called from a browser vs. server-side.
|
||||
* @return true if called from a browser.
|
||||
*/
|
||||
function get_is_browser() {
|
||||
try {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
@@ -25,14 +25,14 @@
|
||||
}:
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "tildefriends";
|
||||
version = "0.0.32";
|
||||
version = "0.0.33";
|
||||
|
||||
src = pkgs.fetchFromGitea {
|
||||
domain = "dev.tildefriends.net";
|
||||
owner = "cory";
|
||||
repo = "tildefriends";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-Dk0NOEQIg2LeENySK0+MgpZEtfsClGq6dZL+eOOpE0U=";
|
||||
hash = "sha256-9D28gmaBTRVyXhY3zZd/W9PsXA1YZt/K69hz41aVP04=";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
|
304
deps/codemirror_src/package-lock.json
generated
vendored
304
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,15 +91,17 @@
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz",
|
||||
"integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==",
|
||||
"version": "6.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||
"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",
|
||||
@@ -147,6 +158,7 @@
|
||||
"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,10 +167,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
|
||||
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
@@ -169,31 +182,35 @@
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"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==",
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"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.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
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.29",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
|
||||
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
|
||||
"version": "0.3.30",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
|
||||
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
@@ -202,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.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",
|
||||
@@ -218,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"
|
||||
}
|
||||
@@ -226,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",
|
||||
@@ -236,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",
|
||||
@@ -246,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",
|
||||
@@ -256,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"
|
||||
}
|
||||
@@ -263,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",
|
||||
@@ -293,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",
|
||||
@@ -314,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",
|
||||
@@ -332,240 +360,260 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz",
|
||||
"integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.4.tgz",
|
||||
"integrity": "sha512-B2wfzCJ+ps/OBzRjeds7DlJumCU3rXMxJJS1vzURyj7+KBHGONm7c9q1TfdBl4vCuNMkDvARn3PBl2wZzuR5mw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz",
|
||||
"integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.4.tgz",
|
||||
"integrity": "sha512-FGJYXvYdn8Bs6lAlBZYT5n+4x0ciEp4cmttsvKAZc/c8/JiPaQK8u0c/86vKX8lA7OY/+37lIQSe0YoAImvBAA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz",
|
||||
"integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.4.tgz",
|
||||
"integrity": "sha512-/9qwE/BM7ATw/W/OFEMTm3dmywbJyLQb4f4v5nmOjgYxPIGpw7HaxRi6LnD4Pjn/q7k55FGeHe1/OD02w63apA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz",
|
||||
"integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.4.tgz",
|
||||
"integrity": "sha512-QkWfNbeRuzFnv2d0aPlrzcA3Ebq2mE8kX/5Pl7VdRShbPBjSnom7dbT8E3Jmhxo2RL784hyqGvR5KHavCJQciw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz",
|
||||
"integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.4.tgz",
|
||||
"integrity": "sha512-+ToyOMYnSfV8D+ckxO6NthPln/PDNp1P6INcNypfZ7muLmEvPKXqduUiD8DlJpMMT8LxHcE5W0dK9kXfJke9Zw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz",
|
||||
"integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.4.tgz",
|
||||
"integrity": "sha512-cGT6ey/W+sje6zywbLiqmkfkO210FgRz7tepWAzzEVgQU8Hn91JJmQWNqs55IuglG8sJdzk7XfNgmGRtcYlo1w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz",
|
||||
"integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.4.tgz",
|
||||
"integrity": "sha512-9fhTJyOb275w5RofPSl8lpr4jFowd+H4oQKJ9XTYzD1JWgxdZKE8bA6d4npuiMemkecQOcigX01FNZNCYnQBdA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz",
|
||||
"integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.4.tgz",
|
||||
"integrity": "sha512-+6kCIM5Zjvz2HwPl/udgVs07tPMIp1VU2Y0c72ezjOvSvEfAIWsUgpcSDvnC7g9NrjYR6X9bZT92mZZ90TfvXw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.4.tgz",
|
||||
"integrity": "sha512-SWuXdnsayCZL4lXoo6jn0yyAj7TTjWE4NwDVt9s7cmu6poMhtiras5c8h6Ih6Y0Zk6Z+8t/mLumvpdSPTWub2Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz",
|
||||
"integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.4.tgz",
|
||||
"integrity": "sha512-vDknMDqtMhrrroa5kyX6tuC0aRZZlQ+ipDfbXd2YGz5HeV2t8HOl/FDAd2ynhs7Ki5VooWiiZcCtxiZ4IjqZwQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.4.tgz",
|
||||
"integrity": "sha512-mCBkjRZWhvjtl/x+Bd4fQkWZT8canStKDxGrHlBiTnZmJnWygGcvBylzLVCZXka4dco5ymkWhZlLwKCGFF4ivw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==",
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.4.tgz",
|
||||
"integrity": "sha512-YMdz2phOTFF+Z66dQfGf0gmeDSi5DJzY5bpZyeg9CPBkV9QDzJ1yFRlmi/j7WWRf3hYIWrOaJj5jsfwgc8GTHQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.4.tgz",
|
||||
"integrity": "sha512-r0WKLSfFAK8ucG024v2yiLSJMedoWvk8yWqfNICX28NHDGeu3F/wBf8KG6mclghx4FsLePxJr/9N8rIj1PtCnw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz",
|
||||
"integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.4.tgz",
|
||||
"integrity": "sha512-IaizpPP2UQU3MNyPH1u0Xxbm73D+4OupL0bjo4Hm0496e2wg3zuvoAIhubkD1NGy9fXILEExPQy87mweujEatA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.4.tgz",
|
||||
"integrity": "sha512-aCM29orANR0a8wk896p6UEgIfupReupnmISz6SUwMIwTGaTI8MuKdE0OD2LvEg8ondDyZdMvnaN3bW4nFbATPA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz",
|
||||
"integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.4.tgz",
|
||||
"integrity": "sha512-0Xj1vZE3cbr/wda8d/m+UeuSL+TDpuozzdD4QaSzu/xSOMK0Su5RhIkF7KVHFQsobemUNHPLEcYllL7ZTCP/Cg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz",
|
||||
"integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.4.tgz",
|
||||
"integrity": "sha512-kM/orjpolfA5yxsx84kI6bnK47AAZuWxglGKcNmokw2yy9i5eHY5UAjcX45jemTJnfHAWo3/hOoRqEeeTdL5hw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz",
|
||||
"integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.4.tgz",
|
||||
"integrity": "sha512-cNLH4psMEsWKILW0isbpQA2OvjXLbKvnkcJFmqAptPQbtLrobiapBJVj6RoIvg6UXVp5w0wnIfd/Q56cNpF+Ew==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz",
|
||||
"integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.4.tgz",
|
||||
"integrity": "sha512-OiEa5lRhiANpv4SfwYVgQ3opYWi/QmPDC5ve21m8G9pf6ZO+aX1g2EEF1/IFaM1xPSP7mK0msTRXlPs6mIagkg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz",
|
||||
"integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.4.tgz",
|
||||
"integrity": "sha512-IKL9mewGZ5UuuX4NQlwOmxPyqielvkAPUS2s1cl6yWjjQvyN3h5JTdVFGD5Jr5xMjRC8setOfGQDVgX8V+dkjg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@@ -574,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"
|
||||
},
|
||||
@@ -597,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",
|
||||
@@ -617,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"
|
||||
}
|
||||
@@ -635,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"
|
||||
@@ -654,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"
|
||||
}
|
||||
@@ -662,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"
|
||||
},
|
||||
@@ -673,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"
|
||||
},
|
||||
@@ -686,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.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -709,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"
|
||||
}
|
||||
@@ -717,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",
|
||||
@@ -733,9 +799,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.45.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz",
|
||||
"integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==",
|
||||
"version": "4.46.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.4.tgz",
|
||||
"integrity": "sha512-YbxoxvoqNg9zAmw4+vzh1FkGAiZRK+LhnSrbSrSXMdZYsRPDWoshcSd/pldKRO6lWzv/e9TiJAVQyirYIeSIPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -747,26 +814,26 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.45.1",
|
||||
"@rollup/rollup-android-arm64": "4.45.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.45.1",
|
||||
"@rollup/rollup-darwin-x64": "4.45.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.45.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.45.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.45.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.45.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.45.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.45.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.45.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.45.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.45.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.45.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.45.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.46.4",
|
||||
"@rollup/rollup-android-arm64": "4.46.4",
|
||||
"@rollup/rollup-darwin-arm64": "4.46.4",
|
||||
"@rollup/rollup-darwin-x64": "4.46.4",
|
||||
"@rollup/rollup-freebsd-arm64": "4.46.4",
|
||||
"@rollup/rollup-freebsd-x64": "4.46.4",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.46.4",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.46.4",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.46.4",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.46.4",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.46.4",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.46.4",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.46.4",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.46.4",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.46.4",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.46.4",
|
||||
"@rollup/rollup-linux-x64-musl": "4.46.4",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.46.4",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.46.4",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.46.4",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -788,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"
|
||||
}
|
||||
@@ -803,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"
|
||||
}
|
||||
@@ -819,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"
|
||||
@@ -827,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"
|
||||
},
|
||||
@@ -845,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",
|
||||
@@ -861,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
deps/openssl_src
vendored
2
deps/openssl_src
vendored
Submodule deps/openssl_src updated: aea7aaf2ab...0893a62353
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-7YPLLUY2.js"></script>
|
||||
<script src="speedscope-HCR63FMT.js"></script>
|
||||
|
||||
|
||||
|
||||
|
6
deps/speedscope/release.txt
vendored
6
deps/speedscope/release.txt
vendored
@@ -1,3 +1,3 @@
|
||||
speedscope@1.23.0
|
||||
Sun Jul 6 20:04:28 PDT 2025
|
||||
aa9bef50789a2989746b576fff182b6f01dfce6a
|
||||
speedscope@1.23.1
|
||||
Mon Aug 11 11:43:09 PDT 2025
|
||||
0cec0f82c334aed6cf19d43cabeadcda0d95e0fc
|
||||
|
File diff suppressed because one or more lines are too long
43
deps/sqlite/sqlite3.c
vendored
43
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.3. 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
|
||||
** 3ce993b8657d6d9deda380a93cdd6404a8c8 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.3"
|
||||
#define SQLITE_VERSION_NUMBER 3050003
|
||||
#define SQLITE_SOURCE_ID "2025-07-17 13:25:10 3ce993b8657d6d9deda380a93cdd6404a8c8ba1b185b2bc423703e41ae5f2543"
|
||||
#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
|
||||
@@ -19440,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 */
|
||||
@@ -21474,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);
|
||||
@@ -115241,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
|
||||
@@ -115925,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
|
||||
@@ -153175,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
|
||||
@@ -168470,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;
|
||||
@@ -169538,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;
|
||||
|
||||
@@ -257280,7 +257307,7 @@ static void fts5SourceIdFunc(
|
||||
){
|
||||
assert( nArg==0 );
|
||||
UNUSED_PARAM2(nArg, apUnused);
|
||||
sqlite3_result_text(pCtx, "fts5: 2025-07-17 13:25:10 3ce993b8657d6d9deda380a93cdd6404a8c8ba1b185b2bc423703e41ae5f2543", -1, SQLITE_TRANSIENT);
|
||||
sqlite3_result_text(pCtx, "fts5: 2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3", -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
/*
|
||||
|
6
deps/sqlite/sqlite3.h
vendored
6
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.3"
|
||||
#define SQLITE_VERSION_NUMBER 3050003
|
||||
#define SQLITE_SOURCE_ID "2025-07-17 13:25:10 3ce993b8657d6d9deda380a93cdd6404a8c8ba1b185b2bc423703e41ae5f2543"
|
||||
#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
|
||||
|
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1750622754,
|
||||
"narHash": "sha256-kMhs+YzV4vPGfuTpD3mwzibWUE6jotw5Al2wczI0Pv8=",
|
||||
"lastModified": 1753749649,
|
||||
"narHash": "sha256-+jkEZxs7bfOKfBIk430K+tK9IvXlwzqQQnppC2ZKFj4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c7ab75210cb8cb16ddd8f290755d9558edde7ee1",
|
||||
"rev": "1f08a4df998e21f4e8be8fb6fbf61d11a1a5076a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
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
|
9
metadata/en-US/changelogs/42.txt
Normal file
9
metadata/en-US/changelogs/42.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
* Private messages interface overhaul in progress.
|
||||
* Added a loading indicator.
|
||||
* Documented the core JavaScript.
|
||||
* Fixed @-completion.
|
||||
* Covered up launch on Android with the splash screen.
|
||||
* Update:
|
||||
* CodeMirror
|
||||
* OpenSSL 3.5.2
|
||||
* speedscope 1.23.1
|
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="40"
|
||||
android:versionName="0.0.33-wip">
|
||||
android:versionCode="42"
|
||||
android:versionName="0.2025.8">
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
|
@@ -25,6 +25,7 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.Window;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.DownloadListener;
|
||||
import android.webkit.JsPromptResult;
|
||||
@@ -50,6 +51,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;
|
||||
@@ -57,6 +59,8 @@ public class TildeFriendsActivity extends Activity {
|
||||
private ValueCallback<Uri[]> upload_message;
|
||||
private final static int FILECHOOSER_RESULT = 1;
|
||||
private float touch_down_y;
|
||||
private boolean ready = false;
|
||||
private boolean loaded = false;
|
||||
|
||||
static {
|
||||
Log.w("tildefriends", "Calling system.loadLibrary().");
|
||||
@@ -67,19 +71,8 @@ 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()));
|
||||
Log.w("tildefriends", String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString()));
|
||||
Log.w("tildefriends", String.format("nativeLibraryDir is %s", getApplicationInfo().nativeLibraryDir));
|
||||
@@ -90,14 +83,13 @@ public class TildeFriendsActivity extends Activity {
|
||||
|
||||
TildeFriendsActivity activity = this;
|
||||
|
||||
Log.w("tildefriends", "Watching for changes in: " + getFilesDir().toString());
|
||||
observer = make_file_observer(getFilesDir().toString(), port_file_path);
|
||||
observer.startWatching();
|
||||
|
||||
set_status("Starting server...");
|
||||
server_thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.w("tildefriends", "Watching for changes in: " + getFilesDir().toString());
|
||||
observer = make_file_observer(getFilesDir().toString(), port_file_path);
|
||||
observer.startWatching();
|
||||
|
||||
Log.w("tildefriends", "Calling tf_server_main.");
|
||||
int result = tf_server_main(
|
||||
getFilesDir().toString(),
|
||||
@@ -109,152 +101,205 @@ public class TildeFriendsActivity extends Activity {
|
||||
});
|
||||
server_thread.start();
|
||||
|
||||
web_view.getSettings().setJavaScriptEnabled(true);
|
||||
web_view.getSettings().setDomStorageEnabled(true);
|
||||
runOnUiThread(() -> {
|
||||
web_view.getSettings().setJavaScriptEnabled(true);
|
||||
web_view.getSettings().setDomStorageEnabled(true);
|
||||
|
||||
set_database_enabled();
|
||||
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() {
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
s_activity.loaded = 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");
|
||||
|
||||
final View content = findViewById(android.R.id.content);
|
||||
content.getViewTreeObserver().addOnPreDrawListener(
|
||||
new ViewTreeObserver.OnPreDrawListener() {
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
if (s_activity.ready && s_activity.loaded) {
|
||||
content.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
create_thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
s_activity.createThread();
|
||||
}
|
||||
});
|
||||
create_thread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -353,19 +398,6 @@ public class TildeFriendsActivity extends Activity {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void set_status(String text) {
|
||||
TextView text_view = (TextView)findViewById(R.id.text);
|
||||
web_view.setVisibility(View.GONE);
|
||||
text_view.setVisibility(View.VISIBLE);
|
||||
text_view.setText(text);
|
||||
}
|
||||
|
||||
private void hide_status() {
|
||||
TextView text_view = (TextView)findViewById(R.id.text);
|
||||
web_view.setVisibility(View.VISIBLE);
|
||||
text_view.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public static void start_sandbox(int pipe_fd) {
|
||||
Log.w("tildefriends", "starting service with fd: " + pipe_fd);
|
||||
Intent intent = new Intent(s_activity, TildeFriendsSandboxService.class);
|
||||
@@ -403,7 +435,7 @@ public class TildeFriendsActivity extends Activity {
|
||||
Log.w("tildefriends", "onServiceDisconnected");
|
||||
}
|
||||
};
|
||||
s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE);
|
||||
s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND);
|
||||
}
|
||||
|
||||
public static void stop_sandbox() {
|
||||
@@ -419,14 +451,11 @@ public class TildeFriendsActivity extends Activity {
|
||||
if (port >= 0) {
|
||||
base_url = "http://127.0.0.1:" + String.valueOf(port) + "/";
|
||||
runOnUiThread(() -> {
|
||||
hide_status();
|
||||
ready = true;
|
||||
web_view.loadUrl(base_url + "login/auto");
|
||||
});
|
||||
observer.stopWatching();
|
||||
observer = null;
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
set_status("Waiting to connect...");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,11 +10,6 @@
|
||||
android:id="@+id/web"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal|center_vertical"/>
|
||||
<TextView
|
||||
android:id="@+id/refresh"
|
||||
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;
|
||||
}
|
||||
|
||||
|
@@ -177,7 +177,7 @@ static void _httpd_endpoint_app_index_file_read(tf_task_t* task, const char* pat
|
||||
char* replacement = tf_strdup("<iframe srcdoc=\"");
|
||||
size_t replacement_size = 0;
|
||||
replacement = _append_encoded(replacement, state->data, state->size, &replacement_size);
|
||||
_append_raw(replacement, &replacement_size, "\"", 1);
|
||||
replacement = _append_raw(replacement, &replacement_size, "\"", 1);
|
||||
|
||||
size_t size = 0;
|
||||
char* document = _replace(data, result, "<iframe", replacement, &size);
|
||||
|
@@ -13,13 +13,13 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.33</string>
|
||||
<string>0.2025.8</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>15</string>
|
||||
<string>16</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
@@ -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)
|
||||
{
|
||||
|
88
src/ssb.c
88
src/ssb.c
@@ -133,6 +133,15 @@ typedef struct _tf_ssb_message_added_callback_node_t
|
||||
tf_ssb_message_added_callback_node_t* next;
|
||||
} tf_ssb_message_added_callback_node_t;
|
||||
|
||||
typedef struct _tf_ssb_blob_stored_callback_node_t tf_ssb_blob_stored_callback_node_t;
|
||||
typedef struct _tf_ssb_blob_stored_callback_node_t
|
||||
{
|
||||
tf_ssb_blob_stored_callback_t* callback;
|
||||
tf_ssb_callback_cleanup_t* cleanup;
|
||||
void* user_data;
|
||||
tf_ssb_blob_stored_callback_node_t* next;
|
||||
} tf_ssb_blob_stored_callback_node_t;
|
||||
|
||||
typedef struct _tf_ssb_blob_want_added_callback_node_t tf_ssb_blob_want_added_callback_node_t;
|
||||
typedef struct _tf_ssb_blob_want_added_callback_node_t
|
||||
{
|
||||
@@ -235,6 +244,9 @@ typedef struct _tf_ssb_t
|
||||
tf_ssb_message_added_callback_node_t* message_added;
|
||||
int message_added_count;
|
||||
|
||||
tf_ssb_blob_stored_callback_node_t* blob_stored;
|
||||
int blob_stored_count;
|
||||
|
||||
tf_ssb_blob_want_added_callback_node_t* blob_want_added;
|
||||
int blob_want_added_count;
|
||||
|
||||
@@ -977,7 +989,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);
|
||||
}
|
||||
|
||||
@@ -2742,6 +2753,17 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
|
||||
}
|
||||
tf_free(node);
|
||||
}
|
||||
while (ssb->blob_stored)
|
||||
{
|
||||
tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored;
|
||||
ssb->blob_stored = node->next;
|
||||
ssb->blob_stored_count--;
|
||||
if (node->cleanup)
|
||||
{
|
||||
node->cleanup(ssb, node->user_data);
|
||||
}
|
||||
tf_free(node);
|
||||
}
|
||||
while (ssb->blob_want_added)
|
||||
{
|
||||
tf_ssb_blob_want_added_callback_node_t* node = ssb->blob_want_added;
|
||||
@@ -2784,6 +2806,16 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
|
||||
|
||||
uv_run(ssb->loop, UV_RUN_NOWAIT);
|
||||
|
||||
if (ssb->own_context)
|
||||
{
|
||||
if (!ssb->quiet)
|
||||
{
|
||||
tf_printf("closing ssb context\n");
|
||||
}
|
||||
JS_FreeContext(ssb->context);
|
||||
JS_FreeRuntime(ssb->runtime);
|
||||
ssb->own_context = false;
|
||||
}
|
||||
if (ssb->loop == &ssb->own_loop)
|
||||
{
|
||||
if (!ssb->quiet)
|
||||
@@ -2800,16 +2832,6 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
|
||||
{
|
||||
tf_printf("uv loop closed.\n");
|
||||
}
|
||||
if (ssb->own_context)
|
||||
{
|
||||
if (!ssb->quiet)
|
||||
{
|
||||
tf_printf("closing ssb context\n");
|
||||
}
|
||||
JS_FreeContext(ssb->context);
|
||||
JS_FreeRuntime(ssb->runtime);
|
||||
ssb->own_context = false;
|
||||
}
|
||||
while (ssb->broadcasts)
|
||||
{
|
||||
tf_ssb_broadcast_t* broadcast = ssb->broadcasts;
|
||||
@@ -3961,9 +3983,53 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca
|
||||
}
|
||||
}
|
||||
|
||||
void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void (*cleanup)(tf_ssb_t* ssb, void* user_data), void* user_data)
|
||||
{
|
||||
tf_ssb_blob_stored_callback_node_t* node = tf_malloc(sizeof(tf_ssb_blob_stored_callback_node_t));
|
||||
*node = (tf_ssb_blob_stored_callback_node_t) {
|
||||
.callback = callback,
|
||||
.cleanup = cleanup,
|
||||
.user_data = user_data,
|
||||
.next = ssb->blob_stored,
|
||||
};
|
||||
ssb->blob_stored = node;
|
||||
ssb->blob_stored_count++;
|
||||
}
|
||||
|
||||
void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data)
|
||||
{
|
||||
tf_ssb_blob_stored_callback_node_t** it = &ssb->blob_stored;
|
||||
while (*it)
|
||||
{
|
||||
if ((*it)->callback == callback && (*it)->user_data == user_data)
|
||||
{
|
||||
tf_ssb_blob_stored_callback_node_t* node = *it;
|
||||
*it = node->next;
|
||||
ssb->blob_stored_count--;
|
||||
if (node->cleanup)
|
||||
{
|
||||
node->cleanup(ssb, node->user_data);
|
||||
}
|
||||
tf_free(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
it = &(*it)->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tf_ssb_notify_blob_stored(tf_ssb_t* ssb, const char* id)
|
||||
{
|
||||
tf_ssb_blob_stored_callback_node_t* next = NULL;
|
||||
ssb->blobs_stored++;
|
||||
for (tf_ssb_blob_stored_callback_node_t* node = ssb->blob_stored; node; node = next)
|
||||
{
|
||||
next = node->next;
|
||||
tf_trace_begin(ssb->trace, "blob stored callback");
|
||||
node->callback(ssb, id, node->user_data);
|
||||
tf_trace_end(ssb->trace);
|
||||
}
|
||||
}
|
||||
|
||||
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_keys)
|
||||
|
@@ -903,10 +903,6 @@ void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id)
|
||||
{
|
||||
tf_printf("blob wants cache update failed: %s.\n", sqlite3_errmsg(db));
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("want: %s\n", id);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
|
25
src/ssb.h
25
src/ssb.h
@@ -680,6 +680,31 @@ void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_ca
|
||||
*/
|
||||
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* author, int32_t sequence, const char* id, JSValue message_with_keys);
|
||||
|
||||
/**
|
||||
** A callback called when a blob is added to the database.
|
||||
** @param ssb The SSB instance.
|
||||
** @param id The blob identifier.
|
||||
** @param user_data The user data.
|
||||
*/
|
||||
typedef void(tf_ssb_blob_stored_callback_t)(tf_ssb_t* ssb, const char* id, void* user_data);
|
||||
|
||||
/**
|
||||
** Register a callback called when a blob is added to the database.
|
||||
** @param ssb The SSB instance.
|
||||
** @param callback The callback function.
|
||||
** @param cleanup A function to call when the callback is removed.
|
||||
** @param user_data User data to pass to the callback.
|
||||
*/
|
||||
void tf_ssb_add_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
|
||||
|
||||
/**
|
||||
** Remove a callback registered for when a blob is added to the database.
|
||||
** @param ssb The SSB instance.
|
||||
** @param callback The callback function.
|
||||
** @param user_data User data registered with the callback.
|
||||
*/
|
||||
void tf_ssb_remove_blob_stored_callback(tf_ssb_t* ssb, tf_ssb_blob_stored_callback_t* callback, void* user_data);
|
||||
|
||||
/**
|
||||
** Record that a new blob was stored.
|
||||
** @param ssb The SSB instance.
|
||||
|
24
src/ssb.js.c
24
src/ssb.js.c
@@ -1627,6 +1627,20 @@ static void _tf_ssb_on_message_added_callback(tf_ssb_t* ssb, const char* author,
|
||||
JS_FreeValue(context, string);
|
||||
}
|
||||
|
||||
static void _tf_ssb_on_blob_stored_callback(tf_ssb_t* ssb, const char* id, void* user_data)
|
||||
{
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue callback = JS_MKPTR(JS_TAG_OBJECT, user_data);
|
||||
JSValue string = JS_NewString(context, id);
|
||||
JSValue response = JS_Call(context, callback, JS_UNDEFINED, 1, &string);
|
||||
if (tf_util_report_error(context, response))
|
||||
{
|
||||
tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, user_data);
|
||||
}
|
||||
JS_FreeValue(context, response);
|
||||
JS_FreeValue(context, string);
|
||||
}
|
||||
|
||||
static void _tf_ssb_on_blob_want_added_callback(tf_ssb_t* ssb, const char* id, void* user_data)
|
||||
{
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
@@ -1747,6 +1761,11 @@ static JSValue _tf_ssb_add_event_listener(JSContext* context, JSValueConst this_
|
||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||
tf_ssb_add_message_added_callback(ssb, _tf_ssb_on_message_added_callback, _tf_ssb_cleanup_value, ptr);
|
||||
}
|
||||
else if (strcmp(event_name, "blob") == 0)
|
||||
{
|
||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||
tf_ssb_add_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, _tf_ssb_cleanup_value, ptr);
|
||||
}
|
||||
else if (strcmp(event_name, "blob_want_added") == 0)
|
||||
{
|
||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||
@@ -1790,6 +1809,11 @@ static JSValue _tf_ssb_remove_event_listener(JSContext* context, JSValueConst th
|
||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||
tf_ssb_remove_message_added_callback(ssb, _tf_ssb_on_message_added_callback, ptr);
|
||||
}
|
||||
else if (strcmp(event_name, "blob") == 0)
|
||||
{
|
||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||
tf_ssb_remove_blob_stored_callback(ssb, _tf_ssb_on_blob_stored_callback, ptr);
|
||||
}
|
||||
else if (strcmp(event_name, "blob_want_added") == 0)
|
||||
{
|
||||
void* ptr = JS_VALUE_GET_PTR(JS_DupValue(context, callback));
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -152,6 +152,12 @@ static void _wait_stored(tf_ssb_t* ssb, bool* stored)
|
||||
}
|
||||
}
|
||||
|
||||
static void _blob_stored(tf_ssb_t* ssb, const char* id, void* user_data)
|
||||
{
|
||||
tf_printf("blob stored %s\n", id);
|
||||
*(bool*)user_data = true;
|
||||
}
|
||||
|
||||
void tf_ssb_test_ssb(const tf_test_options_t* options)
|
||||
{
|
||||
tf_printf("Testing SSB.\n");
|
||||
@@ -224,8 +230,13 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
|
||||
|
||||
char blob_id[k_id_base64_len] = { 0 };
|
||||
const char* k_blob = "Hello, blob!";
|
||||
bool blob_stored = false;
|
||||
tf_ssb_add_blob_stored_callback(ssb0, _blob_stored, NULL, &blob_stored);
|
||||
b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL);
|
||||
tf_ssb_notify_blob_stored(ssb0, blob_id);
|
||||
tf_ssb_remove_blob_stored_callback(ssb0, _blob_stored, &blob_stored);
|
||||
assert(b);
|
||||
assert(blob_stored);
|
||||
|
||||
JSContext* context0 = tf_ssb_get_context(ssb0);
|
||||
JSValue obj = JS_NewObject(context0);
|
||||
|
@@ -1,2 +1,2 @@
|
||||
#define VERSION_NUMBER "0.0.33-wip"
|
||||
#define VERSION_NUMBER "0.2025.8"
|
||||
#define VERSION_NAME "This program kills fascists."
|
||||
|
@@ -93,7 +93,7 @@ try:
|
||||
select(driver, ['#document', 'frame', '#gs_room_name'], ('send_keys', 'test room'))
|
||||
select(driver, ['#document', 'frame', '//*[@id="gs_room_name"]/following-sibling::button'], ('click',))
|
||||
select(driver, ['//button[text()="✅ Allow"]'], ('click',))
|
||||
driver.switch_to.alert.accept()
|
||||
wait.until(expected_conditions.alert_is_present()).accept()
|
||||
|
||||
select(driver, ['tf-navigation', 'shadow_root', '#identity'], ('click',))
|
||||
select(driver, ['tf-navigation', 'shadow_root', '#logout'], ('click',))
|
||||
|
Reference in New Issue
Block a user