Compare commits

..

10 Commits

19 changed files with 486 additions and 253 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

@ -1,18 +1,54 @@
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');
return null;
});
tfrpc.register(async function reload() {
await main();
});
async function main() {
// Get body.html
const body = utf8Decode(await getFile("body.html"));
const body = utf8Decode(await getFile('body.html'));
// Build the document
const document = `
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="tildefriends.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>
<script src="tf-identity-manager.js" type="module"></script>
</head>
<body>
<body class="flex-column">
${body}
</body>
</html>`;

View File

@ -1,130 +1,20 @@
<h1>h1</h1>
<h2>h2</h2>
<h3>hxc3</h3>
<h1>Your settings</h1>
Notice: this example app fetches an image and an audio file from a third-party website.
Those will not work offline. <a href="/">This is a link.</a>
<div class="box flex-column">
<h2>Appearance</h2>
<br />
<br />
<button class="red" onclick="hello()">button.red</button>
<button class="green">button.green</button>
<button class="blue">button.blue</button>
<button class="yellow">button.yellow</button>
<button>button</button>
<br />
<br />
<code>
x = 5;
y = 6;
z = x + y;
</code>
<br />
<br />
<audio controls>
<source src="https://www.audiocheck.net/Audio/audiocheck.net_brownnoise.ogg" type="audio/ogg">
Your browser does not support the audio element.
</audio>
<br />
<img src="https://picsum.photos/id/37/500/250" alt="Italian Trulli">
<div class="box flex">
Hello
<div class="box flex">
Hello
<div class="box flex">
Hello
<div class="box flex">
Hello
</div>
</div>
</div>
<tf-theme-picker></tf-theme-picker>
</div>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce dignissim leo a urna gravida, vel pulvinar magna blandit. Cras eleifend, elit ac faucibus gravida, justo mauris ornare nisi, eget ultrices lorem tortor vitae purus. Quisque et dui arcu. Nam semper, mauris id molestie imperdiet, risus nunc dignissim dolor, nec cursus elit mi sit amet dui. Nulla aliquam id mauris sed posuere. Nam mollis velit luctus accumsan aliquam. In tempor, felis id finibus tincidunt, erat nulla vehicula orci, a venenatis mauris nunc et diam. Aliquam lorem sem, iaculis ut mollis in, feugiat a mauris. Nam laoreet vestibulum leo a aliquet. Nam sit amet neque erat.</p>
<div class="box flex-column">
<h2>Danger Zone</h2>
<p>Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque porttitor, sem ac pretium accumsan, dui sapien placerat ligula, ut maximus lacus eros sed tortor. Vivamus finibus facilisis felis, quis dictum felis vestibulum nec. Mauris eu facilisis est, nec tempor sem. Quisque ac ultricies tortor. Morbi et ante at dolor accumsan molestie. Curabitur facilisis condimentum lorem a luctus. Quisque lectus risus, vestibulum non malesuada quis, porta sed urna. Sed elementum magna in velit sagittis, vel fringilla ipsum pulvinar. Morbi nec lectus egestas, laoreet erat fringilla, tristique quam.</p>
<h3>Manage your identities</h3>
<tf-identity-manager></tf-identity-manager>
<p>Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur eu mattis ante. Donec venenatis pretium ornare. Nulla vel purus cursus, molestie velit a, vehicula mi. Phasellus ac eleifend sapien, in euismod mauris. Donec quis nisi sodales, accumsan mi sed, malesuada purus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent venenatis enim et nisi interdum, nec sodales diam suscipit. Etiam nisl neque, dapibus id felis eget, laoreet posuere eros. Donec arcu neque, aliquam vel fringilla ut, laoreet in velit. Ut tincidunt rutrum eros vel fringilla. Sed at eleifend sem. Pellentesque ut leo in ligula accumsan dignissim quis at justo. Donec luctus felis sed lacus pharetra aliquam. Nam volutpat quis tellus eget lobortis. Proin ultrices ante vitae quam efficitur accumsan.</p>
<h3>Change my password</h3>
<tf-password-form></tf-password-form>
<p>Here is a quote from WWF's website:</p>
<blockquote cite="http://www.worldwildlife.org/who/index.html">
For 60 years, WWF has worked to help people and nature thrive. As the world's leading conservation organization, WWF works in nearly 100 countries. At every level, we collaborate with people around the world to develop and deliver innovative solutions that protect communities, wildlife, and the places in which they live.
</blockquote>
<table>
<tr>
<th>Company</th>
<th>Contact</th>
<th>Country</th>
</tr>
<tr>
<td>Alfreds Futterkiste</td>
<td>Maria Anders</td>
<td>Germany</td>
</tr>
<tr>
<td>Centro comercial Moctezuma</td>
<td>Francisco Chang</td>
<td>Mexico</td>
</tr>
<tr>
<td>Ernst Handel</td>
<td>Roland Mendel</td>
<td>Austria</td>
</tr>
<tr>
<td>Island Trading</td>
<td>Helen Bennett</td>
<td>UK</td>
</tr>
<tr>
<td>Laughing Bacchus Winecellars</td>
<td>Yoshi Tannamuri</td>
<td>Canada</td>
</tr>
<tr>
<td>Magazzini Alimentari Riuniti</td>
<td>Giovanni Rovelli</td>
<td>Italy</td>
</tr>
</table>
<h2>An Unordered HTML List</h2>
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
<h2>An Ordered HTML List</h2>
<ol>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ol>
<form">
<textarea rows="5" cols="32">This is a textarea.</textarea>
<br />
<div class="flex-column" style="align-items: start;">
<input type ="radio" />
<input type ="radio" />
<input type ="checkbox" />
<input type ="checkbox" />
</div>
<br />
<input class="green" type="submit" value="Button">
</form>
<h3>Delete your account</h3>
<tf-delete-account-btn></tf-delete-account-btn>
</div>

120
apps/user_settings/lit-all.min.js vendored Normal file

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,3 +0,0 @@
function hello() {
alert("Hello !");
}

View File

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

View File

@ -0,0 +1,36 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfDeleteAccountButtonElement extends LitElement {
static get properties() {
return {};
}
constructor() {
super();
}
deleteAccount() {
const res = confirm(
'Are you really sure you want to delete your account ?'
);
if (!res) return;
console.warn('TODO');
}
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
<span>This action is irreversible !</span>
<button class="red" @click=${this.deleteAccount}>
[Not implemented] Delete my Tilde Friends account
</button>
`;
}
}
customElements.define('tf-delete-account-btn', TfDeleteAccountButtonElement);

View File

@ -0,0 +1,118 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfIdentityManagerElement extends LitElement {
static get properties() {
return {
ids: {type: Array},
};
}
constructor() {
super();
this.ids = [];
this.load();
}
async load() {
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)) +
'\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-v1.css" />
<style>
.id-span {
font-family: monospace;
margin-left: 8px;
}
</style>
<h4>Create a new identity</h4>
<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="import-id-textarea" style="width: 100%" rows="4"></textarea>
<button class="green" @click=${this.importIdentity}>
Import Identity
</button>
<h4>Warning !</h4>
<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" @click=${() => this.deleteIdentity(id)}>
Delete Identity
</button>
<span class="id-span">${id}</span>
</li>`
)}
</ul>`;
}
}
customElements.define('tf-identity-manager', TfIdentityManagerElement);

View File

@ -0,0 +1,82 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfPasswordFormElement extends LitElement {
static get properties() {
return {
//selected: {type: String},
};
}
constructor() {
super();
}
/**
* Checks a password against different requirements
* @param {string} password the password to validate
* @returns
*/
validatePassword(password) {
// TODO(tasiaiso)
return true;
}
submitPassword() {
const currentPwd = this.shadowRoot.getElementById('current').value;
const newPwd = this.shadowRoot.getElementById('new').value;
const repeatPwd = this.shadowRoot.getElementById('Repeat').value;
if (!(newPwd === repeatPwd)) {
return;
}
// TODO
// tfrpc.changePassword()
}
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
<style>
.grid {
display: grid;
grid-template-columns: auto auto;
}
</style>
<div class="grid">
<label for="current">Current password:</label>
<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"
/>
<label for="repeat">Repeat new password:</label>
<input
type="password"
id="repeat"
name="repeat"
autocomplete="new-password"
/>
</div>
<button @click=${this.submitPassword} class="red">
[Not implemented] Change my password
</button>
`;
}
}
customElements.define('tf-password-form', TfPasswordFormElement);

