Compare commits
36 Commits
latest_rel
...
07605933dc
| Author | SHA1 | Date | |
|---|---|---|---|
| 07605933dc | |||
| 4bdc7ec616 | |||
| 8ca64550e5 | |||
| 25dbac804c | |||
| 6ab3fd168b | |||
| 94858e2371 | |||
| 6d13502e94 | |||
| 77001e595c | |||
| 6fad20ffa3 | |||
| 00fb6c9839 | |||
| 97fcf72d63 | |||
| 5d8d02515d | |||
| 859fe1feb0 | |||
| 8f61d83f41 | |||
| 6423b3e479 | |||
| 2bc8cec8a2 | |||
| b49a6cd685 | |||
| 2885380f40 | |||
| 2ec3b6a249 | |||
| 3ef795452d | |||
| 479d87c8b8 | |||
| a56077dcc7 | |||
| d3f4587c3b | |||
| 623705b7a1 | |||
| 8f87f4751d | |||
| 2ac6dfde9d | |||
| 81ade7a400 | |||
| 63f7ff9f27 | |||
| 8a0fa17a79 | |||
| 0ead5ed967 | |||
| 53261a6fbc | |||
| c60ff86a4d | |||
| 83a0b017c5 | |||
| 3746622a11 | |||
| ccd50cf59f | |||
| 93680eb43d |
1
Doxyfile
1
Doxyfile
@@ -910,7 +910,6 @@ INPUT = README.md \
|
||||
core/app.js \
|
||||
core/client.js \
|
||||
core/core.js \
|
||||
core/http.js \
|
||||
core/tfrpc.js \
|
||||
docs/ \
|
||||
src/
|
||||
|
||||
@@ -16,9 +16,9 @@ MAKEFLAGS += --no-builtin-rules
|
||||
## LD := Linker.
|
||||
## ANDROID_SDK := Path to the Android SDK.
|
||||
|
||||
VERSION_CODE := 43
|
||||
VERSION_CODE_IOS := 17
|
||||
VERSION_NUMBER := 0.2025.9
|
||||
VERSION_CODE := 44
|
||||
VERSION_CODE_IOS := 18
|
||||
VERSION_NUMBER := 0.2025.10-wip
|
||||
VERSION_NAME := This program kills fascists.
|
||||
|
||||
IPHONEOS_VERSION_MIN=14.0
|
||||
@@ -1193,6 +1193,7 @@ out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
|
||||
@echo "[ipa] $@"
|
||||
@rm -rf $@.tmp $@
|
||||
@mkdir -p $@.tmp/Payload/tildefriends.app/
|
||||
@cp src/ios/tildefriends512.png $@.tmp/iTunesArtwork
|
||||
@cp -R $(dir $<)/* $@.tmp/Payload/tildefriends.app/
|
||||
@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./
|
||||
@rm -rf $@.tmp/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "📜",
|
||||
"previous": "&BEf0nraBdHk/+PWqx6tOSu5rheWVaxaL7orAOz3285M=.sha256"
|
||||
"previous": "&sJqeyYjHys6Z8IqqtZ2ij2ZC1E2xieu/FU/u2hE+O1U=.sha256"
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ app.setDocument(`<head>
|
||||
</head>
|
||||
<body style="color:#fff">
|
||||
${markdown(docs.docs.global)}
|
||||
<!--
|
||||
${Object.keys(docs.docs).filter(x => [...treeify('', globalThis)].indexOf(x) == -1).map(x => `<p>STALE: ${x}</p>`).join('')}
|
||||
-->
|
||||
${[...treeify('', globalThis)].map(x => document(x)).join('\n')}
|
||||
<a id="Database"></a>
|
||||
${markdown(docs.docs.database)}
|
||||
|
||||
@@ -195,51 +195,6 @@ Call a function after some delay.
|
||||
* *Number* **timeout** Number of milliseconds to wait before calling the callback function.
|
||||
`;
|
||||
|
||||
docs['parseHttpRequest()'] = `
|
||||
Parses an HTTP request.
|
||||
### Parameters
|
||||
* *Uint8Array* **request** The request data. Maybe be partial or contain extra data. The return value will
|
||||
indicate when and where it is complete.
|
||||
* *Number* **last_length** The length of the data passed on a previous attempt for the same request, or 0 initially.
|
||||
### Returns
|
||||
* *Integer* **-2** if the request is incomplete.
|
||||
* *Integer* **-1** if the request could not be parsed.
|
||||
* *Object* An object with **bytes_parsed**, **minor_version**, **path**, and **headers** fields on successful parse.
|
||||
`;
|
||||
|
||||
docs['parseHttpResponse()'] = `
|
||||
Parses an HTTP response.
|
||||
### Parameters
|
||||
* *Uint8Array* **response** The response data. Maybe be partial or contain extra data. The return value will
|
||||
indicate when and where it is complete.
|
||||
* *Number* **last_length** The length of the data passed on a previous attempt for the same response, or 0 initially.
|
||||
### Returns
|
||||
* *Integer* **-2** if the response is incomplete.
|
||||
* *Integer* **-1** if the response could not be parsed.
|
||||
* *Object* An object with **bytes_parsed**, **minor_version**, **status**, **message**, and **headers** fields on successful parse.
|
||||
`;
|
||||
|
||||
docs['sha1Digest()'] = `
|
||||
Calculates a SHA1 digest.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *String* **value** The value for which to calculate the digest.
|
||||
### Returns
|
||||
*String* The SHA1 digest of UTF-8 encoded \`value\`.
|
||||
`;
|
||||
|
||||
docs['maskBytes()'] = `
|
||||
Masks bytes for WebSocket communication.
|
||||
|
||||
Completes synchronously.
|
||||
### Parameters
|
||||
* *Uint8Array* **bytes** The byte array of data to mask.
|
||||
* *Uint32* **mask** The mask to apply.
|
||||
### Returns
|
||||
*Uint32Array* The masked bytes.
|
||||
`;
|
||||
|
||||
docs['exit()'] = `
|
||||
Exits the app. But why would you want to do that?
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "🦀",
|
||||
"previous": "&IDzjVQjtPyhesUrl45qkZFjzWl0xVlj+2M/XXQRvXO0=.sha256"
|
||||
"previous": "&Cz8miv45HPxRJGyj1dj2Z2us1rL6HTNxZyeR1pmYaWw=.sha256"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class TfMessageElement extends LitElement {
|
||||
channel: {type: String},
|
||||
channel_unread: {type: Number},
|
||||
recent_reactions: {type: Array},
|
||||
depth: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,6 +41,7 @@ class TfMessageElement extends LitElement {
|
||||
this.expanded = {};
|
||||
this.channel_unread = -1;
|
||||
this.recent_reactions = [];
|
||||
this.depth = 0;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@@ -362,12 +364,13 @@ class TfMessageElement extends LitElement {
|
||||
</button>
|
||||
`;
|
||||
} else {
|
||||
return html` <div class="w3-container w3-margin-bottom">
|
||||
${repeat(
|
||||
this.message.child_messages || [],
|
||||
(x) => x.id,
|
||||
(x) =>
|
||||
html`<tf-message
|
||||
return html` <ul class="w3-container w3-margin-bottom w3-ul w3-card-4">
|
||||
${repeat(
|
||||
this.message.child_messages || [],
|
||||
(x) => x.id,
|
||||
(x) =>
|
||||
html`<li style="padding: 0">
|
||||
<tf-message
|
||||
.message=${x}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
@@ -376,16 +379,20 @@ class TfMessageElement extends LitElement {
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
.recent_reactions=${this.recent_reactions}
|
||||
></tf-message>`
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(false)}
|
||||
>
|
||||
Collapse
|
||||
</button>`;
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>
|
||||
</li>`
|
||||
)}
|
||||
<li style="padding: 0" class="w3-margin-bottom">
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(false)}
|
||||
>
|
||||
Collapse
|
||||
</button>
|
||||
</li>
|
||||
</ul>`;
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
@@ -549,7 +556,10 @@ class TfMessageElement extends LitElement {
|
||||
}
|
||||
</style>
|
||||
<div
|
||||
class="w3-card-4 ${this.class_background()} w3-border-theme w3-margin-top"
|
||||
class="w3-card-4 ${this.class_background()} w3-border-theme ${this
|
||||
.depth == 0
|
||||
? 'w3-margin-top'
|
||||
: ''}"
|
||||
style="overflow-wrap: anywhere; display: block; max-width: 100%"
|
||||
>
|
||||
${inner}
|
||||
@@ -576,6 +586,7 @@ class TfMessageElement extends LitElement {
|
||||
channel=${self.channel}
|
||||
channel_unread=${self.channel_unread}
|
||||
.recent_reactions=${self.recent_reactions}
|
||||
depth=${self.depth + 1}
|
||||
></tf-message>
|
||||
`
|
||||
)}
|
||||
@@ -644,6 +655,35 @@ class TfMessageElement extends LitElement {
|
||||
return result;
|
||||
}
|
||||
|
||||
channel_group_by_author() {
|
||||
let sorted = this.message.messages
|
||||
.map((x) => [
|
||||
x.author,
|
||||
x.content.subscribed ? 'subscribed to' : 'unsubscribed from',
|
||||
x.content.channel,
|
||||
x,
|
||||
])
|
||||
.sort();
|
||||
let result = [];
|
||||
let last;
|
||||
let group;
|
||||
for (let row of sorted) {
|
||||
if (last && last[0] == row[0] && last[1] == row[1]) {
|
||||
group.push(row[2]);
|
||||
} else {
|
||||
if (group) {
|
||||
result.push({author: last[0], action: last[1], channels: group});
|
||||
}
|
||||
last = row;
|
||||
group = [row[2]];
|
||||
}
|
||||
}
|
||||
if (group) {
|
||||
result.push({author: last[0], action: last[1], channels: group});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
allow_unread() {
|
||||
return (
|
||||
this.channel == '@' ||
|
||||
@@ -678,6 +718,7 @@ class TfMessageElement extends LitElement {
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>`
|
||||
)}
|
||||
</div>
|
||||
@@ -719,6 +760,56 @@ class TfMessageElement extends LitElement {
|
||||
</button>
|
||||
`);
|
||||
}
|
||||
} else if (this.message?.type === 'channel_group') {
|
||||
if (this.expanded[this.expanded_key()]) {
|
||||
return this.render_frame(html`
|
||||
<div class="w3-padding">
|
||||
${this.message.messages.map(
|
||||
(x) =>
|
||||
html`<tf-message
|
||||
.message=${x}
|
||||
whoami=${this.whoami}
|
||||
.users=${this.users}
|
||||
.drafts=${this.drafts}
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>`
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(false)}
|
||||
>
|
||||
Collapse
|
||||
</button>
|
||||
`);
|
||||
} else {
|
||||
return this.render_frame(html`
|
||||
<div class="w3-padding">
|
||||
${this.channel_group_by_author().map(
|
||||
(x) => html`
|
||||
<div>
|
||||
<tf-user id=${x.author} .users=${this.users}></tf-user>
|
||||
${x.action}
|
||||
${x.channels.map(
|
||||
(y) => html` <tf-tag tag=${'#' + y}></tf-tag> `
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
class="w3-button w3-theme-d1 w3-block w3-bar"
|
||||
style="box-sizing: border-box"
|
||||
@click=${() => self.set_expanded(true)}
|
||||
>
|
||||
Expand
|
||||
</button>
|
||||
`);
|
||||
}
|
||||
} else if (this.message.placeholder) {
|
||||
return this.render_frame(
|
||||
html`<div>
|
||||
@@ -764,6 +855,7 @@ class TfMessageElement extends LitElement {
|
||||
.expanded=${this.expanded}
|
||||
channel=${this.channel}
|
||||
channel_unread=${this.channel_unread}
|
||||
depth=${this.depth + 1}
|
||||
></tf-message>
|
||||
`
|
||||
)}
|
||||
|
||||
@@ -160,11 +160,29 @@ class TfNewsElement extends LitElement {
|
||||
return recursive_sort(roots, true);
|
||||
}
|
||||
|
||||
group_following(messages) {
|
||||
group_messages(messages) {
|
||||
let result = [];
|
||||
let group = [];
|
||||
let type = undefined;
|
||||
for (let message of messages) {
|
||||
if (message?.content?.type === 'contact') {
|
||||
if (
|
||||
message?.content?.type === 'contact' ||
|
||||
message?.content?.type === 'channel'
|
||||
) {
|
||||
if (type && message.content.type !== type) {
|
||||
if (group.length == 1) {
|
||||
result.push(group[0]);
|
||||
group = [];
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
group = [];
|
||||
}
|
||||
}
|
||||
type = message.content.type;
|
||||
group.push(message);
|
||||
} else {
|
||||
if (group.length == 1) {
|
||||
@@ -173,12 +191,13 @@ class TfNewsElement extends LitElement {
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: 'contact_group',
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
group = [];
|
||||
}
|
||||
result.push(message);
|
||||
type = undefined;
|
||||
}
|
||||
}
|
||||
if (group.length == 1) {
|
||||
@@ -187,7 +206,7 @@ class TfNewsElement extends LitElement {
|
||||
} else if (group.length > 1) {
|
||||
result.push({
|
||||
rowid: Math.max(...group.map((x) => x.rowid)),
|
||||
type: 'contact_group',
|
||||
type: `${type}_group`,
|
||||
messages: group,
|
||||
});
|
||||
}
|
||||
@@ -200,7 +219,7 @@ class TfNewsElement extends LitElement {
|
||||
|
||||
load_and_render(messages) {
|
||||
let messages_by_id = this.process_messages(messages);
|
||||
let final_messages = this.group_following(
|
||||
let final_messages = this.group_messages(
|
||||
this.finalize_messages(messages_by_id)
|
||||
);
|
||||
let unread_rowid = -1;
|
||||
|
||||
@@ -256,14 +256,24 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
let t0 = new Date();
|
||||
let initial_messages = await tfrpc.rpc.query(
|
||||
`
|
||||
WITH
|
||||
channels AS (SELECT '#' || value AS value FROM json_each(?5))
|
||||
SELECT TRUE AS is_primary, messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
|
||||
FROM messages
|
||||
JOIN json_each(?) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
||||
messages.content ->> 'type' != 'vote'
|
||||
FROM messages
|
||||
JOIN json_each(?1) AS following ON messages.author = following.value
|
||||
WHERE messages.timestamp < ?3 AND (?2 IS NULL OR messages.timestamp >= ?2) AND
|
||||
messages.content ->> 'type' != 'vote' AND
|
||||
(messages.content ->> 'channel' IS NULL OR ('#' || (messages.content ->> 'channel')) NOT IN (SELECT * FROM channels)) AND
|
||||
NOT EXISTS (SELECT * FROM messages_refs JOIN channels ON messages_refs.message = messages.id AND messages_refs.ref = channels.value)
|
||||
ORDER BY timestamp DESC LIMIT ?4
|
||||
`,
|
||||
[JSON.stringify(this.following), start_time, end_time, k_max_results]
|
||||
[
|
||||
JSON.stringify(this.following),
|
||||
start_time,
|
||||
end_time,
|
||||
k_max_results,
|
||||
JSON.stringify(Object.keys(this.channels_latest)),
|
||||
]
|
||||
);
|
||||
let t1 = new Date();
|
||||
result = await this._fetch_related_messages(initial_messages);
|
||||
@@ -396,6 +406,9 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
this._private_messages =
|
||||
JSON.stringify(this.private_messages) +
|
||||
JSON.stringify(this.grouped_private_messages);
|
||||
this._channels_latest = JSON.stringify(
|
||||
Object.keys(this.channels_latest ?? {})
|
||||
);
|
||||
let now = new Date().valueOf();
|
||||
let start_time = now - 24 * 60 * 60 * 1000;
|
||||
this.start_time = start_time;
|
||||
@@ -471,7 +484,9 @@ class TfTabNewsFeedElement extends LitElement {
|
||||
this._messages_following !== JSON.stringify(this.following) ||
|
||||
this._private_messages !==
|
||||
JSON.stringify(this.private_messages) +
|
||||
JSON.stringify(this.grouped_private_messages)
|
||||
JSON.stringify(this.grouped_private_messages) ||
|
||||
this._channels_latest !==
|
||||
JSON.stringify(Object.keys(this.channels_latest))
|
||||
) {
|
||||
console.log(
|
||||
`loading messages for ${this.whoami} (following ${this.following.length})`
|
||||
|
||||
@@ -211,35 +211,6 @@ class TfTabNewsElement extends LitElement {
|
||||
>
|
||||
×
|
||||
</div>
|
||||
${this.is_administrator
|
||||
? html`
|
||||
<button
|
||||
class="w3-bar-item w3-button"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('refresh', {bubbles: true, composed: true})
|
||||
)}
|
||||
>
|
||||
<span style="display: inline-block; width: 1.8em">↻</span>
|
||||
Sync now
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-ripple"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('toggle_stay_connected', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
)}
|
||||
>
|
||||
<span style="display: inline-block; width: 1.8em"
|
||||
>${this.stay_connected ? '🔗' : '⛓️💥'}</span
|
||||
>
|
||||
${this.stay_connected ? 'Online mode' : 'Passive mode'}
|
||||
</button>
|
||||
`
|
||||
: undefined}
|
||||
${this.hash.startsWith('##') &&
|
||||
this.channels.indexOf(this.hash.substring(2)) == -1
|
||||
? html`
|
||||
@@ -334,6 +305,21 @@ class TfTabNewsElement extends LitElement {
|
||||
>
|
||||
↻ Sync now
|
||||
</button>
|
||||
<button
|
||||
class="w3-bar-item w3-button w3-ripple"
|
||||
@click=${() =>
|
||||
this.dispatchEvent(
|
||||
new Event('toggle_stay_connected', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
)}
|
||||
>
|
||||
<span style="display: inline-block; width: 1.8em"
|
||||
>${this.stay_connected ? '🔗' : '⛓️💥'}</span
|
||||
>
|
||||
${this.stay_connected ? 'Online mode' : 'Passive mode'}
|
||||
</button>
|
||||
<button
|
||||
class=${'w3-bar-item w3-button' +
|
||||
(this.peer_exchange !== false ? ' w3-hide' : '')}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"type": "tildefriends-app",
|
||||
"emoji": "👋",
|
||||
"previous": "&5NkMRSgcMqCYF3xcLOBmaytkoxfV9zx4br7JladKPTs=.sha256"
|
||||
"previous": "&ijyL/pyTwguBd9njagU7Vpc/1EyRermZuzrlq1mnzbY=.sha256"
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
src="googleplay.svg"
|
||||
style="height: 2em; margin: 0"
|
||||
/>
|
||||
Get it on Google Play (Open Testing)
|
||||
Get it on Google Play
|
||||
</a>
|
||||
<a
|
||||
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
|
||||
@@ -298,7 +298,7 @@
|
||||
|
||||
<!-- Technlology Section -->
|
||||
<div class="w3-container w3-padding-64 w3-light-grey w3-center">
|
||||
<h1 class="w3-jumbo"><b>Built the Old Fashioned Way</b></h1>
|
||||
<h1 class="w3-jumbo"><b>Built to Last</b></h1>
|
||||
<p>
|
||||
Tilde Friends strives to use only simple and widely adopted dependencies
|
||||
in order to keep it easy to build for all sorts of platforms and
|
||||
|
||||
@@ -149,7 +149,7 @@ exports.app_socket = async function socket(request, response) {
|
||||
parentApp: parentApp,
|
||||
id: blobId,
|
||||
},
|
||||
await ssb.getIdentityInfo(
|
||||
await ssb_internal.getIdentityInfo(
|
||||
credentials?.session?.name,
|
||||
packageOwner,
|
||||
packageName
|
||||
|
||||
209
core/client.js
209
core/client.js
@@ -9,18 +9,17 @@
|
||||
import {LitElement, html, css, svg} from '/lit/lit-all.min.js';
|
||||
|
||||
let cm6;
|
||||
let gSocket;
|
||||
|
||||
let gCurrentFile;
|
||||
let gFiles = {};
|
||||
let gApp = {files: {}, emoji: '📦'};
|
||||
let gEditor;
|
||||
let gOriginalInput;
|
||||
let gUnloading;
|
||||
let g_socket;
|
||||
let g_current_file;
|
||||
let g_files = {};
|
||||
let g_app = {files: {}, emoji: '📦'};
|
||||
let g_editor;
|
||||
let g_unloading;
|
||||
|
||||
let kErrorColor = '#dc322f';
|
||||
let kDisconnectColor = '#f00';
|
||||
let kStatusColor = '#fff';
|
||||
let k_color_error = '#dc322f';
|
||||
let k_color_disconnect = '#f00';
|
||||
let k_color_status = '#fff';
|
||||
/** \endcond */
|
||||
|
||||
/** Functions that server-side app code can call through the app object. */
|
||||
@@ -410,7 +409,7 @@ class TfNavigationElement extends LitElement {
|
||||
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
|
||||
<div
|
||||
class="w3-bar-item"
|
||||
style="color: ${this.status.color ?? kStatusColor}"
|
||||
style="color: ${this.status.color ?? k_color_status}"
|
||||
>
|
||||
${this.status.message}
|
||||
</div>
|
||||
@@ -429,7 +428,7 @@ class TfNavigationElement extends LitElement {
|
||||
<div class="w3-model w3-animate-top" style="position: absolute; left: 50%; transform: translate(-50%); z-index: 1">
|
||||
<dijv class="w3-modal-content w3-card-4" style="display: block; padding: 1em">
|
||||
<span id="close_error" @click=${self.clear_error} class="w3-button w3-display-topright">×</span>
|
||||
<div style="color: ${this.status.color ?? kErrorColor}"><b>ERROR:</b><p id="error" style="white-space: pre">${this.status.message}</p></div>
|
||||
<div style="color: ${this.status.color ?? k_color_error}"><b>ERROR:</b><p id="error" style="white-space: pre">${this.status.message}</p></div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -521,7 +520,7 @@ class TfFilesElement extends LitElement {
|
||||
for (let file of event.dataTransfer.files) {
|
||||
let buffer = await file.arrayBuffer();
|
||||
let text = new TextDecoder('latin1').decode(buffer);
|
||||
gFiles[file.name] = {
|
||||
g_files[file.name] = {
|
||||
doc: cm6.EditorState.create({
|
||||
doc: text,
|
||||
extensions: cm6.extensions,
|
||||
@@ -529,9 +528,9 @@ class TfFilesElement extends LitElement {
|
||||
buffer: buffer,
|
||||
isNew: true,
|
||||
};
|
||||
gCurrentFile = file.name;
|
||||
g_current_file = file.name;
|
||||
}
|
||||
openFile(gCurrentFile);
|
||||
openFile(g_current_file);
|
||||
updateFiles();
|
||||
}
|
||||
|
||||
@@ -895,11 +894,11 @@ async function edit() {
|
||||
: 'flex';
|
||||
|
||||
try {
|
||||
if (!gEditor) {
|
||||
if (!g_editor) {
|
||||
cm6 = await import('/codemirror/cm6.js');
|
||||
gEditor = cm6.TildeFriendsEditorView(document.getElementById('editor'));
|
||||
g_editor = cm6.TildeFriendsEditorView(document.getElementById('editor'));
|
||||
}
|
||||
gEditor.onDocChange = updateFiles;
|
||||
g_editor.onDocChange = updateFiles;
|
||||
await load();
|
||||
} catch (error) {
|
||||
alert(`${error.message}\n\n${error.stack}`);
|
||||
@@ -932,13 +931,13 @@ function loadFile(name, id) {
|
||||
return response.text();
|
||||
})
|
||||
.then(function (text) {
|
||||
gFiles[name].doc = cm6.EditorState.create({
|
||||
g_files[name].doc = cm6.EditorState.create({
|
||||
doc: text,
|
||||
extensions: cm6.extensions,
|
||||
});
|
||||
gFiles[name].original = gFiles[name].doc.doc.toString();
|
||||
if (!Object.values(gFiles).some((x) => !x.doc)) {
|
||||
openFile(Object.keys(gFiles).sort()[0]);
|
||||
g_files[name].original = g_files[name].doc.doc.toString();
|
||||
if (!Object.values(g_files).some((x) => !x.doc)) {
|
||||
openFile(Object.keys(g_files).sort()[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -956,31 +955,31 @@ async function load(path) {
|
||||
} else if (response.status != 404) {
|
||||
throw new Error(response.status + ' ' + response.statusText);
|
||||
}
|
||||
gFiles = {};
|
||||
g_files = {};
|
||||
let isApp = false;
|
||||
let promises = [];
|
||||
|
||||
if (json && json['type'] == 'tildefriends-app') {
|
||||
isApp = true;
|
||||
Object.keys(json['files']).forEach(function (name) {
|
||||
gFiles[name] = {};
|
||||
g_files[name] = {};
|
||||
promises.push(loadFile(name, json['files'][name]));
|
||||
});
|
||||
if (Object.keys(json['files']).length == 0) {
|
||||
document.getElementById('editPane').style.display = 'flex';
|
||||
}
|
||||
gApp = json;
|
||||
gApp.emoji = gApp.emoji || '📦';
|
||||
document.getElementById('icon').innerHTML = gApp.emoji;
|
||||
g_app = json;
|
||||
g_app.emoji = g_app.emoji || '📦';
|
||||
document.getElementById('icon').innerHTML = g_app.emoji;
|
||||
}
|
||||
if (!isApp) {
|
||||
document.getElementById('editPane').style.display = 'flex';
|
||||
let text = '// New script.\n';
|
||||
gCurrentFile = 'app.js';
|
||||
gFiles[gCurrentFile] = {
|
||||
g_current_file = 'app.js';
|
||||
g_files[g_current_file] = {
|
||||
doc: cm6.EditorState.create({doc: text, extensions: cm6.extensions}),
|
||||
};
|
||||
openFile(gCurrentFile);
|
||||
openFile(g_current_file);
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
@@ -1001,13 +1000,14 @@ function closeEditor() {
|
||||
*/
|
||||
function save(save_to) {
|
||||
document.getElementById('save').disabled = true;
|
||||
if (gCurrentFile) {
|
||||
gFiles[gCurrentFile].doc = gEditor.state;
|
||||
if (g_current_file) {
|
||||
g_files[g_current_file].doc = g_editor.state;
|
||||
if (
|
||||
!gFiles[gCurrentFile].isNew &&
|
||||
!gFiles[gCurrentFile].doc.doc.toString() == gFiles[gCurrentFile].original
|
||||
!g_files[g_current_file].isNew &&
|
||||
!g_files[g_current_file].doc.doc.toString() ==
|
||||
g_files[g_current_file].original
|
||||
) {
|
||||
delete gFiles[gCurrentFile].buffer;
|
||||
delete g_files[g_current_file].buffer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1022,8 +1022,8 @@ function save(save_to) {
|
||||
}
|
||||
|
||||
let promises = [];
|
||||
for (let name of Object.keys(gFiles)) {
|
||||
let file = gFiles[name];
|
||||
for (let name of Object.keys(g_files)) {
|
||||
let file = g_files[name];
|
||||
if (!file.isNew && file.doc.doc.toString() == file.original) {
|
||||
continue;
|
||||
}
|
||||
@@ -1065,14 +1065,14 @@ function save(save_to) {
|
||||
let app = {
|
||||
type: 'tildefriends-app',
|
||||
files: Object.fromEntries(
|
||||
Object.keys(gFiles).map((x) => [x, gFiles[x].id || gApp.files[x]])
|
||||
Object.keys(g_files).map((x) => [x, g_files[x].id || g_app.files[x]])
|
||||
),
|
||||
emoji: gApp.emoji || '📦',
|
||||
emoji: g_app.emoji || '📦',
|
||||
};
|
||||
Object.values(gFiles).forEach(function (file) {
|
||||
Object.values(g_files).forEach(function (file) {
|
||||
delete file.id;
|
||||
});
|
||||
gApp = JSON.parse(JSON.stringify(app));
|
||||
g_app = JSON.parse(JSON.stringify(app));
|
||||
|
||||
return fetch(save_path + 'save', {
|
||||
method: 'POST',
|
||||
@@ -1087,7 +1087,7 @@ function save(save_to) {
|
||||
|
||||
if (save_path != window.location.pathname) {
|
||||
alert('Saved to ' + save_path + '.');
|
||||
} else if (!gFiles['app.js']) {
|
||||
} else if (!g_files['app.js']) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
reconnect(save_path);
|
||||
@@ -1099,7 +1099,7 @@ function save(save_to) {
|
||||
})
|
||||
.finally(function () {
|
||||
document.getElementById('save').disabled = false;
|
||||
Object.values(gFiles).forEach(function (file) {
|
||||
Object.values(g_files).forEach(function (file) {
|
||||
file.original = file.doc.doc.toString();
|
||||
});
|
||||
updateFiles();
|
||||
@@ -1112,8 +1112,8 @@ function save(save_to) {
|
||||
function changeIcon() {
|
||||
let value = prompt('Enter a new app icon emoji:');
|
||||
if (value !== undefined) {
|
||||
gApp.emoji = value || '📦';
|
||||
document.getElementById('icon').innerHTML = gApp.emoji;
|
||||
g_app.emoji = value || '📦';
|
||||
document.getElementById('icon').innerHTML = g_app.emoji;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,9 +1190,12 @@ function api_postMessage(message) {
|
||||
function api_error(error) {
|
||||
if (error) {
|
||||
if (typeof error == 'string') {
|
||||
setStatusMessage('⚠️ ' + error, kErrorColor);
|
||||
setStatusMessage('⚠️ ' + error, k_color_error);
|
||||
} else {
|
||||
setStatusMessage('⚠️ ' + error.message + '\n' + error.stack, kErrorColor);
|
||||
setStatusMessage(
|
||||
'⚠️ ' + error.message + '\n' + error.stack,
|
||||
k_color_error
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log('error', error);
|
||||
@@ -1309,7 +1312,7 @@ function api_setHash(hash) {
|
||||
*/
|
||||
function _receive_websocket_message(message) {
|
||||
if (message && message.action == 'session') {
|
||||
setStatusMessage('🟢 Executing...', kStatusColor);
|
||||
setStatusMessage('🟢 Executing...', k_color_status);
|
||||
let navigation = document.getElementsByTagName('tf-navigation')[0];
|
||||
navigation.credentials = message.credentials;
|
||||
navigation.identities = message.identities;
|
||||
@@ -1409,7 +1412,7 @@ function setStatusMessage(message, color) {
|
||||
document.getElementsByTagName('tf-navigation')[0].status = {
|
||||
message: message,
|
||||
color: color,
|
||||
is_error: color == kErrorColor,
|
||||
is_error: color == k_color_error,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1419,11 +1422,11 @@ function setStatusMessage(message, color) {
|
||||
*/
|
||||
function send(value) {
|
||||
try {
|
||||
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
||||
gSocket.send(JSON.stringify(value));
|
||||
if (g_socket && g_socket.readyState == g_socket.OPEN) {
|
||||
g_socket.send(JSON.stringify(value));
|
||||
}
|
||||
} catch (error) {
|
||||
setStatusMessage('🤷 Send failed: ' + error.toString(), kErrorColor);
|
||||
setStatusMessage('🤷 Send failed: ' + error.toString(), k_color_error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1438,7 +1441,7 @@ function hashChange() {
|
||||
* Make sure the app is connected on window focus, and notify the app.
|
||||
*/
|
||||
function focus() {
|
||||
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
||||
if (g_socket && g_socket.readyState == g_socket.CLOSED) {
|
||||
connectSocket();
|
||||
} else {
|
||||
send({event: 'focus'});
|
||||
@@ -1449,9 +1452,7 @@ function focus() {
|
||||
* Notify the app of lost focus.
|
||||
*/
|
||||
function blur() {
|
||||
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
||||
send({event: 'blur'});
|
||||
}
|
||||
send({event: 'blur'});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1508,8 +1509,8 @@ function message(event) {
|
||||
* @param path The path to which the WebSocket should be connected.
|
||||
*/
|
||||
function reconnect(path) {
|
||||
let oldSocket = gSocket;
|
||||
gSocket = null;
|
||||
let oldSocket = g_socket;
|
||||
g_socket = null;
|
||||
if (oldSocket) {
|
||||
oldSocket.onopen = null;
|
||||
oldSocket.onclose = null;
|
||||
@@ -1524,24 +1525,24 @@ function reconnect(path) {
|
||||
* @param path The path to which to connect.
|
||||
*/
|
||||
function connectSocket(path) {
|
||||
if (!gSocket || gSocket.readyState != gSocket.OPEN) {
|
||||
if (gSocket) {
|
||||
gSocket.onopen = null;
|
||||
gSocket.onclose = null;
|
||||
gSocket.onmessage = null;
|
||||
gSocket.close();
|
||||
if (!g_socket || g_socket.readyState != g_socket.OPEN) {
|
||||
if (g_socket) {
|
||||
g_socket.onopen = null;
|
||||
g_socket.onclose = null;
|
||||
g_socket.onmessage = null;
|
||||
g_socket.close();
|
||||
}
|
||||
setStatusMessage('⚪ Connecting...', kStatusColor);
|
||||
gSocket = new WebSocket(
|
||||
setStatusMessage('⚪ Connecting...', k_color_status);
|
||||
g_socket = new WebSocket(
|
||||
(window.location.protocol == 'https:' ? 'wss://' : 'ws://') +
|
||||
window.location.hostname +
|
||||
(window.location.port.length ? ':' + window.location.port : '') +
|
||||
'/app/socket'
|
||||
);
|
||||
gSocket.onopen = function () {
|
||||
setStatusMessage('🟡 Authenticating...', kStatusColor);
|
||||
g_socket.onopen = function () {
|
||||
setStatusMessage('🟡 Authenticating...', k_color_status);
|
||||
let connect_path = path ?? window.location.pathname;
|
||||
gSocket.send(
|
||||
g_socket.send(
|
||||
JSON.stringify({
|
||||
action: 'hello',
|
||||
path: connect_path,
|
||||
@@ -1553,12 +1554,12 @@ function connectSocket(path) {
|
||||
})
|
||||
);
|
||||
};
|
||||
gSocket.onmessage = function (event) {
|
||||
g_socket.onmessage = function (event) {
|
||||
_receive_websocket_message(JSON.parse(event.data));
|
||||
};
|
||||
gSocket.onclose = function (event) {
|
||||
if (gUnloading) {
|
||||
setStatusMessage('⚪ Closing...', kStatusColor);
|
||||
g_socket.onclose = function (event) {
|
||||
if (g_unloading) {
|
||||
setStatusMessage('⚪ Closing...', k_color_status);
|
||||
} else {
|
||||
const k_codes = {
|
||||
1000: 'Normal closure',
|
||||
@@ -1579,7 +1580,7 @@ function connectSocket(path) {
|
||||
};
|
||||
setStatusMessage(
|
||||
'🔴 Closed: ' + (k_codes[event.code] || event.code),
|
||||
kDisconnectColor
|
||||
k_color_disconnect
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1592,24 +1593,24 @@ function connectSocket(path) {
|
||||
*/
|
||||
function openFile(name) {
|
||||
let newDoc =
|
||||
name && gFiles[name]
|
||||
? gFiles[name].doc
|
||||
name && g_files[name]
|
||||
? g_files[name].doc
|
||||
: cm6.EditorState.create({doc: '', extensions: cm6.extensions});
|
||||
let oldDoc = gEditor.state;
|
||||
gEditor.setState(newDoc);
|
||||
let oldDoc = g_editor.state;
|
||||
g_editor.setState(newDoc);
|
||||
|
||||
if (gFiles[gCurrentFile]) {
|
||||
gFiles[gCurrentFile].doc = oldDoc;
|
||||
if (g_files[g_current_file]) {
|
||||
g_files[g_current_file].doc = oldDoc;
|
||||
if (
|
||||
!gFiles[gCurrentFile].isNew &&
|
||||
gFiles[gCurrentFile].doc.doc.toString() == oldDoc.doc.toString()
|
||||
!g_files[g_current_file].isNew &&
|
||||
g_files[g_current_file].doc.doc.toString() == oldDoc.doc.toString()
|
||||
) {
|
||||
delete gFiles[gCurrentFile].buffer;
|
||||
delete g_files[g_current_file].buffer;
|
||||
}
|
||||
}
|
||||
gCurrentFile = name;
|
||||
g_current_file = name;
|
||||
updateFiles();
|
||||
gEditor.focus();
|
||||
g_editor.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1619,19 +1620,19 @@ function updateFiles() {
|
||||
let files = document.getElementsByTagName('tf-files-pane')[0];
|
||||
if (files) {
|
||||
files.files = Object.fromEntries(
|
||||
Object.keys(gFiles).map((file) => [
|
||||
Object.keys(g_files).map((file) => [
|
||||
file,
|
||||
{
|
||||
clean:
|
||||
(file == gCurrentFile
|
||||
? gEditor.state.doc.toString()
|
||||
: gFiles[file].doc.doc.toString()) == gFiles[file].original,
|
||||
(file == g_current_file
|
||||
? g_editor.state.doc.toString()
|
||||
: g_files[file].doc.doc.toString()) == g_files[file].original,
|
||||
},
|
||||
])
|
||||
);
|
||||
files.current = gCurrentFile;
|
||||
files.current = g_current_file;
|
||||
}
|
||||
gEditor.focus();
|
||||
g_editor.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1639,7 +1640,7 @@ function updateFiles() {
|
||||
* @param name The file's name.
|
||||
*/
|
||||
function makeNewFile(name) {
|
||||
gFiles[name] = {
|
||||
g_files[name] = {
|
||||
doc: cm6.EditorState.create({extensions: cm6.extensions}),
|
||||
};
|
||||
openFile(name);
|
||||
@@ -1650,7 +1651,7 @@ function makeNewFile(name) {
|
||||
*/
|
||||
function newFile() {
|
||||
let name = prompt('Name of new file:', 'file.js');
|
||||
if (name && !gFiles[name]) {
|
||||
if (name && !g_files[name]) {
|
||||
makeNewFile(name);
|
||||
}
|
||||
}
|
||||
@@ -1659,9 +1660,9 @@ function newFile() {
|
||||
* Prompt to remove a file.
|
||||
*/
|
||||
function removeFile() {
|
||||
if (confirm('Remove ' + gCurrentFile + '?')) {
|
||||
delete gFiles[gCurrentFile];
|
||||
openFile(Object.keys(gFiles)[0]);
|
||||
if (confirm('Remove ' + g_current_file + '?')) {
|
||||
delete g_files[g_current_file];
|
||||
openFile(Object.keys(g_files)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1677,13 +1678,13 @@ async function appExport() {
|
||||
`${name}.json`,
|
||||
JSON.stringify({
|
||||
type: 'tildefriends-app',
|
||||
emoji: gApp.emoji || '📦',
|
||||
emoji: g_app.emoji || '📦',
|
||||
})
|
||||
);
|
||||
for (let file of Object.keys(gFiles)) {
|
||||
for (let file of Object.keys(g_files)) {
|
||||
zip.file(
|
||||
`${name}/${file}`,
|
||||
gFiles[file].buffer ?? gFiles[file].doc.doc.toString()
|
||||
g_files[file].buffer ?? g_files[file].doc.doc.toString()
|
||||
);
|
||||
}
|
||||
let content = await zip.generateAsync({
|
||||
@@ -1802,9 +1803,9 @@ async function sourcePretty() {
|
||||
let babel = (await import('/prettier/babel.mjs')).default;
|
||||
let estree = (await import('/prettier/estree.mjs')).default;
|
||||
let prettier_html = (await import('/prettier/html.mjs')).default;
|
||||
let source = gEditor.state.doc.toString();
|
||||
let source = g_editor.state.doc.toString();
|
||||
let formatted = await prettier.format(source, {
|
||||
parser: gCurrentFile?.toLowerCase()?.endsWith('.html') ? 'html' : 'babel',
|
||||
parser: g_current_file?.toLowerCase()?.endsWith('.html') ? 'html' : 'babel',
|
||||
plugins: [babel, estree, prettier_html],
|
||||
trailingComma: 'es5',
|
||||
useTabs: true,
|
||||
@@ -1813,10 +1814,10 @@ async function sourcePretty() {
|
||||
bracketSpacing: false,
|
||||
});
|
||||
if (source !== formatted) {
|
||||
gEditor.dispatch({
|
||||
g_editor.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: gEditor.state.doc.length,
|
||||
to: g_editor.state.doc.length,
|
||||
insert: formatted,
|
||||
},
|
||||
});
|
||||
@@ -1861,7 +1862,7 @@ window.addEventListener('load', function () {
|
||||
window.addEventListener('message', message, false);
|
||||
window.addEventListener('online', connectSocket);
|
||||
window.addEventListener('beforeunload', function () {
|
||||
gUnloading = true;
|
||||
g_unloading = true;
|
||||
});
|
||||
document.getElementById('name').value = window.location.pathname;
|
||||
document
|
||||
|
||||
73
core/core.js
73
core/core.js
@@ -7,7 +7,6 @@
|
||||
|
||||
/** \cond */
|
||||
import * as app from './app.js';
|
||||
import * as http from './http.js';
|
||||
|
||||
export {invoke, getProcessBlob};
|
||||
/** \endcond */
|
||||
@@ -179,6 +178,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
process.task = new Task();
|
||||
process.packageOwner = options.packageOwner;
|
||||
process.packageName = options.packageName;
|
||||
process.url = options?.url;
|
||||
process.eventHandlers = {};
|
||||
if (!options?.script || options?.script === 'app.js') {
|
||||
process.app = new app.App();
|
||||
@@ -192,7 +192,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
});
|
||||
gProcesses[key] = process;
|
||||
process.task.onExit = function (exitCode, terminationSignal) {
|
||||
broadcastEvent('onSessionEnd', [getUser(process, process)]);
|
||||
process.task = null;
|
||||
delete gProcesses[key];
|
||||
};
|
||||
@@ -200,13 +199,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
core: {
|
||||
broadcast: broadcast.bind(process),
|
||||
user: getUser(process, process),
|
||||
users: async function () {
|
||||
try {
|
||||
return JSON.parse(await new Database('auth').get('users'));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
permissionsGranted: async function () {
|
||||
let user = process?.credentials?.session?.name;
|
||||
let settings = await loadSettings();
|
||||
@@ -236,11 +228,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
return settings.userPermissions[user];
|
||||
}
|
||||
},
|
||||
permissionsForUser: async function (user) {
|
||||
let settings = await loadSettings();
|
||||
return settings?.permissions?.[user] ?? [];
|
||||
},
|
||||
getSockets: getSockets,
|
||||
permissionTest: async function (permission) {
|
||||
let user = process?.credentials?.session?.name;
|
||||
let settings = await loadSettings();
|
||||
@@ -301,11 +288,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
throw Error(`Permission denied: ${permission}.`);
|
||||
}
|
||||
},
|
||||
app: {
|
||||
owner: options?.packageOwner,
|
||||
name: options?.packageName,
|
||||
},
|
||||
url: options?.url,
|
||||
},
|
||||
};
|
||||
process.sendIdentities = async function () {
|
||||
@@ -314,7 +296,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
{
|
||||
action: 'identities',
|
||||
},
|
||||
await ssb.getIdentityInfo(
|
||||
await ssb_internal.getIdentityInfo(
|
||||
process?.credentials?.session?.name,
|
||||
options?.packageOwner,
|
||||
options?.packageName
|
||||
@@ -357,7 +339,7 @@ async function getProcessBlob(blobId, key, options) {
|
||||
options.packageName,
|
||||
'setActiveIdentity',
|
||||
[
|
||||
await ssb.getActiveIdentity(
|
||||
await imports.ssb.getActiveIdentity(
|
||||
process.credentials?.session?.name,
|
||||
options.packageOwner,
|
||||
options.packageName
|
||||
@@ -370,19 +352,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
};
|
||||
if (process.credentials?.permissions?.administration) {
|
||||
imports.core.globalSettingsDescriptions = async function () {
|
||||
let settings = Object.assign({}, defaultGlobalSettings());
|
||||
for (let [key, value] of Object.entries(await loadSettings())) {
|
||||
if (settings[key]) {
|
||||
settings[key].value = value;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
};
|
||||
imports.core.globalSettingsGet = async function (key) {
|
||||
let settings = await loadSettings();
|
||||
return settings?.[key];
|
||||
};
|
||||
imports.core.globalSettingsSet = async function (key, value) {
|
||||
await imports.core.permissionTest('set_global_setting');
|
||||
print('Setting', key, value);
|
||||
@@ -464,26 +433,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
}
|
||||
};
|
||||
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
|
||||
imports.ssb.getActiveIdentity = () =>
|
||||
ssb.getActiveIdentity(
|
||||
process.credentials?.session?.name,
|
||||
options.packageOwner,
|
||||
options.packageName
|
||||
);
|
||||
imports.ssb.getOwnerIdentities = function () {
|
||||
if (options.packageOwner) {
|
||||
return ssb.getIdentities(options.packageOwner);
|
||||
}
|
||||
};
|
||||
imports.ssb.getIdentities = function () {
|
||||
if (
|
||||
process.credentials &&
|
||||
process.credentials.session &&
|
||||
process.credentials.session.name
|
||||
) {
|
||||
return ssb.getIdentities(process.credentials.session.name);
|
||||
}
|
||||
};
|
||||
imports.ssb.getPrivateKey = function (id) {
|
||||
if (
|
||||
process.credentials &&
|
||||
@@ -553,13 +502,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
);
|
||||
}
|
||||
};
|
||||
imports.ssb.addEventListener = undefined;
|
||||
imports.ssb.removeEventListener = undefined;
|
||||
imports.ssb.getIdentityInfo = undefined;
|
||||
imports.fetch = async function (url, options) {
|
||||
let settings = await loadSettings();
|
||||
return http.fetch(url, options, settings?.fetch_hosts);
|
||||
};
|
||||
|
||||
if (
|
||||
process.credentials &&
|
||||
@@ -663,7 +605,6 @@ async function getProcessBlob(blobId, key, options) {
|
||||
} catch (e) {
|
||||
printError(e);
|
||||
}
|
||||
broadcastEvent('onSessionBegin', [getUser(process, process)]);
|
||||
if (process.app) {
|
||||
process.app.send({action: 'ready', version: version()});
|
||||
await process.sendPermissions();
|
||||
@@ -689,19 +630,19 @@ async function getProcessBlob(blobId, key, options) {
|
||||
/**
|
||||
* SSB message added callback.
|
||||
*/
|
||||
ssb.addEventListener('message', function () {
|
||||
ssb_internal.addEventListener('message', function () {
|
||||
broadcastEvent('onMessage', [...arguments]);
|
||||
});
|
||||
|
||||
ssb.addEventListener('blob', function () {
|
||||
ssb_internal.addEventListener('blob', function () {
|
||||
broadcastEvent('onBlob', [...arguments]);
|
||||
});
|
||||
|
||||
ssb.addEventListener('broadcasts', function () {
|
||||
ssb_internal.addEventListener('broadcasts', function () {
|
||||
broadcastEvent('onBroadcastsChanged', []);
|
||||
});
|
||||
|
||||
ssb.addEventListener('connections', function () {
|
||||
ssb_internal.addEventListener('connections', function () {
|
||||
broadcastEvent('onConnectionsChanged', []);
|
||||
});
|
||||
|
||||
|
||||
121
core/http.js
121
core/http.js
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* \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.
|
||||
let match = url.match(new RegExp('(\\w+)://([^/:]+)(?::(\\d+))?(.*)'));
|
||||
return {
|
||||
protocol: match[1],
|
||||
host: match[2],
|
||||
path: match[4],
|
||||
port: match[3] ? parseInt(match[3]) : match[1] == 'http' ? 80 : 443,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
let headers = {};
|
||||
while (true) {
|
||||
let endLine = data.indexOf('\r\n');
|
||||
let line = data.substring(0, endLine);
|
||||
data = data.substring(endLine + 2);
|
||||
if (!line.length) {
|
||||
break;
|
||||
} else if (!firstLine) {
|
||||
firstLine = line;
|
||||
} else {
|
||||
let colon = line.indexOf(':');
|
||||
headers[line.substring(colon)] = line.substring(colon + 1);
|
||||
}
|
||||
}
|
||||
return {headers: headers, body: data};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
return new Promise(function (resolve, reject) {
|
||||
if ((allowed_hosts ?? []).indexOf(parsed.host) == -1) {
|
||||
throw new Error(`fetch() request to host ${parsed.host} is not allowed.`);
|
||||
}
|
||||
let socket = new Socket();
|
||||
let buffer = new Uint8Array(0);
|
||||
|
||||
return socket
|
||||
.connect(parsed.host, parsed.port)
|
||||
.then(function () {
|
||||
socket.read(function (data) {
|
||||
if (data && data.length) {
|
||||
let newBuffer = new Uint8Array(buffer.length + data.length);
|
||||
newBuffer.set(buffer, 0);
|
||||
newBuffer.set(data, buffer.length);
|
||||
buffer = newBuffer;
|
||||
} else {
|
||||
let result = parseHttpResponse(buffer);
|
||||
if (!result) {
|
||||
reject(new Exception('Parse failed.'));
|
||||
}
|
||||
if (typeof result == 'number') {
|
||||
if (result == -2) {
|
||||
reject('Incomplete request.');
|
||||
} else {
|
||||
reject('Bad request.');
|
||||
}
|
||||
} else if (typeof result == 'object') {
|
||||
resolve({
|
||||
body: buffer.slice(result.bytes_parsed),
|
||||
status: result.status,
|
||||
message: result.message,
|
||||
headers: result.headers,
|
||||
});
|
||||
} else {
|
||||
reject(new Exception('Unexpected parse result.'));
|
||||
}
|
||||
resolve(parseResponse(utf8Decode(buffer)));
|
||||
}
|
||||
});
|
||||
|
||||
if (parsed.port == 443) {
|
||||
return socket.startTls();
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
let body =
|
||||
typeof options?.body == 'string'
|
||||
? utf8Encode(options.body)
|
||||
: options.body || new Uint8Array(0);
|
||||
let headers = utf8Encode(
|
||||
`${options?.method ?? 'GET'} ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\nContent-Length: ${body.length}\r\n\r\n`
|
||||
);
|
||||
let fullRequest = new Uint8Array(headers.length + body.length);
|
||||
fullRequest.set(headers, 0);
|
||||
fullRequest.set(body, headers.length);
|
||||
socket.write(fullRequest);
|
||||
})
|
||||
.catch(function (error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** @} */
|
||||
@@ -25,14 +25,14 @@
|
||||
}:
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "tildefriends";
|
||||
version = "0.2025.8";
|
||||
version = "0.2025.9";
|
||||
|
||||
src = pkgs.fetchFromGitea {
|
||||
domain = "dev.tildefriends.net";
|
||||
owner = "cory";
|
||||
repo = "tildefriends";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-N/5lp8RL19B6Z43kRxx7c01WVJkK44a/wwNgRJPk5uI=";
|
||||
hash = "sha256-1nhsfhdOO5HIiiTMb+uROB8nDPL/UpOYm52hZ/OpPyk=";
|
||||
fetchSubmodules = true;
|
||||
};
|
||||
|
||||
|
||||
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
220
deps/codemirror_src/package-lock.json
generated
vendored
220
deps/codemirror_src/package-lock.json
generated
vendored
@@ -19,9 +19,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/autocomplete": {
|
||||
"version": "6.18.7",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.7.tgz",
|
||||
"integrity": "sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==",
|
||||
"version": "6.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
|
||||
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@@ -31,9 +31,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
|
||||
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.9.0.tgz",
|
||||
"integrity": "sha512-454TVgjhO6cMufsyyGN70rGIfJxJEjcqjBG2x2Y03Y/+Fm99d3O/Kv1QDYWuG6hvxsgmjXmBuATikIIYvERX+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
@@ -56,9 +56,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-html": {
|
||||
"version": "6.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.10.tgz",
|
||||
"integrity": "sha512-h/SceTVsN5r+WE+TVP2g3KDvNoSzbSrtZXCKo4vkKdbfT5t4otuVgngGdFukOO/rwRD2++pCxoh6xD4TEVMkQA==",
|
||||
"version": "6.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
@@ -69,7 +69,7 @@
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@lezer/css": "^1.1.0",
|
||||
"@lezer/html": "^1.3.0"
|
||||
"@lezer/html": "^1.3.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lang-javascript": {
|
||||
@@ -112,9 +112,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/lint": {
|
||||
"version": "6.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
||||
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.0.tgz",
|
||||
"integrity": "sha512-wZxW+9XDytH3SKvS8cQzMyQCaaazH8XL1EMHleHe00wVzsv7NBQKVW2yzEHrRhmM7ZOhVdItPbvlRBvMp9ej7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
@@ -155,9 +155,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.38.3",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.3.tgz",
|
||||
"integrity": "sha512-x2t87+oqwB1mduiQZ6huIghjMt4uZKFEdj66IcXw7+a5iBEvv9lh7EWDRHI7crnD4BMGpnyq/RzmCGbiEZLcvQ==",
|
||||
"version": "6.38.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.5.tgz",
|
||||
"integrity": "sha512-SFVsNAgsAoou+BjRewMqN+m9jaztB9wCWN9RSRgePqUbq8UVlvJfku5zB2KVhLPgH/h0RLk38tvd4tGeAhygnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
@@ -243,9 +243,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@lezer/html": {
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
|
||||
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
||||
"version": "1.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz",
|
||||
"integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
@@ -360,9 +360,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
|
||||
"integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
|
||||
"integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -373,9 +373,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz",
|
||||
"integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz",
|
||||
"integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -386,9 +386,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz",
|
||||
"integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
|
||||
"integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -399,9 +399,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz",
|
||||
"integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
|
||||
"integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -412,9 +412,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz",
|
||||
"integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz",
|
||||
"integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -425,9 +425,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz",
|
||||
"integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz",
|
||||
"integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -438,9 +438,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz",
|
||||
"integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz",
|
||||
"integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -451,9 +451,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz",
|
||||
"integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz",
|
||||
"integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -464,9 +464,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -477,9 +477,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz",
|
||||
"integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz",
|
||||
"integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -490,9 +490,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -503,9 +503,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -516,9 +516,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -529,9 +529,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz",
|
||||
"integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz",
|
||||
"integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -542,9 +542,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -555,9 +555,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -568,9 +568,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz",
|
||||
"integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz",
|
||||
"integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -581,9 +581,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz",
|
||||
"integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz",
|
||||
"integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -594,9 +594,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz",
|
||||
"integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz",
|
||||
"integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -607,9 +607,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz",
|
||||
"integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz",
|
||||
"integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -620,9 +620,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz",
|
||||
"integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -633,9 +633,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz",
|
||||
"integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz",
|
||||
"integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -825,9 +825,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
|
||||
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
|
||||
"version": "4.52.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
|
||||
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
@@ -840,28 +840,28 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.52.2",
|
||||
"@rollup/rollup-android-arm64": "4.52.2",
|
||||
"@rollup/rollup-darwin-arm64": "4.52.2",
|
||||
"@rollup/rollup-darwin-x64": "4.52.2",
|
||||
"@rollup/rollup-freebsd-arm64": "4.52.2",
|
||||
"@rollup/rollup-freebsd-x64": "4.52.2",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.2",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.52.2",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.52.2",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-x64-musl": "4.52.2",
|
||||
"@rollup/rollup-openharmony-arm64": "4.52.2",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.52.2",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.52.2",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.52.2",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.52.2",
|
||||
"@rollup/rollup-android-arm-eabi": "4.52.4",
|
||||
"@rollup/rollup-android-arm64": "4.52.4",
|
||||
"@rollup/rollup-darwin-arm64": "4.52.4",
|
||||
"@rollup/rollup-darwin-x64": "4.52.4",
|
||||
"@rollup/rollup-freebsd-arm64": "4.52.4",
|
||||
"@rollup/rollup-freebsd-x64": "4.52.4",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.4",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.52.4",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.52.4",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.52.4",
|
||||
"@rollup/rollup-linux-x64-musl": "4.52.4",
|
||||
"@rollup/rollup-openharmony-arm64": "4.52.4",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.52.4",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.52.4",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.52.4",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.52.4",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
|
||||
2
deps/libbacktrace
vendored
2
deps/libbacktrace
vendored
Submodule deps/libbacktrace updated: f1104f3270...2f67a3abfd
2
deps/openssl_src
vendored
2
deps/openssl_src
vendored
Submodule deps/openssl_src updated: c4da9ac23d...7b371d80d9
@@ -45,7 +45,6 @@ options:
|
||||
out_http_port_file (default: ""): File to which to write bound HTTP port.
|
||||
blob_fetch_age_seconds (default: -1): Only blobs mentioned more recently than this age will be automatically fetched.
|
||||
blob_expire_age_seconds (default: -1): Blobs older than this will be automatically deleted.
|
||||
fetch_hosts (default: ""): Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.
|
||||
http_redirect (default: ""): If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "http://example.com")
|
||||
index (default: "/~core/intro/"): Default path.
|
||||
index_map (default: ""): Mappings from hostname to redirect path, one per line, as in: "www.tildefriends.net=/~core/index/"
|
||||
|
||||
6
flake.lock
generated
6
flake.lock
generated
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1756217674,
|
||||
"narHash": "sha256-TH1SfSP523QI7kcPiNtMAEuwZR3Jdz0MCDXPs7TS8uo=",
|
||||
"lastModified": 1758589230,
|
||||
"narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4e7667a90c167f7a81d906e5a75cba4ad8bee620",
|
||||
"rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -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="43"
|
||||
android:versionName="0.2025.9">
|
||||
android:versionCode="44"
|
||||
android:versionName="0.2025.10-wip">
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
|
||||
535
src/api.js.c
535
src/api.js.c
@@ -74,7 +74,7 @@ static void _tf_api_core_apps_after_work(tf_ssb_t* ssb, int status, void* user_d
|
||||
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));
|
||||
JS_SetPropertyStr(context, result, work->apps[i].app, JS_NewString(context, work->apps[i].path ? work->apps[i].path : ""));
|
||||
}
|
||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||
tf_util_report_error(context, error);
|
||||
@@ -147,6 +147,55 @@ static JSValue _tf_api_core_apps(JSContext* context, JSValueConst this_val, int
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _users_t
|
||||
{
|
||||
const char* users;
|
||||
JSContext* context;
|
||||
JSValue promise[2];
|
||||
} users_t;
|
||||
|
||||
static void _tf_api_core_users_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
users_t* work = user_data;
|
||||
work->users = tf_ssb_db_get_property(ssb, "auth", "users");
|
||||
}
|
||||
|
||||
static void _tf_api_core_users_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
users_t* work = user_data;
|
||||
JSContext* context = work->context;
|
||||
JSValue result = JS_UNDEFINED;
|
||||
if (work->users)
|
||||
{
|
||||
result = JS_ParseJSON(context, work->users, strlen(work->users), NULL);
|
||||
tf_free((void*)work->users);
|
||||
}
|
||||
if (JS_IsUndefined(result))
|
||||
{
|
||||
result = JS_NewArray(context);
|
||||
}
|
||||
JSValue error = JS_Call(context, JS_IsArray(context, result) ? work->promise[0] : work->promise[1], JS_UNDEFINED, 1, &result);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
JS_FreeValue(context, result);
|
||||
JS_FreeValue(context, work->promise[0]);
|
||||
JS_FreeValue(context, work->promise[1]);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_api_core_users(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||
{
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
users_t* work = tf_malloc(sizeof(users_t));
|
||||
*work = (users_t) {
|
||||
.context = context,
|
||||
};
|
||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_api_core_users_work, _tf_api_core_users_after_work, work);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_api_core_register(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||
{
|
||||
JSValue event_name = argv[0];
|
||||
@@ -210,6 +259,459 @@ static JSValue _tf_api_core_unregister(JSContext* context, JSValueConst this_val
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
typedef struct _permissions_for_user_t
|
||||
{
|
||||
const char* user;
|
||||
const char* settings;
|
||||
JSContext* context;
|
||||
JSValue promise[2];
|
||||
} permissions_for_user_t;
|
||||
|
||||
static void _tf_api_core_permissions_for_user_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
permissions_for_user_t* work = user_data;
|
||||
work->settings = tf_ssb_db_get_property(ssb, "core", "settings");
|
||||
}
|
||||
|
||||
static void _tf_api_core_permissions_for_user_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
permissions_for_user_t* work = user_data;
|
||||
JSContext* context = work->context;
|
||||
JSValue result = JS_UNDEFINED;
|
||||
if (work->settings)
|
||||
{
|
||||
JSValue json = JS_ParseJSON(context, work->settings, strlen(work->settings), NULL);
|
||||
if (JS_IsObject(json))
|
||||
{
|
||||
JSValue permissions = JS_GetPropertyStr(context, json, "permissions");
|
||||
if (JS_IsObject(permissions))
|
||||
{
|
||||
result = JS_GetPropertyStr(context, permissions, work->user);
|
||||
}
|
||||
JS_FreeValue(context, permissions);
|
||||
}
|
||||
JS_FreeValue(context, json);
|
||||
tf_free((void*)work->settings);
|
||||
}
|
||||
if (JS_IsUndefined(result))
|
||||
{
|
||||
result = JS_NewArray(context);
|
||||
}
|
||||
JSValue error = JS_Call(context, JS_IsArray(context, result) ? work->promise[0] : work->promise[1], JS_UNDEFINED, 1, &result);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
JS_FreeValue(context, result);
|
||||
JS_FreeValue(context, work->promise[0]);
|
||||
JS_FreeValue(context, work->promise[1]);
|
||||
JS_FreeCString(context, work->user);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_api_core_permissionsForUser(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||
{
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
permissions_for_user_t* work = tf_malloc(sizeof(permissions_for_user_t));
|
||||
*work = (permissions_for_user_t) {
|
||||
.context = context,
|
||||
.user = JS_ToCString(context, argv[0]),
|
||||
};
|
||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_api_core_permissions_for_user_work, _tf_api_core_permissions_for_user_after_work, work);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _active_identity_work_t
|
||||
{
|
||||
JSContext* context;
|
||||
const char* name;
|
||||
const char* package_owner;
|
||||
const char* package_name;
|
||||
char identity[k_id_base64_len];
|
||||
int result;
|
||||
JSValue promise[2];
|
||||
} active_identity_work_t;
|
||||
|
||||
static void _tf_ssb_getActiveIdentity_visit(const char* identity, void* user_data)
|
||||
{
|
||||
active_identity_work_t* request = user_data;
|
||||
if (!*request->identity)
|
||||
{
|
||||
snprintf(request->identity, sizeof(request->identity), "@%s", identity);
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_getActiveIdentity_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
active_identity_work_t* request = user_data;
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
tf_ssb_db_identity_get_active(db, request->name, request->package_owner, request->package_name, request->identity, sizeof(request->identity));
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
if (!*request->identity)
|
||||
{
|
||||
tf_ssb_db_identity_visit(ssb, request->name, _tf_ssb_getActiveIdentity_visit, request);
|
||||
}
|
||||
|
||||
if (!*request->identity && tf_ssb_db_user_has_permission(ssb, NULL, request->name, "administration"))
|
||||
{
|
||||
tf_ssb_whoami(ssb, request->identity, sizeof(request->identity));
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_getActiveIdentity_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
active_identity_work_t* request = user_data;
|
||||
JSContext* context = request->context;
|
||||
if (request->result == 0)
|
||||
{
|
||||
JSValue identity = JS_NewString(context, request->identity);
|
||||
JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 1, &identity);
|
||||
JS_FreeValue(context, identity);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSValue error = JS_Call(context, request->promise[1], JS_UNDEFINED, 0, NULL);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
}
|
||||
JS_FreeValue(context, request->promise[0]);
|
||||
JS_FreeValue(context, request->promise[1]);
|
||||
tf_free((void*)request->name);
|
||||
tf_free((void*)request->package_owner);
|
||||
tf_free((void*)request->package_name);
|
||||
tf_free(request);
|
||||
}
|
||||
|
||||
static const char* _tf_ssb_get_process_credentials_session_name(JSContext* context, JSValue process)
|
||||
{
|
||||
JSValue credentials = JS_IsObject(process) ? JS_GetPropertyStr(context, process, "credentials") : JS_UNDEFINED;
|
||||
JSValue session = JS_IsObject(credentials) ? JS_GetPropertyStr(context, credentials, "session") : JS_UNDEFINED;
|
||||
JSValue name_value = JS_IsObject(session) ? JS_GetPropertyStr(context, session, "name") : JS_UNDEFINED;
|
||||
const char* name = JS_ToCString(context, name_value);
|
||||
const char* result = tf_strdup(name);
|
||||
JS_FreeCString(context, name);
|
||||
JS_FreeValue(context, name_value);
|
||||
JS_FreeValue(context, session);
|
||||
JS_FreeValue(context, credentials);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_getActiveIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||
{
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
JSValue process = data[0];
|
||||
JSValue package_owner_value = JS_GetPropertyStr(context, process, "packageOwner");
|
||||
JSValue package_name_value = JS_GetPropertyStr(context, process, "packageName");
|
||||
|
||||
const char* name = _tf_ssb_get_process_credentials_session_name(context, process);
|
||||
const char* package_owner = JS_ToCString(context, package_owner_value);
|
||||
const char* package_name = JS_ToCString(context, package_name_value);
|
||||
active_identity_work_t* work = tf_malloc(sizeof(active_identity_work_t));
|
||||
*work = (active_identity_work_t) {
|
||||
.context = context,
|
||||
.name = tf_strdup(name),
|
||||
.package_owner = tf_strdup(package_owner),
|
||||
.package_name = tf_strdup(package_name),
|
||||
};
|
||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_free((void*)name);
|
||||
JS_FreeCString(context, package_owner);
|
||||
JS_FreeCString(context, package_name);
|
||||
|
||||
JS_FreeValue(context, package_owner_value);
|
||||
JS_FreeValue(context, package_name_value);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_getActiveIdentity_work, _tf_ssb_getActiveIdentity_after_work, work);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _identities_visit_t
|
||||
{
|
||||
JSContext* context;
|
||||
JSValue promise[2];
|
||||
const char** identities;
|
||||
int count;
|
||||
char user[];
|
||||
} identities_visit_t;
|
||||
|
||||
static void _tf_ssb_getIdentities_visit(const char* identity, void* user_data)
|
||||
{
|
||||
identities_visit_t* work = user_data;
|
||||
work->identities = tf_resize_vec(work->identities, (work->count + 1) * sizeof(const char*));
|
||||
char id[k_id_base64_len];
|
||||
snprintf(id, sizeof(id), "@%s", identity);
|
||||
work->identities[work->count++] = tf_strdup(id);
|
||||
}
|
||||
|
||||
static void _tf_ssb_get_all_identities_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
tf_ssb_db_identity_visit_all(ssb, _tf_ssb_getIdentities_visit, user_data);
|
||||
}
|
||||
|
||||
static void _tf_ssb_get_identities_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
identities_visit_t* work = user_data;
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue result = JS_NewArray(context);
|
||||
for (int i = 0; i < work->count; i++)
|
||||
{
|
||||
JS_SetPropertyUint32(context, result, i, JS_NewString(context, work->identities[i]));
|
||||
tf_free((void*)work->identities[i]);
|
||||
}
|
||||
tf_free(work->identities);
|
||||
|
||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||
JS_FreeValue(context, result);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
JS_FreeValue(context, work->promise[0]);
|
||||
JS_FreeValue(context, work->promise[1]);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_getAllIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
if (ssb)
|
||||
{
|
||||
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t));
|
||||
*work = (identities_visit_t) {
|
||||
.context = context,
|
||||
};
|
||||
|
||||
result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_get_all_identities_work, _tf_ssb_get_identities_after_work, work);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void _tf_ssb_get_identities_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
identities_visit_t* work = user_data;
|
||||
if (tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
|
||||
{
|
||||
char id[k_id_base64_len] = "";
|
||||
if (tf_ssb_whoami(ssb, id, sizeof(id)))
|
||||
{
|
||||
_tf_ssb_getIdentities_visit(*id == '@' ? id + 1 : id, work);
|
||||
}
|
||||
}
|
||||
tf_ssb_db_identity_visit(ssb, work->user, _tf_ssb_getIdentities_visit, user_data);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_getIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
JSValue process = data[0];
|
||||
if (ssb)
|
||||
{
|
||||
const char* user = _tf_ssb_get_process_credentials_session_name(context, process);
|
||||
if (user)
|
||||
{
|
||||
size_t user_length = user ? strlen(user) : 0;
|
||||
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t) + user_length + 1);
|
||||
*work = (identities_visit_t) {
|
||||
.context = context,
|
||||
};
|
||||
memcpy(work->user, user, user_length + 1);
|
||||
tf_free((void*)user);
|
||||
|
||||
result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_get_identities_work, _tf_ssb_get_identities_after_work, work);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_getOwnerIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
JSValue process = data[0];
|
||||
if (ssb)
|
||||
{
|
||||
JSValue value = JS_GetPropertyStr(context, process, "packageOwner");
|
||||
const char* user = JS_ToCString(context, value);
|
||||
size_t user_length = user ? strlen(user) : 0;
|
||||
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t) + user_length + 1);
|
||||
*work = (identities_visit_t) {
|
||||
.context = context,
|
||||
};
|
||||
memcpy(work->user, user, user_length + 1);
|
||||
JS_FreeCString(context, user);
|
||||
JS_FreeValue(context, value);
|
||||
|
||||
result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_get_identities_work, _tf_ssb_get_identities_after_work, work);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _settings_descriptions_get_t
|
||||
{
|
||||
const char* settings;
|
||||
JSContext* context;
|
||||
JSValue promise[2];
|
||||
} settings_descriptions_get_t;
|
||||
|
||||
static void _tf_ssb_get_settings_descriptions_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
settings_descriptions_get_t* work = user_data;
|
||||
work->settings = tf_ssb_db_get_property(ssb, "core", "settings");
|
||||
}
|
||||
|
||||
static void _tf_ssb_get_settings_descriptions_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
settings_descriptions_get_t* work = user_data;
|
||||
JSContext* context = work->context;
|
||||
JSValue result = JS_NewObject(context);
|
||||
JSValue settings = JS_ParseJSON(context, work->settings ? work->settings : "", work->settings ? strlen(work->settings) : 0, NULL);
|
||||
const char* name;
|
||||
const char* type;
|
||||
tf_setting_kind_t kind;
|
||||
const char* description;
|
||||
for (int i = 0; tf_util_get_global_setting_by_index(i, &name, &type, &kind, &description); i++)
|
||||
{
|
||||
JSValue entry = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, entry, "type", JS_NewString(context, type));
|
||||
JS_SetPropertyStr(context, entry, "description", JS_NewString(context, description));
|
||||
switch (kind)
|
||||
{
|
||||
case k_kind_unknown:
|
||||
break;
|
||||
case k_kind_bool:
|
||||
JS_SetPropertyStr(context, entry, "default_value", JS_NewBool(context, tf_util_get_default_global_setting_bool(name)));
|
||||
break;
|
||||
case k_kind_int:
|
||||
JS_SetPropertyStr(context, entry, "default_value", JS_NewInt32(context, tf_util_get_default_global_setting_int(name)));
|
||||
break;
|
||||
case k_kind_string:
|
||||
JS_SetPropertyStr(context, entry, "default_value", JS_NewString(context, tf_util_get_default_global_setting_string(name)));
|
||||
break;
|
||||
}
|
||||
if (JS_IsObject(settings))
|
||||
{
|
||||
JS_SetPropertyStr(context, entry, "value", JS_GetPropertyStr(context, settings, name));
|
||||
}
|
||||
JS_SetPropertyStr(context, result, name, entry);
|
||||
}
|
||||
JS_FreeValue(context, settings);
|
||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
JS_FreeValue(context, result);
|
||||
JS_FreeValue(context, work->promise[0]);
|
||||
JS_FreeValue(context, work->promise[1]);
|
||||
tf_free((void*)work->settings);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_globalSettingsDescriptions(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
settings_descriptions_get_t* work = tf_malloc(sizeof(settings_descriptions_get_t));
|
||||
*work = (settings_descriptions_get_t) { .context = context };
|
||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_get_settings_descriptions_work, _tf_ssb_get_settings_descriptions_after_work, work);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _settings_get_t
|
||||
{
|
||||
const char* key;
|
||||
tf_setting_kind_t kind;
|
||||
void* value;
|
||||
JSContext* context;
|
||||
JSValue promise[2];
|
||||
} settings_get_t;
|
||||
|
||||
static void _tf_ssb_settings_get_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
settings_get_t* work = user_data;
|
||||
work->kind = tf_util_get_global_setting_kind(work->key);
|
||||
switch (work->kind)
|
||||
{
|
||||
case k_kind_unknown:
|
||||
break;
|
||||
case k_kind_bool:
|
||||
{
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
bool value = false;
|
||||
tf_ssb_db_get_global_setting_bool(db, work->key, &value);
|
||||
work->value = (void*)(intptr_t)value;
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
}
|
||||
break;
|
||||
case k_kind_int:
|
||||
{
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
int64_t value = 0;
|
||||
tf_ssb_db_get_global_setting_int64(db, work->key, &value);
|
||||
work->value = (void*)(intptr_t)value;
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
}
|
||||
break;
|
||||
case k_kind_string:
|
||||
{
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
work->value = (void*)tf_ssb_db_get_global_setting_string_alloc(db, work->key);
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_settings_get_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
settings_get_t* work = user_data;
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue result = JS_UNDEFINED;
|
||||
switch (work->kind)
|
||||
{
|
||||
case k_kind_unknown:
|
||||
break;
|
||||
case k_kind_bool:
|
||||
result = work->value ? JS_TRUE : JS_FALSE;
|
||||
break;
|
||||
case k_kind_int:
|
||||
result = JS_NewInt64(context, (int64_t)(intptr_t)work->value);
|
||||
break;
|
||||
case k_kind_string:
|
||||
result = JS_NewString(context, (const char*)work->value);
|
||||
tf_free(work->value);
|
||||
break;
|
||||
}
|
||||
|
||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
JS_FreeValue(context, result);
|
||||
JS_FreeValue(context, work->promise[0]);
|
||||
JS_FreeValue(context, work->promise[1]);
|
||||
JS_FreeCString(context, work->key);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_globalSettingsGet(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
tf_ssb_t* ssb = tf_task_get_ssb(task);
|
||||
settings_get_t* work = tf_malloc(sizeof(settings_get_t));
|
||||
*work = (settings_get_t) { .context = context, .key = JS_ToCString(context, argv[0]) };
|
||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_settings_get_work, _tf_ssb_settings_get_after_work, work);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue imports = argv[0];
|
||||
@@ -218,6 +720,37 @@ static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_va
|
||||
JS_SetPropertyStr(context, core, "apps", JS_NewCFunctionData(context, _tf_api_core_apps, 1, 0, 1, &process));
|
||||
JS_SetPropertyStr(context, core, "register", JS_NewCFunctionData(context, _tf_api_core_register, 2, 0, 1, &process));
|
||||
JS_SetPropertyStr(context, core, "unregister", JS_NewCFunctionData(context, _tf_api_core_unregister, 2, 0, 1, &process));
|
||||
|
||||
JS_SetPropertyStr(context, core, "users", JS_NewCFunctionData(context, _tf_api_core_users, 0, 0, 1, &process));
|
||||
|
||||
JS_SetPropertyStr(context, core, "permissionsForUser", JS_NewCFunctionData(context, _tf_api_core_permissionsForUser, 1, 0, 1, &process));
|
||||
|
||||
JSValue app = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, app, "owner", JS_GetPropertyStr(context, process, "packageOwner"));
|
||||
JS_SetPropertyStr(context, app, "name", JS_GetPropertyStr(context, process, "packageName"));
|
||||
JS_SetPropertyStr(context, core, "app", app);
|
||||
|
||||
JS_SetPropertyStr(context, core, "url", JS_GetPropertyStr(context, process, "url"));
|
||||
|
||||
JSValue ssb = JS_GetPropertyStr(context, imports, "ssb");
|
||||
JS_SetPropertyStr(context, ssb, "getAllIdentities", JS_NewCFunction(context, _tf_ssb_getAllIdentities, "getAllIdentities", 0));
|
||||
JS_SetPropertyStr(context, ssb, "getActiveIdentity", JS_NewCFunctionData(context, _tf_ssb_getActiveIdentity, 0, 0, 1, &process));
|
||||
JS_SetPropertyStr(context, ssb, "getIdentities", JS_NewCFunctionData(context, _tf_ssb_getIdentities, 0, 0, 1, &process));
|
||||
JS_SetPropertyStr(context, ssb, "getOwnerIdentities", JS_NewCFunctionData(context, _tf_ssb_getOwnerIdentities, 0, 0, 1, &process));
|
||||
JS_FreeValue(context, ssb);
|
||||
|
||||
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");
|
||||
JSValue permissions = JS_IsObject(credentials) ? JS_GetPropertyStr(context, credentials, "permissions") : JS_UNDEFINED;
|
||||
JSValue administration = JS_IsObject(permissions) ? JS_GetPropertyStr(context, permissions, "administration") : JS_UNDEFINED;
|
||||
if (JS_ToBool(context, administration) > 0)
|
||||
{
|
||||
JS_SetPropertyStr(context, core, "globalSettingsDescriptions", JS_NewCFunction(context, _tf_ssb_globalSettingsDescriptions, "globalSettingsDescriptions", 0));
|
||||
JS_SetPropertyStr(context, core, "globalSettingsGet", JS_NewCFunction(context, _tf_ssb_globalSettingsGet, "globalSettingsGet", 1));
|
||||
}
|
||||
JS_FreeValue(context, administration);
|
||||
JS_FreeValue(context, permissions);
|
||||
JS_FreeValue(context, credentials);
|
||||
|
||||
JS_FreeValue(context, core);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "bcrypt.js.h"
|
||||
|
||||
#include "task.h"
|
||||
|
||||
#include "ow-crypt.h"
|
||||
#include "quickjs.h"
|
||||
#include "uv.h"
|
||||
|
||||
static JSValue _crypt_hashpw(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
const char* key = JS_ToCString(context, argv[0]);
|
||||
const char* salt = JS_ToCString(context, argv[1]);
|
||||
char output[7 + 22 + 31 + 1];
|
||||
char* hash = crypt_rn(key, salt, output, sizeof(output));
|
||||
JSValue result = JS_NewString(context, hash);
|
||||
JS_FreeCString(context, key);
|
||||
JS_FreeCString(context, salt);
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _crypt_gensalt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
int length = 0;
|
||||
JS_ToInt32(context, &length, argv[0]);
|
||||
char buffer[16];
|
||||
tf_task_t* task = tf_task_get(context);
|
||||
size_t bytes = uv_random(tf_task_get_loop(task), &(uv_random_t) { 0 }, buffer, sizeof(buffer), 0, NULL) == 0 ? sizeof(buffer) : 0;
|
||||
char output[7 + 22 + 1];
|
||||
char* salt = crypt_gensalt_rn("$2b$", length, buffer, bytes, output, sizeof(output));
|
||||
JSValue result = JS_NewString(context, salt);
|
||||
return result;
|
||||
}
|
||||
|
||||
void tf_bcrypt_register(JSContext* context)
|
||||
{
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue bcrypt = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, global, "bCrypt", bcrypt);
|
||||
JS_SetPropertyStr(context, bcrypt, "hashpw", JS_NewCFunction(context, _crypt_hashpw, "hashpw", 2));
|
||||
JS_SetPropertyStr(context, bcrypt, "gensalt", JS_NewCFunction(context, _crypt_gensalt, "gensalt", 1));
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
** \defgroup bcrypt_js bCrypt
|
||||
** Exposes bcrypt to script, where it is used for hashing and verifying
|
||||
** passwords.
|
||||
** @{
|
||||
*/
|
||||
|
||||
/** A JS context. */
|
||||
typedef struct JSContext JSContext;
|
||||
|
||||
/**
|
||||
** Register the bcrypt script interface.
|
||||
** @param context The JS context.
|
||||
*/
|
||||
void tf_bcrypt_register(JSContext* context);
|
||||
|
||||
/** @} */
|
||||
36
src/http.c
36
src/http.c
@@ -20,6 +20,9 @@
|
||||
|
||||
static const int k_timeout_ms = 60000;
|
||||
|
||||
static tf_http_t** s_http_instances;
|
||||
int s_http_instance_count;
|
||||
|
||||
typedef struct _tf_http_connection_t
|
||||
{
|
||||
tf_http_t* http;
|
||||
@@ -115,6 +118,8 @@ tf_http_t* tf_http_create(uv_loop_t* loop)
|
||||
*http = (tf_http_t) {
|
||||
.loop = loop,
|
||||
};
|
||||
s_http_instances = tf_resize_vec(s_http_instances, sizeof(tf_http_t*) * (s_http_instance_count + 1));
|
||||
s_http_instances[s_http_instance_count++] = http;
|
||||
return http;
|
||||
}
|
||||
|
||||
@@ -872,6 +877,21 @@ void tf_http_destroy(tf_http_t* http)
|
||||
http->handlers_count = 0;
|
||||
|
||||
tf_free(http);
|
||||
|
||||
for (int i = 0; i < s_http_instance_count; i++)
|
||||
{
|
||||
if (s_http_instances[i] == http)
|
||||
{
|
||||
s_http_instances[i] = s_http_instances[--s_http_instance_count];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s_http_instance_count == 0)
|
||||
{
|
||||
tf_free(s_http_instances);
|
||||
s_http_instances = NULL;
|
||||
}
|
||||
tf_printf("http %p destroyed\n", http);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1215,3 +1235,19 @@ const char* tf_http_get_cookie(const char* cookie_header, const char* name)
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void tf_http_debug_destroy()
|
||||
{
|
||||
for (int i = 0; i < s_http_instance_count; i++)
|
||||
{
|
||||
tf_http_t* http = s_http_instances[i];
|
||||
tf_printf("http %p[%d]\n", http, i);
|
||||
tf_printf(" connections = %d\n", http->connections_count);
|
||||
for (int j = 0; j < http->connections_count; j++)
|
||||
{
|
||||
tf_http_connection_t* connection = http->connections[j];
|
||||
tf_printf(" connection %p[%d] %s tcp=%p timeout=%p shutdown=%p rc=%d\n", connection, j, connection->trace_name, connection->tcp.data, connection->timeout.data,
|
||||
connection->shutdown.data, connection->ref_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,4 +238,9 @@ const char* tf_http_status_text(int status);
|
||||
*/
|
||||
bool tf_http_pattern_matches(const char* pattern, const char* path);
|
||||
|
||||
/**
|
||||
** Log debug information to diagnose shutdown problems.
|
||||
*/
|
||||
void tf_http_debug_destroy();
|
||||
|
||||
/** @} */
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "http.h"
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
#include "sha1.h"
|
||||
#include "ssb.db.h"
|
||||
#include "task.h"
|
||||
#include "tls.h"
|
||||
@@ -14,8 +15,6 @@
|
||||
#include "sodium/crypto_sign.h"
|
||||
#include "sodium/utils.h"
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#define CYAN "\e[1;36m"
|
||||
#define MAGENTA "\e[1;35m"
|
||||
#define YELLOW "\e[1;33m"
|
||||
@@ -169,8 +168,13 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va
|
||||
uint8_t* key_magic = alloca(size);
|
||||
memcpy(key_magic, header_sec_websocket_key, key_length);
|
||||
memcpy(key_magic + key_length, k_magic, 36);
|
||||
|
||||
uint8_t digest[20];
|
||||
SHA1(key_magic, size, digest);
|
||||
SHA1_CTX sha1 = { 0 };
|
||||
SHA1Init(&sha1);
|
||||
SHA1Update(&sha1, key_magic, size);
|
||||
SHA1Final(digest, &sha1);
|
||||
|
||||
char key[41] = { 0 };
|
||||
tf_base64_encode(digest, sizeof(digest), key, sizeof(key));
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.2025.9</string>
|
||||
<string>0.2025.10</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>17</string>
|
||||
<string>18</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
BIN
src/ios/tildefriends512.png
Normal file
BIN
src/ios/tildefriends512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
329
src/sha1.c
Normal file
329
src/sha1.c
Normal file
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* SHA1 hash implementation and interface functions
|
||||
* Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
|
||||
*
|
||||
* This software may be distributed under the terms of the BSD license.
|
||||
* See README for more details.
|
||||
*/
|
||||
|
||||
#include "sha1.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ===== start - public domain SHA1 implementation ===== */
|
||||
|
||||
/*
|
||||
SHA-1 in C
|
||||
By Steve Reid <sreid@sea-to-sky.net>
|
||||
100% Public Domain
|
||||
|
||||
-----------------
|
||||
Modified 7/98
|
||||
By James H. Brown <jbrown@burgoyne.com>
|
||||
Still 100% Public Domain
|
||||
|
||||
Corrected a problem which generated improper hash values on 16 bit machines
|
||||
Routine SHA1Update changed from
|
||||
void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
|
||||
len)
|
||||
to
|
||||
void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
|
||||
long len)
|
||||
|
||||
The 'len' parameter was declared an int which works fine on 32 bit machines.
|
||||
However, on 16 bit machines an int is too small for the shifts being done
|
||||
against it. This caused the hash function to generate incorrect values if len
|
||||
was greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
|
||||
|
||||
Since the file IO in main() reads 16K at a time, any file 8K or larger would be
|
||||
guaranteed to generate the wrong hash (e.g. Test Vector #3, a million "a"s).
|
||||
|
||||
I also changed the declaration of variables i & j in SHA1Update to unsigned
|
||||
long from unsigned int for the same reason.
|
||||
|
||||
These changes should make no difference to any 32 bit implementations since an
|
||||
int and a long are the same size in those environments.
|
||||
|
||||
--
|
||||
I also corrected a few compiler warnings generated by Borland C.
|
||||
1. Added #include <process.h> for exit() prototype
|
||||
2. Removed unused variable 'j' in SHA1Final
|
||||
3. Changed exit(0) to return(0) at end of main.
|
||||
|
||||
ALL changes I made can be located by searching for comments containing 'JHB'
|
||||
-----------------
|
||||
Modified 8/98
|
||||
By Steve Reid <sreid@sea-to-sky.net>
|
||||
Still 100% public domain
|
||||
|
||||
1- Removed #include <process.h> and used return() instead of exit()
|
||||
2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
|
||||
3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
|
||||
|
||||
-----------------
|
||||
Modified 4/01
|
||||
By Saul Kravitz <Saul.Kravitz@celera.com>
|
||||
Still 100% PD
|
||||
Modified to run on Compaq Alpha hardware.
|
||||
|
||||
-----------------
|
||||
Modified 4/01
|
||||
By Jouni Malinen <j@w1.fi>
|
||||
Minor changes to match the coding style used in Dynamics.
|
||||
|
||||
Modified September 24, 2004
|
||||
By Jouni Malinen <j@w1.fi>
|
||||
Fixed alignment issue in SHA1Transform when SHA1HANDSOFF is defined.
|
||||
|
||||
-----------------
|
||||
Modified September 29, 2025
|
||||
By Cory McWilliams <cory@tildefriends.net>
|
||||
Adapted from
|
||||
https://web.mit.edu/freebsd/head/contrib/wpa/src/crypto/sha1-internal.c.
|
||||
Modified to build outside of FreeBSD. Updated with clang-format.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
Test Vectors (from FIPS PUB 180-1)
|
||||
"abc"
|
||||
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
|
||||
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
|
||||
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
|
||||
A million repetitions of "a"
|
||||
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
|
||||
*/
|
||||
|
||||
#define SHA1HANDSOFF
|
||||
|
||||
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
||||
|
||||
/* blk0() and blk() perform the initial expand. */
|
||||
/* I got the idea of expanding during the round function from SSLeay */
|
||||
#ifndef WORDS_BIGENDIAN
|
||||
#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF))
|
||||
#else
|
||||
#define blk0(i) block->l[i]
|
||||
#endif
|
||||
#define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
|
||||
|
||||
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
|
||||
#define R0(v, w, x, y, z, i) \
|
||||
z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
#define R1(v, w, x, y, z, i) \
|
||||
z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
#define R2(v, w, x, y, z, i) \
|
||||
z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
#define R3(v, w, x, y, z, i) \
|
||||
z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
#define R4(v, w, x, y, z, i) \
|
||||
z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
|
||||
w = rol(w, 30);
|
||||
|
||||
#ifdef VERBOSE /* SAK */
|
||||
void SHAPrintContext(SHA1_CTX* context, char* msg)
|
||||
{
|
||||
printf("%s (%d,%d) %x %x %x %x %x\n", msg, context->count[0], context->count[1], context->state[0], context->state[1], context->state[2], context->state[3], context->state[4]);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Hash a single 512-bit block. This is the core of the algorithm. */
|
||||
|
||||
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
|
||||
{
|
||||
uint32_t a, b, c, d, e;
|
||||
typedef union
|
||||
{
|
||||
unsigned char c[64];
|
||||
uint32_t l[16];
|
||||
} CHAR64LONG16;
|
||||
CHAR64LONG16* block;
|
||||
#ifdef SHA1HANDSOFF
|
||||
CHAR64LONG16 workspace;
|
||||
block = &workspace;
|
||||
memcpy(block, buffer, 64);
|
||||
#else
|
||||
block = (CHAR64LONG16*)buffer;
|
||||
#endif
|
||||
/* Copy context->state[] to working vars */
|
||||
a = state[0];
|
||||
b = state[1];
|
||||
c = state[2];
|
||||
d = state[3];
|
||||
e = state[4];
|
||||
/* 4 rounds of 20 operations each. Loop unrolled. */
|
||||
R0(a, b, c, d, e, 0);
|
||||
R0(e, a, b, c, d, 1);
|
||||
R0(d, e, a, b, c, 2);
|
||||
R0(c, d, e, a, b, 3);
|
||||
R0(b, c, d, e, a, 4);
|
||||
R0(a, b, c, d, e, 5);
|
||||
R0(e, a, b, c, d, 6);
|
||||
R0(d, e, a, b, c, 7);
|
||||
R0(c, d, e, a, b, 8);
|
||||
R0(b, c, d, e, a, 9);
|
||||
R0(a, b, c, d, e, 10);
|
||||
R0(e, a, b, c, d, 11);
|
||||
R0(d, e, a, b, c, 12);
|
||||
R0(c, d, e, a, b, 13);
|
||||
R0(b, c, d, e, a, 14);
|
||||
R0(a, b, c, d, e, 15);
|
||||
R1(e, a, b, c, d, 16);
|
||||
R1(d, e, a, b, c, 17);
|
||||
R1(c, d, e, a, b, 18);
|
||||
R1(b, c, d, e, a, 19);
|
||||
R2(a, b, c, d, e, 20);
|
||||
R2(e, a, b, c, d, 21);
|
||||
R2(d, e, a, b, c, 22);
|
||||
R2(c, d, e, a, b, 23);
|
||||
R2(b, c, d, e, a, 24);
|
||||
R2(a, b, c, d, e, 25);
|
||||
R2(e, a, b, c, d, 26);
|
||||
R2(d, e, a, b, c, 27);
|
||||
R2(c, d, e, a, b, 28);
|
||||
R2(b, c, d, e, a, 29);
|
||||
R2(a, b, c, d, e, 30);
|
||||
R2(e, a, b, c, d, 31);
|
||||
R2(d, e, a, b, c, 32);
|
||||
R2(c, d, e, a, b, 33);
|
||||
R2(b, c, d, e, a, 34);
|
||||
R2(a, b, c, d, e, 35);
|
||||
R2(e, a, b, c, d, 36);
|
||||
R2(d, e, a, b, c, 37);
|
||||
R2(c, d, e, a, b, 38);
|
||||
R2(b, c, d, e, a, 39);
|
||||
R3(a, b, c, d, e, 40);
|
||||
R3(e, a, b, c, d, 41);
|
||||
R3(d, e, a, b, c, 42);
|
||||
R3(c, d, e, a, b, 43);
|
||||
R3(b, c, d, e, a, 44);
|
||||
R3(a, b, c, d, e, 45);
|
||||
R3(e, a, b, c, d, 46);
|
||||
R3(d, e, a, b, c, 47);
|
||||
R3(c, d, e, a, b, 48);
|
||||
R3(b, c, d, e, a, 49);
|
||||
R3(a, b, c, d, e, 50);
|
||||
R3(e, a, b, c, d, 51);
|
||||
R3(d, e, a, b, c, 52);
|
||||
R3(c, d, e, a, b, 53);
|
||||
R3(b, c, d, e, a, 54);
|
||||
R3(a, b, c, d, e, 55);
|
||||
R3(e, a, b, c, d, 56);
|
||||
R3(d, e, a, b, c, 57);
|
||||
R3(c, d, e, a, b, 58);
|
||||
R3(b, c, d, e, a, 59);
|
||||
R4(a, b, c, d, e, 60);
|
||||
R4(e, a, b, c, d, 61);
|
||||
R4(d, e, a, b, c, 62);
|
||||
R4(c, d, e, a, b, 63);
|
||||
R4(b, c, d, e, a, 64);
|
||||
R4(a, b, c, d, e, 65);
|
||||
R4(e, a, b, c, d, 66);
|
||||
R4(d, e, a, b, c, 67);
|
||||
R4(c, d, e, a, b, 68);
|
||||
R4(b, c, d, e, a, 69);
|
||||
R4(a, b, c, d, e, 70);
|
||||
R4(e, a, b, c, d, 71);
|
||||
R4(d, e, a, b, c, 72);
|
||||
R4(c, d, e, a, b, 73);
|
||||
R4(b, c, d, e, a, 74);
|
||||
R4(a, b, c, d, e, 75);
|
||||
R4(e, a, b, c, d, 76);
|
||||
R4(d, e, a, b, c, 77);
|
||||
R4(c, d, e, a, b, 78);
|
||||
R4(b, c, d, e, a, 79);
|
||||
/* Add the working vars back into context.state[] */
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
state[4] += e;
|
||||
/* Wipe variables */
|
||||
a = b = c = d = e = 0;
|
||||
#ifdef SHA1HANDSOFF
|
||||
memset(block, 0, 64);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* SHA1Init - Initialize new context */
|
||||
|
||||
void SHA1Init(SHA1_CTX* context)
|
||||
{
|
||||
/* SHA1 initialization constants */
|
||||
context->state[0] = 0x67452301;
|
||||
context->state[1] = 0xEFCDAB89;
|
||||
context->state[2] = 0x98BADCFE;
|
||||
context->state[3] = 0x10325476;
|
||||
context->state[4] = 0xC3D2E1F0;
|
||||
context->count[0] = context->count[1] = 0;
|
||||
}
|
||||
|
||||
/* Run your data through this. */
|
||||
|
||||
void SHA1Update(SHA1_CTX* context, const void* _data, uint32_t len)
|
||||
{
|
||||
uint32_t i, j;
|
||||
const unsigned char* data = _data;
|
||||
|
||||
#ifdef VERBOSE
|
||||
SHAPrintContext(context, "before");
|
||||
#endif
|
||||
j = (context->count[0] >> 3) & 63;
|
||||
if ((context->count[0] += len << 3) < (len << 3))
|
||||
context->count[1]++;
|
||||
context->count[1] += (len >> 29);
|
||||
if ((j + len) > 63)
|
||||
{
|
||||
memcpy(&context->buffer[j], data, (i = 64 - j));
|
||||
SHA1Transform(context->state, context->buffer);
|
||||
for (; i + 63 < len; i += 64)
|
||||
{
|
||||
SHA1Transform(context->state, &data[i]);
|
||||
}
|
||||
j = 0;
|
||||
}
|
||||
else
|
||||
i = 0;
|
||||
memcpy(&context->buffer[j], &data[i], len - i);
|
||||
#ifdef VERBOSE
|
||||
SHAPrintContext(context, "after ");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Add padding and return the message digest. */
|
||||
|
||||
void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
|
||||
{
|
||||
uint32_t i;
|
||||
unsigned char finalcount[8];
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
/* Endian independent */
|
||||
finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255);
|
||||
}
|
||||
SHA1Update(context, (unsigned char*)"\200", 1);
|
||||
while ((context->count[0] & 504) != 448)
|
||||
{
|
||||
SHA1Update(context, (unsigned char*)"\0", 1);
|
||||
}
|
||||
/* Should cause a SHA1Transform() */
|
||||
SHA1Update(context, finalcount, 8);
|
||||
for (i = 0; i < 20; i++)
|
||||
{
|
||||
digest[i] = (unsigned char)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
|
||||
}
|
||||
/* Wipe variables */
|
||||
i = 0;
|
||||
memset(context->buffer, 0, 64);
|
||||
memset(context->state, 0, 20);
|
||||
memset(context->count, 0, 8);
|
||||
memset(finalcount, 0, 8);
|
||||
}
|
||||
|
||||
/* ===== end - public domain SHA1 implementation ===== */
|
||||
71
src/sha1.h
Normal file
71
src/sha1.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* SHA1 internal definitions
|
||||
* Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
|
||||
*
|
||||
* This software may be distributed under the terms of the BSD license.
|
||||
* See README for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
** \defgroup sha1 SHA1
|
||||
** SHA1 API.
|
||||
** Adapted from
|
||||
** https://web.mit.edu/freebsd/head/contrib/wpa/src/crypto/sha1_i.h by Cory
|
||||
** McWilliams 2025-09-28.
|
||||
** @{
|
||||
*/
|
||||
|
||||
#ifndef SHA1_I_H
|
||||
#define SHA1_I_H
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
/**
|
||||
** SHA1 context struct.
|
||||
*/
|
||||
struct SHA1Context
|
||||
{
|
||||
/** SHA1 state. */
|
||||
uint32_t state[5];
|
||||
/** SHA1 count. */
|
||||
uint32_t count[2];
|
||||
/** SHA1 buffer. */
|
||||
unsigned char buffer[64];
|
||||
};
|
||||
|
||||
/**
|
||||
** SHA1 context.
|
||||
*/
|
||||
typedef struct SHA1Context SHA1_CTX;
|
||||
|
||||
/**
|
||||
** Initialize a SHA1 context.
|
||||
** @param context The context.
|
||||
*/
|
||||
void SHA1Init(struct SHA1Context* context);
|
||||
|
||||
/**
|
||||
** Calculate an ongoing hash for a block of data.
|
||||
** @param context The SHA1 context.
|
||||
** @param data The data to hash.
|
||||
** @param len The length of data.
|
||||
*/
|
||||
void SHA1Update(struct SHA1Context* context, const void* data, uint32_t len);
|
||||
|
||||
/**
|
||||
** Calculate the final hash digest.
|
||||
** @param digest Populated with the digest.
|
||||
** @param context The SHA1 context.
|
||||
*/
|
||||
void SHA1Final(unsigned char digest[20], struct SHA1Context* context);
|
||||
|
||||
/**
|
||||
** Perform a SHA1 transformation.
|
||||
** @param state The SHA1 state.
|
||||
** @param buffer The data.
|
||||
*/
|
||||
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
|
||||
|
||||
#endif /* SHA1_I_H */
|
||||
|
||||
/** @} */
|
||||
1165
src/socket.js.c
1165
src/socket.js.c
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
** \defgroup socket_js Socket Interface
|
||||
** Exposes network sockets to script.
|
||||
** @{
|
||||
*/
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
/**
|
||||
** Register the socket script interface.
|
||||
** @param context The JS context.
|
||||
** @return The Socket constructor.
|
||||
*/
|
||||
JSValue tf_socket_register(JSContext* context);
|
||||
|
||||
/**
|
||||
** Get the number of active socket objects.
|
||||
** @return The count.
|
||||
*/
|
||||
int tf_socket_get_count();
|
||||
|
||||
/**
|
||||
** Get the number of connected socket objects.
|
||||
** @return the count.
|
||||
*/
|
||||
int tf_socket_get_open_count();
|
||||
|
||||
/** @} */
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ssb.h"
|
||||
|
||||
#include "http.h"
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
#include "ssb.connections.h"
|
||||
@@ -2711,6 +2712,7 @@ void tf_ssb_destroy(tf_ssb_t* ssb)
|
||||
tf_printf("--\n");
|
||||
uv_print_all_handles(ssb->loop, stdout);
|
||||
}
|
||||
tf_http_debug_destroy();
|
||||
uv_run(ssb->loop, UV_RUN_ONCE);
|
||||
}
|
||||
|
||||
|
||||
26
src/ssb.db.c
26
src/ssb.db.c
@@ -2465,6 +2465,32 @@ bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* ou
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* tf_ssb_db_get_global_setting_string_alloc(sqlite3* db, const char* name)
|
||||
{
|
||||
const char* result = NULL;
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare_v2(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK)
|
||||
{
|
||||
if (sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_type(statement, 0) != SQLITE_NULL)
|
||||
{
|
||||
result = tf_strdup((const char*)sqlite3_column_text(statement, 0));
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
else
|
||||
{
|
||||
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
|
||||
}
|
||||
if (!result)
|
||||
{
|
||||
result = tf_strdup(tf_util_get_default_global_setting_string(name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool tf_ssb_db_set_global_setting_from_string(sqlite3* db, const char* name, char* value)
|
||||
{
|
||||
tf_setting_kind_t kind = tf_util_get_global_setting_kind(name);
|
||||
|
||||
@@ -500,6 +500,14 @@ bool tf_ssb_db_get_global_setting_int64(sqlite3* db, const char* name, int64_t*
|
||||
*/
|
||||
bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* out_value, size_t size);
|
||||
|
||||
/**
|
||||
** Get a string global setting value.
|
||||
** @param db The database.
|
||||
** @param name The setting name.
|
||||
** @return The setting if found.
|
||||
*/
|
||||
const char* tf_ssb_db_get_global_setting_string_alloc(sqlite3* db, const char* name);
|
||||
|
||||
/**
|
||||
** Set a global setting from a string representation of its value.
|
||||
** @param db The database.
|
||||
|
||||
195
src/ssb.js.c
195
src/ssb.js.c
@@ -363,85 +363,6 @@ static JSValue _tf_ssb_swap_with_server_identity(JSContext* context, JSValueCons
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _identities_visit_t
|
||||
{
|
||||
JSContext* context;
|
||||
JSValue promise[2];
|
||||
const char** identities;
|
||||
int count;
|
||||
char user[];
|
||||
} identities_visit_t;
|
||||
|
||||
static void _tf_ssb_getIdentities_visit(const char* identity, void* user_data)
|
||||
{
|
||||
identities_visit_t* work = user_data;
|
||||
work->identities = tf_resize_vec(work->identities, (work->count + 1) * sizeof(const char*));
|
||||
char id[k_id_base64_len];
|
||||
snprintf(id, sizeof(id), "@%s", identity);
|
||||
work->identities[work->count++] = tf_strdup(id);
|
||||
}
|
||||
|
||||
static void _tf_ssb_get_identities_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
identities_visit_t* work = user_data;
|
||||
if (tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
|
||||
{
|
||||
char id[k_id_base64_len] = "";
|
||||
if (tf_ssb_whoami(ssb, id, sizeof(id)))
|
||||
{
|
||||
_tf_ssb_getIdentities_visit(*id == '@' ? id + 1 : id, work);
|
||||
}
|
||||
}
|
||||
tf_ssb_db_identity_visit(ssb, work->user, _tf_ssb_getIdentities_visit, user_data);
|
||||
}
|
||||
|
||||
static void _tf_ssb_get_all_identities_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
tf_ssb_db_identity_visit_all(ssb, _tf_ssb_getIdentities_visit, user_data);
|
||||
}
|
||||
|
||||
static void _tf_ssb_get_identities_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
identities_visit_t* work = user_data;
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue result = JS_NewArray(context);
|
||||
for (int i = 0; i < work->count; i++)
|
||||
{
|
||||
JS_SetPropertyUint32(context, result, i, JS_NewString(context, work->identities[i]));
|
||||
tf_free((void*)work->identities[i]);
|
||||
}
|
||||
tf_free(work->identities);
|
||||
|
||||
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
|
||||
JS_FreeValue(context, result);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
JS_FreeValue(context, work->promise[0]);
|
||||
JS_FreeValue(context, work->promise[1]);
|
||||
tf_free(work);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_getIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb)
|
||||
{
|
||||
size_t user_length = 0;
|
||||
const char* user = JS_ToCStringLen(context, &user_length, argv[0]);
|
||||
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t) + user_length + 1);
|
||||
*work = (identities_visit_t) {
|
||||
.context = context,
|
||||
};
|
||||
memcpy(work->user, user, user_length + 1);
|
||||
JS_FreeCString(context, user);
|
||||
|
||||
result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_get_identities_work, _tf_ssb_get_identities_after_work, work);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _get_private_key_t
|
||||
{
|
||||
JSContext* context;
|
||||
@@ -513,109 +434,6 @@ static JSValue _tf_ssb_getServerIdentity(JSContext* context, JSValueConst this_v
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_getAllIdentities(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb)
|
||||
{
|
||||
identities_visit_t* work = tf_malloc(sizeof(identities_visit_t));
|
||||
*work = (identities_visit_t) {
|
||||
.context = context,
|
||||
};
|
||||
|
||||
result = JS_NewPromiseCapability(context, work->promise);
|
||||
tf_ssb_run_work(ssb, _tf_ssb_get_all_identities_work, _tf_ssb_get_identities_after_work, work);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _active_identity_work_t
|
||||
{
|
||||
JSContext* context;
|
||||
const char* name;
|
||||
const char* package_owner;
|
||||
const char* package_name;
|
||||
char identity[k_id_base64_len];
|
||||
int result;
|
||||
JSValue promise[2];
|
||||
} active_identity_work_t;
|
||||
|
||||
static void _tf_ssb_getActiveIdentity_visit(const char* identity, void* user_data)
|
||||
{
|
||||
active_identity_work_t* request = user_data;
|
||||
if (!*request->identity)
|
||||
{
|
||||
snprintf(request->identity, sizeof(request->identity), "@%s", identity);
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_getActiveIdentity_work(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
active_identity_work_t* request = user_data;
|
||||
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
|
||||
tf_ssb_db_identity_get_active(db, request->name, request->package_owner, request->package_name, request->identity, sizeof(request->identity));
|
||||
tf_ssb_release_db_reader(ssb, db);
|
||||
|
||||
if (!*request->identity)
|
||||
{
|
||||
tf_ssb_db_identity_visit(ssb, request->name, _tf_ssb_getActiveIdentity_visit, request);
|
||||
}
|
||||
|
||||
if (!*request->identity && tf_ssb_db_user_has_permission(ssb, NULL, request->name, "administration"))
|
||||
{
|
||||
tf_ssb_whoami(ssb, request->identity, sizeof(request->identity));
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_getActiveIdentity_after_work(tf_ssb_t* ssb, int status, void* user_data)
|
||||
{
|
||||
active_identity_work_t* request = user_data;
|
||||
JSContext* context = request->context;
|
||||
if (request->result == 0)
|
||||
{
|
||||
JSValue identity = JS_NewString(context, request->identity);
|
||||
JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 1, &identity);
|
||||
JS_FreeValue(context, identity);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSValue error = JS_Call(context, request->promise[1], JS_UNDEFINED, 0, NULL);
|
||||
tf_util_report_error(context, error);
|
||||
JS_FreeValue(context, error);
|
||||
}
|
||||
JS_FreeValue(context, request->promise[0]);
|
||||
JS_FreeValue(context, request->promise[1]);
|
||||
tf_free((void*)request->name);
|
||||
tf_free((void*)request->package_owner);
|
||||
tf_free((void*)request->package_name);
|
||||
tf_free(request);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_getActiveIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
const char* name = JS_ToCString(context, argv[0]);
|
||||
const char* package_owner = JS_ToCString(context, argv[1]);
|
||||
const char* package_name = JS_ToCString(context, argv[2]);
|
||||
active_identity_work_t* work = tf_malloc(sizeof(active_identity_work_t));
|
||||
*work = (active_identity_work_t) {
|
||||
.context = context,
|
||||
.name = tf_strdup(name),
|
||||
.package_owner = tf_strdup(package_owner),
|
||||
.package_name = tf_strdup(package_name),
|
||||
};
|
||||
JSValue result = JS_NewPromiseCapability(context, work->promise);
|
||||
JS_FreeCString(context, name);
|
||||
JS_FreeCString(context, package_owner);
|
||||
JS_FreeCString(context, package_name);
|
||||
|
||||
tf_ssb_run_work(ssb, _tf_ssb_getActiveIdentity_work, _tf_ssb_getActiveIdentity_after_work, work);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct _identity_info_work_t
|
||||
{
|
||||
JSContext* context;
|
||||
@@ -2370,12 +2188,15 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
|
||||
JS_SetPropertyStr(context, global, "ssb", object);
|
||||
JS_SetOpaque(object, ssb);
|
||||
|
||||
JSValue object_internal = JS_NewObjectClass(context, _tf_ssb_classId);
|
||||
JS_SetPropertyStr(context, global, "ssb_internal", object_internal);
|
||||
JS_SetOpaque(object_internal, ssb);
|
||||
|
||||
/* Requires an identity. */
|
||||
JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1));
|
||||
JS_SetPropertyStr(context, object, "addIdentity", JS_NewCFunction(context, _tf_ssb_addIdentity, "addIdentity", 2));
|
||||
JS_SetPropertyStr(context, object, "deleteIdentity", JS_NewCFunction(context, _tf_ssb_deleteIdentity, "deleteIdentity", 2));
|
||||
JS_SetPropertyStr(context, object, "swapWithServerIdentity", JS_NewCFunction(context, _tf_ssb_swap_with_server_identity, "swapWithServerIdentity", 2));
|
||||
JS_SetPropertyStr(context, object, "getIdentities", JS_NewCFunction(context, _tf_ssb_getIdentities, "getIdentities", 1));
|
||||
JS_SetPropertyStr(context, object, "getPrivateKey", JS_NewCFunction(context, _tf_ssb_getPrivateKey, "getPrivateKey", 2));
|
||||
JS_SetPropertyStr(context, object, "privateMessageEncrypt", JS_NewCFunction(context, _tf_ssb_private_message_encrypt, "privateMessageEncrypt", 4));
|
||||
JS_SetPropertyStr(context, object, "privateMessageDecrypt", JS_NewCFunction(context, _tf_ssb_private_message_decrypt, "privateMessageDecrypt", 3));
|
||||
@@ -2385,9 +2206,6 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
|
||||
|
||||
/* Does not require an identity. */
|
||||
JS_SetPropertyStr(context, object, "getServerIdentity", JS_NewCFunction(context, _tf_ssb_getServerIdentity, "getServerIdentity", 0));
|
||||
JS_SetPropertyStr(context, object, "getAllIdentities", JS_NewCFunction(context, _tf_ssb_getAllIdentities, "getAllIdentities", 0));
|
||||
JS_SetPropertyStr(context, object, "getActiveIdentity", JS_NewCFunction(context, _tf_ssb_getActiveIdentity, "getActiveIdentity", 3));
|
||||
JS_SetPropertyStr(context, object, "getIdentityInfo", JS_NewCFunction(context, _tf_ssb_getIdentityInfo, "getIdentityInfo", 3));
|
||||
JS_SetPropertyStr(context, object, "blobGet", JS_NewCFunction(context, _tf_ssb_blobGet, "blobGet", 1));
|
||||
JS_SetPropertyStr(context, object, "connections", JS_NewCFunction(context, _tf_ssb_connections, "connections", 0));
|
||||
JS_SetPropertyStr(context, object, "storedConnections", JS_NewCFunction(context, _tf_ssb_storedConnections, "storedConnections", 0));
|
||||
@@ -2406,8 +2224,9 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
|
||||
JS_SetPropertyStr(context, object, "blobStore", JS_NewCFunction(context, _tf_ssb_blobStore, "blobStore", 1));
|
||||
|
||||
/* Trusted only. */
|
||||
JS_SetPropertyStr(context, object, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2));
|
||||
JS_SetPropertyStr(context, object, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2));
|
||||
JS_SetPropertyStr(context, object_internal, "getIdentityInfo", JS_NewCFunction(context, _tf_ssb_getIdentityInfo, "getIdentityInfo", 3));
|
||||
JS_SetPropertyStr(context, object_internal, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2));
|
||||
JS_SetPropertyStr(context, object_internal, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2));
|
||||
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
|
||||
196
src/ssb.tests.c
196
src/ssb.tests.c
@@ -1220,7 +1220,181 @@ void tf_ssb_test_replicate(const tf_test_options_t* options)
|
||||
tf_printf("done\n");
|
||||
|
||||
tf_printf("Waiting for blob.\n");
|
||||
while (!tf_ssb_db_blob_get(ssb0, blob_id, NULL, NULL))
|
||||
while (!tf_ssb_db_blob_get(ssb1, blob_id, NULL, NULL))
|
||||
{
|
||||
tf_ssb_set_main_thread(ssb1, true);
|
||||
uv_run(&loop, UV_RUN_ONCE);
|
||||
tf_ssb_set_main_thread(ssb1, false);
|
||||
}
|
||||
tf_printf("done\n");
|
||||
|
||||
tf_ssb_send_close(ssb1);
|
||||
|
||||
uv_close((uv_handle_t*)&idle0, NULL);
|
||||
uv_close((uv_handle_t*)&idle1, NULL);
|
||||
|
||||
tf_printf("final run\n");
|
||||
tf_ssb_set_main_thread(ssb0, true);
|
||||
tf_ssb_set_main_thread(ssb1, true);
|
||||
uv_run(&loop, UV_RUN_DEFAULT);
|
||||
tf_ssb_set_main_thread(ssb0, false);
|
||||
tf_ssb_set_main_thread(ssb1, false);
|
||||
tf_printf("done\n");
|
||||
|
||||
tf_printf("destroy 0\n");
|
||||
tf_ssb_destroy(ssb0);
|
||||
tf_printf("destroy 1\n");
|
||||
tf_ssb_destroy(ssb1);
|
||||
|
||||
tf_printf("close\n");
|
||||
uv_loop_close(&loop);
|
||||
}
|
||||
|
||||
void tf_ssb_test_replicate_blob(const tf_test_options_t* options)
|
||||
{
|
||||
tf_printf("Testing blob replication.\n");
|
||||
|
||||
uv_loop_t loop = { 0 };
|
||||
uv_loop_init(&loop);
|
||||
|
||||
unlink("out/test_db0.sqlite");
|
||||
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
|
||||
tf_ssb_register(tf_ssb_get_context(ssb0), ssb0);
|
||||
unlink("out/test_db1.sqlite");
|
||||
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL);
|
||||
tf_ssb_register(tf_ssb_get_context(ssb1), ssb1);
|
||||
|
||||
uv_idle_t idle0 = { .data = ssb0 };
|
||||
uv_idle_init(&loop, &idle0);
|
||||
uv_idle_start(&idle0, _ssb_test_idle);
|
||||
|
||||
uv_idle_t idle1 = { .data = ssb1 };
|
||||
uv_idle_init(&loop, &idle1);
|
||||
uv_idle_start(&idle1, _ssb_test_idle);
|
||||
|
||||
test_t test = {
|
||||
.ssb0 = ssb0,
|
||||
.ssb1 = ssb1,
|
||||
};
|
||||
|
||||
tf_ssb_add_connections_changed_callback(ssb0, _ssb_test_connections_changed, NULL, &test);
|
||||
tf_ssb_add_connections_changed_callback(ssb1, _ssb_test_connections_changed, NULL, &test);
|
||||
|
||||
tf_ssb_generate_keys(ssb0);
|
||||
tf_ssb_generate_keys(ssb1);
|
||||
|
||||
uint8_t priv0[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||
uint8_t priv1[crypto_sign_SECRETKEYBYTES] = { 0 };
|
||||
tf_ssb_get_private_key(ssb0, priv0, sizeof(priv0));
|
||||
tf_ssb_get_private_key(ssb1, priv1, sizeof(priv1));
|
||||
|
||||
char id0[k_id_base64_len] = { 0 };
|
||||
char id1[k_id_base64_len] = { 0 };
|
||||
bool b = tf_ssb_whoami(ssb0, id0, sizeof(id0));
|
||||
(void)b;
|
||||
assert(b);
|
||||
b = tf_ssb_whoami(ssb1, id1, sizeof(id1));
|
||||
assert(b);
|
||||
tf_printf("ID %s and %s\n", id0, id1);
|
||||
|
||||
char priv0_str[512] = { 0 };
|
||||
char priv1_str[512] = { 0 };
|
||||
tf_base64_encode(priv0, sizeof(priv0), priv0_str, sizeof(priv0_str));
|
||||
tf_base64_encode(priv1, sizeof(priv0), priv1_str, sizeof(priv1_str));
|
||||
tf_ssb_db_identity_add(ssb0, "test", id0 + 1, priv0_str);
|
||||
tf_ssb_db_identity_add(ssb1, "test", id1 + 1, priv1_str);
|
||||
|
||||
static const int k_key_count = 5;
|
||||
char public[k_key_count][k_id_base64_len - 1];
|
||||
char private[k_key_count][512];
|
||||
for (int i = 0; i < k_key_count; i++)
|
||||
{
|
||||
tf_ssb_generate_keys_buffer(public[i], sizeof(public[i]), private[i], sizeof(private[i]));
|
||||
bool added = tf_ssb_db_identity_add(ssb0, "test", public[i], private[i]);
|
||||
tf_printf("%s user %d = %s private=%s\n", added ? "added" : "failed", i, public[i], private[i]);
|
||||
}
|
||||
|
||||
tf_printf("ssb0\n");
|
||||
tf_ssb_db_identity_visit_all(ssb0, _test_print_identity, ssb0);
|
||||
tf_printf("ssb1\n");
|
||||
tf_ssb_db_identity_visit_all(ssb1, _test_print_identity, ssb1);
|
||||
|
||||
tf_ssb_server_open(ssb0, 12347);
|
||||
|
||||
uint8_t id0bin[k_id_bin_len];
|
||||
tf_ssb_id_str_to_bin(id0bin, id0);
|
||||
tf_ssb_connect(ssb1, "127.0.0.1", 12347, id0bin, 0, NULL, NULL);
|
||||
|
||||
tf_printf("Waiting for connection.\n");
|
||||
while (test.connection_count0 != 1 || test.connection_count1 != 1)
|
||||
{
|
||||
tf_ssb_set_main_thread(ssb0, true);
|
||||
tf_ssb_set_main_thread(ssb1, true);
|
||||
uv_run(&loop, UV_RUN_ONCE);
|
||||
tf_ssb_set_main_thread(ssb0, false);
|
||||
tf_ssb_set_main_thread(ssb1, false);
|
||||
}
|
||||
tf_ssb_server_close(ssb0);
|
||||
|
||||
char blob_id[k_id_base64_len] = { 0 };
|
||||
const char* k_blob = "Hello, new blob!";
|
||||
b = tf_ssb_db_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id), NULL);
|
||||
assert(b);
|
||||
|
||||
JSContext* context0 = tf_ssb_get_context(ssb0);
|
||||
for (int i = 0; i < k_key_count - 1; i++)
|
||||
{
|
||||
JSValue obj = JS_NewObject(context0);
|
||||
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "contact"));
|
||||
char self[k_id_base64_len];
|
||||
snprintf(self, sizeof(self), "@%s", public[i]);
|
||||
char contact[k_id_base64_len];
|
||||
snprintf(contact, sizeof(contact), "@%s", public[i + 1]);
|
||||
JS_SetPropertyStr(context0, obj, "contact", JS_NewString(context0, contact));
|
||||
JS_SetPropertyStr(context0, obj, "following", JS_TRUE);
|
||||
bool stored = false;
|
||||
uint8_t private_bin[512] = { 0 };
|
||||
tf_base64_decode(private[i], strlen(private[i]) - strlen(".ed25519"), private_bin, sizeof(private_bin));
|
||||
tf_printf("ssb0 %s following %s\n", self, contact);
|
||||
JSValue signed_message = tf_ssb_sign_message(ssb0, self, private_bin, obj, NULL, 0);
|
||||
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
|
||||
JS_FreeValue(context0, signed_message);
|
||||
_wait_stored(ssb0, &stored);
|
||||
JS_FreeValue(context0, obj);
|
||||
|
||||
obj = JS_NewObject(context0);
|
||||
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
|
||||
JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "Hello, world!"));
|
||||
JS_SetPropertyStr(context0, obj, "arbitrary_reference", JS_NewString(context0, blob_id));
|
||||
stored = false;
|
||||
signed_message = tf_ssb_sign_message(ssb0, self, private_bin, obj, NULL, 0);
|
||||
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
|
||||
JS_FreeValue(context0, signed_message);
|
||||
_wait_stored(ssb0, &stored);
|
||||
JS_FreeValue(context0, obj);
|
||||
}
|
||||
|
||||
JSContext* context1 = tf_ssb_get_context(ssb1);
|
||||
{
|
||||
JSValue obj = JS_NewObject(context1);
|
||||
JS_SetPropertyStr(context1, obj, "type", JS_NewString(context1, "contact"));
|
||||
char self[k_id_base64_len];
|
||||
tf_string_set(self, sizeof(self), id1);
|
||||
char contact[k_id_base64_len];
|
||||
snprintf(contact, sizeof(contact), "@%s", public[0]);
|
||||
JS_SetPropertyStr(context1, obj, "contact", JS_NewString(context1, contact));
|
||||
JS_SetPropertyStr(context1, obj, "following", JS_TRUE);
|
||||
bool stored = false;
|
||||
tf_printf("ssb1 %s following %s\n", self, contact);
|
||||
JSValue signed_message = tf_ssb_sign_message(ssb1, self, priv1, obj, NULL, 0);
|
||||
tf_ssb_verify_strip_and_store_message(ssb1, signed_message, _message_stored, &stored);
|
||||
JS_FreeValue(context1, signed_message);
|
||||
_wait_stored(ssb1, &stored);
|
||||
JS_FreeValue(context1, obj);
|
||||
}
|
||||
|
||||
tf_printf("Waiting for blob.\n");
|
||||
while (!tf_ssb_db_blob_get(ssb1, blob_id, NULL, NULL))
|
||||
{
|
||||
tf_ssb_set_main_thread(ssb1, true);
|
||||
uv_run(&loop, UV_RUN_ONCE);
|
||||
@@ -1379,12 +1553,6 @@ void tf_ssb_test_invite(const tf_test_options_t* options)
|
||||
tf_ssb_release_db_writer(ssb0, writer);
|
||||
tf_printf("invite: %s\n", invite);
|
||||
|
||||
int count0 = 0;
|
||||
int count1 = 0;
|
||||
|
||||
tf_ssb_add_message_added_callback(ssb0, _message_added, NULL, &count0);
|
||||
tf_ssb_add_message_added_callback(ssb1, _message_added, NULL, &count1);
|
||||
|
||||
tf_ssb_connect_str(ssb1, invite, 0, NULL, NULL);
|
||||
|
||||
tf_printf("Waiting for connection.\n");
|
||||
@@ -1400,11 +1568,19 @@ void tf_ssb_test_invite(const tf_test_options_t* options)
|
||||
tf_printf("waiting for messages\n");
|
||||
tf_ssb_set_main_thread(ssb0, true);
|
||||
tf_ssb_set_main_thread(ssb1, true);
|
||||
while (count0 != 3 || count1 != 3)
|
||||
|
||||
int32_t sequence0 = 0;
|
||||
int32_t sequence1 = 0;
|
||||
while (sequence0 != 1 || sequence1 != 2)
|
||||
{
|
||||
uv_run(&loop, UV_RUN_ONCE);
|
||||
|
||||
tf_printf("count0=%d count1=%d\n", count0, count1);
|
||||
tf_ssb_set_main_thread(ssb0, false);
|
||||
tf_ssb_set_main_thread(ssb1, false);
|
||||
tf_ssb_db_get_latest_message_by_author(ssb0, id0, &sequence0, NULL, 0);
|
||||
tf_ssb_db_get_latest_message_by_author(ssb1, id1, &sequence1, NULL, 0);
|
||||
tf_ssb_set_main_thread(ssb0, true);
|
||||
tf_ssb_set_main_thread(ssb1, true);
|
||||
tf_printf("sequence0=%d sequence1=%d\n", sequence0, sequence1);
|
||||
}
|
||||
tf_ssb_set_main_thread(ssb0, false);
|
||||
tf_ssb_set_main_thread(ssb1, false);
|
||||
|
||||
@@ -66,13 +66,19 @@ void tf_ssb_test_peer_exchange(const tf_test_options_t* options);
|
||||
void tf_ssb_test_publish(const tf_test_options_t* options);
|
||||
|
||||
/**
|
||||
** Test connecting by string.
|
||||
** Test message and replication.
|
||||
** @param options The test options.
|
||||
*/
|
||||
void tf_ssb_test_replicate(const tf_test_options_t* options);
|
||||
|
||||
/**
|
||||
** Test invites.
|
||||
** Test blob replication for a message received while already connected.
|
||||
** @param options The test options.
|
||||
*/
|
||||
void tf_ssb_test_replicate_blob(const tf_test_options_t* options);
|
||||
|
||||
/**
|
||||
** Test connecting by string.
|
||||
** @param options The test options.
|
||||
*/
|
||||
void tf_ssb_test_connect_str(const tf_test_options_t* options);
|
||||
|
||||
11
src/task.c
11
src/task.c
@@ -1,20 +1,18 @@
|
||||
#include "task.h"
|
||||
|
||||
#include "api.js.h"
|
||||
#include "bcrypt.js.h"
|
||||
#include "database.js.h"
|
||||
#include "file.js.h"
|
||||
#include "http.h"
|
||||
#include "httpd.js.h"
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
#include "packetstream.h"
|
||||
#include "serialize.h"
|
||||
#include "socket.js.h"
|
||||
#include "ssb.db.h"
|
||||
#include "ssb.h"
|
||||
#include "ssb.js.h"
|
||||
#include "taskstub.js.h"
|
||||
#include "tlscontext.js.h"
|
||||
#include "trace.h"
|
||||
#include "util.js.h"
|
||||
#include "version.h"
|
||||
@@ -827,9 +825,6 @@ static JSValue _tf_task_getStats(JSContext* context, JSValueConst this_val, int
|
||||
JS_SetPropertyStr(context, result, "tls_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_tls_malloc_size() / total_memory));
|
||||
JS_SetPropertyStr(context, result, "tf_malloc_percent", JS_NewFloat64(context, 100.0 * tf_mem_get_tf_malloc_size() / total_memory));
|
||||
|
||||
JS_SetPropertyStr(context, result, "socket_count", JS_NewInt32(context, tf_socket_get_count()));
|
||||
JS_SetPropertyStr(context, result, "socket_open_count", JS_NewInt32(context, tf_socket_get_open_count()));
|
||||
|
||||
if (task->_ssb)
|
||||
{
|
||||
tf_ssb_stats_t ssb_stats = { 0 };
|
||||
@@ -1666,8 +1661,6 @@ void tf_task_activate(tf_task_t* task)
|
||||
sqlite3_open(task->_db_path, &task->_db);
|
||||
|
||||
JS_SetPropertyStr(context, global, "Task", tf_taskstub_register(context));
|
||||
JS_SetPropertyStr(context, global, "Socket", tf_socket_register(context));
|
||||
JS_SetPropertyStr(context, global, "TlsContext", tf_tls_context_register(context));
|
||||
tf_file_register(context);
|
||||
tf_database_register(context);
|
||||
|
||||
@@ -1728,7 +1721,6 @@ void tf_task_activate(tf_task_t* task)
|
||||
tf_trace_set_write_callback(task->_trace, _tf_task_trace_to_parent, task);
|
||||
}
|
||||
|
||||
tf_bcrypt_register(context);
|
||||
tf_util_register(context);
|
||||
JS_SetPropertyStr(context, global, "exit", JS_NewCFunction(context, _tf_task_exit, "exit", 1));
|
||||
JS_SetPropertyStr(context, global, "version", JS_NewCFunction(context, _tf_task_version, "version", 0));
|
||||
@@ -1884,6 +1876,7 @@ void tf_task_destroy(tf_task_t* task)
|
||||
tf_printf("--\n");
|
||||
uv_print_all_handles(&task->_loop, stdout);
|
||||
}
|
||||
tf_http_debug_destroy();
|
||||
uv_run(&task->_loop, UV_RUN_ONCE);
|
||||
}
|
||||
if (task->_trace)
|
||||
|
||||
89
src/tests.c
89
src/tests.c
@@ -549,93 +549,6 @@ static void _test_float(const tf_test_options_t* options)
|
||||
unlink("out/child.js");
|
||||
}
|
||||
|
||||
static void _test_socket(const tf_test_options_t* options)
|
||||
{
|
||||
_write_file("out/test.js",
|
||||
"'use strict';\n"
|
||||
"\n"
|
||||
"var s = new Socket();\n"
|
||||
"print('connecting');\n"
|
||||
"print('before connect', s.isConnected);\n"
|
||||
"s.onError(function(e) {\n"
|
||||
" print(e);\n"
|
||||
"});\n"
|
||||
"print('noDelay', s.noDelay);\n"
|
||||
"s.noDelay = true;\n"
|
||||
"s.connect('www.unprompted.com', 80).then(function() {\n"
|
||||
" print('connected', 'www.unprompted.com', 80, s.isConnected);\n"
|
||||
" print(s.peerName);\n"
|
||||
" s.read(function(data) {\n"
|
||||
" print('read', data ? data.length : null);\n"
|
||||
" });\n"
|
||||
" s.write('GET / HTTP/1.0\\r\\n\\r\\n');\n"
|
||||
"}).then(function(e) {\n"
|
||||
" print('closed 1');\n"
|
||||
"});\n"
|
||||
"\n"
|
||||
"var s2 = new Socket();\n"
|
||||
"print('connecting');\n"
|
||||
"print('before connect', s2.isConnected);\n"
|
||||
"s2.onError(function(e) {\n"
|
||||
" print('error');\n"
|
||||
" print(e);\n"
|
||||
"});\n"
|
||||
"print('noDelay', s2.noDelay);\n"
|
||||
"s2.noDelay = true;\n"
|
||||
"s2.connect('www.unprompted.com', 443).then(function() {\n"
|
||||
" print('connected', 'www.unprompted.com', 443);\n"
|
||||
" s2.read(function(data) {\n"
|
||||
" print('read', data ? data.length : null);\n"
|
||||
" });\n"
|
||||
" return s2.startTls();\n"
|
||||
"}).then(function() {\n"
|
||||
" print('ready');\n"
|
||||
" print(s2.peerName);\n"
|
||||
" s2.write('GET / HTTP/1.0\\r\\nConnection: close\\r\\n\\r\\n').then(function() {\n"
|
||||
" s2.shutdown();\n"
|
||||
" });\n"
|
||||
"}).catch(function(e) {\n"
|
||||
" print('caught');\n"
|
||||
" print(e);\n"
|
||||
"});\n"
|
||||
"var s3 = new Socket();\n"
|
||||
"print('connecting s3');\n"
|
||||
"print('before connect', s3.isConnected);\n"
|
||||
"s3.onError(function(e) {\n"
|
||||
" print('error');\n"
|
||||
" print(e);\n"
|
||||
"});\n"
|
||||
"print('noDelay', s3.noDelay);\n"
|
||||
"s3.noDelay = true;\n"
|
||||
"s3.connect('0.0.0.0', 443).then(function() {\n"
|
||||
" print('connected', '0.0.0.0', 443);\n"
|
||||
" s3.read(function(data) {\n"
|
||||
" print('read', data ? data.length : null);\n"
|
||||
" });\n"
|
||||
" return s3.startTls();\n"
|
||||
"}).then(function() {\n"
|
||||
" print('ready');\n"
|
||||
" print(s3.peerName);\n"
|
||||
" s3.write('GET / HTTP/1.0\\r\\nConnection: close\\r\\n\\r\\n').then(function() {\n"
|
||||
" s3.shutdown();\n"
|
||||
" });\n"
|
||||
"}).catch(function(e) {\n"
|
||||
" print('caught');\n"
|
||||
" print(e);\n"
|
||||
"});\n");
|
||||
|
||||
char command[256];
|
||||
unlink("out/test_db0.sqlite");
|
||||
snprintf(command, sizeof(command), "%s run --db-path=out/test_db0.sqlite -s out/test.js" TEST_ARGS, options->exe_path);
|
||||
tf_printf("%s\n", command);
|
||||
int result = system(command);
|
||||
tf_printf("returned %d\n", WEXITSTATUS(result));
|
||||
assert(WIFEXITED(result));
|
||||
assert(WEXITSTATUS(result) == 0);
|
||||
|
||||
unlink("out/test.js");
|
||||
}
|
||||
|
||||
static void _test_file(const tf_test_options_t* options)
|
||||
{
|
||||
_write_file("out/test.js",
|
||||
@@ -1065,7 +978,6 @@ void tf_tests(const tf_test_options_t* options)
|
||||
_tf_test_run(options, "icu", _test_icu, false);
|
||||
_tf_test_run(options, "uint8array", _test_uint8array, false);
|
||||
_tf_test_run(options, "float", _test_float, false);
|
||||
_tf_test_run(options, "socket", _test_socket, false);
|
||||
_tf_test_run(options, "file", _test_file, false);
|
||||
_tf_test_run(options, "b64", _test_b64, false);
|
||||
_tf_test_run(options, "rooms", tf_ssb_test_rooms, false);
|
||||
@@ -1076,6 +988,7 @@ void tf_tests(const tf_test_options_t* options)
|
||||
_tf_test_run(options, "peer_exchange", tf_ssb_test_peer_exchange, false);
|
||||
_tf_test_run(options, "publish", tf_ssb_test_publish, false);
|
||||
_tf_test_run(options, "replicate", tf_ssb_test_replicate, false);
|
||||
_tf_test_run(options, "replicate_blob", tf_ssb_test_replicate_blob, false);
|
||||
_tf_test_run(options, "connect_str", tf_ssb_test_connect_str, false);
|
||||
_tf_test_run(options, "invite", tf_ssb_test_invite, false);
|
||||
_tf_test_run(options, "triggers", tf_ssb_test_triggers, false);
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
#include "tlscontext.js.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "mem.h"
|
||||
#include "task.h"
|
||||
#include "tls.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static JSClassID _classId;
|
||||
static int _count;
|
||||
|
||||
typedef struct _tf_tls_context_t
|
||||
{
|
||||
tf_tls_context_t* context;
|
||||
tf_task_t* task;
|
||||
JSValue object;
|
||||
} tf_tls_context_t;
|
||||
|
||||
static JSValue _tls_context_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static void _tls_context_finalizer(JSRuntime* runtime, JSValue value);
|
||||
|
||||
static JSValue _tls_context_set_certificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_tls_context_t* tls = JS_GetOpaque(this_val, _classId);
|
||||
const char* value = JS_ToCString(context, argv[0]);
|
||||
tf_tls_context_set_certificate(tls->context, value);
|
||||
JS_FreeCString(context, value);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue _tls_context_set_private_key(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_tls_context_t* tls = JS_GetOpaque(this_val, _classId);
|
||||
const char* value = JS_ToCString(context, argv[0]);
|
||||
tf_tls_context_set_private_key(tls->context, value);
|
||||
JS_FreeCString(context, value);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue _tls_context_add_trusted_certificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_tls_context_t* tls = JS_GetOpaque(this_val, _classId);
|
||||
const char* value = JS_ToCString(context, argv[0]);
|
||||
tf_tls_context_add_trusted_certificate(tls->context, value);
|
||||
JS_FreeCString(context, value);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue tf_tls_context_register(JSContext* context)
|
||||
{
|
||||
JS_NewClassID(&_classId);
|
||||
JSClassDef def = {
|
||||
.class_name = "TlsContext",
|
||||
.finalizer = _tls_context_finalizer,
|
||||
};
|
||||
if (JS_NewClass(JS_GetRuntime(context), _classId, &def) != 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to register TlsContext.\n");
|
||||
}
|
||||
return JS_NewCFunction2(context, _tls_context_create, "TlsContext", 0, JS_CFUNC_constructor, 0);
|
||||
}
|
||||
|
||||
tf_tls_context_t* tf_tls_context_get(JSValue value)
|
||||
{
|
||||
tf_tls_context_t* tls = JS_GetOpaque(value, _classId);
|
||||
return tls ? tls->context : NULL;
|
||||
}
|
||||
|
||||
int tf_tls_context_get_count()
|
||||
{
|
||||
return _count;
|
||||
}
|
||||
|
||||
static JSValue _tls_context_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_tls_context_t* tls = tf_malloc(sizeof(tf_tls_context_t));
|
||||
memset(tls, 0, sizeof(*tls));
|
||||
|
||||
++_count;
|
||||
tls->object = JS_NewObjectClass(context, _classId);
|
||||
JS_SetOpaque(tls->object, tls);
|
||||
|
||||
JS_SetPropertyStr(context, tls->object, "setCertificate", JS_NewCFunction(context, _tls_context_set_certificate, "setCertificate", 1));
|
||||
JS_SetPropertyStr(context, tls->object, "setPrivateKey", JS_NewCFunction(context, _tls_context_set_private_key, "setPrivateKey", 1));
|
||||
JS_SetPropertyStr(context, tls->object, "addTrustedCertificate", JS_NewCFunction(context, _tls_context_add_trusted_certificate, "addTrustedCertificate", 1));
|
||||
|
||||
tls->context = tf_tls_context_create();
|
||||
tls->task = tf_task_get(context);
|
||||
|
||||
return tls->object;
|
||||
}
|
||||
|
||||
static void _tls_context_finalizer(JSRuntime* runtime, JSValue value)
|
||||
{
|
||||
tf_tls_context_t* tls = JS_GetOpaque(value, _classId);
|
||||
if (tls->context)
|
||||
{
|
||||
tf_tls_context_destroy(tls->context);
|
||||
tls->context = NULL;
|
||||
}
|
||||
--_count;
|
||||
tf_free(tls);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
** \defgroup tls_js TLS Interface
|
||||
** Exposes \ref tls to JS.
|
||||
** @{
|
||||
*/
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
/**
|
||||
** A TLS context instance.
|
||||
*/
|
||||
typedef struct _tf_tls_context_t tf_tls_context_t;
|
||||
|
||||
/**
|
||||
** Register TLS script interface.
|
||||
** @param context The TLS context.
|
||||
** @return the TlsContext constructor.
|
||||
*/
|
||||
JSValue tf_tls_context_register(JSContext* context);
|
||||
|
||||
/**
|
||||
** Get a TLS context instance from its JS object.
|
||||
** @param value A TlsContext JS object.
|
||||
** @return The corresponding instance.
|
||||
*/
|
||||
tf_tls_context_t* tf_tls_context_get(JSValue value);
|
||||
|
||||
/**
|
||||
** Get the number of active TLS context instances.
|
||||
** @return The number of TlsContext objects created that have not been
|
||||
** finalized.
|
||||
*/
|
||||
int tf_tls_context_get_count();
|
||||
|
||||
/** @} */
|
||||
@@ -253,66 +253,6 @@ bool tf_util_report_error(JSContext* context, JSValue value)
|
||||
return is_error;
|
||||
}
|
||||
|
||||
static JSValue _util_parseHttpResponse(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
int status = 0;
|
||||
int minor_version = 0;
|
||||
const char* message = NULL;
|
||||
size_t message_length = 0;
|
||||
struct phr_header headers[100];
|
||||
size_t header_count = sizeof(headers) / sizeof(*headers);
|
||||
int previous_length = 0;
|
||||
JS_ToInt32(context, &previous_length, argv[1]);
|
||||
|
||||
JSValue buffer = JS_UNDEFINED;
|
||||
size_t length;
|
||||
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
|
||||
if (!array)
|
||||
{
|
||||
size_t offset;
|
||||
size_t element_size;
|
||||
buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
|
||||
if (!JS_IsException(buffer))
|
||||
{
|
||||
array = tf_util_try_get_array_buffer(context, &length, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (array)
|
||||
{
|
||||
int parse_result = phr_parse_response((const char*)array, length, &minor_version, &status, &message, &message_length, headers, &header_count, previous_length);
|
||||
if (parse_result > 0)
|
||||
{
|
||||
result = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, result, "bytes_parsed", JS_NewInt32(context, parse_result));
|
||||
JS_SetPropertyStr(context, result, "minor_version", JS_NewInt32(context, minor_version));
|
||||
JS_SetPropertyStr(context, result, "status", JS_NewInt32(context, status));
|
||||
JS_SetPropertyStr(context, result, "message", JS_NewStringLen(context, message, message_length));
|
||||
JSValue header_object = JS_NewObject(context);
|
||||
for (int i = 0; i < (int)header_count; i++)
|
||||
{
|
||||
char name[256];
|
||||
snprintf(name, sizeof(name), "%.*s", (int)headers[i].name_len, headers[i].name);
|
||||
JS_SetPropertyStr(context, header_object, name, JS_NewStringLen(context, headers[i].value, headers[i].value_len));
|
||||
}
|
||||
JS_SetPropertyStr(context, result, "headers", header_object);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = JS_NewInt32(context, parse_result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = JS_ThrowTypeError(context, "Could not convert argument to array.");
|
||||
}
|
||||
|
||||
JS_FreeValue(context, buffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char* k_kind_name[] = {
|
||||
[k_kind_bool] = "bool",
|
||||
[k_kind_int] = "int",
|
||||
@@ -359,10 +299,6 @@ static const setting_t k_settings[] = {
|
||||
.type = "integer",
|
||||
.description = "Blobs older than this will be automatically deleted.",
|
||||
.default_value = { .kind = k_kind_int, .int_value = TF_IS_MOBILE ? (int)(1.0f * 365 * 24 * 60 * 60) : -1 } },
|
||||
{ .name = "fetch_hosts",
|
||||
.type = "string",
|
||||
.description = "Comma-separated list of host names to which HTTP fetch requests are allowed. None if empty.",
|
||||
.default_value = { .kind = k_kind_string, .string_value = NULL } },
|
||||
{ .name = "http_redirect",
|
||||
.type = "string",
|
||||
.description = "If connecting by HTTP and HTTPS is configured, Location header prefix (ie, \"http://example.com\")",
|
||||
@@ -445,6 +381,31 @@ const char* tf_util_get_default_global_setting_string(const char* name)
|
||||
return setting && setting->default_value.string_value ? setting->default_value.string_value : "";
|
||||
}
|
||||
|
||||
bool tf_util_get_global_setting_by_index(int index, const char** out_name, const char** out_type, tf_setting_kind_t* out_kind, const char** out_description)
|
||||
{
|
||||
if (index >= 0 && index < tf_countof(k_settings))
|
||||
{
|
||||
if (out_name)
|
||||
{
|
||||
*out_name = k_settings[index].name;
|
||||
}
|
||||
if (out_type)
|
||||
{
|
||||
*out_type = k_settings[index].type;
|
||||
}
|
||||
if (out_kind)
|
||||
{
|
||||
*out_kind = k_settings[index].default_value.kind;
|
||||
}
|
||||
if (out_description)
|
||||
{
|
||||
*out_description = k_settings[index].description;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static JSValue _util_defaultGlobalSettings(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue settings = JS_NewObject(context);
|
||||
@@ -523,7 +484,6 @@ void tf_util_register(JSContext* context)
|
||||
JS_SetPropertyStr(context, global, "bip39Words", JS_NewCFunction(context, _util_bip39_words, "bip39Words", 1));
|
||||
JS_SetPropertyStr(context, global, "bip39Bytes", JS_NewCFunction(context, _util_bip39_bytes, "bip39Bytes", 1));
|
||||
JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _util_print, "print", 1));
|
||||
JS_SetPropertyStr(context, global, "parseHttpResponse", JS_NewCFunction(context, _util_parseHttpResponse, "parseHttpResponse", 2));
|
||||
JS_SetPropertyStr(context, global, "defaultGlobalSettings", JS_NewCFunction(context, _util_defaultGlobalSettings, "defaultGlobalSettings", 2));
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
|
||||
@@ -212,6 +212,16 @@ const char* tf_util_get_default_global_setting_string(const char* name);
|
||||
*/
|
||||
tf_setting_kind_t tf_util_get_global_setting_kind(const char* name);
|
||||
|
||||
/**
|
||||
** Get the index-th global setting.
|
||||
** @param index The index.
|
||||
** @param out_name Populated with the setting name.
|
||||
** @param out_type Populated with the setting type.
|
||||
** @param out_kind Populated with the setting kind.
|
||||
** @param out_description Populated with the setting description.
|
||||
*/
|
||||
bool tf_util_get_global_setting_by_index(int index, const char** out_name, const char** out_type, tf_setting_kind_t* out_kind, const char** out_description);
|
||||
|
||||
/**
|
||||
** Log documentation for the available settings.
|
||||
** @param line_prefix Text to prefix each line with."
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#define VERSION_NUMBER "0.2025.9"
|
||||
#define VERSION_NUMBER "0.2025.10-wip"
|
||||
#define VERSION_NAME "This program kills fascists."
|
||||
|
||||
Reference in New Issue
Block a user