Compare commits

...

60 Commits

Author SHA1 Message Date
969a8da6bf build: update nix package to 0.0.20 2024-06-28 10:37:26 +02:00
2338b26329 Start working on 0.0.21. 2024-06-26 20:47:44 -04:00
d4df206740 Oh, I think I see how to nix this now. 2024-06-26 20:36:54 -04:00
8a93cdd33c Let's release 0.0.20. 2024-06-26 20:29:07 -04:00
92b31de4a9 Latest libbacktrace. 2024-06-26 20:20:41 -04:00
5452f3f623 Appease -fsanitize. 2024-06-26 20:20:34 -04:00
256614dbaf Actually stop stomping settings. 2024-06-26 19:58:59 -04:00
049449b213 I think this is how I lost settings. 2024-06-26 19:44:45 -04:00
85b46336b1 Better draft cleanup on submit. 2024-06-26 19:30:58 -04:00
590afa7b01 Fix content warnings. 2024-06-26 19:27:15 -04:00
574292b798 Reduce some common log noise. 2024-06-23 15:11:18 -04:00
21cf503a59 Fix a navigation bar option I neglected to button-ify. 2024-06-23 11:47:12 -04:00
3630cdbfe0 Consolidate the acount/login navigation bar options to try to save some space on mobile. 2024-06-20 20:41:27 -04:00
0f3be229e6 Actually, let's minify this thing using svgomg. 2024-06-20 20:07:58 -04:00
8e5a024d3d SVG favicon. 2024-06-20 20:05:00 -04:00
410bb7c09d Fix a ref count mistake and add a long-overdue tf_util_print_backtrace() that helped me find it. 2024-06-20 19:49:21 -04:00
9de8b0f449 Oops. 2024-06-20 12:36:21 -04:00
d47c3a1222 Fix a ref/unref mismatch. 2024-06-17 21:45:51 -04:00
df99b3aa90 Trying to catch an issue I think I saw in the debugger. 2024-06-17 21:23:48 -04:00
0090850e10 Forgot the other end of blobs.get. 2024-06-17 20:59:25 -04:00
9efd64bd18 Actually enforce _tf_ssb_assert_not_main_thread. 2024-06-17 12:36:54 -04:00
b16c37e48b Make ssb.privateMessageDecrypt do its work not on the main thread. I think that's finally everything for real. 2024-06-16 17:22:26 -04:00
3ee2c00726 Build fix. 2024-06-16 17:08:10 -04:00
d5a7e19f1a Move the bulk of ssb.privateMessageEncrypt work (CPU + DB) off the main thread. 2024-06-16 17:07:12 -04:00
9b52415b35 Make ssb.setServerFollowingMe not use the DB from the main thread. Two left?? 2024-06-16 16:22:59 -04:00
dbe24494d9 Remove ssb.messageContentGet. It's easy to do this with ssb.sqlAsync, and this wasn't being used productively. Three uses of DB on the main thread remaining. 2024-06-16 16:02:39 -04:00
3eab5a5f70 Make ssb.forgetStoredConnection not use the DB on the main thread. Four remaining? 2024-06-16 15:57:19 -04:00
548febfb22 Make ssb.storedConnections do its DB work not on the main thread. Five remaining by my new count? 2024-06-16 15:29:59 -04:00
b40f72443a A little format, as a treat. 2024-06-16 12:18:19 -04:00
2c03496373 Make databases.list, database.remove, and database.getLike all do their DB work off the main thread. That's the last thing I'm aware of. 2024-06-16 12:17:51 -04:00
b6a937c954 Move db.exchange DB work off of the main thread. 2024-06-16 10:16:39 -04:00
63776d40bd Update codemirror. 2024-06-16 09:23:14 -04:00
cb3c7afade Move ssb.getPrivateKey's DB work off the main thread. 2024-06-16 08:07:02 -04:00
991022adfc Move ssb.appendMessageWithIdentity's DB work off the main thread. 2024-06-16 07:51:06 -04:00
2bc71a18a6 Make ssb.deleteIdentity not block the main thread with DB work. 2024-06-14 17:39:24 -04:00
57ca864fbb Build fix. 2024-06-12 21:08:41 -04:00
a09edfb612 ssb.addIdentity without hitting the DB from the main thread. 2024-06-12 21:06:30 -04:00
7997a739ab ssb.createIdentity without hitting the database from the main thread. 2024-06-12 20:47:48 -04:00
248b258413 Make database.getAll() not block the main thread on database access. 2024-06-12 20:29:39 -04:00
0423ed7fb4 Login without hitting the DB from the main thread. 2024-06-12 20:12:35 -04:00
c29378c2f8 Yes, curl, follow redirects. 2024-06-10 21:19:06 -04:00
163fbd85e7 Fix docs. 2024-06-10 20:23:11 -04:00
58bb86ebe1 Make http.auth_query async and get its DB work off the main thread. 2024-06-10 20:22:28 -04:00
c5140ee8e8 Move DB work for ssb.getIdentities() and ssb.getAllIdentities() off the main thread. 2024-06-10 17:18:29 -04:00
6270fd8118 We don't need to go to the DB to get our public key. 2024-06-10 16:56:21 -04:00
3fff706848 Get the code of conduct and JWT signing key without hitting the database from the main thread. 2024-06-10 16:37:12 -04:00
c259defab5 Move database.get and database.set off the main thread. 2024-06-10 15:30:14 -04:00
e5fee5c306 Buildfix. 2024-06-10 12:01:49 -04:00
9d35b4bdfb Resuming work to move all DB access off the main thread. 2024-06-10 11:45:20 -04:00
9497d7cf64 Fix some shutdown hangs/leaks. 2024-06-06 20:31:24 -04:00
c7d3e602cb Fix &-mentions while I'm at it. 2024-06-06 20:14:00 -04:00
0076eb4ed4 Fix autocomplete again/more. #65 2024-06-06 20:05:24 -04:00
6070bde413 Avoid a null dereference. 2024-06-06 19:57:36 -04:00
c7a6d426f0 Fix autocomplete on Chrome, because contenteditable and shadowRoots are tricksy, and this module for @mentions is aging. #65 2024-06-06 19:52:37 -04:00
f66cf0f802 Unused. 2024-06-06 19:11:48 -04:00
e4b6c81024 No need to show your identity in the navigation bar if you have a name. 2024-06-06 18:51:40 -04:00
44d784cd04 OpenSSL 3.3.1. 2024-06-05 12:51:26 -04:00
0394201113 Merge pull request 'buld(nix): Misc Nix-related improvements' (#68) from tasiaiso/tildefriends:tasiaiso-nix-misc into main
Reviewed-on: cory/tildefriends#68
2024-06-04 20:16:15 -04:00
e270c16516 lit 3.1.4. 2024-06-04 20:10:27 -04:00
4c10538632 buld(nix): Misc Nix-related improvements
- Nixpkgs 23.11 is deprecated, use 24.05 instead
- update flake.lock
- add glibc as a build dependency
- add doxygen and graphviz as development dependencies for `make format`
2024-06-04 15:22:18 +02:00
54 changed files with 1974 additions and 993 deletions

View File

@ -2,6 +2,7 @@ node_modules
src
deps
.clang-format
flake.lock
# Minified files
**/*.min.css

View File

@ -3,9 +3,9 @@
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 20
VERSION_NUMBER := 0.0.20-wip
VERSION_NAME := One word all lowercase four words all uppercase.
VERSION_CODE := 21
VERSION_NUMBER := 0.0.21-wip
VERSION_NAME := Psst. Look behind you.
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3460000.zip
LIBUV_URL := https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🐌",
"previous": "&zRv7YNZBT/NoliiTS7Jn/Q+3przdFZljUl8yPBIpSSE=.sha256"
"previous": "&TqpkOAi38Oi6gW6guh95KIvWY2M/vjBE8NLLNHK+M00=.sha256"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -76,15 +76,9 @@ class TfComposeElement extends LitElement {
let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = this.process_text(edit.innerText);
let content_warning = this.renderRoot.getElementById('content_warning');
let content_warning_preview = this.renderRoot.getElementById(
'content_warning_preview'
);
if (content_warning && content_warning_preview) {
content_warning_preview.innerText = content_warning.value;
}
let draft = this.get_draft();
draft.text = edit.innerText;
draft.content_warning = content_warning?.innerText;
draft.content_warning = content_warning?.value;
setTimeout(() => this.notify(draft), 0);
}
@ -221,12 +215,8 @@ class TfComposeElement extends LitElement {
console.log('encrypted as', message);
}
try {
await tfrpc.rpc.appendMessage(this.whoami, message).then(function () {
edit.innerText = '';
self.input();
self.notify(undefined);
self.requestUpdate();
});
await tfrpc.rpc.appendMessage(this.whoami, message);
self.notify(undefined);
} catch (error) {
alert(error.message);
}
@ -253,9 +243,9 @@ class TfComposeElement extends LitElement {
try {
let rows = await tfrpc.rpc.query(
`
SELECT json(messages.content) FROM messages_fts(?)
SELECT json(messages.content) AS content FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
WHERE messages.content LIKE ?
WHERE json(messages.content) LIKE ?
ORDER BY timestamp DESC LIMIT 10
`,
['"' + text.replace('"', '""') + '"', `%![%${text}%](%)%`]
@ -291,6 +281,7 @@ class TfComposeElement extends LitElement {
);
}
let tribute = new Tribute({
iframe: this.shadowRoot,
collection: [
{
values: values,
@ -325,6 +316,7 @@ class TfComposeElement extends LitElement {
let encrypt = this.renderRoot.getElementById('encrypt_to');
if (encrypt) {
let tribute = new Tribute({
iframe: this.shadowRoot,
values: Object.entries(this.users).map((x) => ({
key: x[1].name,
value: x[0],
@ -457,7 +449,7 @@ class TfComposeElement extends LitElement {
<input type="checkbox" class="w3-check w3-theme-d1" id="cw" @change=${() => self.set_content_warning(undefined)} checked="checked"></input>
<label for="cw">CW</label>
</p>
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
<input type="text" class="w3-input w3-border w3-theme-d1" id="content_warning" placeholder="Enter a content warning here." @input=${self.input} value=${draft.content_warning}></input>
</div>
`;
} else {

View File

@ -482,16 +482,7 @@ class TributeRange {
}
getDocument() {
let iframe;
if (this.tribute.current.collection) {
iframe = this.tribute.current.collection.iframe;
}
if (!iframe) {
return document
}
return iframe.contentWindow.document
return document;
}
positionMenuAtCaret(scrollTo) {
@ -653,8 +644,8 @@ class TributeRange {
}
getWindowSelection() {
if (this.tribute.collection.iframe) {
return this.tribute.collection.iframe.contentWindow.getSelection()
if (this.tribute.collection[0].iframe?.getSelection) {
return this.tribute.collection[0].iframe.getSelection()
}
return window.getSelection()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -83,10 +83,10 @@ App.prototype.send = function (message) {
* @param {*} response
* @param {*} client
*/
function socket(request, response, client) {
async function socket(request, response, client) {
let process;
let options = {};
let credentials = httpd.auth_query(request.headers);
let credentials = await httpd.auth_query(request.headers);
response.onClose = async function () {
if (process && process.task) {
@ -222,7 +222,7 @@ function socket(request, response, client) {
} else if (message.action == 'setActiveIdentity') {
process.setActiveIdentity(message.identity);
} else if (message.action == 'createIdentity') {
process.createIdentity();
await process.createIdentity();
} else if (message.message == 'tfrpc') {
if (message.id && g_calls[message.id]) {
if (message.error !== undefined) {

View File

@ -3,7 +3,7 @@
<head>
<title>Tilde Friends Sign-in</title>
<link type="text/css" rel="stylesheet" href="/static/style.css" />
<link type="image/png" rel="shortcut icon" href="/static/favicon.png" />
<link type="image/svg+xml" rel="icon" href="/static/tildefriends.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>

View File

@ -118,28 +118,6 @@ class TfNavigationElement extends LitElement {
return this.spark_lines[key];
}
/**
* TODOC
* @returns
*/
render_login() {
if (this?.credentials?.session?.name) {
return html`<a
class="w3-bar-item w3-right"
id="login"
href="/login/logout?return=${url() + hash()}"
>logout ${this.credentials.session.name}</a
>`;
} else {
return html`<a
class="w3-bar-item w3-right"
id="login"
href="/login?return=${url() + hash()}"
>login</a
>`;
}
}
set_active_identity(id) {
send({action: 'setActiveIdentity', identity: id});
this.renderRoot.getElementById('id_dropdown').classList.remove('w3-show');
@ -159,70 +137,105 @@ class TfNavigationElement extends LitElement {
window.location.href = '/~core/ssb/#' + this.identity;
}
logout() {
window.location.href = `/login/logout?return=${encodeURIComponent(url() + hash())}`;
}
render_identity() {
let self = this;
if (this.identities?.length) {
return html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<div class="w3-dropdown-click w3-right" style="max-width: 100%">
<button
class="w3-button w3-rest w3-cyan"
style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap; max-width: 100%"
@click=${self.toggle_id_dropdown}
>
${self.names[this.identity]}${self.names[this.identity] ===
this.identity
? ''
: html` - ${this.identity}`}
</button>
<div
id="id_dropdown"
class="w3-dropdown-content w3-bar-block w3-card-4"
style="max-width: 100%"
>
if (this?.credentials?.session?.name) {
if (this.identities?.length) {
return html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<div class="w3-dropdown-click w3-right" style="max-width: 100%">
<button
class="w3-bar-item w3-button w3-border"
@click=${() => (window.location.href = '/~core/identity')}
class="w3-button w3-rest w3-cyan"
style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap; max-width: 100%"
id="identity"
@click=${self.toggle_id_dropdown}
>
Manage Identities...
${self.names[this.identity]}
</button>
<button
class="w3-bar-item w3-button w3-border"
@click=${self.edit_profile}
<div
id="id_dropdown"
class="w3-dropdown-content w3-bar-block w3-card-4"
style="max-width: 100%; right: 0"
>
Edit Profile...
</button>
${this.identities.map(
(x) => html`
<button
class="w3-bar-item w3-button ${x === self.identity
? 'w3-cyan'
: ''}"
@click=${() => self.set_active_identity(x)}
style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
>
${self.names[x]}${self.names[x] === x ? '' : html` - ${x}`}
</button>
`
)}
<button
class="w3-bar-item w3-button w3-border"
@click=${() => (window.location.href = '/~core/identity')}
>
Manage Identities...
</button>
<button
class="w3-bar-item w3-button w3-border"
@click=${self.edit_profile}
>
Edit Profile...
</button>
${this.identities.map(
(x) => html`
<button
class="w3-bar-item w3-button ${x === self.identity
? 'w3-cyan'
: ''}"
@click=${() => self.set_active_identity(x)}
style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
>
${self.names[x]}${self.names[x] === x ? '' : html` - ${x}`}
</button>
`
)}
<button
class="w3-bar-item w3-button w3-border"
id="logout"
@click=${self.logout}
>
Logout ${this.credentials.session.name}
</button>
</div>
</div>
</div>
`;
} else if (
this.credentials?.session?.name &&
this.credentials.session.name !== 'guest'
) {
return html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<button
id="create_identity"
@click=${this.create_identity}
class="w3-button w3-mobile w3-blue w3-right"
>
Create an Identity
</button>
`;
`;
} else if (
this.credentials?.session?.name &&
this.credentials.session.name !== 'guest'
) {
return html`
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<button
class="w3-bar-item w3-button w3-right w3-cyan"
id="logout"
@click=${self.logout}
>
Logout ${this.credentials.session.name}
</button>
<button
id="create_identity"
@click=${this.create_identity}
class="w3-button w3-mobile w3-red w3-right"
>
Create an Identity
</button>
`;
} else {
return html`
<button
class="w3-bar-item w3-button w3-right w3-cyan"
id="logout"
@click=${self.logout}
>
Logout ${this.credentials.session.name}
</button>
`;
}
} else {
return html`<a
class="w3-bar-item w3-cyan w3-right"
id="login"
href="/login?return=${url() + hash()}"
>login</a
>`;
}
}
@ -362,7 +375,7 @@ class TfNavigationElement extends LitElement {
${Object.keys(this.spark_lines)
.sort()
.map((x) => this.spark_lines[x])}
${this.render_login()} ${this.render_identity()}
${this.render_identity()}
</div>
${this.status?.is_error
? html`

View File

@ -206,7 +206,7 @@ function getUser(caller, process) {
* @param {*} process
* @returns
*/
function getApps(user, process) {
async function getApps(user, process) {
if (
process.credentials &&
process.credentials.session &&
@ -221,10 +221,12 @@ function getApps(user, process) {
if (user) {
let db = new Database(user);
try {
let names = JSON.parse(db.get('apps'));
return Object.fromEntries(
names.map((name) => [name, db.get('path:' + name)])
);
let names = JSON.parse(await db.get('apps'));
let result = {};
for (let name of names) {
result[name] = await db.get('path:' + name);
}
return result;
} catch {}
}
return {};
@ -320,9 +322,9 @@ async function getProcessBlob(blobId, key, options) {
}
},
user: getUser(process, process),
users: function () {
users: async function () {
try {
return JSON.parse(new Database('auth').get('users'));
return JSON.parse(await new Database('auth').get('users'));
} catch {
return [];
}
@ -470,7 +472,7 @@ async function getProcessBlob(blobId, key, options) {
process.credentials.session.name &&
process.credentials.session.name !== 'guest'
) {
let id = ssb.createIdentity(process.credentials.session.name);
let id = await ssb.createIdentity(process.credentials.session.name);
await process.sendIdentities();
broadcastAppEventToUser(
process?.credentials?.session?.name,
@ -509,25 +511,20 @@ async function getProcessBlob(blobId, key, options) {
setGlobalSettings(gGlobalSettings);
print('Done.');
};
imports.core.deleteUser = function (user) {
return Promise.resolve(
imports.core.permissionTest('delete_user')
).then(function () {
let db = new Database('auth');
db.remove('user:' + user);
let users = new Set();
let users_original = db.get('users');
try {
users = new Set(JSON.parse(users_original));
} catch {}
users.delete(user);
users = JSON.stringify([...users].sort());
if (users !== users_original) {
db.set('users', users);
}
});
imports.core.deleteUser = async function (user) {
await imports.core.permissionTest('delete_user');
let db = new Database('auth');
db.remove('user:' + user);
let users = new Set();
let users_original = await db.get('users');
try {
users = new Set(JSON.parse(users_original));
} catch {}
users.delete(user);
users = JSON.stringify([...users].sort());
if (users !== users_original) {
await db.set('users', users);
}
};
}
if (options.api) {
@ -752,7 +749,7 @@ async function getProcessBlob(blobId, key, options) {
};
process.task.setImports(imports);
process.task.activate();
let source = await getBlobOrContent(blobId);
let source = await ssb.blobGet(blobId);
let appSourceName = blobId;
let appSource = utf8Decode(source);
try {
@ -760,7 +757,7 @@ async function getProcessBlob(blobId, key, options) {
if (appObject.type == 'tildefriends-app') {
appSourceName = options?.script ?? 'app.js';
let id = appObject.files[appSourceName];
let blob = await getBlobOrContent(id);
let blob = await ssb.blobGet(id);
appSource = utf8Decode(blob);
await process.task.loadFile([
'/tfrpc.js',
@ -770,7 +767,7 @@ async function getProcessBlob(blobId, key, options) {
Object.keys(appObject.files).map(async function (f) {
await process.task.loadFile([
f,
await getBlobOrContent(appObject.files[f]),
await ssb.blobGet(appObject.files[f]),
]);
})
);
@ -806,33 +803,15 @@ async function getProcessBlob(blobId, key, options) {
* @param {*} settings
* @returns
*/
function setGlobalSettings(settings) {
async function setGlobalSettings(settings) {
gGlobalSettings = settings;
try {
return new Database('core').set('settings', JSON.stringify(settings));
return await new Database('core').set('settings', JSON.stringify(settings));
} catch (error) {
print('Error storing settings:', error);
}
}
/**
* TODOC
* @param {*} data
* @param {*} bytes
* @returns
*/
function startsWithBytes(data, bytes) {
if (data.byteLength >= bytes.length) {
let dataBytes = new Uint8Array(data.slice(0, bytes.length));
for (let i = 0; i < bytes.length; i++) {
if (dataBytes[i] !== bytes[i] && bytes[i] !== null) {
return;
}
}
return true;
}
}
/**
* TODOC
* @param {*} response
@ -872,21 +851,6 @@ function sendData(response, data, type, headers, status_code) {
}
}
/**
* TODOC
* @param {*} id
* @returns
*/
async function getBlobOrContent(id) {
if (!id) {
return;
} else if (id.startsWith('&')) {
return ssb.blobGet(id);
} else if (id.startsWith('%')) {
return ssb.messageContentGet(id);
}
}
let g_handler_index = 0;
/**
@ -929,7 +893,7 @@ async function useAppHandler(
},
respond: do_resolve,
},
credentials: httpd.auth_query(headers),
credentials: await httpd.auth_query(headers),
packageOwner: packageOwner,
packageName: packageName,
}
@ -1014,7 +978,7 @@ async function blobHandler(request, response, blobId, uri) {
response.writeHead(304, headers);
response.end();
} else {
data = await getBlobOrContent(id);
data = await ssb.blobGet(id);
if (match[3]) {
let appObject = JSON.parse(data);
data = appObject.files[match[3]];
@ -1046,7 +1010,7 @@ async function blobHandler(request, response, blobId, uri) {
response.writeHead(304, headers);
response.end();
} else {
data = await getBlobOrContent(blobId);
data = await ssb.blobGet(blobId);
sendData(
response,
data,
@ -1060,7 +1024,7 @@ async function blobHandler(request, response, blobId, uri) {
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1];
let appName = match[2];
let credentials = httpd.auth_query(request.headers);
let credentials = await httpd.auth_query(request.headers);
if (
credentials &&
credentials.session &&
@ -1070,7 +1034,7 @@ async function blobHandler(request, response, blobId, uri) {
let database = new Database(user);
let app_object = JSON.parse(utf8Decode(request.body));
let previous_id = database.get('path:' + appName);
let previous_id = await database.get('path:' + appName);
if (previous_id) {
try {
let previous_object = JSON.parse(
@ -1091,7 +1055,7 @@ async function blobHandler(request, response, blobId, uri) {
let newBlobId = await ssb.blobStore(JSON.stringify(app_object));
let apps = new Set();
let apps_original = database.get('apps');
let apps_original = await database.get('apps');
try {
apps = new Set(JSON.parse(apps_original));
} catch {}
@ -1100,9 +1064,9 @@ async function blobHandler(request, response, blobId, uri) {
}
apps = JSON.stringify([...apps].sort());
if (apps != apps_original) {
database.set('apps', apps);
await database.set('apps', apps);
}
database.set('path:' + appName, newBlobId);
await database.set('path:' + appName, newBlobId);
response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('/' + newBlobId);
} else {
@ -1123,7 +1087,7 @@ async function blobHandler(request, response, blobId, uri) {
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1];
let appName = match[2];
let credentials = httpd.auth_query(request.headers);
let credentials = await httpd.auth_query(request.headers);
if (
credentials &&
credentials.session &&
@ -1133,10 +1097,10 @@ async function blobHandler(request, response, blobId, uri) {
let database = new Database(user);
let apps = new Set();
try {
apps = new Set(JSON.parse(database.get('apps')));
apps = new Set(JSON.parse(await database.get('apps')));
} catch {}
if (apps.delete(appName)) {
database.set('apps', JSON.stringify([...apps].sort()));
await database.set('apps', JSON.stringify([...apps].sort()));
}
database.remove('path:' + appName);
} else {
@ -1162,9 +1126,9 @@ async function blobHandler(request, response, blobId, uri) {
app_id = await db.get('path:' + match[2]);
}
let app_object = JSON.parse(utf8Decode(await getBlobOrContent(app_id)));
id = app_object.files[uri.substring(1)];
if (!id && app_object.files['handler.js']) {
let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app_id)));
id = app_object?.files[uri.substring(1)];
if (!id && app_object?.files['handler.js']) {
let answer;
try {
answer = await useAppHandler(
@ -1218,7 +1182,7 @@ async function blobHandler(request, response, blobId, uri) {
'Access-Control-Allow-Origin': '*',
'Content-Security-Policy': k_content_security_policy,
};
data = await getBlobOrContent(id);
data = await ssb.blobGet(id);
let type =
httpd.mime_type_from_extension(uri) ||
httpd.mime_type_from_magic_bytes(data);
@ -1248,7 +1212,7 @@ ssb.addEventListener('connections', function () {
async function loadSettings() {
let data = {};
try {
let settings = new Database('core').get('settings');
let settings = await new Database('core').get('settings');
if (settings) {
data = JSON.parse(settings);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 B

View File

@ -4,7 +4,7 @@
<title>Tilde Friends</title>
<link type="text/css" rel="stylesheet" href="/static/style.css" />
<link type="text/css" rel="stylesheet" href="/static/w3.css" />
<link type="image/png" rel="shortcut icon" href="/static/favicon.png" />
<link type="image/svg+xml" rel="icon" href="/static/tildefriends.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
function set_access_key_title(event) {

1
core/tildefriends.svg Normal file
View File

@ -0,0 +1 @@
<svg width="65" height="65" viewBox="0 0 61 65" fill="none" xmlns="http://www.w3.org/2000/svg"><path style="fill:#0af;stroke-width:.712717;fill-opacity:1" d="M6 0h49a8 8 45 0 1 8 8v49a8 8 135 0 1-8 8H6a8 8 45 0 1-8-8V8a8 8 135 0 1 8-8Z"/><text xml:space="preserve" style="font-style:normal;font-weight:400;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000;fill-opacity:1;stroke:none" x="-.023" y="47.568"><tspan x="-.023" y="47.568" style="font-style:normal;font-variant:normal;font-weight:400;font-stretch:normal;font-size:40px;font-family:Arial;-inkscape-font-specification:'Arial, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal">~</tspan></text><g transform="translate(16.213 5.975) scale(.72923)"><circle cx="36" cy="36" r="23" fill="#fcea2b"/><path fill="#3f3f3f" d="M45.331 38.564c3.963 0 7.178-2.862 7.178-6.389 0-1.765.448-3.53-.852-4.685-1.299-1.156-4.345-1.704-6.326-1.704-2.357 0-5.143.143-6.451 1.704-.894 1.065-.727 3.253-.727 4.685 0 3.527 3.213 6.389 7.178 6.389zM25.738 38.564c3.963 0 7.179-2.862 7.179-6.389 0-1.765.447-3.53-.852-4.685-1.3-1.156-4.345-1.704-6.327-1.704-2.356 0-5.142.143-6.451 1.704-.893 1.065-.727 3.253-.727 4.685 0 3.527 3.213 6.389 7.178 6.389z"/></g><g stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" transform="translate(16.213 5.975) scale(.72923)"><circle cx="35.887" cy="36.056" r="23"/><path d="M45.702 44.862c-6.574 3.525-14.045 3.658-19.63 0M18.883 30.464s-.953 8.55 6.86 7.918c2.62-.212 7.817-.65 7.867-8.342.005-.698-.007-1.6-.81-2.63-1.065-1.367-3.572-1.971-9.945-1.422 0 0-3.446-.1-3.972 4.476z"/><path d="m18.953 29.931-.433-3.372 3.833-.527M52.741 30.464s.953 8.55-6.86 7.918c-2.62-.212-7.817-.65-7.868-8.342-.004-.698.008-1.6.811-2.63 1.065-1.367 3.572-1.971 9.945-1.422 0 0 3.446-.1 3.972 4.476z"/><path d="M31.505 26.416s4.124 2.534 8.657 0M33.536 31.318s2.202-3.751 4.536 0M52.664 29.933l.433-3.371-3.833-.528"/><path d="M33.955 30.027s1.795-3.75 3.699 0"/></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -21,24 +21,26 @@
}:
pkgs.stdenv.mkDerivation rec {
pname = "tildefriends";
version = "0.0.19";
version = "0.0.20";
src = pkgs.fetchFromGitea {
domain = "dev.tildefriends.net";
owner = "cory";
repo = "tildefriends";
rev = "v${version}";
hash = "sha256-ttqL2wz06Jvn2f6kKIAGpF0nSSle+g4nSlj4jL0D+Fk=";
hash = "sha256-q7PQS/OnfPyU74FBsTmuwWn+G8XTJ11ulvTxf1sgUQk=";
fetchSubmodules = true;
};
nativeBuildInputs = with pkgs; [
glibc
gnumake
openssl
which
];
buildInputs = with pkgs; [
glibc
openssl
which
];

File diff suppressed because one or more lines are too long

View File

@ -19,9 +19,9 @@
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.16.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.0.tgz",
"integrity": "sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==",
"version": "6.16.2",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.2.tgz",
"integrity": "sha512-MjfDrHy0gHKlPWsvSsikhO1+BOh+eBHNgfH1OXs1+DAf30IonQldgMM3kxLDTG9ktE7kDLaA1j/l7KMPA4KNfw==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
@ -36,13 +36,13 @@
}
},
"node_modules/@codemirror/commands": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz",
"integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==",
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz",
"integrity": "sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.0.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
},
@ -98,9 +98,9 @@
}
},
"node_modules/@codemirror/language": {
"version": "6.10.1",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz",
"integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
@ -147,9 +147,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.26.3",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz",
"integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==",
"version": "6.28.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.1.tgz",
"integrity": "sha512-BUWr+zCJpMkA/u69HlJmR+YkV4yPpM81HeMkOMZuwFa8iM5uJdEPKAs1icIRZKkKmy0Ub1x9/G3PQLTXdpBxrQ==",
"dependencies": {
"@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0",
@ -238,9 +238,9 @@
}
},
"node_modules/@lezer/html": {
"version": "1.3.9",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.9.tgz",
"integrity": "sha512-MXxeCMPyrcemSLGaTQEZx0dBUH0i+RPl8RN5GwMAzo53nTsd/Unc/t5ZxACeQoyPUM5/GkPLRUs2WliOImzkRA==",
"version": "1.3.10",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
@ -248,9 +248,9 @@
}
},
"node_modules/@lezer/javascript": {
"version": "1.4.16",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.16.tgz",
"integrity": "sha512-84UXR3N7s11MPQHWgMnjb9571fr19MmXnr5zTv2XX0gHXXUvW3uPJ8GCjKrfTXmSdfktjRK0ayKklw+A13rk4g==",
"version": "1.4.17",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.17.tgz",
"integrity": "sha512-bYW4ctpyGK+JMumDApeUzuIezX01H76R1foD6LcRX224FWfyYit/HYxiPGDjXXe/wQWASjCvVGoukTH68+0HIA==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
@ -268,9 +268,9 @@
}
},
"node_modules/@lezer/lr": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.1.tgz",
"integrity": "sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
@ -545,9 +545,9 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
"integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@ -819,9 +819,9 @@
}
},
"node_modules/terser": {
"version": "5.31.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz",
"integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==",
"version": "5.31.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz",
"integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

118
flake.lock generated
View File

@ -1,61 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1715395895,
"narHash": "sha256-DreMqi6+qa21ffLQqhMQL2XRUkAGt3N7iVB5FhJKie4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "71bae31b7dbc335528ca7e96f479ec93462323ff",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1717281328,
"narHash": "sha256-evZPzpf59oNcDUXxh2GHcxHkTEG4fjae2ytWP85jXRo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b3b2b28c1daa04fe2ae47c21bb76fd226eac4ca1",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@ -2,7 +2,7 @@
description = "Tilde Friends is a platform for making, running, and sharing web applications.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
flake-utils.url = "github:numtide/flake-utils";
};
@ -31,6 +31,8 @@
openssl
llvmPackages_17.clang-unwrapped
unzip
doxygen
graphviz
];
};
});

View File

@ -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="20"
android:versionName="0.0.20-wip">
android:versionCode="21"
android:versionName="0.0.21-wip">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

View File

@ -4,6 +4,7 @@
#include "mem.h"
#include "ssb.h"
#include "task.h"
#include "util.js.h"
#include "sqlite3.h"
@ -50,7 +51,7 @@ void tf_database_register(JSContext* context)
JS_SetPropertyStr(context, global, "Database", constructor);
JSValue databases = JS_NewObject(context);
JS_SetPropertyStr(context, global, "databases", databases);
JS_SetPropertyStr(context, databases, "list", JS_NewCFunctionData(context, _databases_list, 0, 0, 0, NULL));
JS_SetPropertyStr(context, databases, "list", JS_NewCFunctionData(context, _databases_list, 1, 0, 0, NULL));
JS_FreeValue(context, global);
}
@ -91,159 +92,455 @@ static void _database_finalizer(JSRuntime* runtime, JSValue value)
--_database_count;
}
typedef struct _database_get_t
{
const char* id;
const char* key;
size_t key_length;
char* out_value;
size_t out_length;
JSValue promise[2];
} database_get_t;
static void _database_get_work(tf_ssb_t* ssb, void* user_data)
{
database_get_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
{
size_t length = sqlite3_column_bytes(statement, 0);
char* data = tf_malloc(length + 1);
memcpy(data, sqlite3_column_text(statement, 0), length);
data[length] = '\0';
work->out_value = data;
work->out_length = length;
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _database_get_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_get_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_UNDEFINED;
if (work->out_value)
{
result = JS_NewStringLen(context, work->out_value, work->out_length);
}
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(work->out_value);
tf_free(work);
}
static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue entry = JS_UNDEFINED;
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK)
{
size_t length;
const char* keyString = JS_ToCStringLen(context, &length, argv[0]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW)
{
entry = JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0));
}
JS_FreeCString(context, keyString);
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
size_t length;
const char* key = JS_ToCStringLen(context, &length, argv[0]);
database_get_t* work = tf_malloc(sizeof(database_get_t) + strlen(database->id) + 1 + length + 1);
*work = (database_get_t) {
.id = (const char*)(work + 1),
.key = (const char*)(work + 1) + strlen(database->id) + 1,
.key_length = length,
};
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
memcpy((char*)work->key, key, length + 1);
JS_FreeCString(context, key);
tf_ssb_run_work(ssb, _database_get_work, _database_get_after_work, work);
result = JS_NewPromiseCapability(context, work->promise);
}
return entry;
return result;
}
typedef struct _database_set_t
{
const char* id;
const char* key;
size_t key_length;
const char* value;
size_t value_length;
bool result;
JSValue promise[2];
} database_set_t;
static void _database_set_work(tf_ssb_t* ssb, void* user_data)
{
database_set_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
{
work->result = true;
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
static void _database_set_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_set_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = work->result ? JS_TRUE : JS_UNDEFINED;
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(work);
}
static JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?1, ?2, ?3)", -1, &statement, NULL) == SQLITE_OK)
size_t key_length = 0;
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
size_t value_length = 0;
const char* value = JS_ToCStringLen(context, &value_length, argv[1]);
database_set_t* work = tf_malloc(sizeof(database_set_t) + strlen(database->id) + 1 + key_length + 1 + value_length + 1);
*work = (database_set_t) {
.id = (const char*)(work + 1),
.key = (const char*)(work + 1) + strlen(database->id) + 1,
.value = (const char*)(work + 1) + strlen(database->id) + 1 + key_length + 1,
.key_length = key_length,
.value_length = value_length,
};
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
memcpy((char*)work->key, key, key_length + 1);
memcpy((char*)work->value, value, value_length + 1);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _database_set_work, _database_set_after_work, work);
JS_FreeCString(context, key);
JS_FreeCString(context, value);
}
return result;
}
typedef struct _database_exchange_t
{
const char* id;
const char* key;
size_t key_length;
const char* expected;
size_t expected_length;
const char* value;
size_t value_length;
bool result;
JSValue promise[2];
} database_exchange_t;
static void _database_exchange_work(tf_ssb_t* ssb, void* user_data)
{
database_exchange_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
sqlite3_stmt* statement;
if (!work->expected)
{
if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{
size_t keyLength;
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
size_t valueLength;
const char* valueString = JS_ToCStringLen(context, &valueLength, argv[1]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, valueString, valueLength, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_OK)
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
{
work->result = sqlite3_changes(db) != 0;
}
JS_FreeCString(context, keyString);
JS_FreeCString(context, valueString);
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
return JS_UNDEFINED;
else if (sqlite3_prepare(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->value, work->value_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->id, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 4, work->expected, work->expected_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
{
work->result = sqlite3_changes(db) != 0;
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
static void _database_exchange_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_exchange_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = work->result ? JS_TRUE : JS_UNDEFINED;
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);
JS_FreeCString(context, work->expected);
JS_FreeCString(context, work->value);
tf_free((char*)work->id);
tf_free(work);
}
static JSValue _database_exchange(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue exchanged = JS_UNDEFINED;
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (JS_IsNull(argv[1]) || JS_IsUndefined(argv[1]))
{
if (sqlite3_prepare(db, "INSERT INTO properties (id, key, value) VALUES (?1, ?2, ?3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK)
{
size_t key_length;
size_t set_length;
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
const char* set = JS_ToCStringLen(context, &set_length, argv[2]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, key_length, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, set, set_length, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE)
{
exchanged = sqlite3_changes(db) != 0 ? JS_TRUE : JS_FALSE;
}
JS_FreeCString(context, key);
JS_FreeCString(context, set);
sqlite3_finalize(statement);
}
}
else if (sqlite3_prepare(db, "UPDATE properties SET value = ?1 WHERE id = ?2 AND key = ?3 AND value = ?4", -1, &statement, NULL) == SQLITE_OK)
{
size_t key_length;
size_t expected_length;
size_t set_length;
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
const char* expected = JS_ToCStringLen(context, &expected_length, argv[1]);
const char* set = JS_ToCStringLen(context, &set_length, argv[2]);
if (sqlite3_bind_text(statement, 1, set, set_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, database->id, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, key, key_length, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 4, expected, expected_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_DONE)
{
exchanged = sqlite3_changes(db) != 0 ? JS_TRUE : JS_FALSE;
}
JS_FreeCString(context, key);
JS_FreeCString(context, expected);
JS_FreeCString(context, set);
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
database_exchange_t* work = tf_malloc(sizeof(database_exchange_t));
*work = (database_exchange_t) {
.id = tf_strdup(database->id),
};
work->key = JS_ToCStringLen(context, &work->key_length, argv[0]);
work->expected = (JS_IsNull(argv[1]) || JS_IsUndefined(argv[1])) ? NULL : JS_ToCStringLen(context, &work->expected_length, argv[1]);
work->value = JS_ToCStringLen(context, &work->value_length, argv[2]);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _database_exchange_work, _database_exchange_after_work, work);
}
return exchanged;
return result;
}
typedef struct _database_remove_t
{
const char* id;
size_t key_length;
JSValue promise[2];
char key[];
} database_remove_t;
static void _database_remove_work(tf_ssb_t* ssb, void* user_data)
{
database_remove_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->key, work->key_length, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_OK)
{
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
}
static void _database_remove_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_remove_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_UNDEFINED;
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((char*)work->id);
tf_free(work);
}
static JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ?1 AND key = ?2", -1, &statement, NULL) == SQLITE_OK)
{
size_t keyLength;
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_OK)
{
}
JS_FreeCString(context, keyString);
sqlite3_finalize(statement);
}
tf_ssb_release_db_writer(ssb, db);
size_t key_length = 0;
const char* key = JS_ToCStringLen(context, &key_length, argv[0]);
database_remove_t* work = tf_malloc(sizeof(database_remove_t) + key_length + 1);
*work = (database_remove_t) {
.id = tf_strdup(database->id),
.key_length = key_length,
};
memcpy(work->key, key, key_length + 1);
JS_FreeCString(context, key);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(tf_task_get_ssb(database->task), _database_remove_work, _database_remove_after_work, work);
}
return JS_UNDEFINED;
return result;
}
typedef struct _database_get_all_t
{
const char* id;
const char* key;
size_t key_length;
char** out_values;
size_t* out_lengths;
int out_values_length;
JSValue promise[2];
} database_get_all_t;
static void _database_get_all_work(tf_ssb_t* ssb, void* user_data)
{
database_get_all_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
work->out_values = tf_resize_vec(work->out_values, sizeof(char*) * (work->out_values_length + 1));
work->out_lengths = tf_resize_vec(work->out_lengths, sizeof(size_t) * (work->out_values_length + 1));
size_t length = sqlite3_column_bytes(statement, 0);
char* data = tf_malloc(length + 1);
memcpy(data, sqlite3_column_text(statement, 0), length);
data[length] = '\0';
work->out_values[work->out_values_length] = data;
work->out_lengths[work->out_values_length] = length;
work->out_values_length++;
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _database_get_all_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_get_all_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_NewArray(context);
;
for (int i = 0; i < work->out_values_length; i++)
{
JS_SetPropertyUint32(context, result, i, JS_NewStringLen(context, work->out_values[i], work->out_lengths[i]));
tf_free((void*)work->out_values[i]);
}
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(work->out_values);
tf_free(work->out_lengths);
tf_free(work);
}
static JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue array = JS_UNDEFINED;
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ?1", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK)
{
array = JS_NewArray(context);
uint32_t index = 0;
while (sqlite3_step(statement) == SQLITE_ROW)
{
JS_SetPropertyUint32(context, array, index++, JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0)));
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
size_t length;
const char* key = JS_ToCStringLen(context, &length, argv[0]);
database_get_all_t* work = tf_malloc(sizeof(database_get_all_t) + strlen(database->id) + 1 + length + 1);
*work = (database_get_all_t) {
.id = (const char*)(work + 1),
.key = (const char*)(work + 1) + strlen(database->id) + 1,
.key_length = length,
};
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
memcpy((char*)work->key, key, length + 1);
JS_FreeCString(context, key);
tf_ssb_run_work(ssb, _database_get_all_work, _database_get_all_after_work, work);
result = JS_NewPromiseCapability(context, work->promise);
}
return array;
return result;
}
typedef struct _key_value_t
{
char* key;
size_t key_length;
char* value;
size_t value_length;
} key_value_t;
typedef struct _database_get_like_t
{
const char* id;
const char* pattern;
key_value_t* results;
int results_length;
JSValue promise[2];
} database_get_like_t;
static void _database_get_like_work(tf_ssb_t* ssb, void* user_data)
{
database_get_like_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, work->pattern, -1, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
work->results = tf_resize_vec(work->results, sizeof(key_value_t) * (work->results_length + 1));
key_value_t* out = &work->results[work->results_length];
*out = (key_value_t) {
.key_length = sqlite3_column_bytes(statement, 0),
.value_length = sqlite3_column_bytes(statement, 1),
};
out->key = tf_malloc(out->key_length + 1);
memcpy(out->key, sqlite3_column_text(statement, 0), out->key_length + 1);
out->value = tf_malloc(out->value_length + 1);
memcpy(out->value, sqlite3_column_text(statement, 1), out->value_length + 1);
work->results_length++;
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _database_get_like_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_get_like_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_NewObject(context);
for (int i = 0; i < work->results_length; i++)
{
const key_value_t* row = &work->results[i];
JS_SetPropertyStr(context, result, row->key, JS_NewStringLen(context, row->value, row->value_length));
tf_free(row->key);
tf_free(row->value);
}
JS_FreeCString(context, work->pattern);
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->id);
tf_free(work->results);
tf_free(work);
}
static JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
@ -252,51 +549,77 @@ static JSValue _database_get_like(JSContext* context, JSValueConst this_val, int
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ? AND KEY LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{
const char* pattern = JS_ToCString(context, argv[0]);
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, pattern, -1, NULL) == SQLITE_OK)
{
result = JS_NewObject(context);
while (sqlite3_step(statement) == SQLITE_ROW)
{
JS_SetPropertyStr(context, result, (const char*)sqlite3_column_text(statement, 0),
JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 1), sqlite3_column_bytes(statement, 1)));
}
}
JS_FreeCString(context, pattern);
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
database_get_like_t* work = tf_malloc(sizeof(database_get_like_t));
*work = (database_get_like_t) {
.id = tf_strdup(database->id),
.pattern = JS_ToCString(context, argv[0]),
};
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _database_get_like_work, _database_get_like_after_work, work);
}
return result;
}
typedef struct _databases_list_t
{
const char* pattern;
char** names;
int names_length;
JSValue promise[2];
} databases_list_t;
static void _databases_list_work(tf_ssb_t* ssb, void* user_data)
{
databases_list_t* work = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->pattern, -1, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
work->names = tf_resize_vec(work->names, sizeof(char*) * (work->names_length + 1));
work->names[work->names_length] = tf_strdup((const char*)sqlite3_column_text(statement, 0));
work->names_length++;
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _databases_list_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
databases_list_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_NewArray(context);
for (int i = 0; i < work->names_length; i++)
{
JS_SetPropertyUint32(context, result, i, JS_NewString(context, work->names[i]));
tf_free(work->names[i]);
}
JS_FreeCString(context, work->pattern);
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(work->names);
tf_free(work);
}
static JSValue _databases_list(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);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
JSValue array = JS_UNDEFINED;
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT DISTINCT id FROM properties WHERE id LIKE ?", -1, &statement, NULL) == SQLITE_OK)
{
const char* pattern = JS_ToCString(context, argv[0]);
if (sqlite3_bind_text(statement, 1, pattern, -1, NULL) == SQLITE_OK)
{
array = JS_NewArray(context);
uint32_t index = 0;
while (sqlite3_step(statement) == SQLITE_ROW)
{
JS_SetPropertyUint32(context, array, index++, JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0)));
}
}
JS_FreeCString(context, pattern);
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
return array;
databases_list_t* work = tf_malloc(sizeof(databases_list_t));
*work = (databases_list_t) {
.pattern = JS_ToCString(context, argv[0]),
};
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _databases_list_work, _databases_list_after_work, work);
return result;
}

View File

@ -1019,7 +1019,7 @@ void tf_http_request_unref(tf_http_request_t* request)
tf_free(request);
}
if (--connection->ref_count == 0)
if (connection && --connection->ref_count == 0)
{
if (connection->http->is_shutting_down)
{

View File

@ -37,7 +37,7 @@
const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000;
static JSValue _authenticate_jwt(JSContext* context, const char* jwt);
static JSValue _authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char* jwt);
static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
static const char* _make_session_jwt(tf_ssb_t* ssb, const char* name);
static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie);
@ -330,7 +330,7 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va
tf_ssb_t* ssb = tf_task_get_ssb(tf_task_get(context));
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
JSValue jwt = _authenticate_jwt(context, session);
JSValue jwt = _authenticate_jwt(ssb, context, session);
tf_free((void*)session);
JSValue name = !JS_IsUndefined(jwt) ? JS_GetPropertyStr(context, jwt, "name") : JS_UNDEFINED;
const char* name_string = !JS_IsUndefined(name) ? JS_ToCString(context, name) : NULL;
@ -446,6 +446,55 @@ static JSValue _httpd_set_http_redirect(JSContext* context, JSValueConst this_va
return JS_UNDEFINED;
}
typedef struct _auth_query_work_t
{
const char* settings;
JSValue entry;
JSValue result;
JSValue promise[2];
} auth_query_work_t;
static void _httpd_auth_query_work(tf_ssb_t* ssb, void* user_data)
{
auth_query_work_t* work = user_data;
work->settings = tf_ssb_db_get_property(ssb, "core", "settings");
}
static void _httpd_auth_query_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
auth_query_work_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue name = JS_GetPropertyStr(context, work->entry, "name");
const char* name_string = JS_ToCString(context, name);
JSValue settings_value = work->settings ? JS_ParseJSON(context, work->settings, strlen(work->settings), NULL) : JS_UNDEFINED;
JSValue out_permissions = JS_NewObject(context);
JS_SetPropertyStr(context, work->result, "permissions", out_permissions);
JSValue permissions = !JS_IsUndefined(settings_value) ? JS_GetPropertyStr(context, settings_value, "permissions") : JS_UNDEFINED;
JSValue user_permissions = !JS_IsUndefined(permissions) ? JS_GetPropertyStr(context, permissions, name_string) : JS_UNDEFINED;
int length = !JS_IsUndefined(user_permissions) ? tf_util_get_length(context, user_permissions) : 0;
for (int i = 0; i < length; i++)
{
JSValue permission = JS_GetPropertyUint32(context, user_permissions, i);
const char* permission_string = JS_ToCString(context, permission);
JS_SetPropertyStr(context, out_permissions, permission_string, JS_TRUE);
JS_FreeCString(context, permission_string);
JS_FreeValue(context, permission);
}
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &work->result);
JS_FreeValue(context, work->result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
JS_FreeValue(context, user_permissions);
JS_FreeValue(context, permissions);
JS_FreeValue(context, settings_value);
tf_free((void*)work->settings);
JS_FreeCString(context, name_string);
JS_FreeValue(context, name);
tf_free(work);
}
static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_task_t* task = tf_task_get(context);
@ -459,7 +508,7 @@ static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int
JSValue cookie = JS_GetPropertyStr(context, headers, "cookie");
const char* cookie_string = JS_ToCString(context, cookie);
const char* session = tf_http_get_cookie(cookie_string, "session");
JSValue entry = _authenticate_jwt(context, session);
JSValue entry = _authenticate_jwt(ssb, context, session);
tf_free((void*)session);
JS_FreeCString(context, cookie_string);
JS_FreeValue(context, cookie);
@ -467,33 +516,16 @@ static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int
JSValue result = JS_UNDEFINED;
if (!JS_IsUndefined(entry))
{
result = JS_NewObject(context);
JS_SetPropertyStr(context, result, "session", entry);
JSValue out_permissions = JS_NewObject(context);
JS_SetPropertyStr(context, result, "permissions", out_permissions);
JSValue value = JS_NewObject(context);
JS_SetPropertyStr(context, value, "session", entry);
JSValue name = JS_GetPropertyStr(context, entry, "name");
const char* name_string = JS_ToCString(context, name);
const char* settings = tf_ssb_db_get_property(ssb, "core", "settings");
JSValue settings_value = settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
JSValue permissions = !JS_IsUndefined(settings_value) ? JS_GetPropertyStr(context, settings_value, "permissions") : JS_UNDEFINED;
JSValue user_permissions = !JS_IsUndefined(permissions) ? JS_GetPropertyStr(context, permissions, name_string) : JS_UNDEFINED;
int length = !JS_IsUndefined(user_permissions) ? tf_util_get_length(context, user_permissions) : 0;
for (int i = 0; i < length; i++)
{
JSValue permission = JS_GetPropertyUint32(context, user_permissions, i);
const char* permission_string = JS_ToCString(context, permission);
JS_SetPropertyStr(context, out_permissions, permission_string, JS_TRUE);
JS_FreeCString(context, permission_string);
JS_FreeValue(context, permission);
}
JS_FreeValue(context, user_permissions);
JS_FreeValue(context, permissions);
JS_FreeValue(context, settings_value);
tf_free((void*)settings);
JS_FreeCString(context, name_string);
JS_FreeValue(context, name);
auth_query_work_t* work = tf_malloc(sizeof(auth_query_work_t));
*work = (auth_query_work_t) {
.entry = entry,
.result = value,
};
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _httpd_auth_query_work, _httpd_auth_query_after_work, work);
}
return result;
}
@ -852,7 +884,7 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
const char* k_static_files[] = {
"index.html",
"client.js",
"favicon.png",
"tildefriends.svg",
"jszip.min.js",
"style.css",
"tfrpc.js",
@ -1062,13 +1094,17 @@ const char* _form_data_get(const char** form_data, const char* key)
typedef struct _login_request_t
{
tf_http_request_t* request;
const char* session_cookie;
JSValue jwt;
const char* name;
const char* error;
const char* settings;
const char* code_of_conduct;
bool have_administrator;
bool session_is_new;
char location_header[1024];
const char* set_cookie_header;
int pending;
} login_request_t;
static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie)
@ -1083,18 +1119,29 @@ static const char* _make_set_session_cookie_header(tf_http_request_t* request, c
return cookie;
}
static void _login_release(login_request_t* login)
{
int ref_count = --login->pending;
if (ref_count == 0)
{
tf_free((void*)login->name);
tf_free((void*)login->code_of_conduct);
tf_free((void*)login->set_cookie_header);
tf_free(login);
}
}
static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char* path, int result, const void* data, void* user_data)
{
login_request_t* login = user_data;
tf_http_request_t* request = login->request;
if (result >= 0)
{
const char* cookie = _make_set_session_cookie_header(request, login->session_cookie);
const char* headers[] = {
"Content-Type",
"text/html; charset=utf-8",
"Set-Cookie",
cookie ? cookie : "",
login->set_cookie_header ? login->set_cookie_header : "",
};
const char* replace_me = "$AUTH_DATA";
const char* auth = strstr(data, replace_me);
@ -1128,7 +1175,6 @@ static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char
{
tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result);
}
tf_free((void*)cookie);
}
else
{
@ -1136,10 +1182,7 @@ static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char
tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload));
}
tf_http_request_unref(request);
tf_free((void*)login->name);
tf_free((void*)login->code_of_conduct);
tf_free((void*)login->session_cookie);
tf_free(login);
_login_release(login);
}
static bool _string_property_equals(JSContext* context, JSValue object, const char* name, const char* value)
@ -1152,12 +1195,7 @@ static bool _string_property_equals(JSContext* context, JSValue object, const ch
return equals;
}
static void _public_key_visit(const char* identity, void* user_data)
{
snprintf(user_data, k_id_base64_len, "%s", identity);
}
static JSValue _authenticate_jwt(JSContext* context, const char* jwt)
static JSValue _authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char* jwt)
{
if (!jwt)
{
@ -1198,10 +1236,8 @@ static JSValue _authenticate_jwt(JSContext* context, const char* jwt)
return JS_UNDEFINED;
}
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
char public_key_b64[k_id_base64_len] = { 0 };
tf_ssb_db_identity_visit(ssb, ":admin", _public_key_visit, public_key_b64);
tf_ssb_whoami(ssb, public_key_b64, sizeof(public_key_b64));
const char* payload = jwt + dot[0] + 1;
size_t payload_length = dot[1] - dot[0] - 1;
@ -1260,25 +1296,6 @@ static bool _is_name_valid(const char* name)
return true;
}
static void _visit_auth_identity(const char* identity, void* user_data)
{
if (!*(char*)user_data)
{
snprintf((char*)user_data, k_id_base64_len, "%s", identity);
}
}
static bool _get_auth_private_key(tf_ssb_t* ssb, uint8_t* out_private_key)
{
char id[k_id_base64_len] = { 0 };
tf_ssb_db_identity_visit(ssb, ":admin", _visit_auth_identity, id);
if (*id)
{
return tf_ssb_db_identity_get_private_key(ssb, ":admin", id, out_private_key, crypto_sign_SECRETKEYBYTES);
}
return false;
}
static const char* _make_session_jwt(tf_ssb_t* ssb, const char* name)
{
if (!name || !*name)
@ -1309,17 +1326,16 @@ static const char* _make_session_jwt(tf_ssb_t* ssb, const char* name)
char signature_base64[256] = { 0 };
uint8_t private_key[crypto_sign_SECRETKEYBYTES] = { 0 };
if (_get_auth_private_key(ssb, private_key))
tf_ssb_get_private_key(ssb, private_key, sizeof(private_key));
if (crypto_sign_detached(signature, &signature_length, (const uint8_t*)payload_base64, strlen(payload_base64), private_key) == 0)
{
if (crypto_sign_detached(signature, &signature_length, (const uint8_t*)payload_base64, strlen(payload_base64), private_key) == 0)
{
sodium_bin2base64(signature_base64, sizeof(signature_base64), signature, sizeof(signature), sodium_base64_VARIANT_URLSAFE_NO_PADDING);
size_t size = strlen(header_base64) + 1 + strlen(payload_base64) + 1 + strlen(signature_base64) + 1;
result = tf_malloc(size);
snprintf(result, size, "%s.%s.%s", header_base64, payload_base64, signature_base64);
}
sodium_memzero(private_key, sizeof(private_key));
sodium_bin2base64(signature_base64, sizeof(signature_base64), signature, sizeof(signature), sodium_base64_VARIANT_URLSAFE_NO_PADDING);
size_t size = strlen(header_base64) + 1 + strlen(payload_base64) + 1 + strlen(signature_base64) + 1;
result = tf_malloc(size);
snprintf(result, size, "%s.%s.%s", header_base64, payload_base64, signature_base64);
}
sodium_memzero(private_key, sizeof(private_key));
JS_FreeCString(context, payload_string);
JS_FreeValue(context, payload_json);
@ -1335,26 +1351,10 @@ static bool _verify_password(const char* password, const char* hash)
return out_hash && strcmp(hash, out_hash) == 0;
}
static const char* _get_code_of_conduct(tf_ssb_t* ssb)
static bool _make_administrator_if_first(tf_ssb_t* ssb, JSContext* context, const char* account_name_copy, bool may_become_first_admin)
{
JSContext* context = tf_ssb_get_context(ssb);
const char* settings = tf_ssb_db_get_property(ssb, "core", "settings");
JSValue settings_value = settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
JSValue code_of_conduct_value = JS_GetPropertyStr(context, settings_value, "code_of_conduct");
const char* code_of_conduct = JS_ToCString(context, code_of_conduct_value);
const char* result = tf_strdup(code_of_conduct);
JS_FreeCString(context, code_of_conduct);
JS_FreeValue(context, code_of_conduct_value);
JS_FreeValue(context, settings_value);
tf_free((void*)settings);
return result;
}
static bool _make_administrator_if_first(tf_ssb_t* ssb, const char* account_name_copy, bool may_become_first_admin)
{
JSContext* context = tf_ssb_get_context(ssb);
const char* settings = tf_ssb_db_get_property(ssb, "core", "settings");
JSValue settings_value = settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
JSValue settings_value = settings && *settings ? JS_ParseJSON(context, settings, strlen(settings), NULL) : JS_UNDEFINED;
if (JS_IsUndefined(settings_value))
{
settings_value = JS_NewObject(context);
@ -1423,30 +1423,32 @@ static bool _make_administrator_if_first(tf_ssb_t* ssb, const char* account_name
return have_administrator;
}
static void _httpd_endpoint_login(tf_http_request_t* request)
static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data)
{
tf_task_t* task = request->user_data;
JSContext* context = tf_task_get_context(task);
tf_ssb_t* ssb = tf_task_get_ssb(task);
login_request_t* login = user_data;
tf_http_request_t* request = login->request;
JSMallocFunctions funcs = { 0 };
tf_get_js_malloc_functions(&funcs);
JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL);
JSContext* context = JS_NewContext(runtime);
const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session");
const char** form_data = _form_data_decode(request->query, request->query ? strlen(request->query) : 0);
const char* account_name_copy = NULL;
JSValue jwt = _authenticate_jwt(context, session);
JSValue jwt = _authenticate_jwt(ssb, context, session);
if (_session_is_authenticated_as_user(context, jwt))
{
const char* return_url = _form_data_get(form_data, "return");
char url[1024];
if (!return_url)
if (return_url)
{
snprintf(url, sizeof(url), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
return_url = url;
snprintf(login->location_header, sizeof(login->location_header), "%s", return_url);
}
else
{
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
}
const char* headers[] = {
"Location",
return_url,
};
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
goto done;
}
@ -1520,47 +1522,51 @@ static void _httpd_endpoint_login(tf_http_request_t* request)
tf_free(post_form_data);
}
bool have_administrator = _make_administrator_if_first(ssb, account_name_copy, may_become_first_admin);
bool have_administrator = _make_administrator_if_first(ssb, context, account_name_copy, may_become_first_admin);
if (session_is_new && _form_data_get(form_data, "return") && !login_error)
{
const char* return_url = _form_data_get(form_data, "return");
char url[1024];
if (!return_url)
if (return_url)
{
snprintf(url, sizeof(url), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
return_url = url;
snprintf(login->location_header, sizeof(login->location_header), "%s", return_url);
}
const char* cookie = _make_set_session_cookie_header(request, send_session);
const char* headers[] = {
"Location",
return_url,
"Set-Cookie",
cookie ? cookie : "",
};
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
tf_free((void*)cookie);
else
{
snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host"));
}
login->set_cookie_header = _make_set_session_cookie_header(request, send_session);
tf_free((void*)send_session);
}
else
{
login->name = account_name_copy;
login->error = login_error;
login->set_cookie_header = _make_set_session_cookie_header(request, send_session);
tf_free((void*)send_session);
login->session_is_new = session_is_new;
login->have_administrator = have_administrator;
login->settings = tf_ssb_db_get_property(ssb, "core", "settings");
if (login->settings)
{
JSValue settings_value = JS_ParseJSON(context, login->settings, strlen(login->settings), NULL);
JSValue code_of_conduct_value = JS_GetPropertyStr(context, settings_value, "code_of_conduct");
const char* code_of_conduct = JS_ToCString(context, code_of_conduct_value);
const char* result = tf_strdup(code_of_conduct);
JS_FreeCString(context, code_of_conduct);
JS_FreeValue(context, code_of_conduct_value);
JS_FreeValue(context, settings_value);
tf_free((void*)login->settings);
login->settings = NULL;
login->code_of_conduct = result;
}
login->pending++;
tf_http_request_ref(request);
tf_file_read(login->request->user_data, "core/auth.html", _httpd_endpoint_login_file_read_callback, login);
login_request_t* login = tf_malloc(sizeof(login_request_t));
const char* code_of_conduct = _get_code_of_conduct(ssb);
*login = (login_request_t) {
.request = request,
.name = account_name_copy,
.jwt = jwt,
.error = login_error,
.session_cookie = send_session,
.session_is_new = session_is_new,
.code_of_conduct = code_of_conduct,
.have_administrator = have_administrator,
};
tf_file_read(request->user_data, "core/auth.html", _httpd_endpoint_login_file_read_callback, login);
jwt = JS_UNDEFINED;
account_name_copy = NULL;
}
@ -1569,6 +1575,44 @@ done:
tf_free(form_data);
tf_free((void*)account_name_copy);
JS_FreeValue(context, jwt);
JS_FreeContext(context);
JS_FreeRuntime(runtime);
}
static void _httpd_endpoint_login_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
login_request_t* login = user_data;
tf_http_request_t* request = login->request;
if (login->pending == 1)
{
if (*login->location_header)
{
const char* headers[] = {
"Location",
login->location_header,
"Set-Cookie",
login->set_cookie_header ? login->set_cookie_header : "",
};
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
}
}
tf_http_request_unref(request);
_login_release(login);
}
static void _httpd_endpoint_login(tf_http_request_t* request)
{
tf_task_t* task = request->user_data;
tf_http_request_ref(request);
tf_ssb_t* ssb = tf_task_get_ssb(task);
login_request_t* login = tf_malloc(sizeof(login_request_t));
*login = (login_request_t) {
.request = request,
};
login->pending++;
tf_ssb_run_work(ssb, _httpd_endpoint_login_work, _httpd_endpoint_login_after_work, login);
}
static void _httpd_endpoint_logout(tf_http_request_t* request)

View File

@ -381,6 +381,7 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
tf_ssb_import(tf_task_get_ssb(task), "core", "apps");
}
}
tf_ssb_set_main_thread(tf_task_get_ssb(task), true);
if (tf_task_execute(task, args->script))
{
tf_task_run(task);

View File

@ -1674,10 +1674,6 @@ static void _tf_ssb_connection_rpc_recv(tf_ssb_connection_t* connection, uint8_t
tf_trace_end(connection->ssb->trace);
}
}
else
{
tf_printf("No request callback for %p %d\n", connection, -request_number);
}
}
if (close_connection)
@ -1784,18 +1780,28 @@ static bool _tf_ssb_connection_box_stream_recv(tf_ssb_connection_t* connection)
return true;
}
JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message)
JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message, const char* previous_id, int64_t previous_sequence)
{
char previous_id[crypto_hash_sha256_BYTES * 2];
int64_t previous_sequence = 0;
bool have_previous = tf_ssb_db_get_latest_message_by_author(ssb, author, &previous_sequence, previous_id, sizeof(previous_id));
char actual_previous_id[crypto_hash_sha256_BYTES * 2];
int64_t actual_previous_sequence = 0;
bool have_previous = false;
if (previous_id)
{
have_previous = *previous_id && previous_sequence > 0;
snprintf(actual_previous_id, sizeof(actual_previous_id), "%s", previous_id);
actual_previous_sequence = previous_sequence;
}
else
{
have_previous = tf_ssb_db_get_latest_message_by_author(ssb, author, &actual_previous_sequence, actual_previous_id, sizeof(actual_previous_id));
}
JSContext* context = ssb->context;
JSValue root = JS_NewObject(context);
JS_SetPropertyStr(context, root, "previous", have_previous ? JS_NewString(context, previous_id) : JS_NULL);
JS_SetPropertyStr(context, root, "previous", have_previous ? JS_NewString(context, actual_previous_id) : JS_NULL);
JS_SetPropertyStr(context, root, "author", JS_NewString(context, author));
JS_SetPropertyStr(context, root, "sequence", JS_NewInt64(context, previous_sequence + 1));
JS_SetPropertyStr(context, root, "sequence", JS_NewInt64(context, actual_previous_sequence + 1));
int64_t now = (int64_t)time(NULL);
JS_SetPropertyStr(context, root, "timestamp", JS_NewInt64(context, now * 1000LL));
@ -2260,6 +2266,7 @@ static void _tf_ssb_assert_not_main_thread(tf_ssb_t* ssb)
const char* bt = tf_util_backtrace_string();
tf_printf("Acquiring DB from the main thread:\n%s\n", bt);
tf_free((void*)bt);
abort();
}
}
@ -3749,7 +3756,12 @@ void tf_ssb_ref(tf_ssb_t* ssb)
void tf_ssb_unref(tf_ssb_t* ssb)
{
ssb->ref_count--;
int new_count = --ssb->ref_count;
if (new_count < 0)
{
tf_printf("tf_ssb_unref past 0: %d\n", new_count);
abort();
}
}
void tf_ssb_set_main_thread(tf_ssb_t* ssb, bool main_thread)

View File

@ -281,7 +281,7 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
tf_ssb_release_db_writer(ssb, db);
}
static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author, int64_t sequence, const char* previous)
static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author, int64_t sequence, const char* previous, bool* out_id_mismatch)
{
bool exists = false;
if (sequence == 1)
@ -291,12 +291,13 @@ static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author,
else
{
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT COUNT(*) FROM messages WHERE author = ?1 AND sequence = ?2 AND id = ?3", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, "SELECT COUNT(*), id != ?3 AS is_mismatch FROM messages WHERE author = ?1 AND sequence = ?2", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, sequence - 1) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, previous, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{
exists = sqlite3_column_int(statement, 0) != 0;
*out_id_mismatch = sqlite3_column_int(statement, 1) != 0;
}
sqlite3_finalize(statement);
}
@ -309,8 +310,9 @@ static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const
{
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
int64_t last_row_id = -1;
bool id_mismatch = false;
if (_tf_ssb_db_previous_message_exists(db, author, sequence, previous))
if (_tf_ssb_db_previous_message_exists(db, author, sequence, previous, &id_mismatch))
{
const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, flags) VALUES (?, ?, ?, ?, ?, jsonb(?), "
"?, ?, ?) ON CONFLICT DO NOTHING";
@ -345,8 +347,14 @@ static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
}
}
else
else if (id_mismatch)
{
/*
** Only warn if we find a previous message with the wrong ID.
** If a feed is forked, we would otherwise warn on every
** message when trying to receive what we don't have, and
** that's not helping anybody.
*/
tf_printf("%p: Previous message doesn't exist for author=%s sequence=%" PRId64 " previous=%s.\n", db, author, sequence, previous);
}
tf_ssb_release_db_writer(ssb, db);
@ -635,6 +643,44 @@ bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_
return result;
}
typedef struct _blob_get_async_t
{
tf_ssb_t* ssb;
char id[k_blob_id_len];
tf_ssb_db_blob_get_callback_t* callback;
void* user_data;
bool out_found;
uint8_t* out_data;
size_t out_size;
} blob_get_async_t;
static void _tf_ssb_db_blob_get_async_work(tf_ssb_t* ssb, void* user_data)
{
blob_get_async_t* async = user_data;
async->out_found = tf_ssb_db_blob_get(ssb, async->id, &async->out_data, &async->out_size);
}
static void _tf_ssb_db_blob_get_async_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
blob_get_async_t* async = user_data;
async->callback(async->out_found, async->out_data, async->out_size, async->user_data);
tf_free(async->out_data);
tf_free(async);
}
void tf_ssb_db_blob_get_async(tf_ssb_t* ssb, const char* id, tf_ssb_db_blob_get_callback_t* callback, void* user_data)
{
blob_get_async_t* async = tf_malloc(sizeof(blob_get_async_t));
*async = (blob_get_async_t) {
.ssb = ssb,
.callback = callback,
.user_data = user_data,
};
snprintf(async->id, sizeof(async->id), "%s", id);
tf_ssb_run_work(ssb, _tf_ssb_db_blob_get_async_work, _tf_ssb_db_blob_get_async_after_work, async);
}
typedef struct _blob_store_work_t
{
const uint8_t* blob;
@ -648,7 +694,10 @@ typedef struct _blob_store_work_t
static void _tf_ssb_db_blob_store_work(tf_ssb_t* ssb, void* user_data)
{
blob_store_work_t* blob_work = user_data;
tf_ssb_db_blob_store(ssb, blob_work->blob, blob_work->size, blob_work->id, sizeof(blob_work->id), &blob_work->is_new);
if (!tf_ssb_is_shutting_down(ssb))
{
tf_ssb_db_blob_store(ssb, blob_work->blob, blob_work->size, blob_work->id, sizeof(blob_work->id), &blob_work->is_new);
}
}
static void _tf_ssb_db_blob_store_after_work(tf_ssb_t* ssb, int status, void* user_data)
@ -1046,6 +1095,8 @@ bool tf_ssb_db_identity_create(tf_ssb_t* ssb, const char* user, uint8_t* out_pub
if (out_private_key)
{
tf_ssb_id_str_to_bin(out_private_key, private);
/* HACK: tf_ssb_id_str_to_bin only produces 32 bytes even though the full private key is 32 + 32. */
tf_ssb_id_str_to_bin(out_private_key + crypto_sign_PUBLICKEYBYTES, public);
}
return true;
}

View File

@ -55,6 +55,24 @@ bool tf_ssb_db_blob_has(tf_ssb_t* ssb, const char* id);
*/
bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_t* out_size);
/**
** A function called when a blob is retrieved from the database.
** @param found Whether the blob was found.
** @param data The blob data if found.
** @param size The size of the blob data if found, in bytes.
** @param user_data The user data.
*/
typedef void(tf_ssb_db_blob_get_callback_t)(bool found, const uint8_t* data, size_t size, void* user_data);
/**
** Retrieve a blob from the database asynchronously.
** @param ssb The SSB instance.
** @param id The blob identifier.
** @param callback Callback called with the result.
** @param user_data The user data.
*/
void tf_ssb_db_blob_get_async(tf_ssb_t* ssb, const char* id, tf_ssb_db_blob_get_callback_t* callback, void* user_data);
/**
** A function called when a message is stored in the database.
** @param id The message identifier.

View File

@ -278,9 +278,11 @@ void tf_ssb_run(tf_ssb_t* ssb);
** @param author The author's public key.
** @param private_key The author's private key.
** @param message The message to sign.
** @param previous_id The ID of the previous message in the feed. Optional.
** @param previous_sequence The sequence number of the previous message in the feed. Optional.
** @return The signed message.
*/
JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message);
JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message, const char* previous_id, int64_t previous_sequence);
/**
** Get the server's identity.

File diff suppressed because it is too large Load Diff

View File

@ -67,6 +67,40 @@ static void _tf_ssb_rpc_blobs_get_callback(
{
}
typedef struct _blobs_get_work_t
{
int64_t request_number;
char id[k_id_base64_len];
bool found;
uint8_t* blob;
size_t size;
} blobs_get_work_t;
static void _tf_ssb_rpc_blobs_get_work(tf_ssb_connection_t* connection, void* user_data)
{
blobs_get_work_t* work = user_data;
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
work->found = tf_ssb_db_blob_get(ssb, work->id, &work->blob, &work->size);
}
static void _tf_ssb_rpc_blobs_get_after_work(tf_ssb_connection_t* connection, int status, void* user_data)
{
blobs_get_work_t* work = user_data;
if (work->found)
{
const size_t k_send_max = 8192;
for (size_t offset = 0; offset < work->size; offset += k_send_max)
{
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_binary | k_ssb_rpc_flag_stream, -work->request_number, NULL, work->blob + offset,
offset + k_send_max <= work->size ? k_send_max : (work->size - offset), NULL, NULL, NULL);
}
tf_free(work->blob);
}
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error | k_ssb_rpc_flag_stream, -work->request_number, NULL,
(const uint8_t*)(work->found ? "true" : "false"), strlen(work->found ? "true" : "false"), NULL, NULL, NULL);
tf_free(work);
}
static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{
if (flags & k_ssb_rpc_flag_end_error)
@ -75,11 +109,10 @@ static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags
}
tf_ssb_connection_add_request(connection, -request_number, "blobs.get", _tf_ssb_rpc_blobs_get_callback, NULL, NULL, NULL);
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
JSContext* context = tf_ssb_connection_get_context(connection);
JSValue ids = JS_GetPropertyStr(context, args, "args");
int length = tf_util_get_length(context, ids);
bool success = false;
for (int i = 0; i < length; i++)
{
JSValue arg = JS_GetPropertyUint32(context, ids, i);
@ -94,25 +127,18 @@ static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags
id = JS_ToCString(context, key);
JS_FreeValue(context, key);
}
uint8_t* blob = NULL;
size_t size = 0;
const size_t k_send_max = 8192;
if (tf_ssb_db_blob_get(ssb, id, &blob, &size))
{
for (size_t offset = 0; offset < size; offset += k_send_max)
{
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_binary | k_ssb_rpc_flag_stream, -request_number, NULL, blob + offset,
offset + k_send_max <= size ? k_send_max : (size - offset), NULL, NULL, NULL);
}
success = true;
tf_free(blob);
}
blobs_get_work_t* work = tf_malloc(sizeof(blobs_get_work_t));
*work = (blobs_get_work_t) {
.request_number = request_number,
};
snprintf(work->id, sizeof(work->id), "%s", id);
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_blobs_get_work, _tf_ssb_rpc_blobs_get_after_work, work);
JS_FreeCString(context, id);
JS_FreeValue(context, arg);
}
JS_FreeValue(context, ids);
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error | k_ssb_rpc_flag_stream, -request_number, NULL,
(const uint8_t*)(success ? "true" : "false"), strlen(success ? "true" : "false"), NULL, NULL, NULL);
}
static void _tf_ssb_rpc_blobs_has(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
@ -480,6 +506,45 @@ static void _tf_ssb_rpc_connection_blobs_get(tf_ssb_connection_t* connection, co
JS_FreeValue(context, message);
}
typedef struct _blob_create_wants_work_t
{
tf_ssb_connection_t* connection;
char blob_id[k_blob_id_len];
bool out_result;
int64_t size;
size_t out_size;
} blob_create_wants_work_t;
static void _tf_ssb_rpc_connection_blobs_create_wants_work(tf_ssb_connection_t* connection, void* user_data)
{
blob_create_wants_work_t* work = user_data;
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(work->connection);
work->out_result = tf_ssb_db_blob_get(ssb, work->blob_id, NULL, &work->out_size);
}
static void _tf_ssb_rpc_connection_blobs_create_wants_after_work(tf_ssb_connection_t* connection, int result, void* user_data)
{
blob_create_wants_work_t* work = user_data;
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(work->connection);
tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection);
JSContext* context = tf_ssb_get_context(ssb);
if (work->out_result)
{
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, work->blob_id, JS_NewInt64(context, work->out_size));
tf_ssb_connection_rpc_send_json(work->connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
}
else if (work->size == -1LL)
{
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, work->blob_id, JS_NewInt64(context, -2));
tf_ssb_connection_rpc_send_json(work->connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
}
tf_free(work);
}
static void _tf_ssb_rpc_connection_blobs_createWants_callback(
tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{
@ -489,7 +554,6 @@ static void _tf_ssb_rpc_connection_blobs_createWants_callback(
return;
}
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
JSContext* context = tf_ssb_connection_get_context(connection);
JSValue name = JS_GetPropertyStr(context, args, "name");
@ -524,21 +588,13 @@ static void _tf_ssb_rpc_connection_blobs_createWants_callback(
}
if (size < 0)
{
size_t blob_size = 0;
if (tf_ssb_db_blob_get(ssb, blob_id, NULL, &blob_size))
{
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, blob_id, JS_NewInt64(context, blob_size));
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
}
else if (size == -1LL)
{
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, blob_id, JS_NewInt64(context, -2));
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
}
blob_create_wants_work_t* work = tf_malloc(sizeof(blob_create_wants_work_t));
*work = (blob_create_wants_work_t) {
.connection = connection,
.size = size,
};
snprintf(work->blob_id, sizeof(work->blob_id), "%s", blob_id);
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_connection_blobs_create_wants_work, _tf_ssb_rpc_connection_blobs_create_wants_after_work, work);
}
else
{
@ -776,6 +832,10 @@ static void _tf_ssb_connection_send_history_stream_callback(tf_ssb_connection_t*
{
tf_ssb_connection_run_work(connection, _tf_ssb_connection_send_history_stream_work, _tf_ssb_connection_send_history_stream_after_work, user_data);
}
else
{
_tf_ssb_connection_send_history_stream_after_work(connection, -1, user_data);
}
}
static void _tf_ssb_connection_send_history_stream(tf_ssb_connection_t* connection, int32_t request_number, const char* author, int64_t sequence, bool keys, bool live)
@ -1225,7 +1285,6 @@ static void _tf_ssb_rpc_delete_blobs_work(tf_ssb_t* ssb, void* user_data)
static void _tf_ssb_rpc_delete_blobs_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
tf_ssb_unref(ssb);
}
static void _tf_ssb_rpc_start_delete_callback(tf_ssb_t* ssb, void* user_data)

View File

@ -16,6 +16,11 @@
#include <time.h>
#include <unistd.h>
#if defined(_WIN32)
#define WIFEXITED(x) 1
#define WEXITSTATUS(x) (x)
#endif
void tf_ssb_test_id_conversion(const tf_test_options_t* options)
{
tf_printf("Testing id conversion.\n");
@ -192,7 +197,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "Hello, world!"));
bool stored = false;
JSValue signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
JSValue signed_message = tf_ssb_sign_message(ssb0, id0, priv0, 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);
@ -202,7 +207,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "First post."));
stored = false;
signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
signed_message = tf_ssb_sign_message(ssb0, id0, priv0, 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);
@ -217,7 +222,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
JS_SetPropertyUint32(context0, mentions, 0, mention);
JS_SetPropertyStr(context0, obj, "mentions", mentions);
stored = false;
signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
signed_message = tf_ssb_sign_message(ssb0, id0, priv0, 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);
@ -276,7 +281,7 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
JS_SetPropertyStr(context0, obj, "type", JS_NewString(context0, "post"));
JS_SetPropertyStr(context0, obj, "text", JS_NewString(context0, "Message to self."));
stored = false;
signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
signed_message = tf_ssb_sign_message(ssb0, id0, priv0, 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);
@ -549,7 +554,7 @@ void tf_ssb_test_following(const tf_test_options_t* options)
JS_SetPropertyStr(context, message, "contact", JS_NewString(context, contact)); \
JS_SetPropertyStr(context, message, "following", follow ? JS_TRUE : JS_FALSE); \
JS_SetPropertyStr(context, message, "blocking", block ? JS_TRUE : JS_FALSE); \
signed_message = tf_ssb_sign_message(ssb0, id, priv, message); \
signed_message = tf_ssb_sign_message(ssb0, id, priv, message, NULL, 0); \
stored = false; \
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored); \
_wait_stored(ssb0, &stored); \
@ -608,7 +613,7 @@ void tf_ssb_test_bench(const tf_test_options_t* options)
for (int i = 0; i < k_messages; i++)
{
bool stored = false;
JSValue signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj);
JSValue signed_message = tf_ssb_sign_message(ssb0, id0, priv0, obj, NULL, 0);
tf_ssb_verify_strip_and_store_message(ssb0, signed_message, _message_stored, &stored);
JS_FreeValue(tf_ssb_get_context(ssb0), signed_message);
_wait_stored(ssb0, &stored);
@ -819,3 +824,45 @@ void tf_ssb_test_go_ssb_room(const tf_test_options_t* options)
uv_loop_close(&loop);
}
#if !TARGET_OS_IPHONE
static void _write_file(const char* path, const char* contents)
{
FILE* file = fopen(path, "w");
if (!file)
{
printf("Unable to write %s: %s.\n", path, strerror(errno));
fflush(stdout);
abort();
}
fputs(contents, file);
fclose(file);
}
#define TEST_ARGS " --ssb-port=0 --http-port=0 --https-port=0"
void tf_ssb_test_encrypt(const tf_test_options_t* options)
{
_write_file("out/test.js",
"async function main() {\n"
" let a = await ssb.createIdentity('test');\n"
" let b = await ssb.createIdentity('test');\n"
" let c = await ssb.privateMessageEncrypt('test', a, [a, b], \"{'foo': 1}\");\n"
" if (!c.endsWith('.box')) {\n"
" exit(1);\n"
" }\n"
" print(await ssb.privateMessageDecrypt('test', a, c));\n"
"}\n"
"main().catch(() => exit(2));\n");
unlink("out/testdb.sqlite");
char command[256];
snprintf(command, sizeof(command), "%s run --db-path=out/testdb.sqlite -s out/test.js" TEST_ARGS, options->exe_path);
tf_printf("%s\n", command);
int result = system(command);
(void)result;
assert(WIFEXITED(result));
printf("returned %d\n", WEXITSTATUS(result));
assert(WEXITSTATUS(result) == 0);
}
#endif

View File

@ -47,4 +47,10 @@ void tf_ssb_test_bench(const tf_test_options_t* options);
*/
void tf_ssb_test_go_ssb_room(const tf_test_options_t* options);
/**
** Test encrypting a private message.
** @param options The test options.
*/
void tf_ssb_test_encrypt(const tf_test_options_t* options);
/** @} */

View File

@ -266,32 +266,47 @@ static void _test_promise_remote_reject(const tf_test_options_t* options)
static void _test_database(const tf_test_options_t* options)
{
_write_file("out/test.js",
"var db = new Database('testdb');\n"
"if (db.get('a')) {\n"
" exit(1);\n"
"}\n"
"db.set('a', 1);\n"
"if (db.get('a') != 1) {\n"
" exit(2);\n"
"}\n"
"db.set('b', 2);\n"
"db.set('c', 3);\n"
"async function main() {\n"
" var db = new Database('testdb');\n"
" if (await db.get('a')) {\n"
" exit(1);\n"
" }\n"
" await db.set('a', 1);\n"
" if (await db.get('a') != 1) {\n"
" exit(2);\n"
" }\n"
" await db.exchange('b', null, 1);\n"
" await db.exchange('b', 1, 2);\n"
" if (await db.get('b') != 2) {\n"
" exit(5);\n"
" }\n"
" await db.set('c', 3);\n"
" await db.set('d', 3);\n"
" await db.remove('d', 3);\n"
" if (JSON.stringify(await db.getLike('b%')) != '{\"b\":\"2\"}') {\n"
" exit(6);\n"
" }\n"
"\n"
"var expected = ['a', 'b', 'c'];\n"
"var have = db.getAll();\n"
"for (var i = 0; i < have.length; i++) {\n"
" var item = have[i];\n"
" if (expected.indexOf(item) == -1) {\n"
" print('Did not find ' + item + ' in db.');\n"
" exit(3);\n"
" } else {\n"
" expected.splice(expected.indexOf(item), 1);\n"
" var expected = ['a', 'b', 'c'];\n"
" var have = await db.getAll();\n"
" for (var i = 0; i < have.length; i++) {\n"
" var item = have[i];\n"
" if (expected.indexOf(item) == -1) {\n"
" print('Did not find ' + item + ' in db.');\n"
" exit(3);\n"
" } else {\n"
" expected.splice(expected.indexOf(item), 1);\n"
" }\n"
" }\n"
" if (expected.length) {\n"
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
" exit(4);\n"
" }\n"
" if (JSON.stringify(await databases.list('%')) != '[\"testdb\"]') {\n"
" exit(7);\n"
" }\n"
"}\n"
"if (expected.length) {\n"
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
" exit(4);\n"
"}\n");
"main();");
char command[256];
unlink("out/test_db0.sqlite");
@ -899,6 +914,7 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "bench", tf_ssb_test_bench, false);
_tf_test_run(options, "auto", _test_auto, false);
_tf_test_run(options, "go-ssb-room", tf_ssb_test_go_ssb_room, true);
_tf_test_run(options, "encrypt", tf_ssb_test_encrypt, false);
tf_printf("Tests completed.\n");
#endif
}

View File

@ -435,6 +435,16 @@ const char* tf_util_backtrace_string()
return tf_util_backtrace_to_string(buffer, count);
}
void tf_util_print_backtrace()
{
const char* bt = tf_util_backtrace_string();
if (bt)
{
tf_printf("%s\n", bt);
}
tf_free((void*)bt);
}
#if defined(__ANDROID__)
typedef struct _android_backtrace_t
{

View File

@ -126,6 +126,11 @@ const char* tf_util_backtrace_to_string(void* const* buffer, int count);
*/
const char* tf_util_backtrace_string();
/**
** Print a stack backtrace of the calling thread.
*/
void tf_util_print_backtrace();
/**
** Convert a function pointer to its name, if possible.
** @return The function name or null.

View File

@ -1,2 +1,2 @@
#define VERSION_NUMBER "0.0.20-wip"
#define VERSION_NAME "One word all lowercase four words all uppercase."
#define VERSION_NUMBER "0.0.21-wip"
#define VERSION_NAME "Psst. Look behind you."

View File

@ -42,20 +42,19 @@ try:
driver.switch_to.frame(driver.find_element(By.ID, 'document'))
wait.until(expected_conditions.presence_of_element_located((By.LINK_TEXT, 'identity'))).click()
driver.switch_to.default_content()
wait.until(expected_conditions.presence_of_element_located((By.ID, 'content')))
# StaleElementReferenceException
while True:
try:
driver.switch_to.default_content()
wait.until(expected_conditions.presence_of_element_located((By.ID, 'content')))
driver.switch_to.frame(wait.until(expected_conditions.presence_of_element_located((By.ID, 'document'))))
wait.until(expected_conditions.presence_of_element_located((By.ID, 'create_id'))).click()
driver.switch_to.alert.accept()
break
except:
pass
wait.until(expected_conditions.presence_of_element_located((By.ID, 'create_id'))).click()
driver.switch_to.alert.accept()
# StaleElementReferenceException
while True:
try:
@ -126,7 +125,8 @@ try:
driver.switch_to.default_content()
driver.find_element(By.ID, 'allow').click()
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.LINK_TEXT, 'logout testuser').click()
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.ID, 'identity').click()
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.ID, 'logout').click()
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'login_label').click()
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'name').send_keys('testuser')
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'password').send_keys('test_password')
@ -134,13 +134,8 @@ try:
wait.until(expected_conditions.presence_of_element_located((By.ID, 'content')))
# NoSuchElementException
while True:
try:
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.LINK_TEXT, 'logout testuser').click()
break
except:
pass
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.ID, 'identity').click()
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.ID, 'logout').click()
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'guest_label').click()
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'guestButton').click()
@ -149,7 +144,7 @@ try:
wait.until(expected_conditions.presence_of_element_located((By.TAG_NAME, 'tf-app'))).shadow_root
driver.switch_to.default_content()
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.LINK_TEXT, 'logout guest').click()
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.ID, 'logout').click()
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'login_label').click()
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'name').send_keys('testuser')
@ -190,7 +185,8 @@ try:
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'confirm').send_keys('new_password')
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'loginButton').click()
wait.until(expected_conditions.presence_of_element_located((By.ID, 'document')))
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.LINK_TEXT, 'logout testuser').click()
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.ID, 'identity').click()
driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.ID, 'logout').click()
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'login_label').click()
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'name').send_keys('testuser')
driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'password').send_keys('test_password')

View File

@ -3,7 +3,7 @@
if [ -z $ANDROID_NDK_ROOT ]; then
ANDROID_NDK_ROOT=~/Android/Sdk/ndk/26.1.10909125
fi
OPENSSL_VERSION=3.3.0
OPENSSL_VERSION=3.3.1
API_LEVEL=24

View File

@ -1,6 +1,6 @@
#!/bin/bash
OPENSSL_VERSION=3.3.0
OPENSSL_VERSION=3.3.1
API_LEVEL=28
@ -14,7 +14,7 @@ if [ ! -d out/openssl-${OPENSSL_VERSION} ]
then
if [ ! -f out/openssl-${OPENSSL_VERSION}.tar.gz ]
then
curl https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz -o out/openssl-${OPENSSL_VERSION}.tar.gz || exit 128
curl -L https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz -o out/openssl-${OPENSSL_VERSION}.tar.gz || exit 128
fi
tar -C out/ -xzf out/openssl-${OPENSSL_VERSION}.tar.gz || exit 128
fi

View File

@ -1,6 +1,6 @@
#!/bin/bash
OPENSSL_VERSION=3.3.0
OPENSSL_VERSION=3.3.1
API_LEVEL=24

View File

@ -1,4 +1,4 @@
VERSION=3.1.3
VERSION=3.1.4
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js -O deps/lit/lit-all.min.js
wget https://cdn.jsdelivr.net/gh/lit/dist@$VERSION/all/lit-all.min.js.map -O deps/lit/lit-all.min.js.map
cp -fv deps/lit/* apps/blog/