View File

@ -0,0 +1,51 @@
import {LitElement, html, nothing} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfThemePickerElement extends LitElement {
static get properties() {
return {
selected: {type: String},
themes: {type: Array},
};
}
constructor() {
super();
this.load();
}
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) {
this.selected = event.srcElement.value;
console.log('selected theme', this.selected);
// TODO
}
render() {
return html`
<link rel="stylesheet" href="/static/tildefriends-v1.css" />
<label for="theme">[Not implemented] Choose your theme:</label>
<select
name="theme"
id="theme-select"
?hidden=${!this.themes?.length}
@change=${this.changed}
>
${(this.themes ?? []).map(
(name) => html`<option value=${name}>${name}</option>`
)}
</select>
`;
}
}
customElements.define('tf-theme-picker', TfThemePickerElement);

BIN
bleh.tar.xz Normal file

Binary file not shown.

View File

@ -1,15 +1,14 @@
/*
* Tilde Friends core stylesheet
* This is a prototype; things may change based on feedback.
*
* This Software is an external library that is part of
* Tilde Friends and is shared under the MIT license.
*
* Inject this file in your app at tildefriends.css
* and use this tag to import it:
* <link rel="stylesheet" href="tildefriends.css"/>
* <link rel="stylesheet" href="/static/tildefriends-v1.css"/>
*
* Revision 0 / 2024 M02 19
* v1.0 / 2024 M03 21
*/
body {
@ -19,9 +18,9 @@ body {
button,
.button,
input[type=button],
input[type=submit],
input[type=dropdown] {
input[type='button'],
input[type='submit'],
select {
border: none;
border-radius: 8px;
padding: 8px 12px;
@ -76,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

@ -644,6 +644,7 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
"style.css",
"tfrpc.js",
"w3.css",
"tildefriends-v1.css"
};
const char* k_map[][2] = {

View File

@ -485,17 +485,18 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
{
tf_printf("\n%s run [options]\n\n", file);
tf_printf("options\n");
tf_printf(" -s, --script script Script to run (default: core/core.js).\n");
tf_printf(" -b, --ssb-port port Port on which to run SSB (default: 8008, 0 disables).\n");
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(" -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");
tf_printf(" -z, --zip path Zip archive from which to load files.\n");
tf_printf(" -v, --verbose Log raw messages.\n");
tf_printf(" -h, --help Show this usage information.\n");
tf_printf(" -s, --script script Script to run (default: core/core.js).\n");
tf_printf(" -b, --ssb-port port Port on which to run SSB (default: 8008, 0 disables).\n");
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");
tf_printf(" -z, --zip path Zip archive from which to load files.\n");
tf_printf(" -v, --verbose Log raw messages.\n");
tf_printf(" -h, --help Show this usage information.\n");
return EXIT_FAILURE;
}