Compare commits

...

9 Commits

16 changed files with 153 additions and 156 deletions

View File

@ -867,7 +867,9 @@ dist: release-apk iosrelease-ipa
--exclude=deps/sqlite/shell.c \
--exclude=deps/zlib/contrib/vstudio \
--exclude=deps/zlib/doc \
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz out/tildefriends-$(VERSION_NUMBER)
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz \
-C out/ \
tildefriends-$(VERSION_NUMBER)
@echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk"
@cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"

View File

@ -1,5 +0,0 @@
{
"type": "tildefriends-app",
"emoji": "🪪",
"previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256"
}

View File

@ -1,93 +0,0 @@
import * as tfrpc from '/tfrpc.js';
tfrpc.register(async function get_private_key(id) {
return bip39Words(await ssb.getPrivateKey(id));
});
tfrpc.register(async function create_id(id) {
return await ssb.createIdentity();
});
tfrpc.register(async function add_id(id) {
return await ssb.addIdentity(bip39Bytes(id));
});
tfrpc.register(async function delete_id(id) {
return await ssb.deleteIdentity(id);
});
tfrpc.register(async function reload() {
await main();
});
async function main() {
let ids = await ssb.getIdentities();
await app.setDocument(
`<body style="color: #fff">
<script>const handler = {};</script>
<script type="module">
import * as tfrpc from '/static/tfrpc.js';
handler.export_id = async function export_id(event) {
let id = event.srcElement.dataset.id;
let element = document.createElement('textarea');
element.value = await tfrpc.rpc.get_private_key(id);
element.style = 'width: 100%; read-only: true';
element.readOnly = true;
event.srcElement.parentElement.appendChild(element);
event.srcElement.onclick = event => handler.hide_id(event, element);
}
handler.add_id = async function add_id(event) {
let id = document.getElementById('add_id').value;
try {
let new_id = await tfrpc.rpc.add_id(id);
alert('Successfully imported: ' + new_id);
await tfrpc.rpc.reload();
} catch (e) {
alert('Error importing identity: ' + e);
}
}
handler.create_id = async function create_id(event) {
try {
let id = await tfrpc.rpc.create_id();
alert('Successfully created: ' + id);
await tfrpc.rpc.reload();
} catch (e) {
alert('Error creating identity: ' + e);
}
}
handler.hide_id = function hide_id(event, element) {
element.parentNode.removeChild(element);
event.srcElement.onclick = handler.export_id;
}
handler.delete_id = async function delete_id(event) {
let id = event.srcElement.dataset.id;
try {
if (prompt('Are you sure you want to delete "' + id + '"? It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.') === 'DELETE') {
if (await tfrpc.rpc.delete_id(id)) {
alert('Successfully deleted ID: ' + id);
}
await tfrpc.rpc.reload();
}
} catch (e) {
alert('Error deleting ID: ' + e);
}
}
</script>
<h1>SSB Identity Management</h1>
<h2>Create a new identity</h2>
<button id="create_id" onclick="handler.create_id()">Create Identity</button>
<h2>Import an SSB Identity from 12 BIP39 English Words</h2>
<textarea id="add_id" style="width: 100%" rows="4"></textarea><button id="add" onclick="handler.add_id(event)">Import Identity</button>
<h2>Identities</h2>
<ul>` +
ids
.map(
(id) => `<li>
<button onclick="handler.export_id(event)" data-id="${id}">Export Identity</button>
<button onclick="handler.delete_id(event)" data-id="${id}">Delete Identity</button>
${id}
</li>`
)
.join('\n') +
` </ul>
</body>`
);
}
main();

View File

@ -1,4 +1 @@
{
"type": "tildefriends-app",
"emoji": "⚙️"
}
{"type": "tildefriends-app", "emoji": "⚙️"}

View File

@ -3,18 +3,34 @@ import * as tfrpc from '/tfrpc.js';
tfrpc.register(async function getIdentities() {
return ssb.getIdentities();
});
tfrpc.register(async function createID(id) {
return await ssb.createIdentity();
});
tfrpc.register(async function getPrivateKey(id) {
return bip39Words(await ssb.getPrivateKey(id));
});
tfrpc.register(async function addID(id) {
return await ssb.addIdentity(bip39Bytes(id));
});
tfrpc.register(async function deleteID(id) {
return await ssb.deleteIdentity(id);
});
tfrpc.register(async function getThemes() {
// TODO
return ['solarized', 'gruvbox', 'light'];
});
tfrpc.register(async function getTheme() {
// TODO
return 'gruvbox';
});
tfrpc.register(async function setTheme() {
// TODO
console.warn("setTheme called - not implemented")
console.warn('setTheme called - not implemented');
return null;
});
tfrpc.register(async function reload() {
await main();
});
async function main() {
// Get body.html
@ -25,9 +41,7 @@ async function main() {
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/static/tildefriends-latest.css"/>
<link rel="stylesheet" href="style.css"/>
<script src="script.js" type="module"></script>
<link rel="stylesheet" href="/static/tildefriends-v1.css"/>
<script src="tf-theme-picker.js" type="module"></script>
<script src="tf-password-form.js" type="module"></script>
<script src="tf-delete-account-btn.js" type="module"></script>

View File

@ -9,12 +9,12 @@
<div class="box flex-column">
<h2>Danger Zone</h2>
<h3>Change my password</h3>
<tf-password-form></tf-password-form>
<h3>Manage your identities</h3>
<tf-identity-manager></tf-identity-manager>
<h3>Change my password</h3>
<tf-password-form></tf-password-form>
<h3>Delete your account</h3>
<tf-delete-account-btn></tf-delete-account-btn>
</div>

View File

@ -1 +0,0 @@
/* */

View File

@ -1 +0,0 @@
/* */

View File

@ -22,7 +22,7 @@ class TfDeleteAccountButtonElement extends LitElement {
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-latest.css"/>
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
<span>This action is irreversible !</span>

View File

@ -18,13 +18,60 @@ class TfIdentityManagerElement extends LitElement {
this.ids = await tfrpc.rpc.getIdentities();
}
async createIdentity() {
try {
const id = await tfrpc.rpc.createID();
alert('Successfully created: ' + id);
await tfrpc.rpc.reload();
} catch (err) {
alert('Error creating identity: ' + err);
}
}
async importIdentity() {
const words = this.renderRoot?.querySelector('#import-id-textarea').value;
if (!words) return;
try {
const newID = await tfrpc.rpc.addID(words);
if (newID) alert('Successfully imported a new identity.');
else alert('This identity already exists or is invalid.');
await tfrpc.rpc.reload();
} catch (err) {
alert('Error importing identity: ' + err);
}
}
async exportIdentity(id) {
alert('Your private key is:\n' + (await tfrpc.rpc.getPrivateKey(id)));
alert(
'Your private key is:\n' +
(await tfrpc.rpc.getPrivateKey(id)) +
'\nDo not share it with anyone!'
);
}
async deleteIdentity(id) {
try {
if (
prompt(
'Are you sure you want to delete "' +
id +
'"? It cannot be recovered without the exported phrase.\\n\\nEnter the word "DELETE" to confirm you wish to delete it.'
) === 'DELETE'
) {
if (await tfrpc.rpc.deleteID(id)) {
alert('Successfully deleted ID: ' + id);
}
await tfrpc.rpc.reload();
}
} catch (e) {
alert('Error deleting ID: ' + e);
}
}
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-latest.css"/>
return html` <link rel="stylesheet" href="/static/tildefriends-v1.css" />
<style>
.id-span {
font-family: monospace;
@ -33,24 +80,34 @@ class TfIdentityManagerElement extends LitElement {
</style>
<h4>Create a new identity</h4>
<button id="create-id" class="btn-green">[Not implemented] Create Identity</button>
<button id="create-id" class="green" @click=${this.createIdentity}>
Create Identity
</button>
<h4>Import an SSB Identity from 12 BIP39 English Words</h4>
<textarea id="add-id" style="width: 100%" rows="4"></textarea>
<button class="green">[Not implemented] Import Identity</button>
<textarea id="import-id-textarea" style="width: 100%" rows="4"></textarea>
<button class="green" @click=${this.importIdentity}>
Import Identity
</button>
<h4>Warning !</h4>
<strong>Anybody that has access to your private key can gain total access over your account.</strong>
<br><br>
<strong
>Anybody that knows your private key can gain total access over your
account.</strong
>
<br /><br />
Tilde Friends' contributors will never ask you for your private key !
<ul>
${this.ids.map(
(id) =>
html`
<li>
<button class="blue" @click=${() => this.exportIdentity(id)}>Export Identity</button>
<button class="red">[Not implemented] Delete Identity</button>
html` <li>
<button class="blue" @click=${() => this.exportIdentity(id)}>
Export Identity
</button>
<button class="red" @click=${() => this.deleteIdentity(id)}>
Delete Identity
</button>
<span class="id-span">${id}</span>
</li>`
)}

View File

@ -37,7 +37,7 @@ class TfPasswordFormElement extends LitElement {
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-latest.css"/>
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
<style>
.grid {
@ -48,19 +48,33 @@ class TfPasswordFormElement extends LitElement {
<div class="grid">
<label for="current">Current password:</label>
<input type="password" id="current" name="current" autocomplete="current-password" />
<input
type="password"
id="current"
name="current"
autocomplete="current-password"
/>
<label for="new">Enter new password:</label>
<input type="password" id="new" name="new" autocomplete="new-password" />
<input
type="password"
id="new"
name="new"
autocomplete="new-password"
/>
<label for="repeat">Repeat new password:</label>
<input type="password" id="repeat" name="repeat" autocomplete="new-password" />
<input
type="password"
id="repeat"
name="repeat"
autocomplete="new-password"
/>
</div>
<button @click=${this.submitPassword} class="red">
[Not implemented] Change my password
</button>
`;
}
}

View File

@ -1,4 +1,4 @@
import {LitElement, html} from './lit-all.min.js';
import {LitElement, html, nothing} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfThemePickerElement extends LitElement {
@ -16,6 +16,10 @@ class TfThemePickerElement extends LitElement {
async load() {
this.themes = await tfrpc.rpc.getThemes();
this.selected = await tfrpc.rpc.getTheme();
let select = this.renderRoot?.querySelector('#theme-select');
select.value = this.selected;
}
changed(event) {
@ -26,12 +30,19 @@ class TfThemePickerElement extends LitElement {
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-latest.css"/>
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
<label for="theme">[Not implemented] Choose your theme:</label>
<select name="theme" ?hidden=${!this.themes?.length} @change=${this.changed}>
${(this.themes ?? []).map((id) => html`<option value=${id}>${id}</option>`)}
<select
name="theme"
id="theme-select"
?hidden=${!this.themes?.length}
@change=${this.changed}
>
${(this.themes ?? []).map(
(name) => html`<option value=${name}>${name}</option>`
)}
</select>
`;
}

BIN
bleh.tar.xz Normal file

Binary file not shown.

View File

@ -8,7 +8,7 @@
* and use this tag to import it:
* <link rel="stylesheet" href="/static/tildefriends-v1.css"/>
*
* v1.0.0 / 2024 M03 21
* v1.0 / 2024 M03 21
*/
body {
@ -18,8 +18,8 @@ body {
button,
.button,
input[type=button],
input[type=submit],
input[type='button'],
input[type='submit'],
select {
border: none;
border-radius: 8px;
@ -75,7 +75,8 @@ table {
width: 100%;
}
td, th {
td,
th {
border: 1px solid #ffffff40;
text-align: left;
padding: 8px;

BIN
sqlite3

Binary file not shown.

View File

@ -490,6 +490,7 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
tf_printf(" -p, --http-port port Port on which to run Tilde Friends web server (default: 12345).\n");
tf_printf(" -q, --https-port port Port on which to run secure Tilde Friends web server (default: 12346).\n");
tf_printf(" -d, --db-path path SQLite database path (default: %s).\n", k_db_path_default);
tf_printf(" -k, --ssb-network-key key SSB network key to use.\n");
tf_printf(" -n, --count count Number of instances to run.\n");
tf_printf(" -a, --args args Arguments of the format key=value,foo=bar,verbose=true.\n");
tf_printf(" -o, --one-proc Run everything in one process (unsafely!).\n");