Compare commits

..

44 Commits

Author SHA1 Message Date
fae2771645 docs: docs, misc changes
- instruction for writing apps
- add NOTES.md to .gitignore
2024-05-11 23:40:16 +02:00
2bb6d68122 docs: running.md 2024-05-11 23:02:58 +02:00
5c8c6e8760 docs: remove unicode chars 2024-05-11 21:54:55 +02:00
85ac8080f4 chore(doc): run and modify formatting rules 2024-05-11 21:48:06 +02:00
0751699bc8 docs: more docs guidelines 2024-05-11 21:22:25 +02:00
5551fd2dea chore: small changes in README and LICENSE 2024-05-11 19:55:00 +02:00
69b2e2a955 chore: rename lint script to format, tell prettier to ignore markdown files 2024-05-11 19:48:47 +02:00
34c7fa8312 docs: new documentation 2024-05-11 19:47:14 +02:00
396f37ee3b chore(docs): add markdownlint-cli 2024-05-11 14:47:28 +02:00
7caf4a0173 Fix numerous issues around setting the first registered used as an admin. 2024-05-10 22:21:59 -04:00
385524352c Refactor most uses of uv_queue_work to go through a helper that keeps track of thread business, traces, and is generally less code. 2024-05-08 21:00:37 -04:00
5ca5323782 Fix /speedscope/ => deps/speedscope/index.html. 2024-05-08 20:57:53 -04:00
ba6da856bb Let trace truncate names more if it means we can generate valid JSON. 2024-05-08 20:56:44 -04:00
c0e72246cc Trying to understand a lingering 'previous message doesn't exist.' And format. 2024-05-08 12:20:57 -04:00
c7ab5447ea Move / redirect handling to C 2024-05-05 15:24:15 -04:00
5fdd461159 Fix setting multiline admin settings. 2024-05-05 15:19:00 -04:00
421955f2a0 getIdentityInfo => C. 2024-05-05 13:48:22 -04:00
a28f6985ed getActiveIdentity => C. 2024-05-05 12:55:32 -04:00
8244dddab7 Latest libbacktrace. 2024-05-05 12:03:57 -04:00
a5ca436eaa Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-05-02 20:35:24 -04:00
d7fc1c2c88 Minor style/layout changes. 2024-05-02 20:35:00 -04:00
382627ef8d Latest codemirror. 2024-05-02 20:11:54 -04:00
17667b4cf8 make format 2024-05-02 20:10:56 -04:00
5231ec22e7 More trying to clean up lingering requests. 2024-05-02 19:59:54 -04:00
929ae1b709 After eyeballing lingering requests, clean up requests after the response to an async (non-streaming) request is done. 2024-05-02 19:37:38 -04:00
f01f7a5ab9 Show active RPC requests in the connections tab. Probably TMI, but I want greater introspection into what is going on, and this seemed like a positive step. 2024-05-02 19:02:23 -04:00
a2dce833f8 Fix another shutdown issue. 2024-05-02 12:30:22 -04:00
de6c7a4fd4 SSB app stylin'. 2024-05-01 12:34:36 -04:00
4edee0f7f6 Allow importing from a single app .json. 2024-04-30 21:43:14 -04:00
988a807fa4 Admin app styles. 2024-04-30 19:18:33 -04:00
5258e4253d Better identity app layout on mobile. 2024-04-29 12:24:35 -04:00
09ba86dec5 Show replies to gatherings. 2024-04-28 12:55:17 -04:00
78d8a1aa23 Set the core room app icon. 2024-04-28 12:33:19 -04:00
22def15209 Merge branch 'main' of https://dev.tildefriends.net/cory/tildefriends 2024-04-28 12:25:31 -04:00
4cbda7a849 Improve file errors so that it doesn't look like everything has failed when we see there's no https cert available. 2024-04-28 12:25:12 -04:00
be85a620ef Fix app delete. 2024-04-28 12:11:13 -04:00
0b07b678b4 Theme the identity app a bit. 2024-04-28 11:57:35 -04:00
4733ce9287 Fix ssb draft discard. 2024-04-28 11:23:28 -04:00
48d6bf4c15 Hook up onJsAlert on android. 2024-04-28 11:04:29 -04:00
8c759bcbac Hide the reactions button if there aren't any. 2024-04-26 18:19:13 -04:00
b5ed7014f6 Fix attaching files (aka WebView file picking) on Android. 2024-04-26 18:10:22 -04:00
6cd9dea186 Merge v0.0.18 compose fixes. 2024-04-24 20:35:36 -04:00
c5ddf3ac99 Fix some ssb compose issues. 2024-04-24 20:19:14 -04:00
a9cb913a47 Working on 0.0.19. 2024-04-24 19:29:17 -04:00
58 changed files with 2771 additions and 776 deletions

View File

@ -1,4 +1,5 @@
.svn
db.sqlite
db.*
out/**/*.o
out/**/*.d
NOTES.md

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ out
*.swo
*.swp
.zsign_cache/
NOTES.md

5
.markdownlint.yaml Normal file
View File

@ -0,0 +1,5 @@
default: true
MD010: false # Ignore tabs in code blocks
MD013: false # Don't wrap lines by default
MD046:
style: "fenced" # Force fenced code blocks

View File

@ -12,3 +12,8 @@ deps
apps/ssb/tribute.esm.js
apps/api/app.js
**/emojis.json
# only markdownlint should deal with the documentation
docs/**/*.md
NOTES.md

View File

@ -3,9 +3,9 @@
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 18
VERSION_NUMBER := 0.0.18
VERSION_NAME := Celebrating totality for upwards of 3m1.4s.
VERSION_CODE := 19
VERSION_NUMBER := 0.0.19-wip
VERSION_NAME := Don't let your loyalty become a burden.
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3450300.zip
LIBUV_URL := https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz
@ -57,11 +57,11 @@ CFLAGS += \
-fno-exceptions \
-g
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-34
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
ANDROID_MIN_SDK_VERSION := 24
ANDROID_TARGET_SDK_VERSION := 34
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-$(ANDROID_TARGET_SDK_VERSION)
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
ANDROID_ARMV7A_TARGETS := \
out/androiddebug-armv7a/tildefriends \
@ -693,7 +693,7 @@ CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefrien
$(CLASS_FILES) &: $(JAVA_FILES)
@echo "[javac] $(CLASS_FILES)"
@javac --release 8 -Xlint:deprecation -classpath $(ANDROID_PLATFORM)/android.jar -d out/classes $(JAVA_FILES)
@javac --release 8 -encoding UTF-8 -Xlint:deprecation -XDuseUnsharedTable=true -classpath $(ANDROID_PLATFORM)/android.jar:$(ANDROID_BUILD_TOOLS)/core-lambda-stubs.jar -d out/classes $(JAVA_FILES)
out/apk/classes.dex: $(CLASS_FILES)
@mkdir -p $(dir $@)
@ -759,7 +759,7 @@ release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-releas
releaseapkgo: out/TildeFriends-arm-release.apk
@adb install -r $<
@adb shell am start com.unprompted.tildefriends/.MainActivity
@adb shell am start com.unprompted.tildefriends/.TildeFriendsActivity
.PHONY: releaseapkgo
# iOS Support

View File

@ -1,4 +1,4 @@
Copyright 2014 Cory McWilliams
Copyright 2014-2024 Cory McWilliams
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@ -4,46 +4,19 @@ Tilde Friends is a tool for making and sharing.
A public instance lives at https://www.tildefriends.net/.
It is both a peer-to-peer social network client, participating in Secure
Scuttlebutt, as well as a platform for writing and running web applications.
It is both a peer-to-peer social network client, participating in Secure Scuttlebutt, as well as a platform for writing and running web applications.
## Goals
1. Make it easy and fun to run all sorts of web applications.
2. Provide security that is easy to understand and protects your data.
3. Make creating and sharing web applications accessible to anyone with a
browser.
## Building
Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for
all of those host platforms plus mingw64, iOS, and android.
1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies
are kept up to date in the tree.
2. To build, run `make debug` or `make release`. An executable will be
generated in a subdirectory of `out/`.
3. It's possible to build for Android, iOS, and Windows on Linux, if you have
the right dependencies in the right places. `make windebug winrelease
iosdebug-ipa iosrelease-ipa release-apk`.
4. To build in docker, `docker build .`.
5. `make format` will normalize formatting to the coding standard.
## Running
By default, running the built `tildefriends` executable will start a web server
at <http://localhost:12345/>. `tildefriends -h` lists further options.
The first user to create an account and log in will be granted administrative
privileges. Further administration can be done at
<http://localhost:12345/~core/admin/>.
3. Make creating and sharing web applications accessible to anyone with a browser.
## Documentation
Docs are a work in progress:
<https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
Docs are a work in progress in the `docs` folder, or alternatively in Tilde Friends: <https://www.tildefriends.net/~cory/wiki/#test-wiki/tf-app-quick-reference>.
## License
All code unless otherwise noted in is provided under the
[MIT](https://opensource.org/licenses/MIT) license.
All code, documentation and assets unless otherwise noted in is provided under the
[MIT](https://opensource.org/licenses/MIT/) license.

View File

@ -1,4 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🎛"
"emoji": "🎛",
"previous": "&vrpS/vE7n588iYv1p8HafDxHB+YDHTrtUbJiu9nGA9I=.sha256"
}

View File

@ -4,9 +4,37 @@
<script>
const g_data = $data;
</script>
<link rel="stylesheet" href="w3.css"></link>
<style>
/* 2018 Valiant Poppy */
.w3-theme-l5 {color:#000 !important; background-color:#fbf3f3 !important}
.w3-theme-l4 {color:#000 !important; background-color:#f3d7d6 !important}
.w3-theme-l3 {color:#000 !important; background-color:#e6afae !important}
.w3-theme-l2 {color:#fff !important; background-color:#da8785 !important}
.w3-theme-l1 {color:#fff !important; background-color:#cd5f5d !important}
.w3-theme-d1 {color:#fff !important; background-color:#a93634 !important}
.w3-theme-d2 {color:#fff !important; background-color:#96302e !important}
.w3-theme-d3 {color:#fff !important; background-color:#832a28 !important}
.w3-theme-d4 {color:#fff !important; background-color:#702423 !important}
.w3-theme-d5 {color:#fff !important; background-color:#5e1e1d !important}
.w3-theme-light {color:#000 !important; background-color:#fbf3f3 !important}
.w3-theme-dark {color:#fff !important; background-color:#5e1e1d !important}
.w3-theme-action {color:#fff !important; background-color:#5e1e1d !important}
.w3-theme {color:#fff !important; background-color:#bd3d3a !important}
.w3-text-theme {color:#bd3d3a !important}
.w3-border-theme {border-color:#bd3d3a !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#bd3d3a !important}
.w3-hover-text-theme:hover {color:#bd3d3a !important}
.w3-hover-border-theme:hover {border-color:#bd3d3a !important}
</style>
</head>
<body style="color: #fff; width: 100%">
<h1>Tilde Friends Administration</h1>
<body class="w3-theme-l4">
<header class="w3-row w3-padding w3-header w3-theme-l1">
<h1>Tilde Friends Administration</h1>
</header>
</body>
<script type="module" src="script.js"></script>
</html>

View File

@ -32,59 +32,54 @@ window.addEventListener('load', function () {
function input_template(key, description) {
if (description.type === 'boolean') {
return html`
<div style="margin-top: 1em">
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
<div>
<input type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input>
<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.checked)}>Set</button>
<div>${description.description}</div>
</div>
</div>
<li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label>
<div class="w3-quarter w3-padding">${description.description}</div>
<input class="w3-quarter w3-check" type="checkbox" ?checked=${description.value} id=${'gs_' + key}></input>
<button class="w3-quarter w3-button w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.checked)}>Set</button>
</li>
`;
} else if (description.type === 'textarea') {
return html`
<div style="margin-top: 1em"">
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
<div style="width: 100%; padding: 0; margin: 0">
<div style="width: 90%; padding: 0 margin: 0">
<textarea style="vertical-align: top; width: 100%" rows=20 cols=80 id=${'gs_' + key}>${description.value}</textarea>
</div>
<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.firstElementChild.value)}>Set</button>
<div>${description.description}</div>
</div>
</div>
<li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label>
<div class="w3-rest w3-padding">${description.description}</div>
<textarea class="w3-input" style="vertical-align: top; resize: vertical" id=${'gs_' + key}>${description.value}</textarea>
<button class="w3-button w3-right w3-quarter w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
</li>
`;
} else {
return html`
<div style="margin-top: 1em">
<label for=${'gs_' + key} style="font-weight: bold">${key}: </label>
<div>
<input type="text" value="${description.value}" id=${'gs_' + key}></input>
<button @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
<div>${description.description}</div>
</div>
</div>
<li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${key}</label>
<div class="w3-quarter w3-padding">${description.description}</div>
<input class="w3-input w3-quarter" type="text" value="${description.value}" id=${'gs_' + key}></input>
<button class="w3-button w3-quarter w3-theme-action" @click=${(e) => global_settings_set(key, e.srcElement.previousElementSibling.value)}>Set</button>
</li>
`;
}
}
const user_template = (user, permissions) => html`
<li>
<button @click=${(e) => delete_user(user)}>Delete</button>
<li class="w3-card w3-margin">
<button class="w3-button w3-theme-action" @click=${(e) => delete_user(user)}>Delete</button>
${user}: ${permissions.map((x) => permission_template(x))}
</li>
`;
const users_template = (users) =>
html`<h2>Users</h2>
<ul>
html`
<header class="w3-container w3-theme-l2"><h2>Users</h2></header>
<ul class="w3-ul">
${Object.entries(users).map((u) => user_template(u[0], u[1]))}
</ul>`;
const page_template = (data) =>
html`<div style="padding: 0; margin: 0; width: 100%; max-width: 100%">
<h2>Global Settings</h2>
<div>
${Object.keys(data.settings)
.sort()
.map((x) => html`${input_template(x, data.settings[x])}`)}
<header class="w3-container w3-theme-l2"><h2>Global Settings</h2></header>
<div class="w3-container">
<ul class="w3-ul">
${Object.keys(data.settings)
.sort()
.map((x) => html`${input_template(x, data.settings[x])}`)}
</ul>
</div>
${users_template(data.users)}
</div> `;

235
apps/admin/w3.css Normal file
View File

@ -0,0 +1,235 @@
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
a{background-color:transparent}a:active,a:hover{outline-width:0}
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
button,input{overflow:visible}button,select{text-transform:none}
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
[type=checkbox],[type=radio]{padding:0}
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
/* End extract */
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
.w3-main,#main{transition:margin-left .4s}
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
.w3-bar .w3-button{white-space:normal}
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
.w3-responsive{display:block;overflow-x:auto}
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
@media (max-width:1205px){.w3-auto{max-width:95%}}
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
.w3-display-position{position:absolute}
.w3-circle{border-radius:50%}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
.w3-left{float:left!important}.w3-right{float:right!important}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

View File

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

View File

@ -19,7 +19,36 @@ tfrpc.register(async function reload() {
async function main() {
let ids = await ssb.getIdentities();
await app.setDocument(
`<body style="color: #fff">
`
<head>
<link rel="stylesheet" href="w3.css"></link>
<style>
/* "2018 Sargasso Sea" */
.w3-theme-l5 {color:#000 !important; background-color:#f3f4f7 !important}
.w3-theme-l4 {color:#000 !important; background-color:#d7dbe3 !important}
.w3-theme-l3 {color:#000 !important; background-color:#b0b6c8 !important}
.w3-theme-l2 {color:#fff !important; background-color:#8892ac !important}
.w3-theme-l1 {color:#fff !important; background-color:#636f8e !important}
.w3-theme-d1 {color:#fff !important; background-color:#40485c !important}
.w3-theme-d2 {color:#fff !important; background-color:#394052 !important}
.w3-theme-d3 {color:#fff !important; background-color:#323848 !important}
.w3-theme-d4 {color:#fff !important; background-color:#2b303d !important}
.w3-theme-d5 {color:#fff !important; background-color:#242833 !important}
.w3-theme-light {color:#000 !important; background-color:#f3f4f7 !important}
.w3-theme-dark {color:#fff !important; background-color:#242833 !important}
.w3-theme-action {color:#fff !important; background-color:#242833 !important}
.w3-theme {color:#fff !important; background-color:#485167 !important}
.w3-text-theme {color:#485167 !important}
.w3-border-theme {border-color:#485167 !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#485167 !important}
.w3-hover-text-theme:hover {color:#485167 !important}
.w3-hover-border-theme:hover {border-color:#485167 !important}
</style>
</head>
<body class="w3-theme-l3">
<script>const handler = {};</script>
<script type="module">
import * as tfrpc from '/static/tfrpc.js';
@ -27,7 +56,8 @@ async function main() {
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.style = 'width: 100%; height: auto; read-only: true; resize: none';
element.classList.add('w3-input');
element.readOnly = true;
event.srcElement.parentElement.appendChild(element);
event.srcElement.onclick = event => handler.hide_id(event, element);
@ -69,23 +99,34 @@ async function main() {
}
}
</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>
<header class="w3-theme w3-padding"><h1>SSB Identity Management</h1></header>
<div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Create a new identity</h2></header>
<footer class="w3-padding">
<button id="create_id" onclick="handler.create_id()" class="w3-button w3-theme">Create Identity</button>
</footer>
</div>
<div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Import an SSB Identity from 12 BIP39 English Words</h2></header>
<textarea id="add_id" style="width: 100%" rows="4" class="w3-input"></textarea>
<footer class="w3-padding">
<button id="add" onclick="handler.add_id(event)" class="w3-button w3-theme">Import Identity</button>
</footer>
</div>
<div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Identities</h2></header>
<ul class="w3-ul">` +
ids
.map(
(id) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis">
<button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button>
<button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button>
${id}
</li>`
)
.join('\n') +
` </ul>
</div>
</body>`
);
}

235
apps/identity/w3.css Normal file
View File

@ -0,0 +1,235 @@
/* W3.CSS 4.15 December 2020 by Jan Egil and Borge Refsnes */
html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}
/* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */
html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}
article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item}
audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline}
audio:not([controls]){display:none;height:0}[hidden],template{display:none}
a{background-color:transparent}a:active,a:hover{outline-width:0}
abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}
b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000}
small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none}
code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible}
button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold}
button,input{overflow:visible}button,select{text-transform:none}
button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}
button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}
button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em}
legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}
[type=checkbox],[type=radio]{padding:0}
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}
[type=search]{-webkit-appearance:textfield;outline-offset:-2px}
[type=search]::-webkit-search-decoration{-webkit-appearance:none}
::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}
/* End extract */
html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden}
h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}
.w3-serif{font-family:serif}.w3-sans-serif{font-family:sans-serif}.w3-cursive{font-family:cursive}.w3-monospace{font-family:monospace}
h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px}
hr{border:0;border-top:1px solid #eee;margin:20px 0}
.w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit}
.w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
.w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1}
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
.w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}
.w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none}
.w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none}
.w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%}
.w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none}
.w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block}
.w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s}
.w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%}
.w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc}
.w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer}
.w3-dropdown-hover:hover .w3-dropdown-content{display:block}
.w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000}
.w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000}
.w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1}
.w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px}
.w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto}
.w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%}
.w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%}
.w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px}
.w3-main,#main{transition:margin-left .4s}
.w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}
.w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px}
.w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto}
.w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0}
.w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left}
.w3-bar .w3-button{white-space:normal}
.w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0}
.w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%}
.w3-responsive{display:block;overflow-x:auto}
.w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
.w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both}
.w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%}
.w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%}
.w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%}
.w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%}
@media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%}
.w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%}
.w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}}
@media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%}
.w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%}
.w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}}
.w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px}
.w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px}
.w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell}
.w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom}
.w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important}
@media (max-width:1205px){.w3-auto{max-width:95%}}
@media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px}
.w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative}
.w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center}
.w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}}
@media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}}
@media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}}
@media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}}
@media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}}
.w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0}
.w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2}
.w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0}
.w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0}
.w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)}
.w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)}
.w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)}
.w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)}
.w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none}
.w3-display-position{position:absolute}
.w3-circle{border-radius:50%}
.w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px}
.w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px}
.w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px}
.w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px}
.w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word}
.w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%}
.w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}
.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)}
.w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}
.w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}}
.w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}}
.w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}}
.w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}}
.w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}}
.w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}}
.w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}}
.w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important}
.w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1}
.w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75}
.w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)}
.w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)}
.w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)}
.w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important}
.w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important}
.w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important}
.w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important}
.w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important}
.w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important}
.w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important}
.w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important}
.w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important}
.w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important}
.w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important}
.w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important}
.w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important}
.w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important}
.w3-padding-64{padding-top:64px!important;padding-bottom:64px!important}
.w3-padding-top-64{padding-top:64px!important}.w3-padding-top-48{padding-top:48px!important}
.w3-padding-top-32{padding-top:32px!important}.w3-padding-top-24{padding-top:24px!important}
.w3-left{float:left!important}.w3-right{float:right!important}
.w3-button:hover{color:#000!important;background-color:#ccc!important}
.w3-transparent,.w3-hover-none:hover{background-color:transparent!important}
.w3-hover-none:hover{box-shadow:none!important}
/* Colors */
.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important}
.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important}
.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important}
.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important}
.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important}
.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important}
.w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important}
.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important}
.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important}
.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important}
.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important}
.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important}
.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important}
.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important}
.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important}
.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important}
.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important}
.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important}
.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important}
.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important}
.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important}
.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important}
.w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important}
.w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
.w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important}
.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important}
.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important}
.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important}
.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important}
.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important}
.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important}
.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important}
.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important}
.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important}
.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important}
.w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important}
.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important}
.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important}
.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important}
.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important}
.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important}
.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important}
.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important}
.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important}
.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important}
.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important}
.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important}
.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important}
.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important}
.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important}
.w3-text-white,.w3-hover-text-white:hover{color:#fff!important}
.w3-text-black,.w3-hover-text-black:hover{color:#000!important}
.w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important}
.w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important}
.w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important}
.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important}
.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important}
.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important}
.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important}
.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important}
.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important}
.w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important}
.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important}
.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important}
.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important}
.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important}
.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important}
.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important}
.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important}
.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important}
.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important}
.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important}
.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important}
.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important}
.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important}
.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important}
.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important}
.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important}
.w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important}
.w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important}
.w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important}
.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important}
.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important}

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📦",
"previous": "&IU+TwyM7TznD8NBfnw7tgW2zxVlMqTVxSqWFjuosLwo=.sha256"
"emoji": "🚪",
"previous": "&HXCdDG8gGYXElTyEFbg85jqa6lDXNL2ENPIA9UoJNbI=.sha256"
}

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🐌",
"previous": "&UDqtNEELPRZAP6jSrcKfoXpAr8s7GjWmWLOQINN4kmg=.sha256"
"previous": "&vEaOZjrNb0u9rhNqrQ8eU9TlOFlo4HsgW6hbI7VdIT0=.sha256"
}

View File

@ -358,7 +358,10 @@ class TfElement extends LitElement {
`;
let contents = !this.loaded
? this.loading
? html`<div>Loading...</div>`
? html`<div class="w3-panel w3-theme-l5 w3-card-4 w3-padding-large w3-round-xlarge">
Loading...
</div>
${this.render_tab()}`
: html`<div>Select or create an identity.</div>`
: this.render_tab();
return html`
@ -366,8 +369,8 @@ class TfElement extends LitElement {
style="width: 100vw; min-height: 100vh; height: 100%"
class="w3-theme-dark"
>
${tabs}
<div style="padding: 8px">
${tabs}
${this.tags.map(
(x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`
)}

View File

@ -233,17 +233,11 @@ class TfComposeElement extends LitElement {
}
discard() {
let edit = this.renderRoot.getElementById('edit');
edit.innerText = '';
this.input();
let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = '';
this.notify(undefined);
}
attach() {
let self = this;
let edit = this.renderRoot.getElementById('edit');
let input = document.createElement('input');
input.type = 'file';
input.onchange = function (event) {

View File

@ -72,19 +72,21 @@ class TfMessageElement extends LitElement {
return expression;
}
}
return html`<div class="w3-button" @click=${this.show_reactions}>
${(this.message.votes || []).map(
(vote) => html`
<span
title="${this.users[vote.author]?.name ?? vote.author} ${new Date(
vote.timestamp
)}"
>
${normalize_expression(vote.content.vote.expression)}
</span>
`
)}
</div>`;
if (this.message?.votes?.length) {
return html`<div class="w3-button" @click=${this.show_reactions}>
${(this.message.votes || []).map(
(vote) => html`
<span
title="${this.users[vote.author]?.name ?? vote.author} ${new Date(
vote.timestamp
)}"
>
${normalize_expression(vote.content.vote.expression)}
</span>
`
)}
</div>`;
}
}
render_raw() {
@ -246,7 +248,7 @@ ${JSON.stringify(mention, null, 2)}</pre
let self = this;
return html`
<fieldset
style="backdrop-filter: brightness(1.2); padding: 0.5em; border: 1px solid black"
style="padding: 0.5em; border: 1px solid black"
>
<legend>Mentions</legend>
${mentions.map((x) => self.render_mention(x))}
@ -337,6 +339,9 @@ ${JSON.stringify(mention, null, 2)}</pre
if (this.message?.decrypted?.type == 'post') {
content = this.message.decrypted;
}
let class_background = this.message?.decrypted
? 'w3-pale-red'
: 'w3-theme-d4';
let self = this;
let raw_button;
switch (this.format) {
@ -395,8 +400,8 @@ ${JSON.stringify(mention, null, 2)}</pre
let body;
return html`
<div
class="w3-card-4"
style="backdrop-filter: brightness(1.2); margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere"
class="w3-card-4 w3-theme-d4 w3-border-theme"
style="margin-top: 8px; padding: 16px; display: inline-block; overflow-wrap: anywhere"
>
<tf-user id=${self.message.author} .users=${self.users}></tf-user>
<span style="padding-right: 8px"
@ -406,13 +411,24 @@ ${JSON.stringify(mention, null, 2)}</pre
>
${raw_button} ${self.format == 'raw' ? self.render_raw() : inner}
${self.render_votes()}
${(self.message.child_messages || []).map(
(x) => html`
<tf-message
.message=${x}
whoami=${self.whoami}
.users=${self.users}
.drafts=${self.drafts}
.expanded=${self.expanded}
></tf-message>
`
)}
</div>
`;
}
if (this.message?.type === 'contact_group') {
return html` <div
class="w3-card-4"
style="border: 1px solid black; backdrop-filter: brightness(1.2); margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
class="w3-card-4 w3-theme-d4 w3-border-theme"
style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
>
${this.message.messages.map(
(x) =>
@ -427,8 +443,8 @@ ${JSON.stringify(mention, null, 2)}</pre
</div>`;
} else if (this.message.placeholder) {
return html` <div
class="w3-card-4"
style="border: 1px solid black; backdrop-filter: brightness(1.2); margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
class="w3-card-4 w3-theme-d4 w3-border-theme"
style="margin-top: 8px; padding: 16px; overflow-wrap: anywhere"
>
<a target="_top" href=${'#' + this.message.id}>${this.message.id}</a>
(placeholder)
@ -557,9 +573,6 @@ ${JSON.stringify(content, null, 2)}</pre
let is_encrypted = this.message?.decrypted
? html`<span style="align-self: center">🔓</span>`
: undefined;
let style_background = this.message?.decrypted
? 'background-color: rgba(255, 0, 0, 0.2)'
: 'backdrop-filter: brightness(1.2)';
return html`
<style>
code {
@ -576,8 +589,8 @@ ${JSON.stringify(content, null, 2)}</pre
}
</style>
<div
class="w3-card-4"
style="border: 1px solid black; ${style_background}; margin-top: 8px; padding: 16px"
class="w3-card-4 ${class_background} w3-border-theme"
style="margin-top: 8px; padding: 16px"
>
<div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
@ -603,9 +616,6 @@ ${JSON.stringify(content, null, 2)}</pre
let is_encrypted = this.message?.decrypted
? html`<span style="align-self: center">🔓</span>`
: undefined;
let style_background = this.message?.decrypted
? 'background: rgba(255, 0, 0, 0.2)'
: 'backdrop-filter: brightness(1.2)';
return html`
<style>
code {
@ -622,8 +632,8 @@ ${JSON.stringify(content, null, 2)}</pre
}
</style>
<div
class="w3-card-4"
style="border: 1px solid black; ${style_background}; margin-top: 8px; padding: 16px"
class="w3-card-4 ${class_background} w3-border-theme"
style="margin-top: 8px; padding: 16px"
>
<div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
@ -713,8 +723,8 @@ ${JSON.stringify(content, null, 2)}</pre
}
</style>
<div
class="w3-card-4"
style="border: 1px solid black; backdrop-filter: brightness(1.2); margin-top: 8px; padding: 16px"
class="w3-card-4 w3-theme-d4 w3-border-theme"
style="margin-top: 8px; padding: 16px"
>
<div style="display: flex; flex-direction: row">
<tf-user id=${this.message.author} .users=${this.users}></tf-user>

View File

@ -34,12 +34,9 @@ const tf = css`
content: ' ±';
}
code {
background-color: #444;
padding-left: 3px;
padding-right: 3px;
border: 1px dotted #fff;
border-radius: 4px;
pre code {
display: block;
padding: 8px;
}
blockquote {
@ -289,29 +286,29 @@ hr{border:0;border-top:1px solid #eee;margin:20px 0}
`;
// prettier-ignore
const w3_2016_snorkel_blue = css`
.w3-theme-l5 {color:#000 !important; background-color:#e9f5ff !important}
.w3-theme-l4 {color:#000 !important; background-color:#b5dffd !important}
.w3-theme-l3 {color:#000 !important; background-color:#6bc0fc !important}
.w3-theme-l2 {color:#fff !important; background-color:#21a0fa !important}
.w3-theme-l1 {color:#fff !important; background-color:#0479cc !important}
.w3-theme-d1 {color:#fff !important; background-color:#024575 !important}
.w3-theme-d2 {color:#fff !important; background-color:#023e68 !important}
.w3-theme-d3 {color:#fff !important; background-color:#02365b !important}
.w3-theme-d4 {color:#fff !important; background-color:#022e4e !important}
.w3-theme-d5 {color:#fff !important; background-color:#012641 !important}
const w3_2016_riverside = css`
.w3-theme-l5 {color:#000 !important; background-color:#f4f6f9 !important}
.w3-theme-l4 {color:#000 !important; background-color:#d9e1ec !important}
.w3-theme-l3 {color:#000 !important; background-color:#b4c3d8 !important}
.w3-theme-l2 {color:#fff !important; background-color:#8ea6c5 !important}
.w3-theme-l1 {color:#fff !important; background-color:#6888b1 !important}
.w3-theme-d1 {color:#fff !important; background-color:#456185 !important}
.w3-theme-d2 {color:#fff !important; background-color:#3d5676 !important}
.w3-theme-d3 {color:#fff !important; background-color:#354b68 !important}
.w3-theme-d4 {color:#fff !important; background-color:#2e4059 !important}
.w3-theme-d5 {color:#fff !important; background-color:#26364a !important}
.w3-theme-light {color:#000 !important; background-color:#e9f5ff !important}
.w3-theme-dark {color:#fff !important; background-color:#012641 !important}
.w3-theme-action {color:#fff !important; background-color:#012641 !important}
.w3-theme-light {color:#000 !important; background-color:#f4f6f9 !important}
.w3-theme-dark {color:#fff !important; background-color:#26364a !important}
.w3-theme-action {color:#fff !important; background-color:#26364a !important}
.w3-theme {color:#fff !important; background-color:#034f84 !important}
.w3-text-theme {color:#034f84 !important}
.w3-border-theme {border-color:#034f84 !important}
.w3-theme {color:#fff !important; background-color:#4c6a92 !important}
.w3-text-theme {color:#4c6a92 !important}
.w3-border-theme {border-color:#4c6a92 !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#034f84 !important}
.w3-hover-text-theme:hover {color:#034f84 !important}
.w3-hover-border-theme:hover {border-color:#034f84 !important}
.w3-hover-theme:hover {color:#fff !important; background-color:#4c6a92 !important}
.w3-hover-text-theme:hover {color:#4c6a92 !important}
.w3-hover-border-theme:hover {border-color:#4c6a92 !important}
`;
export let styles = [tf, w3, w3_2016_snorkel_blue];
export let styles = [tf, w3, w3_2016_riverside];

View File

@ -107,6 +107,9 @@ class TfTabConnectionsElement extends LitElement {
${connection.tunnel !== undefined
? '🚇'
: html`(${connection.host}:${connection.port})`}
<div>${connection.requests.map(x => html`
<span class="w3-tag w3-small">${x.request_number > 0 ? '🟩' : '🟥'} ${x.name}</span>
`)}</div>
<ul>
${this.connections
.filter((x) => x.tunnel === this.connections.indexOf(connection))

View File

@ -84,7 +84,6 @@ class TfTabNewsElement extends LitElement {
} else {
delete this.drafts[id];
}
/* Only trigger a re-render if we're creating a new draft or discarding an old one. */
this.drafts = Object.assign({}, this.drafts);
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
}

View File

@ -1,5 +1,7 @@
import * as hashtagify from './commonmark-hashtag.js';
const k_code_classes = 'w3-theme-l4 w3-theme-border w3-round';
function image(node, entering) {
if (
node.firstChild?.type === 'text' &&
@ -60,10 +62,20 @@ function image(node, entering) {
}
}
function code(node) {
let attrs = this.attrs(node);
attrs.push(['class', k_code_classes]);
this.tag('code', attrs);
this.out(node.literal);
this.tag('/code');
}
function attrs(node) {
let result = commonmark.HtmlRenderer.prototype.attrs.bind(this)(node);
if (node.type == 'block_quote') {
result.push(['class', 'w3-theme-d1']);
} else if (node.type == 'code_block') {
result.push(['class', k_code_classes]);
}
return result;
}
@ -72,6 +84,7 @@ export function markdown(md) {
let reader = new commonmark.Parser({safe: true});
let writer = new commonmark.HtmlRenderer();
writer.image = image;
writer.code = code;
writer.attrs = attrs;
let parsed = reader.parse(md || '');
parsed = hashtagify.transform(parsed);

View File

@ -149,7 +149,7 @@ function socket(request, response, client) {
parentApp: parentApp,
id: blobId,
},
await core.getIdentityInfo(
await ssb.getIdentityInfo(
credentials?.session?.name,
packageOwner,
packageName

View File

@ -545,7 +545,7 @@ async function getProcessBlob(blobId, key, options) {
{
action: 'identities',
},
await getIdentityInfo(
await ssb.getIdentityInfo(
process?.credentials?.session?.name,
options?.packageOwner,
options?.packageName
@ -587,7 +587,7 @@ async function getProcessBlob(blobId, key, options) {
options.packageName,
'setActiveIdentity',
[
await getActiveIdentity(
await ssb.getActiveIdentity(
process.credentials?.session?.name,
options.packageOwner,
options.packageName
@ -696,7 +696,7 @@ async function getProcessBlob(blobId, key, options) {
};
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
imports.ssb.getActiveIdentity = () =>
getActiveIdentity(
ssb.getActiveIdentity(
process.credentials?.session?.name,
options.packageOwner,
options.packageName
@ -785,6 +785,7 @@ async function getProcessBlob(blobId, key, options) {
);
}
};
imports.ssb.getIdentityInfo = undefined;
imports.fetch = function (url, options) {
return http.fetch(url, options, gGlobalSettings.fetch_hosts);
};
@ -1248,7 +1249,7 @@ async function blobHandler(request, response, blobId, uri) {
if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) {
let user = match[1];
let appName = match[2];
let credentials = https.auth_query(request.headers);
let credentials = httpd.auth_query(request.headers);
if (
credentials &&
credentials.session &&
@ -1424,34 +1425,7 @@ loadSettings()
httpd.all('/app/socket', app.socket);
httpd.all('', function default_http_handler(request, response) {
let match;
if (request.uri === '/' || request.uri === '') {
let host = request.headers['x-forwarded-host'] ?? request.headers.host;
try {
for (let line of (gGlobalSettings.index_map || '').split('\n')) {
let parts = line.split('=');
if (parts.length == 2 && host.match(new RegExp(parts[0], 'i'))) {
response.writeHead(303, {
Location:
(request.client.tls ? 'https://' : 'http://') +
host +
parts[1],
'Content-Length': '0',
});
return response.end();
}
}
} catch (e) {
print(e);
}
response.writeHead(303, {
Location:
(request.client.tls ? 'https://' : 'http://') +
host +
gGlobalSettings.index,
'Content-Length': '0',
});
return response.end();
} else if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) {
if ((match = /^(\/~[^\/]+\/[^\/]+)(\/?.*)$/.exec(request.uri))) {
return blobHandler(request, response, match[1], match[2]);
} else if (
(match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri))
@ -1491,8 +1465,15 @@ loadSettings()
async function start_tls() {
const kCertificatePath = 'data/httpd/certificate.pem';
const kPrivateKeyPath = 'data/httpd/privatekey.pem';
let privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
let certificate = utf8Decode(await File.readFile(kCertificatePath));
let privateKey;
let certificate;
try {
privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
certificate = utf8Decode(await File.readFile(kCertificatePath));
} catch (e) {
print(`TLS disabled (${e.message}).`);
return;
}
let context = new TlsContext();
context.setPrivateKey(privateKey);
context.setCertificate(certificate);
@ -1546,57 +1527,10 @@ function storePermission(user, packageOwner, packageName, permission, allow) {
}
}
async function getActiveIdentity(user, packageOwner, packageName) {
if (user && packageOwner && packageName) {
let id = await new Database(user).get(`id:${packageOwner}:${packageName}`);
if (!id) {
let ids = await ssb.getIdentities(user);
if (ids) {
id = ids[0];
}
}
return id;
}
}
async function getIdentityInfo(user, packageOwner, packageName) {
let identities = await ssb.getIdentities(user);
let names = new Object();
for (let identity of identities) {
names[identity] = identity;
}
await ssb.sqlAsync(
`
SELECT author, name FROM (
SELECT
messages.author,
RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank,
messages.content ->> 'name' AS name
FROM messages
JOIN json_each(?) AS ids
ON messages.author = ids.value
WHERE json_extract(messages.content, '$.type') = 'about' AND content ->> 'about' = messages.author AND name IS NOT NULL)
WHERE author_rank = 1
`,
[JSON.stringify(identities)],
function (row) {
names[row.author] = row.name;
}
);
return {
identities: identities,
identity: await getActiveIdentity(user, packageOwner, packageName),
names: names,
};
}
export {
gGlobalSettings as globalSettings,
setGlobalSettings,
enableStats,
invoke,
getSessionProcessBlob,
getActiveIdentity,
getIdentityInfo,
};

File diff suppressed because one or more lines are too long

158
deps/codemirror_src/package-lock.json generated vendored
View File

@ -36,9 +36,9 @@
}
},
"node_modules/@codemirror/commands": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.3.3.tgz",
"integrity": "sha512-dO4hcF0fGT9tu1Pj1D2PvGvxjeGkbC6RGcZw6Qs74TH+Ed1gw98jmUgd2axWvIZEqTeTuFrg1lEB1KV6cK9h1A==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz",
"integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
@ -111,9 +111,9 @@
}
},
"node_modules/@codemirror/lint": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.5.0.tgz",
"integrity": "sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==",
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.7.0.tgz",
"integrity": "sha512-LTLOL2nT41ADNSCCCCw8Q/UmdAFzB23OUYSjsHTdsVaH0XEo+orhuqbDNWzrzodm14w6FOxqxpmy4LF8Lixqjw==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
@ -248,9 +248,9 @@
}
},
"node_modules/@lezer/javascript": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.14.tgz",
"integrity": "sha512-GEdUyspTRgc5dwIGebUk+f3BekvqEWVIYsIuAC3pA8e8wcikGwBZRWRa450L0s8noGWuULwnmi4yjxTnYz9PpA==",
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.15.tgz",
"integrity": "sha512-B082ZdjI0vo2AgLqD834GlRTE9gwRX8NzHzKq5uDwEnQ9Dq+A/CEhd3nf68tiNA2f9O+8jS1NeSTUYT9IAqcTw==",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
@ -343,9 +343,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz",
"integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz",
"integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==",
"cpu": [
"arm"
],
@ -355,9 +355,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz",
"integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz",
"integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==",
"cpu": [
"arm64"
],
@ -367,9 +367,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz",
"integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz",
"integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==",
"cpu": [
"arm64"
],
@ -379,9 +379,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz",
"integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz",
"integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==",
"cpu": [
"x64"
],
@ -391,9 +391,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz",
"integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz",
"integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==",
"cpu": [
"arm"
],
@ -403,9 +403,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz",
"integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz",
"integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==",
"cpu": [
"arm"
],
@ -415,9 +415,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz",
"integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz",
"integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==",
"cpu": [
"arm64"
],
@ -427,9 +427,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz",
"integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz",
"integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==",
"cpu": [
"arm64"
],
@ -439,9 +439,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz",
"integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz",
"integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==",
"cpu": [
"ppc64"
],
@ -451,9 +451,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz",
"integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz",
"integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==",
"cpu": [
"riscv64"
],
@ -463,9 +463,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz",
"integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz",
"integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==",
"cpu": [
"s390x"
],
@ -475,9 +475,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz",
"integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz",
"integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==",
"cpu": [
"x64"
],
@ -487,9 +487,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz",
"integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz",
"integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==",
"cpu": [
"x64"
],
@ -499,9 +499,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz",
"integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz",
"integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==",
"cpu": [
"arm64"
],
@ -511,9 +511,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz",
"integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz",
"integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==",
"cpu": [
"ia32"
],
@ -523,9 +523,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz",
"integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz",
"integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==",
"cpu": [
"x64"
],
@ -715,9 +715,9 @@
}
},
"node_modules/rollup": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz",
"integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==",
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
"integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
"dependencies": {
"@types/estree": "1.0.5"
},
@ -729,22 +729,22 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.14.3",
"@rollup/rollup-android-arm64": "4.14.3",
"@rollup/rollup-darwin-arm64": "4.14.3",
"@rollup/rollup-darwin-x64": "4.14.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.14.3",
"@rollup/rollup-linux-arm-musleabihf": "4.14.3",
"@rollup/rollup-linux-arm64-gnu": "4.14.3",
"@rollup/rollup-linux-arm64-musl": "4.14.3",
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.3",
"@rollup/rollup-linux-riscv64-gnu": "4.14.3",
"@rollup/rollup-linux-s390x-gnu": "4.14.3",
"@rollup/rollup-linux-x64-gnu": "4.14.3",
"@rollup/rollup-linux-x64-musl": "4.14.3",
"@rollup/rollup-win32-arm64-msvc": "4.14.3",
"@rollup/rollup-win32-ia32-msvc": "4.14.3",
"@rollup/rollup-win32-x64-msvc": "4.14.3",
"@rollup/rollup-android-arm-eabi": "4.17.2",
"@rollup/rollup-android-arm64": "4.17.2",
"@rollup/rollup-darwin-arm64": "4.17.2",
"@rollup/rollup-darwin-x64": "4.17.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.17.2",
"@rollup/rollup-linux-arm-musleabihf": "4.17.2",
"@rollup/rollup-linux-arm64-gnu": "4.17.2",
"@rollup/rollup-linux-arm64-musl": "4.17.2",
"@rollup/rollup-linux-powerpc64le-gnu": "4.17.2",
"@rollup/rollup-linux-riscv64-gnu": "4.17.2",
"@rollup/rollup-linux-s390x-gnu": "4.17.2",
"@rollup/rollup-linux-x64-gnu": "4.17.2",
"@rollup/rollup-linux-x64-musl": "4.17.2",
"@rollup/rollup-win32-arm64-msvc": "4.17.2",
"@rollup/rollup-win32-ia32-msvc": "4.17.2",
"@rollup/rollup-win32-x64-msvc": "4.17.2",
"fsevents": "~2.3.2"
}
},
@ -819,9 +819,9 @@
}
},
"node_modules/terser": {
"version": "5.30.3",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz",
"integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==",
"version": "5.31.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz",
"integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",

View File

@ -1,4 +1,6 @@
# Philosophy
# Tilde Friends
## Philosophy
Tilde Friends is a platform for making, running, and sharing web applications.
@ -18,7 +20,7 @@ Above the terminal, an "Edit" link brings a visitor to the source code for the
current Tilde Friends application, which they can then edit, save as their own,
and run.
# Architecture
## Architecture
Tilde Friends is a C++ application with a JavaScript runtime that provides
restricted access to filesystem, network, and other system resources. The core
@ -66,7 +68,7 @@ performance reasons to minimize the data size transferred between processes.
// Receive the above message and call the function.
core.register("onMessage", function(sender, message) {
message.add(3, 4).then(x => terminal.print(x.toString()));
message.add(3, 4).then(x => terminal.print(x.toString()));
});
Finally, there is a core web interface that runs on the client's browser that
@ -135,16 +137,18 @@ Sets the browser window/tab title.
Reconfigures the terminal layout, potentially into multiple split panes.
terminal.split([
{
type: "horizontal",
children: [
{name: "left", basis: "2in", grow: 0, shrink: 0},
{name: "middle", grow: 1},
{name: "right", basis: "2in", grow: 0, shrink: 0},
],
},
]);
```javascript
terminal.split(
[{
type: "horizontal",
children: [
{name: "left", basis: "2in", grow: 0, shrink: 0},
{name: "middle", grow: 1},
{name: "right", basis: "2in", grow: 0, shrink: 0},
],
}]
);
```
#### terminal.select(name)

50
docs/apps/quickstart.md Normal file
View File

@ -0,0 +1,50 @@
# Writing Tilde Friends applications7
TODO
## Creating your environment
1. Open an existing application (ie: `identity`);
2. Open the editing panel;
3. Save the app under a new name (ie `/~YOUR_USERNAME/my-app/`);
4. Go back to the main menu and open your new app;
5. You can now edit your app, save it and see changes in the real time.
## Project structure
An application has a `app.js` file that gets run when a user enters the app.
This file contains a function (typically called `main()`) that's considered the entry point.
Paste this in `app.js`:
```javascript
async function main() {
let ids = await ssb.getIdentities();
await app.setDocument(`
<body style="font-family: sans-serif; color: white">
<h1>Hello world!</h1>
</body>
</body>`);
}
main();
```
Save the app, and you should now be seeing `Hello world!` on the screen.
## Components
Once your app grows to a certain size, you'll want to introduce components.
In Tilde Friends, the de facto standard is [Lit](TODO).
Althogh you an use any framework you want, you're encouraged to use Lit as you can reuse
First, add lit-all-min.js into your project.
TODO
<!-- mention shadow dom -->
TODO: tfrpc
TODO: sharing apps

76
docs/building.md Normal file
View File

@ -0,0 +1,76 @@
# How to build Tilde Friends
> Disclaimer: this documentation has been written by a Linux user and has not been reviewed by other people on other platforms. The procedure may vary slightly depending on your operating system.
Builds **on** Linux (`x86_64` and `aarch64`), MacOS, OpenBSD, and Haiku.
Builds **for** all of those host platforms plus `mingw64`, iOS, and android.
Dependencies:
- `openssl` (`libssl-dev`, in debian-speak)
Dependencies for Android:
- TODO
Dependencies for iOS:
- TODO
Dependencies for Windows:
- TODO
> All other dependencies are kept up to date as git submodules.
1. Clone the repository with the submodules: `git clone --recursive https://dev.tildefriends.net/cory/tildefriends.git`
2. Run `make -j $(nproc) debug` or `make -j $(nproc) release`
If you're unsure whether you should choose `debug` or `release`, stick to `release`.
> `-j $(nproc)` will start a compiler for every CPU thread, which will dramatically reduce the time needed to compile Tilde Friends.
An executable will be generated in a subdirectory of `out/`
It's possible to build for Android, iOS, and Windows on Linux, if you have the right dependencies in the right places. Run `make -j $(nproc) windebug winrelease iosdebug-ipa iosrelease-ipa release-apk`
To build in docker, `docker build .`
<!-- On NixOS: TODO -->
<!-- Add shell.nix and nix derivs first -->
Now that you have a binary, head over to <running.md>.
## Troubleshooting
### The compiler throws an error and I can't build the binary
Open `GNUMakefile` and edit the CFLAGS environment variable around line 50.
For example given this error:
```text
src/http.c: In function 'tf_http_get_cookie':
src/http.c:1089:128: error: check of 'name' for NULL after already dereferencing it [-Werror=analyzer-deref-before-check]
```
Add:
```diff
CFLAGS += \
-std=gnu11 \
-Wall \
-Wextra \
-Wno-unused-parameter \
+ -Wno-analyzer-deref-before-check \
-MMD \
-MP \
-ffunction-sections \
-fdata-sections \
-fno-exceptions \
-g
```
Now the compiler will ignore this error and *should* continue building anyways.

42
docs/contributing.md Normal file
View File

@ -0,0 +1,42 @@
# How to contribute
- Fork this repository
- Clone your repository
Alternatively, you can change the `origin` remote on your existing clone:
`git remote set-url origin https://dev.tildefriends.net/YOUR_USERNAME/tildefriends.git`
- Make your changes
- I want to edit C code !
TODO
- I want to edit JavaScript code !
TODO
- I want to write documentation !
Great! Before you do, have a look at the [documentation guidelines](guidelines/documentation-guidelines.md) to learn how to write consistent documentation.
In all cases:
- Make sure that your commit messages are descriptive.
<!-- - hi -->
- Format your changes:
If you've edited C code: run `make format`
If you've edited JavaScript code or the documentation: run `npm run format`
- Open a pull request
TODO
- Get your changes reviewed and merged
TODO

19
docs/documentation.md Normal file
View File

@ -0,0 +1,19 @@
# Tilde Friends documentation
## Building
See <building.md>.
## Contibuting
See <contributing.md>.
## FAQ / Troubleshooting
See <faq.md>.
## Guide
This document will be phased out and integrated into the new documentation.
See <.guide.md>.

9
docs/faq.md Normal file
View File

@ -0,0 +1,9 @@
# Troubleshooting
## I started tildefriends. Now what ?
See <running.md>.
### The compiler throws an error and I can't build the binary
See <building.md>.

View File

@ -0,0 +1 @@
TODO

View File

@ -0,0 +1,64 @@
# Documentation guidelines
This document defines the rules used to write documentation in order to make it more consistent.
This documentation is a living document and so are it's rules; you are free to propose changes but in the meantime, please stick to them.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119/).
## File naming
Files SHOULD be named using [kebab-case](https://www.freecodecamp.org/news/snake-case-vs-camel-case-vs-pascal-case-vs-kebab-case-whats-the-difference/#kebab-case).
Their names should be meaningful and SHOULD not conflict with other files in other directories:
> Example: this document is named `docs/guidelines/documentation-guidelines.md` instead of `docs/guidelines/documentation.md` because it could cause confusion with `docs/documentation.md`.
## Documentation
When writing documentation, the author should have in mind it's target audience: people with varying technical skills and backgrounds, fluency in peer-to-peer-specific terms and mental ability.
The documentation should therefore be acessible and usefule to most people interested in building, using and contributing to Tilde Friends.
### Terminology
`Tilde Friends` refers to the projectas a whole. This can be abbreviated to `TF`.
`tildefriends` refers to the program.
### Style guide
1. Lines SHOULD NOT be wrapped, to allow clients to dynamically wrap them however they want:
```text
This is not very pleasant to read because
the text
is manually wrapped, but the size of the
screen is
smaller than the size the text is wrapped
at. I
need to write even more useless text here
so I get
my point across. Also hi! If you're here
that
means you're either going to contribute to
Tilde
Friends, or that you're reviewing my
stupid
changes. Either way, you're awesome!
```
You MAY use one line per sentence.
2. Lines ending with an `inline code block` SHOULD NOT end with a period.
> Example: To build in docker, `docker build .`
NB: this does not apply to file names or other text that are not meant to be copy-pasted.
> Example: this document is named `docs/guidelines/documentation-guidelines.md` instead of `docs/guidelines/documentation.md` because it could cause confusion with `docs/documentation.md`.
More TODO
## License
As per the rest of the code in this repository, the documentation is shared under the [MIT](https://opensource.org/licenses/MIT/) license.

View File

@ -0,0 +1 @@
TODO

50
docs/running.md Normal file
View File

@ -0,0 +1,50 @@
# Running Tilde Friends
> Disclaimer: this documentation has been written by a Linux user and has not been reviewed by other people on other platforms. The procedure may vary slightly depending on your operating system.
The binaries should appear at `out/debug/tildefriends` and `out/release/tildefriends`.
For Android, iOS and Windows: TODO
You can now start the server by running `./out/debug/tildefriends` or `./out/release/tildefriends`.
By default, running the built `tildefriends` executable will start a web server
at <http://localhost:12345/>. `tildefriends -h` lists further options.
## How to use TF
### Initial setup
Now you have a Tilde Friends instance running. The first thing you'll want to do is create your account. Click "login" in the top right corner, then "Register".
Enter your username and password.
> The first user to create an account and log in will be granted administrative privileges.
> Further administration can be done at <http://localhost:12345/~core/admin/>
Next, create a Scuttlebutt identity by pressing the "Create an identity" button.
This will create a pair of keys that are used to sign your messages with.
Because of the way Scuttlebutt is designed, you cannot log into your account without your keys.
Tilde Friends locks your keys behind a password, but if you were to destroy your database, the keys would be gone forever, and with it your possibility to send messages using this account. Click on the `identity` app and under "Identities", export your newly created identity.
You'll be prompted with a dialog box saying "This app is requesting the following permission:ssb_id_export".
This is because applications are not trusted to have access to your keys by default.
Click on "Allow" and you'll see a list of 12 words. You need to write those down in a password manager or on a piece of paperand keep it private and secure.
> Warning: Nobody needs to know these 12 words. Anybody that has access to those keys can post messages as you, see your private messages and documents and much more.
Now that your keys are safe, we can start connecting to the outside world.
### Replication
You've probably noticed asdtring of random characters by now. This is your public key, a unique identifier for your account you can share to anyone. If you go back to the home menu and into the `ssb` app, you can click on your public key. This will lead you to your profile, which is empty at the time. Edit it and enter your name.
TODO: joining a room
TODO: initial sync
TODO: send messages
TODO: how messages spread to friends
TODO: other apps

716
package-lock.json generated
View File

@ -6,13 +6,442 @@
"": {
"name": "tildefriends",
"license": "MIT",
"devDependencies": {
"markdownlint-cli": "0.40.0",
"prettier": "3.2.5"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": {
"prettier": "^3.2.5"
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/commander": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
"integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
"dev": true,
"engines": {
"node": ">=18"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true,
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/get-stdin": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/glob": {
"version": "10.3.14",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.14.tgz",
"integrity": "sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
"minipass": "^7.0.4",
"path-scurry": "^1.11.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/ini": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz",
"integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==",
"dev": true,
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsonc-parser": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
"dev": true
},
"node_modules/jsonpointer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
"integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"dev": true,
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/lru-cache": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
"integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdownlint": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.34.0.tgz",
"integrity": "sha512-qwGyuyKwjkEMOJ10XN6OTKNOVYvOIi35RNvDLNxTof5s8UmyGHlCdpngRHoRGNvQVGuxO3BJ7uNSgdeX166WXw==",
"dev": true,
"dependencies": {
"markdown-it": "14.1.0",
"markdownlint-micromark": "0.1.9"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/DavidAnson"
}
},
"node_modules/markdownlint-cli": {
"version": "0.40.0",
"resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.40.0.tgz",
"integrity": "sha512-JXhI3dRQcaqwiFYpPz6VJ7aKYheD53GmTz9y4D/d0F1MbZDGOp9pqKlbOfUX/pHP/iAoeiE4wYRmk8/kjLakxA==",
"dev": true,
"dependencies": {
"commander": "~12.0.0",
"get-stdin": "~9.0.0",
"glob": "~10.3.12",
"ignore": "~5.3.1",
"js-yaml": "^4.1.0",
"jsonc-parser": "~3.2.1",
"jsonpointer": "5.0.1",
"markdownlint": "~0.34.0",
"minimatch": "~9.0.4",
"run-con": "~1.3.2",
"toml": "~3.0.0"
},
"bin": {
"markdownlint": "markdownlint.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/markdownlint-micromark": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.9.tgz",
"integrity": "sha512-5hVs/DzAFa8XqYosbEAEg6ok6MF2smDj89ztn9pKkCtdKHVdPQuGMH7frFfYL9mLkvfFe4pTyAMffLbjf3/EyA==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/DavidAnson"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"dev": true
},
"node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz",
"integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.0.tgz",
"integrity": "sha512-LNHTaVkzaYaLGlO+0u3rQTz7QrHTFOuKyba9JMTQutkmtNew8dw8wOD7mTU/5fCPZzCWpfW0XnQKzY61P0aTaw==",
"dev": true,
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/prettier": {
"version": "3.2.5",
"license": "MIT",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@ -22,6 +451,289 @@
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/run-con": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz",
"integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==",
"dev": true,
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~4.1.0",
"minimist": "^1.2.8",
"strip-json-comments": "~3.1.1"
},
"bin": {
"run-con": "cli.js"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/toml": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
"dev": true
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"dev": true
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
}
}
}

View File

@ -1,11 +1,14 @@
{
"name": "tildefriends",
"scripts": {
"prettier": "prettier . --check --cache --write"
"format": "npm run prettier && npm run markdown",
"prettier": "npx prettier --cache --write --check .",
"markdown": "npx markdownlint-cli --fix 'docs/**/*.md'"
},
"author": "Cory McWilliams",
"license": "MIT",
"dependencies": {
"prettier": "^3.2.5"
"devDependencies": {
"markdownlint-cli": "0.40.0",
"prettier": "3.2.5"
}
}

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="18"
android:versionName="0.0.18">
android:versionCode="19"
android:versionName="0.0.19-wip">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
@ -12,7 +12,7 @@
android:extractNativeLibs="true">
<meta-data android:name="android.max_aspect" android:value="2.1"/>
<activity
android:name=".MainActivity"
android:name=".TildeFriendsActivity"
android:icon="@drawable/icon"
android:configChanges="orientation|screenSize"
android:exported="true">

View File

@ -21,12 +21,13 @@ import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
//import android.webkit.WebView;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.TextView;
@ -49,8 +50,8 @@ import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.concurrent.TimeUnit;
public class MainActivity extends Activity {
WebView web_view;
public class TildeFriendsActivity extends Activity {
TildeFriendsWebView web_view;
String base_url;
Process process;
Thread thread;
@ -69,7 +70,7 @@ public class MainActivity extends Activity {
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
web_view = (WebView)findViewById(R.id.web);
web_view = (TildeFriendsWebView)findViewById(R.id.web);
set_status("Extracting executable...");
Log.w("tildefriends", String.format("getFilesDir() is %s", getFilesDir().toString()));
Log.w("tildefriends", String.format("getPackageResourcePath() is %s", getPackageResourcePath().toString()));
@ -79,7 +80,7 @@ public class MainActivity extends Activity {
new File(port_file_path).delete();
base_url = "http://127.0.0.1:12345/";
MainActivity activity = this;
TildeFriendsActivity activity = this;
thread = new Thread(new Runnable() {
@Override
@ -182,7 +183,8 @@ public class MainActivity extends Activity {
});
web_view.setWebChromeClient(new WebChromeClient() {
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("Tilde Friends")
.setMessage(message)
@ -205,20 +207,39 @@ public class MainActivity extends Activity {
return true;
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("Tilde Friends")
.setMessage(message)
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
result.confirm();
}
})
.create()
.show();
return true;
}
/*
** https://stackoverflow.com/questions/5907369/file-upload-in-webview
** https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android
*/
@Override
public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> message, WebChromeClient.FileChooserParams params) {
upload_message = message;
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
MainActivity.this.startActivityForResult(Intent.createChooser(intent, "File Chooser"), MainActivity.FILECHOOSER_RESULT);
TildeFriendsActivity.this.startActivityForResult(Intent.createChooser(intent, "File Chooser"), TildeFriendsActivity.FILECHOOSER_RESULT);
return true;
}
@Override
public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) {
Log.d("tildefriends", consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId());
return true;

View File

@ -4,14 +4,14 @@ import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
public class WebView extends android.webkit.WebView {
public class TildeFriendsWebView extends android.webkit.WebView {
boolean overscrolledY = false;
public WebView(final Context context) {
public TildeFriendsWebView(final Context context) {
super(context);
}
public WebView(final Context context, final AttributeSet attrs) {
public TildeFriendsWebView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}

View File

@ -4,7 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.unprompted.tildefriends.WebView
<com.unprompted.tildefriends.TildeFriendsWebView
android:id="@+id/web"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@ -78,7 +78,7 @@ static void _file_write_write_callback(uv_fs_t* req)
}
else
{
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to write %s: %s", req->path, uv_strerror(req->result)));
}
int result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
if (result < 0)
@ -91,6 +91,7 @@ static void _file_write_write_callback(uv_fs_t* req)
static void _file_write_open_callback(uv_fs_t* req)
{
fs_req_t* fsreq = (fs_req_t*)req;
const char* path = tf_strdup(req->path);
uv_fs_req_cleanup(req);
tf_task_t* task = req->loop->data;
JSContext* context = tf_task_get_context(task);
@ -102,7 +103,8 @@ static void _file_write_open_callback(uv_fs_t* req)
int result = uv_fs_write(req->loop, req, fsreq->file, &buf, 1, 0, _file_write_write_callback);
if (result < 0)
{
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
uv_fs_req_cleanup(req);
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to write %s: %s", path, uv_strerror(result)));
result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
if (result < 0)
{
@ -114,9 +116,9 @@ static void _file_write_open_callback(uv_fs_t* req)
else
{
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
uv_fs_req_cleanup(req);
tf_free(req);
}
tf_free((void*)path);
}
static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
@ -156,7 +158,7 @@ static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int a
int result = uv_fs_open(tf_task_get_loop(task), &req->fs, file_name, UV_FS_O_CREAT | UV_FS_O_WRONLY, 0644, _file_write_open_callback);
if (result < 0)
{
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for write: %s", file_name, uv_strerror(result)));
}
JS_FreeCString(context, file_name);
return promise_value;
@ -164,7 +166,6 @@ static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int a
static void _file_read_read_callback(uv_fs_t* req)
{
uv_fs_req_cleanup(req);
fs_req_t* fsreq = (fs_req_t*)req;
tf_task_t* task = req->loop->data;
JSContext* context = tf_task_get_context(task);
@ -177,8 +178,9 @@ static void _file_read_read_callback(uv_fs_t* req)
}
else
{
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to read %s: %s", req->path, uv_strerror(req->result)));
}
uv_fs_req_cleanup(req);
int result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
if (result < 0)
{
@ -189,6 +191,7 @@ static void _file_read_read_callback(uv_fs_t* req)
static void _file_read_open_callback(uv_fs_t* req)
{
const char* path = tf_strdup(req->path);
uv_fs_req_cleanup(req);
fs_req_t* fsreq = (fs_req_t*)req;
tf_task_t* task = req->loop->data;
@ -201,7 +204,7 @@ static void _file_read_open_callback(uv_fs_t* req)
int result = uv_fs_read(req->loop, req, fsreq->file, &buf, 1, 0, _file_read_read_callback);
if (result < 0)
{
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to read %s: %s", path, uv_strerror(result)));
result = uv_fs_close(req->loop, req, fsreq->file, _file_async_close_callback);
if (result < 0)
{
@ -212,10 +215,11 @@ static void _file_read_open_callback(uv_fs_t* req)
}
else
{
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(req->result)));
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for read: %s", path, uv_strerror(req->result)));
uv_fs_req_cleanup(req);
tf_free(req);
}
tf_free((void*)path);
}
static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
@ -238,7 +242,7 @@ static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int ar
int result = uv_fs_open(tf_task_get_loop(task), &req->fs, file_name, UV_FS_O_RDONLY, 0, _file_read_open_callback);
if (result < 0)
{
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "%s", uv_strerror(result)));
tf_task_reject_promise(task, promise, JS_ThrowInternalError(context, "Failed to open %s for read: %s", file_name, uv_strerror(result)));
uv_fs_req_cleanup(&req->fs);
tf_free(req);
}
@ -322,7 +326,7 @@ static void _file_read_file_zip_after_work(uv_work_t* work, int status)
}
else
{
tf_task_reject_promise(data->task, data->promise, JS_ThrowInternalError(data->context, "Error: %d.", data->result));
tf_task_reject_promise(data->task, data->promise, JS_ThrowInternalError(data->context, "Failed to read %s: %d.", data->file_path, data->result));
}
tf_free(data->buffer);
tf_free((void*)data->file_path);
@ -352,7 +356,7 @@ static JSValue _file_read_file_zip(JSContext* context, JSValueConst this_val, in
int r = uv_queue_work(tf_task_get_loop(task), &work->request, _file_read_file_zip_work, _file_read_file_zip_after_work);
if (r)
{
tf_task_reject_promise(task, work->promise, JS_ThrowInternalError(context, "%s", uv_strerror(r)));
tf_task_reject_promise(task, work->promise, JS_ThrowInternalError(context, "Failed to create read work for %s: %s", file_name, uv_strerror(r)));
tf_free((void*)work->file_path);
tf_free(work);
}

View File

@ -67,6 +67,7 @@ typedef struct _tf_http_connection_t
typedef struct _tf_http_handler_t
{
const char* pattern;
bool is_wildcard;
tf_http_callback_t* callback;
tf_http_cleanup_t* cleanup;
void* user_data;
@ -127,12 +128,48 @@ static void _http_allocate_buffer(uv_handle_t* handle, size_t suggested_size, uv
*buf = uv_buf_init(connection->incoming, sizeof(connection->incoming));
}
static bool _http_pattern_matches(const char* pattern, const char* path, bool is_wildcard)
{
if (!pattern || !*pattern || (!is_wildcard && strcmp(path, pattern) == 0))
{
return true;
}
if (is_wildcard)
{
int i = 0;
int j = 0;
while (pattern[i] && path[j] && pattern[i] != '*' && pattern[i] == path[j])
{
i++;
j++;
}
if (pattern[i] == '*')
{
while (true)
{
if (_http_pattern_matches(pattern + i + 1, path + j, strchr(pattern + i + 1, '*') != NULL))
{
return true;
}
if (!path[j])
{
break;
}
j++;
}
}
return !pattern[i] && !path[j];
}
return false;
}
static bool _http_find_handler(tf_http_t* http, const char* path, tf_http_callback_t** out_callback, const char** out_trace_name, void** out_user_data)
{
for (int i = 0; i < http->handlers_count; i++)
{
if (!http->handlers[i].pattern || !*http->handlers[i].pattern || strcmp(path, http->handlers[i].pattern) == 0 ||
(*http->handlers[i].pattern && strncmp(path, http->handlers[i].pattern, strlen(http->handlers[i].pattern)) == 0 && path[strlen(http->handlers[i].pattern) - 1] == '/'))
if (_http_pattern_matches(http->handlers[i].pattern, path, http->handlers[i].is_wildcard))
{
*out_callback = http->handlers[i].callback;
*out_trace_name = http->handlers[i].pattern;
@ -694,6 +731,7 @@ void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_
http->handlers = tf_resize_vec(http->handlers, sizeof(tf_http_handler_t) * (http->handlers_count + 1));
http->handlers[http->handlers_count++] = (tf_http_handler_t) {
.pattern = tf_strdup(pattern),
.is_wildcard = pattern && strchr(pattern, '*') != NULL,
.callback = callback,
.cleanup = cleanup,
.user_data = user_data,

View File

@ -626,11 +626,15 @@ static const char* _ext_to_content_type(const char* ext)
{
if (ext)
{
if (strcmp(ext, ".js") == 0 || strcmp(ext, ".mjs") == 0)
if (strcmp(ext, ".html") == 0)
{
return "text/html; charset=UTF-8";
}
else if (strcmp(ext, ".js") == 0 || strcmp(ext, ".mjs") == 0)
{
return "text/javascript; charset=UTF-8";
}
if (strcmp(ext, ".css") == 0)
else if (strcmp(ext, ".css") == 0)
{
return "text/css; charset=UTF-8";
}
@ -748,6 +752,11 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
is_core = is_core || (after && i == 0);
}
if (strcmp(request->path, "/speedscope/") == 0)
{
after = "index.html";
}
if (!after || strstr(after, ".."))
{
const char* k_payload = tf_http_status_text(404);
@ -783,6 +792,38 @@ static void _httpd_endpoint_static(tf_http_request_t* request)
tf_file_stat(task, path, _httpd_endpoint_static_stat, request);
}
static void _httpd_endpoint_root_callback(const char* path, void* user_data)
{
tf_http_request_t* request = user_data;
const char* host = tf_http_request_get_header(request, "x-forwarded-host");
if (!host)
{
host = tf_http_request_get_header(request, "host");
}
char url[1024];
snprintf(url, sizeof(url), "%s%s%s", request->is_tls ? "https://" : "http://", host, path ? path : "/~core/apps/");
const char* headers[] = {
"Location",
url,
};
tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0);
tf_http_request_unref(request);
}
static void _httpd_endpoint_root(tf_http_request_t* request)
{
const char* host = tf_http_request_get_header(request, "x-forwarded-host");
if (!host)
{
host = tf_http_request_get_header(request, "host");
}
tf_task_t* task = request->user_data;
tf_ssb_t* ssb = tf_task_get_ssb(task);
tf_http_request_ref(request);
tf_ssb_db_resolve_index_async(ssb, host, _httpd_endpoint_root_callback, request);
}
static void _httpd_endpoint_robots_txt(tf_http_request_t* request)
{
if (_httpd_redirect(request))
@ -1172,6 +1213,94 @@ 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)
{
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;
if (JS_IsUndefined(settings_value))
{
settings_value = JS_NewObject(context);
}
bool have_administrator = false;
JSValue permissions = JS_GetPropertyStr(context, settings_value, "permissions");
JSPropertyEnum* ptab = NULL;
uint32_t plen = 0;
JS_GetOwnPropertyNames(context, &ptab, &plen, permissions, JS_GPN_STRING_MASK);
for (int i = 0; i < (int)plen; i++)
{
JSPropertyDescriptor desc = { 0 };
if (JS_GetOwnProperty(context, &desc, permissions, ptab[i].atom) == 1)
{
int permission_length = tf_util_get_length(context, desc.value);
for (int i = 0; i < permission_length; i++)
{
JSValue entry = JS_GetPropertyUint32(context, desc.value, i);
const char* permission = JS_ToCString(context, entry);
if (permission && strcmp(permission, "administration") == 0)
{
have_administrator = true;
}
JS_FreeCString(context, permission);
JS_FreeValue(context, entry);
}
JS_FreeValue(context, desc.setter);
JS_FreeValue(context, desc.getter);
JS_FreeValue(context, desc.value);
}
}
for (uint32_t i = 0; i < plen; ++i)
{
JS_FreeAtom(context, ptab[i].atom);
}
js_free(context, ptab);
if (!have_administrator && may_become_first_admin)
{
if (JS_IsUndefined(permissions))
{
permissions = JS_NewObject(context);
JS_SetPropertyStr(context, settings_value, "permissions", JS_DupValue(context, permissions));
}
JSValue user = JS_GetPropertyStr(context, permissions, account_name_copy);
if (JS_IsUndefined(user))
{
user = JS_NewArray(context);
JS_SetPropertyStr(context, permissions, account_name_copy, JS_DupValue(context, user));
}
JS_SetPropertyUint32(context, user, tf_util_get_length(context, user), JS_NewString(context, "administration"));
JS_FreeValue(context, user);
JSValue settings_json = JS_JSONStringify(context, settings_value, JS_NULL, JS_NULL);
const char* settings_string = JS_ToCString(context, settings_json);
tf_ssb_db_set_property(ssb, "core", "settings", settings_string);
JS_FreeCString(context, settings_string);
JS_FreeValue(context, settings_json);
}
JS_FreeValue(context, permissions);
JS_FreeValue(context, settings_value);
tf_free((void*)settings);
return have_administrator;
}
static void _httpd_endpoint_login(tf_http_request_t* request)
{
tf_task_t* task = request->user_data;
@ -1269,6 +1398,8 @@ 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);
if (session_is_new && _form_data_get(form_data, "return") && !login_error)
{
const char* return_url = _form_data_get(form_data, "return");
@ -1293,69 +1424,8 @@ static void _httpd_endpoint_login(tf_http_request_t* request)
{
tf_http_request_ref(request);
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);
bool have_administrator = false;
JSValue permissions = JS_GetPropertyStr(context, settings_value, "permissions");
JSPropertyEnum* ptab = NULL;
uint32_t plen = 0;
JS_GetOwnPropertyNames(context, &ptab, &plen, permissions, JS_GPN_STRING_MASK);
for (int i = 0; i < (int)plen; i++)
{
JSPropertyDescriptor desc = { 0 };
if (JS_GetOwnProperty(context, &desc, permissions, ptab[i].atom) == 1)
{
int permission_length = tf_util_get_length(context, desc.value);
for (int i = 0; i < permission_length; i++)
{
JSValue entry = JS_GetPropertyUint32(context, desc.value, i);
const char* permission = JS_ToCString(context, entry);
if (permission && strcmp(permission, "administration") == 0)
{
have_administrator = true;
}
JS_FreeCString(context, permission);
JS_FreeValue(context, entry);
}
JS_FreeValue(context, desc.setter);
JS_FreeValue(context, desc.getter);
JS_FreeValue(context, desc.value);
}
}
for (uint32_t i = 0; i < plen; ++i)
{
JS_FreeAtom(context, ptab[i].atom);
}
js_free(context, ptab);
if (!have_administrator && may_become_first_admin)
{
if (JS_IsUndefined(permissions))
{
permissions = JS_NewObject(context);
JS_SetPropertyStr(context, settings_value, "permissions", permissions);
}
JSValue user = JS_GetPropertyStr(context, permissions, account_name_copy);
if (JS_IsUndefined(user))
{
user = JS_NewArray(context);
JS_SetPropertyStr(context, permissions, account_name_copy, user);
}
JS_SetPropertyUint32(context, user, tf_util_get_length(context, user), JS_NewString(context, "administration"));
JSValue settings_json = JS_JSONStringify(context, settings_value, JS_NULL, JS_NULL);
const char* settings_string = JS_ToCString(context, settings_json);
tf_ssb_db_set_property(ssb, "core", "settings", settings_string);
JS_FreeCString(context, settings_string);
JS_FreeValue(context, settings_json);
}
JS_FreeValue(context, permissions);
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,
@ -1363,14 +1433,10 @@ static void _httpd_endpoint_login(tf_http_request_t* request)
.error = login_error,
.session_cookie = send_session,
.session_is_new = session_is_new,
.code_of_conduct = tf_strdup(code_of_conduct),
.code_of_conduct = code_of_conduct,
.have_administrator = have_administrator,
};
JS_FreeCString(context, code_of_conduct);
JS_FreeValue(context, code_of_conduct_value);
JS_FreeValue(context, settings_value);
tf_free((void*)settings);
tf_file_read(request->user_data, "core/auth.html", _httpd_endpoint_login_file_read_callback, login);
jwt = JS_UNDEFINED;
account_name_copy = NULL;
@ -1429,12 +1495,13 @@ void tf_httpd_register(JSContext* context)
tf_http_set_trace(http, tf_task_get_trace(task));
JS_SetOpaque(httpd, http);
tf_http_add_handler(http, "/codemirror/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/lit/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/prettier/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/speedscope/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/static/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/.well-known/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/", _httpd_endpoint_root, NULL, task);
tf_http_add_handler(http, "/codemirror/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/lit/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/prettier/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/speedscope/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/static/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/.well-known/*", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL);
tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task);

223
src/ssb.c
View File

@ -98,6 +98,7 @@ typedef struct _tf_ssb_debug_close_t
typedef struct _tf_ssb_request_t
{
char name[256];
tf_ssb_rpc_callback_t* callback;
tf_ssb_callback_cleanup_t* cleanup;
void* user_data;
@ -347,15 +348,16 @@ static JSClassID _connection_class_id;
static int s_connection_index;
static int s_tunnel_index;
static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const char* reason);
static void _tf_ssb_connection_client_send_hello(tf_ssb_connection_t* connection);
static void _tf_ssb_connection_on_close(uv_handle_t* handle);
static void _tf_ssb_connection_close(tf_ssb_connection_t* connection, const char* reason);
static void _tf_ssb_nonce_inc(uint8_t* nonce);
static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t size);
static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const char* reason);
static void _tf_ssb_connection_finalizer(JSRuntime* runtime, JSValue value);
static void _tf_ssb_update_settings(tf_ssb_t* ssb);
static void _tf_ssb_connection_on_close(uv_handle_t* handle);
static void _tf_ssb_nonce_inc(uint8_t* nonce);
static void _tf_ssb_notify_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection);
static void _tf_ssb_start_update_settings(tf_ssb_t* ssb);
static void _tf_ssb_update_settings(tf_ssb_t* ssb);
static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t size);
static void _tf_ssb_add_debug_close(tf_ssb_t* ssb, tf_ssb_connection_t* connection, const char* reason)
{
@ -482,7 +484,8 @@ static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t si
}
else if (connection->tunnel_connection)
{
tf_ssb_connection_rpc_send(connection->tunnel_connection, k_ssb_rpc_flag_binary | k_ssb_rpc_flag_stream, -connection->tunnel_request_number, data, size, NULL, NULL, NULL);
tf_ssb_connection_rpc_send(
connection->tunnel_connection, k_ssb_rpc_flag_binary | k_ssb_rpc_flag_stream, -connection->tunnel_request_number, NULL, data, size, NULL, NULL, NULL);
}
}
@ -640,8 +643,8 @@ static bool _tf_ssb_connection_get_request_callback(tf_ssb_connection_t* connect
return false;
}
void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t request_number, tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data,
tf_ssb_connection_t* dependent_connection)
void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t request_number, const char* name, tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup,
void* user_data, tf_ssb_connection_t* dependent_connection)
{
tf_ssb_request_t* existing =
connection->requests_count ? bsearch(&request_number, connection->requests, connection->requests_count, sizeof(tf_ssb_request_t), _request_compare) : NULL;
@ -666,6 +669,7 @@ void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t requ
.user_data = user_data,
.dependent_connection = dependent_connection,
};
snprintf(request.name, sizeof(request.name), "%s", name);
int index = tf_util_insert_index(&request_number, connection->requests, connection->requests_count, sizeof(tf_ssb_request_t), _request_compare);
connection->requests = tf_resize_vec(connection->requests, sizeof(tf_ssb_request_t) * (connection->requests_count + 1));
if (connection->requests_count - index)
@ -677,6 +681,7 @@ void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t requ
connection->ssb->request_count++;
}
_tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_update, connection);
}
static int _message_request_compare(const void* a, const void* b)
@ -737,11 +742,12 @@ void tf_ssb_connection_remove_request(tf_ssb_connection_t* connection, int32_t r
connection->requests_count--;
connection->requests = tf_resize_vec(connection->requests, sizeof(tf_ssb_request_t) * connection->requests_count);
connection->ssb->request_count--;
_tf_ssb_notify_connections_changed(connection->ssb, k_tf_ssb_change_update, connection);
}
}
void tf_ssb_connection_rpc_send(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const uint8_t* message, size_t size, tf_ssb_rpc_callback_t* callback,
tf_ssb_callback_cleanup_t* cleanup, void* user_data)
void tf_ssb_connection_rpc_send(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const char* new_request_name, const uint8_t* message, size_t size,
tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data)
{
if (!connection)
{
@ -755,10 +761,18 @@ void tf_ssb_connection_rpc_send(tf_ssb_connection_t* connection, uint8_t flags,
{
assert(request_number > 0);
assert(!_tf_ssb_connection_get_request_callback(connection, request_number, NULL, NULL));
assert(new_request_name);
}
else if (!_tf_ssb_connection_get_request_callback(connection, request_number, NULL, NULL))
{
tf_printf("Dropping message with no active request (%d): %.*s\n", request_number, (int)size, message);
if (flags & k_ssb_rpc_flag_binary)
{
tf_printf("Dropping message with no active request (%d): (%zd bytes).\n", request_number, size);
}
else
{
tf_printf("Dropping message with no active request (%d): %.*s\n", request_number, (int)size, message);
}
return;
}
@ -779,24 +793,25 @@ void tf_ssb_connection_rpc_send(tf_ssb_connection_t* connection, uint8_t flags,
_tf_ssb_connection_box_stream_send(connection, combined, 1 + 2 * sizeof(uint32_t) + size);
tf_free(combined);
connection->ssb->rpc_out++;
if (flags & k_ssb_rpc_flag_end_error)
if ((flags & k_ssb_rpc_flag_end_error) || (request_number < 0 && !(flags & k_ssb_rpc_flag_stream)))
{
tf_ssb_connection_remove_request(connection, request_number);
}
else if (flags & k_ssb_rpc_flag_new_request)
{
tf_ssb_connection_add_request(connection, request_number, callback, cleanup, user_data, NULL);
tf_ssb_connection_add_request(connection, request_number, new_request_name, callback, cleanup, user_data, NULL);
}
}
void tf_ssb_connection_rpc_send_json(
tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue message, tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data)
void tf_ssb_connection_rpc_send_json(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const char* new_request_name, JSValue message,
tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data)
{
JSContext* context = connection->ssb->context;
JSValue json = JS_JSONStringify(context, message, JS_NULL, JS_NULL);
size_t size = 0;
const char* json_string = JS_ToCStringLen(context, &size, json);
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | (flags & ~k_ssb_rpc_mask_type), request_number, (const uint8_t*)json_string, size, callback, cleanup, user_data);
tf_ssb_connection_rpc_send(
connection, k_ssb_rpc_flag_json | (flags & ~k_ssb_rpc_mask_type), request_number, new_request_name, (const uint8_t*)json_string, size, callback, cleanup, user_data);
JS_FreeCString(context, json_string);
JS_FreeValue(context, json);
}
@ -810,7 +825,7 @@ void tf_ssb_connection_rpc_send_error(tf_ssb_connection_t* connection, uint8_t f
JS_SetPropertyStr(context, message, "stack", JS_NewString(context, stack ? stack : "stack unavailable"));
JS_SetPropertyStr(context, message, "message", JS_NewString(context, error));
tf_ssb_connection_rpc_send_json(
connection, ((flags & k_ssb_rpc_flag_stream) ? (k_ssb_rpc_flag_stream) : 0) | k_ssb_rpc_flag_end_error, request_number, message, NULL, NULL, NULL);
connection, ((flags & k_ssb_rpc_flag_stream) ? (k_ssb_rpc_flag_stream) : 0) | k_ssb_rpc_flag_end_error, request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
tf_free((void*)stack);
}
@ -1545,16 +1560,43 @@ static void _tf_ssb_connection_rpc_recv(tf_ssb_connection_t* connection, uint8_t
callback(connection, flags, request_number, val, message, size, user_data);
POST_CALLBACK(connection->ssb, callback);
tf_trace_end(connection->ssb->trace);
if (!(flags & k_ssb_rpc_flag_stream))
{
tf_ssb_connection_remove_request(connection, -request_number);
}
}
}
else if (JS_IsObject(val))
{
bool found = false;
tf_ssb_connection_add_request(connection, -request_number, NULL, NULL, NULL, NULL);
char namebuf[256] = "";
JSValue name = JS_GetPropertyStr(context, val, "name");
if (JS_IsArray(context, name))
{
int length = tf_util_get_length(context, name);
int offset = 0;
for (int i = 0; i < length; i++)
{
JSValue part = JS_GetPropertyUint32(context, name, i);
const char* part_str = JS_ToCString(context, part);
offset += snprintf(namebuf + offset, sizeof(namebuf) - offset, "%s%s", i == 0 ? "" : ".", part_str);
JS_FreeCString(context, part_str);
JS_FreeValue(context, part);
}
}
else if (JS_IsString(name))
{
const char* part_str = JS_ToCString(context, name);
snprintf(namebuf, sizeof(namebuf), "%s", part_str);
JS_FreeCString(context, part_str);
}
JS_FreeValue(context, name);
for (tf_ssb_rpc_callback_node_t* it = connection->ssb->rpc; it; it = it->next)
{
if (_tf_ssb_name_equals(context, val, it->name))
{
tf_ssb_connection_add_request(connection, -request_number, namebuf, NULL, NULL, NULL, NULL);
tf_trace_begin(connection->ssb->trace, it->flattened_name);
PRE_CALLBACK(connection->ssb, it->callback);
it->callback(connection, flags, request_number, val, message, size, it->user_data);
@ -2607,7 +2649,7 @@ static void _tf_ssb_connection_tunnel_callback(
tf_ssb_connection_t* tunnel = user_data;
if (flags & k_ssb_rpc_flag_end_error)
{
tf_ssb_connection_rpc_send(connection, flags, -request_number, (const uint8_t*)"false", strlen("false"), NULL, NULL, NULL);
tf_ssb_connection_rpc_send(connection, flags, -request_number, NULL, (const uint8_t*)"false", strlen("false"), NULL, NULL, NULL);
tf_ssb_connection_close(tunnel);
}
else
@ -2644,7 +2686,7 @@ tf_ssb_connection_t* tf_ssb_connection_tunnel_create(tf_ssb_t* ssb, const char*
ssb->connections_count++;
_tf_ssb_notify_connections_changed(ssb, k_tf_ssb_change_create, tunnel);
tf_ssb_connection_add_request(connection, request_number, _tf_ssb_connection_tunnel_callback, NULL, tunnel, tunnel);
tf_ssb_connection_add_request(connection, request_number, "tunnel.connect", _tf_ssb_connection_tunnel_callback, NULL, tunnel, tunnel);
if (request_number > 0)
{
tunnel->state = k_tf_ssb_state_connected;
@ -3379,7 +3421,7 @@ void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* id, JSValue message_
if (message_request)
{
tf_ssb_connection_rpc_send_json(
connection, k_ssb_rpc_flag_stream, message_request->request_number, message_request->keys ? message_keys : message, NULL, NULL, NULL);
connection, k_ssb_rpc_flag_stream, message_request->request_number, NULL, message_request->keys ? message_keys : message, NULL, NULL, NULL);
}
}
@ -3679,6 +3721,8 @@ typedef struct _connection_work_t
{
uv_work_t work;
tf_ssb_connection_t* connection;
const char* name;
const char* after_name;
void (*work_callback)(tf_ssb_connection_t* connection, void* user_data);
void (*after_work_callback)(tf_ssb_connection_t* connection, int result, void* user_data);
void* user_data;
@ -3690,7 +3734,9 @@ static void _tf_ssb_connection_work_callback(uv_work_t* work)
tf_ssb_record_thread_busy(data->connection->ssb, true);
if (data->work_callback)
{
tf_trace_begin(data->connection->ssb->trace, data->name);
data->work_callback(data->connection, data->user_data);
tf_trace_end(data->connection->ssb->trace);
}
tf_ssb_record_thread_busy(data->connection->ssb, false);
}
@ -3700,13 +3746,17 @@ static void _tf_ssb_connection_after_work_callback(uv_work_t* work, int status)
connection_work_t* data = work->data;
if (data->after_work_callback)
{
tf_trace_begin(data->connection->ssb->trace, data->after_name);
data->after_work_callback(data->connection, status, data->user_data);
tf_trace_end(data->connection->ssb->trace);
}
data->connection->ref_count--;
if (data->connection->ref_count == 0 && data->connection->closing)
{
_tf_ssb_connection_destroy(data->connection, "work completed");
}
tf_free((void*)data->name);
tf_free((void*)data->after_name);
tf_free(data);
}
@ -3734,6 +3784,70 @@ void tf_ssb_connection_run_work(tf_ssb_connection_t* connection, void (*work_cal
}
}
typedef struct _ssb_work_t
{
uv_work_t work;
tf_ssb_t* ssb;
const char* name;
const char* after_name;
void (*work_callback)(tf_ssb_t* ssb, void* user_data);
void (*after_work_callback)(tf_ssb_t* ssb, int result, void* user_data);
void* user_data;
} ssb_work_t;
static void _tf_ssb_work_callback(uv_work_t* work)
{
ssb_work_t* data = work->data;
tf_ssb_record_thread_busy(data->ssb, true);
if (data->work_callback)
{
tf_trace_begin(data->ssb->trace, data->name);
data->work_callback(data->ssb, data->user_data);
tf_trace_end(data->ssb->trace);
}
tf_ssb_record_thread_busy(data->ssb, false);
}
static void _tf_ssb_after_work_callback(uv_work_t* work, int status)
{
ssb_work_t* data = work->data;
if (data->after_work_callback)
{
tf_trace_begin(data->ssb->trace, data->after_name);
data->after_work_callback(data->ssb, status, data->user_data);
tf_trace_end(data->ssb->trace);
}
tf_ssb_unref(data->ssb);
tf_free((void*)data->name);
tf_free((void*)data->after_name);
tf_free(data);
}
void tf_ssb_run_work(tf_ssb_t* ssb, void (*work_callback)(tf_ssb_t* ssb, void* user_data), void (*after_work_callback)(tf_ssb_t* ssb, int result, void* user_data), void* user_data)
{
ssb_work_t* work = tf_malloc(sizeof(ssb_work_t));
*work = (ssb_work_t)
{
.work =
{
.data = work,
},
.name = tf_util_function_to_string(work_callback),
.after_name = tf_util_function_to_string(after_work_callback),
.ssb = ssb,
.work_callback = work_callback,
.after_work_callback = after_work_callback,
.user_data = user_data,
};
tf_ssb_ref(ssb);
int result = uv_queue_work(ssb->loop, &work->work, _tf_ssb_work_callback, _tf_ssb_after_work_callback);
if (result)
{
_tf_ssb_connection_after_work_callback(&work->work, result);
}
}
bool tf_ssb_is_room(tf_ssb_t* ssb)
{
return ssb->is_room;
@ -3757,8 +3871,6 @@ void tf_ssb_set_room_name(tf_ssb_t* ssb, const char* room_name)
typedef struct _update_settings_t
{
uv_work_t work;
tf_ssb_t* ssb;
bool is_room;
char room_name[1024];
} update_settings_t;
@ -3812,58 +3924,35 @@ static bool _get_global_setting_bool(tf_ssb_t* ssb, const char* name, bool defau
return result;
}
static void _tf_ssb_update_settings_work(uv_work_t* work)
static void _tf_ssb_update_settings_work(tf_ssb_t* ssb, void* user_data)
{
update_settings_t* update = work->data;
tf_ssb_record_thread_busy(update->ssb, true);
update->is_room = _get_global_setting_bool(update->ssb, "room", true);
_get_global_setting_string(update->ssb, "room_name", update->room_name, sizeof(update->room_name));
tf_ssb_record_thread_busy(update->ssb, false);
update_settings_t* update = user_data;
update->is_room = _get_global_setting_bool(ssb, "room", true);
_get_global_setting_string(ssb, "room_name", update->room_name, sizeof(update->room_name));
}
static void _tf_ssb_update_settings_after_work(uv_work_t* work, int result)
static void _tf_ssb_update_settings_after_work(tf_ssb_t* ssb, int result, void* user_data)
{
update_settings_t* update = work->data;
tf_ssb_unref(update->ssb);
tf_ssb_set_is_room(update->ssb, update->is_room);
tf_ssb_set_room_name(update->ssb, update->room_name);
_tf_ssb_start_update_settings(update->ssb);
update_settings_t* update = user_data;
tf_ssb_set_is_room(ssb, update->is_room);
tf_ssb_set_room_name(ssb, update->room_name);
_tf_ssb_start_update_settings(ssb);
tf_free(update);
}
static void _tf_ssb_start_update_settings_timer(tf_ssb_t* ssb, void* user_data)
{
update_settings_t* update = tf_malloc(sizeof(update_settings_t));
*update = (update_settings_t)
{
.work =
{
.data = update,
},
.ssb = ssb,
};
tf_ssb_ref(ssb);
int result = uv_queue_work(tf_ssb_get_loop(ssb), &update->work, _tf_ssb_update_settings_work, _tf_ssb_update_settings_after_work);
if (result)
{
_tf_ssb_update_settings_after_work(&update->work, result);
}
*update = (update_settings_t) { 0 };
tf_ssb_run_work(ssb, _tf_ssb_update_settings_work, _tf_ssb_update_settings_after_work, update);
}
static void _tf_ssb_update_settings(tf_ssb_t* ssb)
{
update_settings_t* update = tf_malloc(sizeof(update_settings_t));
*update = (update_settings_t)
{
.work =
{
.data = update,
},
.ssb = ssb,
};
tf_ssb_ref(ssb);
_tf_ssb_update_settings_work(&update->work);
_tf_ssb_update_settings_after_work(&update->work, 0);
*update = (update_settings_t) { 0 };
_tf_ssb_update_settings_work(ssb, update);
_tf_ssb_update_settings_after_work(ssb, 0, update);
}
static void _tf_ssb_start_update_settings(tf_ssb_t* ssb)
@ -3937,3 +4026,17 @@ bool tf_ssb_hmacsha256_verify(const char* public_key, const void* payload, size_
}
return result;
}
JSValue tf_ssb_connection_requests_to_object(tf_ssb_connection_t* connection)
{
JSContext* context = connection->ssb->context;
JSValue object = JS_NewArray(context);
for (int i = 0; i < connection->requests_count; i++)
{
JSValue request = JS_NewObject(context);
JS_SetPropertyStr(context, request, "name", JS_NewString(context, connection->requests[i].name));
JS_SetPropertyStr(context, request, "request_number", JS_NewInt32(context, connection->requests[i].request_number));
JS_SetPropertyUint32(context, object, i, request);
}
return object;
}

View File

@ -44,6 +44,7 @@ static void _tf_ssb_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t
}
break;
case k_tf_ssb_change_remove:
case k_tf_ssb_change_update:
break;
}
}
@ -75,32 +76,28 @@ static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connec
typedef struct _tf_ssb_connections_get_next_t
{
uv_work_t work;
tf_ssb_connections_t* connections;
tf_ssb_t* ssb;
bool ready;
char host[256];
int port;
char key[k_id_base64_len];
} tf_ssb_connections_get_next_t;
static void _tf_ssb_connections_get_next_work(uv_work_t* work)
static void _tf_ssb_connections_get_next_work(tf_ssb_t* ssb, void* user_data)
{
tf_ssb_connections_get_next_t* next = work->data;
tf_ssb_record_thread_busy(next->ssb, true);
tf_ssb_connections_get_next_t* next = user_data;
next->ready = _tf_ssb_connections_get_next_connection(next->connections, next->host, sizeof(next->host), &next->port, next->key, sizeof(next->key));
tf_ssb_record_thread_busy(next->ssb, false);
}
static void _tf_ssb_connections_get_next_after_work(uv_work_t* work, int status)
static void _tf_ssb_connections_get_next_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
tf_ssb_connections_get_next_t* next = work->data;
tf_ssb_connections_get_next_t* next = user_data;
if (next->ready)
{
uint8_t key_bin[k_id_bin_len];
if (tf_ssb_id_str_to_bin(key_bin, next->key))
{
tf_ssb_connect(next->ssb, next->host, next->port, key_bin);
tf_ssb_connect(ssb, next->host, next->port, key_bin);
}
}
tf_free(next);
@ -114,20 +111,10 @@ static void _tf_ssb_connections_timer(uv_timer_t* timer)
if (count < (int)_countof(active))
{
tf_ssb_connections_get_next_t* next = tf_malloc(sizeof(tf_ssb_connections_get_next_t));
*next = (tf_ssb_connections_get_next_t)
{
.work =
{
.data = next,
},
.ssb = connections->ssb,
*next = (tf_ssb_connections_get_next_t) {
.connections = connections,
};
int result = uv_queue_work(tf_ssb_get_loop(connections->ssb), &next->work, _tf_ssb_connections_get_next_work, _tf_ssb_connections_get_next_after_work);
if (result)
{
_tf_ssb_connections_get_next_after_work(&next->work, result);
}
tf_ssb_run_work(connections->ssb, _tf_ssb_connections_get_next_work, _tf_ssb_connections_get_next_after_work, next);
}
}
@ -162,8 +149,6 @@ void tf_ssb_connections_destroy(tf_ssb_connections_t* connections)
typedef struct _tf_ssb_connections_update_t
{
uv_work_t work;
tf_ssb_t* ssb;
char host[256];
int port;
char key[k_id_base64_len];
@ -171,12 +156,11 @@ typedef struct _tf_ssb_connections_update_t
bool succeeded;
} tf_ssb_connections_update_t;
static void _tf_ssb_connections_update_work(uv_work_t* work)
static void _tf_ssb_connections_update_work(tf_ssb_t* ssb, void* user_data)
{
tf_ssb_connections_update_t* update = work->data;
tf_ssb_record_thread_busy(update->ssb, true);
tf_ssb_connections_update_t* update = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_writer(update->ssb);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
if (update->attempted)
{
if (sqlite3_prepare(db, "UPDATE connections SET last_attempt = strftime('%s', 'now') WHERE host = ?1 AND port = ?2 AND key = ?3", -1, &statement, NULL) == SQLITE_OK)
@ -223,31 +207,23 @@ static void _tf_ssb_connections_update_work(uv_work_t* work)
sqlite3_finalize(statement);
}
}
tf_ssb_release_db_writer(update->ssb, db);
tf_ssb_record_thread_busy(update->ssb, false);
tf_ssb_release_db_writer(ssb, db);
}
static void _tf_ssb_connections_update_after_work(uv_work_t* work, int status)
static void _tf_ssb_connections_update_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
tf_ssb_connections_update_t* update = work->data;
tf_free(update);
tf_free(user_data);
}
static void _tf_ssb_connections_queue_update(tf_ssb_connections_t* connections, tf_ssb_connections_update_t* update)
{
update->work.data = update;
int result = uv_queue_work(tf_ssb_get_loop(connections->ssb), &update->work, _tf_ssb_connections_update_work, _tf_ssb_connections_update_after_work);
if (result)
{
_tf_ssb_connections_update_after_work(&update->work, result);
}
tf_ssb_run_work(connections->ssb, _tf_ssb_connections_update_work, _tf_ssb_connections_update_after_work, update);
}
void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
{
tf_ssb_connections_update_t* update = tf_malloc(sizeof(tf_ssb_connections_update_t));
*update = (tf_ssb_connections_update_t) {
.ssb = connections->ssb,
.port = port,
};
snprintf(update->host, sizeof(update->host), "%s", host);
@ -259,7 +235,6 @@ void tf_ssb_connections_set_attempted(tf_ssb_connections_t* connections, const c
{
tf_ssb_connections_update_t* update = tf_malloc(sizeof(tf_ssb_connections_update_t));
*update = (tf_ssb_connections_update_t) {
.ssb = connections->ssb,
.port = port,
.attempted = true,
};
@ -272,7 +247,6 @@ void tf_ssb_connections_set_succeeded(tf_ssb_connections_t* connections, const c
{
tf_ssb_connections_update_t* update = tf_malloc(sizeof(tf_ssb_connections_update_t));
*update = (tf_ssb_connections_update_t) {
.ssb = connections->ssb,
.port = port,
.succeeded = true,
};

View File

@ -19,8 +19,7 @@
typedef struct _message_store_t message_store_t;
static void _tf_ssb_db_store_message_work_finish(message_store_t* store);
static void _tf_ssb_db_store_message_after_work(uv_work_t* work, int status);
static void _tf_ssb_db_store_message_after_work(tf_ssb_t* ssb, int status, void* user_data);
static void _tf_ssb_db_exec(sqlite3* db, const char* statement)
{
@ -347,7 +346,7 @@ static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const
}
else
{
tf_printf("%p: Previous message doesn't exist for author=%s sequence=%" PRId64 ".\n", db, author, sequence);
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);
return last_row_id;
@ -400,8 +399,6 @@ static char* _tf_ssb_db_get_message_blob_wants(tf_ssb_t* ssb, int64_t rowid)
typedef struct _message_store_t
{
uv_work_t work;
tf_ssb_t* ssb;
char id[k_id_base64_len];
char signature[512];
int flags;
@ -421,21 +418,16 @@ typedef struct _message_store_t
message_store_t* next;
} message_store_t;
static void _tf_ssb_db_store_message_work(uv_work_t* work)
static void _tf_ssb_db_store_message_work(tf_ssb_t* ssb, void* user_data)
{
message_store_t* store = work->data;
tf_ssb_record_thread_busy(store->ssb, true);
tf_trace_t* trace = tf_ssb_get_trace(store->ssb);
tf_trace_begin(trace, "message_store_work");
int64_t last_row_id = _tf_ssb_db_store_message_raw(store->ssb, store->id, *store->previous ? store->previous : NULL, store->author, store->sequence, store->timestamp,
store->content, store->length, store->signature, store->flags);
message_store_t* store = user_data;
int64_t last_row_id = _tf_ssb_db_store_message_raw(
ssb, store->id, *store->previous ? store->previous : NULL, store->author, store->sequence, store->timestamp, store->content, store->length, store->signature, store->flags);
if (last_row_id != -1)
{
store->out_stored = true;
store->out_blob_wants = _tf_ssb_db_get_message_blob_wants(store->ssb, last_row_id);
store->out_blob_wants = _tf_ssb_db_get_message_blob_wants(ssb, last_row_id);
}
tf_trace_end(trace);
tf_ssb_record_thread_busy(store->ssb, false);
}
static void _wake_up_queue(tf_ssb_t* ssb, tf_ssb_store_queue_t* queue)
@ -452,38 +444,19 @@ static void _wake_up_queue(tf_ssb_t* ssb, tf_ssb_store_queue_t* queue)
}
next->next = NULL;
queue->running = true;
int r = uv_queue_work(tf_ssb_get_loop(ssb), &next->work, _tf_ssb_db_store_message_work, _tf_ssb_db_store_message_after_work);
if (r)
{
_tf_ssb_db_store_message_work_finish(next);
}
tf_ssb_run_work(ssb, _tf_ssb_db_store_message_work, _tf_ssb_db_store_message_after_work, next);
}
}
}
static void _tf_ssb_db_store_message_work_finish(message_store_t* store)
static void _tf_ssb_db_store_message_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
JSContext* context = tf_ssb_get_context(store->ssb);
if (store->callback)
{
store->callback(store->id, store->out_stored, store->user_data);
}
JS_FreeCString(context, store->content);
tf_ssb_store_queue_t* queue = tf_ssb_get_store_queue(store->ssb);
queue->running = false;
_wake_up_queue(store->ssb, queue);
tf_free(store);
}
static void _tf_ssb_db_store_message_after_work(uv_work_t* work, int status)
{
message_store_t* store = work->data;
tf_trace_t* trace = tf_ssb_get_trace(store->ssb);
tf_trace_begin(trace, "message_store_after_work");
message_store_t* store = user_data;
tf_trace_t* trace = tf_ssb_get_trace(ssb);
if (store->out_stored)
{
tf_trace_begin(trace, "notify_message_added");
JSContext* context = tf_ssb_get_context(store->ssb);
JSContext* context = tf_ssb_get_context(ssb);
JSValue formatted =
tf_ssb_format_message(context, store->previous, store->author, store->sequence, store->timestamp, "sha256", store->content, store->signature, store->flags);
JSValue message = JS_NewObject(context);
@ -492,7 +465,7 @@ static void _tf_ssb_db_store_message_after_work(uv_work_t* work, int status)
char timestamp_string[256];
snprintf(timestamp_string, sizeof(timestamp_string), "%f", store->timestamp);
JS_SetPropertyStr(context, message, "timestamp", JS_NewString(context, timestamp_string));
tf_ssb_notify_message_added(store->ssb, store->id, message);
tf_ssb_notify_message_added(ssb, store->id, message);
JS_FreeValue(context, message);
tf_trace_end(trace);
}
@ -501,13 +474,22 @@ static void _tf_ssb_db_store_message_after_work(uv_work_t* work, int status)
tf_trace_begin(trace, "notify_blob_wants_added");
for (char* p = store->out_blob_wants; *p; p = p + strlen(p))
{
tf_ssb_notify_blob_want_added(store->ssb, p);
tf_ssb_notify_blob_want_added(ssb, p);
}
tf_free(store->out_blob_wants);
tf_trace_end(trace);
}
_tf_ssb_db_store_message_work_finish(store);
tf_trace_end(trace);
JSContext* context = tf_ssb_get_context(ssb);
if (store->callback)
{
store->callback(store->id, store->out_stored, store->user_data);
}
JS_FreeCString(context, store->content);
tf_ssb_store_queue_t* queue = tf_ssb_get_store_queue(ssb);
queue->running = false;
_wake_up_queue(ssb, queue);
tf_free(store);
}
void tf_ssb_db_store_message(
@ -539,13 +521,7 @@ void tf_ssb_db_store_message(
JS_FreeValue(context, contentval);
message_store_t* store = tf_malloc(sizeof(message_store_t));
*store = (message_store_t)
{
.work =
{
.data = store,
},
.ssb = ssb,
*store = (message_store_t) {
.sequence = sequence,
.timestamp = timestamp,
.content = contentstr,
@ -660,8 +636,6 @@ bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_
typedef struct _blob_store_work_t
{
uv_work_t work;
tf_ssb_t* ssb;
const uint8_t* blob;
size_t size;
char id[k_blob_id_len];
@ -670,25 +644,18 @@ typedef struct _blob_store_work_t
void* user_data;
} blob_store_work_t;
static void _tf_ssb_db_blob_store_work(uv_work_t* work)
static void _tf_ssb_db_blob_store_work(tf_ssb_t* ssb, void* user_data)
{
blob_store_work_t* blob_work = work->data;
tf_ssb_record_thread_busy(blob_work->ssb, true);
tf_trace_t* trace = tf_ssb_get_trace(blob_work->ssb);
tf_trace_begin(trace, "blob_store_work");
tf_ssb_db_blob_store(blob_work->ssb, blob_work->blob, blob_work->size, blob_work->id, sizeof(blob_work->id), &blob_work->is_new);
tf_trace_end(trace);
tf_ssb_record_thread_busy(blob_work->ssb, false);
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);
}
static void _tf_ssb_db_blob_store_after_work(uv_work_t* work, int status)
static void _tf_ssb_db_blob_store_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
blob_store_work_t* blob_work = work->data;
tf_trace_t* trace = tf_ssb_get_trace(blob_work->ssb);
tf_trace_begin(trace, "blob_store_after_work");
blob_store_work_t* blob_work = user_data;
if (status == 0 && *blob_work->id)
{
tf_ssb_notify_blob_stored(blob_work->ssb, blob_work->id);
tf_ssb_notify_blob_stored(ssb, blob_work->id);
}
if (status != 0)
{
@ -698,35 +665,19 @@ static void _tf_ssb_db_blob_store_after_work(uv_work_t* work, int status)
{
blob_work->callback(status == 0 ? blob_work->id : NULL, blob_work->is_new, blob_work->user_data);
}
tf_trace_end(trace);
tf_free(blob_work);
}
void tf_ssb_db_blob_store_async(tf_ssb_t* ssb, const uint8_t* blob, size_t size, tf_ssb_db_blob_store_callback_t* callback, void* user_data)
{
blob_store_work_t* work = tf_malloc(sizeof(blob_store_work_t));
*work = (blob_store_work_t)
{
.work =
{
.data = work,
},
.ssb = ssb,
*work = (blob_store_work_t) {
.blob = blob,
.size = size,
.callback = callback,
.user_data = user_data,
};
int r = uv_queue_work(tf_ssb_get_loop(ssb), &work->work, _tf_ssb_db_blob_store_work, _tf_ssb_db_blob_store_after_work);
if (r)
{
tf_printf("tf_ssb_db_blob_store_async -> uv_queue_work failed immediately: %s\n", uv_strerror(r));
if (callback)
{
callback(NULL, false, user_data);
}
tf_free(work);
}
tf_ssb_run_work(ssb, _tf_ssb_db_blob_store_work, _tf_ssb_db_blob_store_after_work, work);
}
bool tf_ssb_db_blob_store(tf_ssb_t* ssb, const uint8_t* blob, size_t size, char* out_id, size_t out_id_size, bool* out_new)
@ -1735,3 +1686,101 @@ bool tf_ssb_db_set_property(tf_ssb_t* ssb, const char* id, const char* key, cons
tf_ssb_release_db_writer(ssb, db);
return result;
}
bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* package_owner, const char* package_name, char* out_identity, size_t out_identity_size)
{
sqlite3_stmt* statement = NULL;
bool found = false;
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = 'id:' || ? || ':' || ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, package_owner, -1, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 3, package_name, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
{
snprintf(out_identity, out_identity_size, "%s", (const char*)sqlite3_column_text(statement, 0));
found = true;
}
sqlite3_finalize(statement);
}
return found;
}
typedef struct _resolve_index_t
{
const char* host;
const char* path;
void (*callback)(const char* path, void* user_data);
void* user_data;
} resolve_index_t;
static void _tf_ssb_db_resolve_index_work(tf_ssb_t* ssb, void* user_data)
{
resolve_index_t* request = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.index_map') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
{
const char* index_map = (const char*)sqlite3_column_text(statement, 0);
const char* start = index_map;
while (start)
{
const char* end = strchr(start, '\n');
const char* equals = strchr(start, '=');
if (equals && strncasecmp(request->host, start, equals - start) == 0)
{
size_t value_length = end && equals < end ? (size_t)(end - (equals + 1)) : strlen(equals + 1);
char* path = tf_malloc(value_length + 1);
memcpy(path, equals + 1, value_length);
path[value_length] = '\0';
request->path = path;
break;
}
start = end ? end + 1 : NULL;
}
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
}
if (!request->path)
{
if (sqlite3_prepare(db, "SELECT json_extract(value, '$.index') FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
{
request->path = tf_strdup((const char*)sqlite3_column_text(statement, 0));
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
}
}
tf_ssb_release_db_reader(ssb, db);
}
static void _tf_ssb_db_resolve_index_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
resolve_index_t* request = user_data;
request->callback(request->path, request->user_data);
tf_free((void*)request->host);
tf_free((void*)request->path);
tf_free(request);
}
void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callback)(const char* path, void* user_data), void* user_data)
{
resolve_index_t* request = tf_malloc(sizeof(resolve_index_t));
*request = (resolve_index_t) {
.host = tf_strdup(host),
.callback = callback,
.user_data = user_data,
};
tf_ssb_run_work(ssb, _tf_ssb_db_resolve_index_work, _tf_ssb_db_resolve_index_after_work, request);
}

View File

@ -195,6 +195,18 @@ bool tf_ssb_db_identity_delete(tf_ssb_t* ssb, const char* user, const char* publ
*/
bool tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_key, const char* private_key);
/**
** Get the active identity for a user for a given package.
** @param db An sqlite3 database.
** @param user The username.
** @param package_owner The username of the package owner.
** @param package_name The name of the package.
** @param[out] out_identity Populated with the identity.
** @param out_identity_size The size of the out_identity buffer.
** @return true If the identity was retrieved.
*/
bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* package_owner, const char* package_name, char* out_identity, size_t out_identity_size);
/**
** Call a function for each identity owned by a user.
** @param ssb The SSB instance.
@ -358,6 +370,15 @@ const char* tf_ssb_db_get_property(tf_ssb_t* ssb, const char* id, const char* ke
*/
bool tf_ssb_db_set_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value);
/**
** Resolve a hostname to its index path by global settings.
** @param ssb The SSB instance.
** @param host The hostname.
** @param callback The callback.
** @param user_data The callback user data.
*/
void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callback)(const char* path, void* user_data), void* user_data);
/**
** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use.
** @param user_data User data registered with the authorizer.

View File

@ -38,6 +38,7 @@ typedef enum _tf_ssb_change_t
k_tf_ssb_change_create,
k_tf_ssb_change_connect,
k_tf_ssb_change_remove,
k_tf_ssb_change_update,
} tf_ssb_change_t;
/**
@ -658,27 +659,29 @@ void tf_ssb_remove_rpc_callback(tf_ssb_t* ssb, const char** name, tf_ssb_rpc_cal
** @param connection The connection on which to send the message.
** @param flags The message flags.
** @param request_number The request number.
** @param new_request_name The name of the request if it is new.
** @param message The message payload.
** @param size The size of the message.
** @param callback A callback to call if a response is received.
** @param cleanup A callback to call if the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_connection_rpc_send(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const uint8_t* message, size_t size, tf_ssb_rpc_callback_t* callback,
tf_ssb_callback_cleanup_t* cleanup, void* user_data);
void tf_ssb_connection_rpc_send(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const char* new_request_name, const uint8_t* message, size_t size,
tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Send a JSON MUXRPC message.
** @param connection The connection on which to send the message.
** @param flags The message flags.
** @param request_number The request number.
** @param new_request_name The name of the request if it is new.
** @param message The JS message payload.
** @param callback A callback to call if a response is received.
** @param cleanup A callback to call if the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_connection_rpc_send_json(
tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue message, tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
void tf_ssb_connection_rpc_send_json(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const char* new_request_name, JSValue message,
tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Send a MUXRPC error message.
@ -703,13 +706,14 @@ void tf_ssb_connection_rpc_send_error_method_not_allowed(tf_ssb_connection_t* co
** request number.
** @param connection The connection on which to register the callback.
** @param request_number The request number.
** @param name The name of the RPC request.
** @param callback The callback.
** @param cleanup The function to call when the callback is removed.
** @param user_data User data to pass to the callback.
** @param dependent_connection A connection, which, if removed, invalidates this request.
*/
void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t request_number, tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data,
tf_ssb_connection_t* dependent_connection);
void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t request_number, const char* name, tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup,
void* user_data, tf_ssb_connection_t* dependent_connection);
/**
** Remove a callback registered to be called when a message is received for the
@ -719,6 +723,13 @@ void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t requ
*/
void tf_ssb_connection_remove_request(tf_ssb_connection_t* connection, int32_t request_number);
/**
** Get a debug representation of active requests.
** @param connection The connection.
** @return The active requests as a JS object.
*/
JSValue tf_ssb_connection_requests_to_object(tf_ssb_connection_t* connection);
/**
** A function scheduled to be run later.
** @param connection The owning connection.
@ -744,6 +755,16 @@ void tf_ssb_connection_schedule_idle(tf_ssb_connection_t* connection, tf_ssb_sch
void tf_ssb_connection_run_work(tf_ssb_connection_t* connection, void (*work_callback)(tf_ssb_connection_t* connection, void* user_data),
void (*after_work_callback)(tf_ssb_connection_t* connection, int result, void* user_data), void* user_data);
/**
** Schedule work to run on a worker thread.
** @param ssb The owning SSB instance.
** @param work_callback The callback to run on a thread.
** @param after_work_callback The callback to run on the main thread when the work is complete.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_run_work(
tf_ssb_t* ssb, void (*work_callback)(tf_ssb_t* ssb, void* user_data), void (*after_work_callback)(tf_ssb_t* ssb, int result, void* user_data), void* user_data);
/**
** Register for new messages on a connection.
** @param connection The SHS connection.

View File

@ -231,28 +231,35 @@ static void _tf_ssb_import_app_json(tf_ssb_t* ssb, uv_loop_t* loop, JSContext* c
void tf_ssb_import(tf_ssb_t* ssb, const char* user, const char* path)
{
uv_fs_t req = { 0 };
int r = uv_fs_scandir(tf_ssb_get_loop(ssb), &req, path, 0, NULL);
if (r >= 0)
if (strlen(path) > strlen(".json") && strcasecmp(path + strlen(path) - strlen(".json"), ".json") == 0)
{
uv_dirent_t ent;
while (uv_fs_scandir_next(&req, &ent) == 0)
{
size_t len = strlen(path) + strlen(ent.name) + 2;
char* full_path = tf_malloc(len);
snprintf(full_path, len, "%s/%s", path, ent.name);
if (strlen(ent.name) > strlen(".json") && strcasecmp(ent.name + strlen(ent.name) - strlen(".json"), ".json") == 0)
{
_tf_ssb_import_app_json(ssb, tf_ssb_get_loop(ssb), tf_ssb_get_context(ssb), user, full_path);
}
tf_free(full_path);
}
_tf_ssb_import_app_json(ssb, tf_ssb_get_loop(ssb), tf_ssb_get_context(ssb), user, path);
}
else
{
tf_printf("Failed to scan directory %s: %s.", path, uv_strerror(r));
uv_fs_t req = { 0 };
int r = uv_fs_scandir(tf_ssb_get_loop(ssb), &req, path, 0, NULL);
if (r >= 0)
{
uv_dirent_t ent;
while (uv_fs_scandir_next(&req, &ent) == 0)
{
size_t len = strlen(path) + strlen(ent.name) + 2;
char* full_path = tf_malloc(len);
snprintf(full_path, len, "%s/%s", path, ent.name);
if (strlen(ent.name) > strlen(".json") && strcasecmp(ent.name + strlen(ent.name) - strlen(".json"), ".json") == 0)
{
_tf_ssb_import_app_json(ssb, tf_ssb_get_loop(ssb), tf_ssb_get_context(ssb), user, full_path);
}
tf_free(full_path);
}
}
else
{
tf_printf("Failed to scan directory %s: %s.", path, uv_strerror(r));
}
uv_fs_req_cleanup(&req);
}
uv_fs_req_cleanup(&req);
}
static char* _tf_ssb_import_read_current_file_from_zip(unzFile zip, size_t* size)

View File

@ -4,7 +4,6 @@
#include "mem.h"
#include "ssb.db.h"
#include "ssb.h"
#include "trace.h"
#include "util.js.h"
#include "sodium/crypto_box.h"
@ -300,6 +299,224 @@ static JSValue _tf_ssb_getAllIdentities(JSContext* context, JSValueConst this_va
return result;
}
typedef struct _active_identity_work_t
{
JSContext* context;
const char* name;
const char* package_owner;
const char* package_name;
char identity[k_id_base64_len];
int result;
JSValue promise[2];
} active_identity_work_t;
static void _tf_ssb_getActiveIdentity_visit(const char* identity, void* user_data)
{
active_identity_work_t* request = user_data;
if (!*request->identity)
{
snprintf(request->identity, sizeof(request->identity), "%s", identity);
}
}
static void _tf_ssb_getActiveIdentity_work(tf_ssb_t* ssb, void* user_data)
{
active_identity_work_t* request = user_data;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_identity_get_active(db, request->name, request->package_owner, request->package_name, request->identity, sizeof(request->identity));
tf_ssb_release_db_reader(ssb, db);
if (!*request->identity)
{
tf_ssb_db_identity_visit(ssb, request->name, _tf_ssb_getActiveIdentity_visit, request);
}
}
static void _tf_ssb_getActiveIdentity_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
active_identity_work_t* request = user_data;
JSContext* context = request->context;
if (request->result == 0)
{
JSValue identity = JS_NewString(context, request->identity);
JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 1, &identity);
JS_FreeValue(context, identity);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
}
else
{
JSValue error = JS_Call(context, request->promise[1], JS_UNDEFINED, 0, NULL);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
}
JS_FreeValue(context, request->promise[0]);
JS_FreeValue(context, request->promise[1]);
tf_free((void*)request->name);
tf_free((void*)request->package_owner);
tf_free((void*)request->package_name);
tf_free(request);
}
static JSValue _tf_ssb_getActiveIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
const char* name = JS_ToCString(context, argv[0]);
const char* package_owner = JS_ToCString(context, argv[1]);
const char* package_name = JS_ToCString(context, argv[2]);
active_identity_work_t* work = tf_malloc(sizeof(active_identity_work_t));
*work = (active_identity_work_t) {
.context = context,
.name = tf_strdup(name),
.package_owner = tf_strdup(package_owner),
.package_name = tf_strdup(package_name),
};
JSValue result = JS_NewPromiseCapability(context, work->promise);
JS_FreeCString(context, name);
JS_FreeCString(context, package_owner);
JS_FreeCString(context, package_name);
tf_ssb_run_work(ssb, _tf_ssb_getActiveIdentity_work, _tf_ssb_getActiveIdentity_after_work, work);
return result;
}
typedef struct _identity_info_work_t
{
JSContext* context;
const char* name;
const char* package_owner;
const char* package_name;
int count;
char** identities;
char** names;
int result;
char active_identity[k_id_base64_len];
JSValue promise[2];
} identity_info_work_t;
static void _tf_ssb_getIdentityInfo_visit(const char* identity, void* data)
{
identity_info_work_t* request = data;
request->identities = tf_resize_vec(request->identities, (request->count + 1) * sizeof(char*));
request->names = tf_resize_vec(request->names, (request->count + 1) * sizeof(char*));
request->identities[request->count] = tf_strdup(identity);
request->names[request->count] = NULL;
request->count++;
;
}
static void _tf_ssb_getIdentityInfo_work(tf_ssb_t* ssb, void* user_data)
{
identity_info_work_t* request = user_data;
tf_ssb_db_identity_visit(ssb, request->name, _tf_ssb_getIdentityInfo_visit, request);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement = NULL;
request->result = sqlite3_prepare(db,
"SELECT author, name FROM ( "
" SELECT "
" messages.author, "
" RANK() OVER (PARTITION BY messages.author ORDER BY messages.sequence DESC) AS author_rank, "
" messages.content ->> 'name' AS name "
" FROM messages "
" JOIN identities ON messages.author = ids.value "
" WHERE WHERE identities.user = ? AND json_extract(messages.content, '$.type') = 'about' AND content ->> 'about' = messages.author AND name IS NOT NULL) "
"WHERE author_rank = 1 ",
-1, &statement, NULL);
if (request->result == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, request->name, -1, NULL) == SQLITE_OK)
{
int r = SQLITE_OK;
while ((r = sqlite3_step(statement)) == SQLITE_OK)
{
for (int i = 0; i < request->count; i++)
{
const char* identity = (const char*)sqlite3_column_text(statement, 0);
const char* name = (const char*)sqlite3_column_text(statement, 1);
if (strcmp(request->identities[i], identity) == 0 && !request->names[i])
{
request->names[i] = tf_strdup(name);
}
break;
}
}
}
sqlite3_finalize(statement);
}
tf_ssb_db_identity_get_active(db, request->name, request->package_owner, request->package_name, request->active_identity, sizeof(request->active_identity));
if (!*request->active_identity && request->count)
{
snprintf(request->active_identity, sizeof(request->active_identity), "%s", request->identities[0]);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _tf_ssb_getIdentityInfo_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
identity_info_work_t* request = user_data;
JSContext* context = request->context;
JSValue result = JS_NewObject(context);
JSValue identities = JS_NewArray(context);
for (int i = 0; i < request->count; i++)
{
JS_SetPropertyUint32(context, identities, i, JS_NewString(context, request->identities[i]));
}
JS_SetPropertyStr(context, result, "identities", identities);
JSValue names = JS_NewObject(context);
for (int i = 0; i < request->count; i++)
{
JS_SetPropertyStr(context, names, request->identities[i], JS_NewString(context, request->names[i] ? request->names[i] : request->identities[i]));
}
JS_SetPropertyStr(context, result, "names", names);
JS_SetPropertyStr(context, result, "identity", JS_NewString(context, request->active_identity));
JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, request->promise[0]);
JS_FreeValue(context, request->promise[1]);
for (int i = 0; i < request->count; i++)
{
tf_free(request->identities[i]);
tf_free(request->names[i]);
}
tf_free(request->identities);
tf_free(request->names);
tf_free((void*)request->name);
tf_free((void*)request->package_owner);
tf_free((void*)request->package_name);
tf_free(request);
}
static JSValue _tf_ssb_getIdentityInfo(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
const char* name = JS_ToCString(context, argv[0]);
const char* package_owner = JS_ToCString(context, argv[1]);
const char* package_name = JS_ToCString(context, argv[2]);
identity_info_work_t* work = tf_malloc(sizeof(identity_info_work_t));
*work = (identity_info_work_t) {
.context = context,
.name = tf_strdup(name),
.package_owner = tf_strdup(package_owner),
.package_name = tf_strdup(package_name),
};
JSValue result = JS_NewPromiseCapability(context, work->promise);
JS_FreeCString(context, name);
JS_FreeCString(context, package_owner);
JS_FreeCString(context, package_name);
tf_ssb_run_work(ssb, _tf_ssb_getIdentityInfo_work, _tf_ssb_getIdentityInfo_after_work, work);
return result;
}
typedef struct _append_message_t
{
JSContext* context;
@ -545,6 +762,7 @@ static JSValue _tf_ssb_connections(JSContext* context, JSValueConst this_val, in
}
JS_SetPropertyStr(context, object, "tunnel", JS_NewInt32(context, tunnel_index));
}
JS_SetPropertyStr(context, object, "requests", tf_ssb_connection_requests_to_object(connection));
JS_SetPropertyUint32(context, result, i, object);
}
}
@ -605,7 +823,6 @@ typedef struct _sql_work_t
uint8_t* rows;
size_t binds_count;
size_t rows_count;
uv_work_t request;
uv_async_t async;
uv_timer_t timeout;
uv_mutex_t lock;
@ -621,13 +838,10 @@ static void _tf_ssb_sql_append(uint8_t** rows, size_t* rows_count, const void* d
*rows_count += size;
}
static void _tf_ssb_sqlAsync_work(uv_work_t* work)
static void _tf_ssb_sqlAsync_work(tf_ssb_t* ssb, void* user_data)
{
sql_work_t* sql_work = work->data;
tf_ssb_record_thread_busy(sql_work->ssb, true);
tf_trace_t* trace = tf_ssb_get_trace(sql_work->ssb);
tf_trace_begin(trace, "sql_async_work");
sqlite3* db = tf_ssb_acquire_db_reader_restricted(sql_work->ssb);
sql_work_t* sql_work = user_data;
sqlite3* db = tf_ssb_acquire_db_reader_restricted(ssb);
uv_mutex_lock(&sql_work->lock);
sql_work->db = db;
uv_mutex_unlock(&sql_work->lock);
@ -735,9 +949,7 @@ static void _tf_ssb_sqlAsync_work(uv_work_t* work)
uv_mutex_lock(&sql_work->lock);
sql_work->db = NULL;
uv_mutex_unlock(&sql_work->lock);
tf_ssb_release_db_reader(sql_work->ssb, db);
tf_ssb_record_thread_busy(sql_work->ssb, false);
tf_trace_end(trace);
tf_ssb_release_db_reader(ssb, db);
}
static void _tf_ssb_sqlAsync_handle_close(uv_handle_t* handle)
@ -763,12 +975,10 @@ static void _tf_ssb_sqlAsync_destroy(sql_work_t* work)
uv_close((uv_handle_t*)&work->async, _tf_ssb_sqlAsync_handle_close);
}
static void _tf_ssb_sqlAsync_after_work(uv_work_t* work, int status)
static void _tf_ssb_sqlAsync_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
sql_work_t* sql_work = work->data;
tf_trace_t* trace = tf_ssb_get_trace(sql_work->ssb);
tf_trace_begin(trace, "sql_async_after_work");
JSContext* context = tf_ssb_get_context(sql_work->ssb);
sql_work_t* sql_work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
uint8_t* p = sql_work->rows;
while (p < sql_work->rows + sql_work->rows_count)
{
@ -853,7 +1063,6 @@ static void _tf_ssb_sqlAsync_after_work(uv_work_t* work, int status)
JS_FreeValue(context, sql_work->callback);
JS_FreeCString(context, sql_work->query);
_tf_ssb_sqlAsync_destroy(sql_work);
tf_trace_end(trace);
}
static void _tf_ssb_sqlAsync_timeout(uv_timer_t* timer)
@ -880,10 +1089,6 @@ static JSValue _tf_ssb_sqlAsync(JSContext* context, JSValueConst this_val, int a
sql_work_t* work = tf_malloc(sizeof(sql_work_t));
*work = (sql_work_t)
{
.request =
{
.data = work,
},
.async =
{
.data = work,
@ -943,11 +1148,7 @@ static JSValue _tf_ssb_sqlAsync(JSContext* context, JSValueConst this_val, int a
}
JS_FreeValue(context, value);
}
int r = uv_queue_work(tf_ssb_get_loop(ssb), &work->request, _tf_ssb_sqlAsync_work, _tf_ssb_sqlAsync_after_work);
if (r)
{
error_value = JS_ThrowInternalError(context, "uv_queue_work failed: %s", uv_strerror(r));
}
tf_ssb_run_work(ssb, _tf_ssb_sqlAsync_work, _tf_ssb_sqlAsync_after_work, work);
}
if (!JS_IsUndefined(error_value))
{
@ -1146,6 +1347,22 @@ static void _tf_ssb_on_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change
{
case k_tf_ssb_change_create:
break;
case k_tf_ssb_change_update:
{
JSValue object = JS_DupValue(context, tf_ssb_connection_get_object(connection));
JSValue args[] = {
JS_NewString(context, "update"),
object,
};
response = JS_Call(context, callback, JS_UNDEFINED, 2, args);
if (tf_util_report_error(context, response))
{
tf_ssb_remove_connections_changed_callback(ssb, _tf_ssb_on_connections_changed_callback, user_data);
}
JS_FreeValue(context, args[0]);
JS_FreeValue(context, object);
}
break;
case k_tf_ssb_change_connect:
{
JSValue object = JS_DupValue(context, tf_ssb_connection_get_object(connection));
@ -1304,7 +1521,7 @@ static JSValue _tf_ssb_createTunnel(JSContext* context, JSValueConst this_val, i
JS_SetPropertyUint32(context, args, 0, arg);
JS_SetPropertyStr(context, message, "args", args);
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "duplex"));
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, request_number, message, NULL, NULL, NULL);
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, request_number, "tunnel.connect", message, NULL, NULL, NULL);
JS_FreeValue(context, message);
tf_ssb_connection_tunnel_create(ssb, portal_id, request_number, target_id);
@ -1553,8 +1770,6 @@ static JSValue _tf_ssb_private_message_decrypt(JSContext* context, JSValueConst
typedef struct _following_t
{
uv_work_t work;
tf_ssb_t* ssb;
JSContext* context;
JSValue promise[2];
@ -1565,17 +1780,15 @@ typedef struct _following_t
const char* ids[];
} following_t;
static void _tf_ssb_following_work(uv_work_t* work)
static void _tf_ssb_following_work(tf_ssb_t* ssb, void* user_data)
{
following_t* following = work->data;
tf_ssb_record_thread_busy(following->ssb, true);
following->out_following = tf_ssb_db_following_deep(following->ssb, following->ids, following->ids_count, following->depth);
tf_ssb_record_thread_busy(following->ssb, false);
following_t* following = user_data;
following->out_following = tf_ssb_db_following_deep(ssb, following->ids, following->ids_count, following->depth);
}
static void _tf_ssb_following_after_work(uv_work_t* work, int status)
static void _tf_ssb_following_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
following_t* following = work->data;
following_t* following = user_data;
JSContext* context = following->context;
if (status == 0)
{
@ -1622,14 +1835,8 @@ static JSValue _tf_ssb_following(JSContext* context, JSValueConst this_val, int
int ids_count = tf_util_get_length(context, argv[0]);
following_t* following = tf_malloc(sizeof(following_t) + sizeof(char*) * ids_count);
*following = (following_t)
{
.work =
{
.data = following,
},
*following = (following_t) {
.context = context,
.ssb = ssb,
};
JS_ToInt32(context, &following->depth, argv[1]);
@ -1647,11 +1854,7 @@ static JSValue _tf_ssb_following(JSContext* context, JSValueConst this_val, int
}
}
int r = uv_queue_work(tf_ssb_get_loop(ssb), &following->work, _tf_ssb_following_work, _tf_ssb_following_after_work);
if (r)
{
_tf_ssb_following_after_work(&following->work, r);
}
tf_ssb_run_work(ssb, _tf_ssb_following_work, _tf_ssb_following_after_work, following);
return result;
}
@ -1686,6 +1889,8 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
/* Does not require an identity. */
JS_SetPropertyStr(context, object, "getServerIdentity", JS_NewCFunction(context, _tf_ssb_getServerIdentity, "getServerIdentity", 0));
JS_SetPropertyStr(context, object, "getAllIdentities", JS_NewCFunction(context, _tf_ssb_getAllIdentities, "getAllIdentities", 0));
JS_SetPropertyStr(context, object, "getActiveIdentity", JS_NewCFunction(context, _tf_ssb_getActiveIdentity, "getActiveIdentity", 3));
JS_SetPropertyStr(context, object, "getIdentityInfo", JS_NewCFunction(context, _tf_ssb_getIdentityInfo, "getIdentityInfo", 3));
JS_SetPropertyStr(context, object, "getMessage", JS_NewCFunction(context, _tf_ssb_getMessage, "getMessage", 2));
JS_SetPropertyStr(context, object, "blobGet", JS_NewCFunction(context, _tf_ssb_blobGet, "blobGet", 1));
JS_SetPropertyStr(context, object, "messageContentGet", JS_NewCFunction(context, _tf_ssb_messageContentGet, "messageContentGet", 1));

View File

@ -50,7 +50,7 @@ static void _tf_ssb_rpc_gossip_ping_callback(
{
char buffer[256];
snprintf(buffer, sizeof(buffer), "%" PRId64, (int64_t)time(NULL) * 1000);
tf_ssb_connection_rpc_send(connection, flags, -request_number, (const uint8_t*)buffer, strlen(buffer), NULL, NULL, NULL);
tf_ssb_connection_rpc_send(connection, flags, -request_number, NULL, (const uint8_t*)buffer, strlen(buffer), NULL, NULL, NULL);
if (flags & k_ssb_rpc_flag_end_error)
{
tf_ssb_connection_remove_request(connection, request_number);
@ -59,7 +59,7 @@ static void _tf_ssb_rpc_gossip_ping_callback(
static void _tf_ssb_rpc_gossip_ping(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{
tf_ssb_connection_add_request(connection, -request_number, _tf_ssb_rpc_gossip_ping_callback, NULL, NULL, NULL);
tf_ssb_connection_add_request(connection, -request_number, "gossip.ping", _tf_ssb_rpc_gossip_ping_callback, NULL, NULL, NULL);
}
static void _tf_ssb_rpc_blobs_get_callback(
@ -74,7 +74,7 @@ static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags
return;
}
tf_ssb_connection_add_request(connection, -request_number, _tf_ssb_rpc_blobs_get_callback, NULL, NULL, NULL);
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");
@ -101,7 +101,7 @@ static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags
{
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, blob + offset,
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;
@ -111,8 +111,8 @@ static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags
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, (const uint8_t*)(success ? "true" : "false"),
strlen(success ? "true" : "false"), NULL, NULL, NULL);
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)
@ -126,7 +126,7 @@ static void _tf_ssb_rpc_blobs_has(tf_ssb_connection_t* connection, uint8_t flags
JS_FreeCString(context, id_str);
JS_FreeValue(context, id);
JS_FreeValue(context, ids);
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json, -request_number, (const uint8_t*)(has ? "true" : "false"), strlen(has ? "true" : "false"), NULL, NULL, NULL);
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json, -request_number, NULL, (const uint8_t*)(has ? "true" : "false"), strlen(has ? "true" : "false"), NULL, NULL, NULL);
}
static void _tf_ssb_rpc_blob_wants_added_callback(tf_ssb_t* ssb, const char* id, void* user_data)
@ -136,7 +136,7 @@ static void _tf_ssb_rpc_blob_wants_added_callback(tf_ssb_t* ssb, const char* id,
JSContext* context = tf_ssb_get_context(ssb);
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, id, JS_NewInt64(context, -1));
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, message, NULL, NULL, NULL);
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);
}
@ -195,7 +195,7 @@ static void _tf_ssb_request_blob_wants_after_work(tf_ssb_connection_t* connectio
{
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, work->out_id[i], JS_NewInt32(context, -1));
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, message, NULL, NULL, NULL);
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_wants->wants_sent++;
}
@ -239,7 +239,7 @@ static void _tf_ssb_rpc_tunnel_callback(tf_ssb_connection_t* connection, uint8_t
}
else
{
tf_ssb_connection_rpc_send(tun->connection, flags, tun->request_number, message, size, NULL, NULL, NULL);
tf_ssb_connection_rpc_send(tun->connection, flags, tun->request_number, NULL, message, size, NULL, NULL, NULL);
}
}
@ -290,7 +290,8 @@ static void _tf_ssb_rpc_tunnel_connect(tf_ssb_connection_t* connection, uint8_t
JS_SetPropertyStr(context, message, "args", arg_array);
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "duplex"));
tf_ssb_connection_rpc_send_json(target_connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tunnel_request_number, message, NULL, NULL, NULL);
tf_ssb_connection_rpc_send_json(
target_connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tunnel_request_number, "tunnel.connect", message, NULL, NULL, NULL);
tunnel_t* data0 = tf_malloc(sizeof(tunnel_t));
*data0 = (tunnel_t) {
@ -302,8 +303,8 @@ static void _tf_ssb_rpc_tunnel_connect(tf_ssb_connection_t* connection, uint8_t
.connection = connection,
.request_number = -request_number,
};
tf_ssb_connection_add_request(connection, -request_number, _tf_ssb_rpc_tunnel_callback, _tf_ssb_rpc_tunnel_cleanup, data0, target_connection);
tf_ssb_connection_add_request(target_connection, tunnel_request_number, _tf_ssb_rpc_tunnel_callback, _tf_ssb_rpc_tunnel_cleanup, data1, connection);
tf_ssb_connection_add_request(connection, -request_number, "tunnel.connect", _tf_ssb_rpc_tunnel_callback, _tf_ssb_rpc_tunnel_cleanup, data0, target_connection);
tf_ssb_connection_add_request(target_connection, tunnel_request_number, "tunnel.connect", _tf_ssb_rpc_tunnel_callback, _tf_ssb_rpc_tunnel_cleanup, data1, connection);
JS_FreeValue(context, message);
JS_FreeCString(context, portal_str);
@ -348,7 +349,7 @@ static void _tf_ssb_rpc_room_meta(tf_ssb_connection_t* connection, uint8_t flags
JS_SetPropertyUint32(context, features, 2, JS_NewString(context, "room2"));
JS_SetPropertyStr(context, response, "features", features);
}
tf_ssb_connection_rpc_send_json(connection, flags, -request_number, response, NULL, NULL, NULL);
tf_ssb_connection_rpc_send_json(connection, flags, -request_number, NULL, response, NULL, NULL, NULL);
JS_FreeValue(context, response);
}
@ -384,11 +385,11 @@ static void _tf_ssb_rpc_room_attendants(tf_ssb_connection_t* connection, uint8_t
{
JS_SetPropertyUint32(context, ids, id_count++, JS_NewString(context, id));
tf_ssb_connection_rpc_send_json(connections[i], flags, -tf_ssb_connection_get_attendant_request_number(connections[i]), joined, NULL, NULL, NULL);
tf_ssb_connection_rpc_send_json(connections[i], flags, -tf_ssb_connection_get_attendant_request_number(connections[i]), NULL, joined, NULL, NULL, NULL);
}
}
JS_SetPropertyStr(context, state, "ids", ids);
tf_ssb_connection_rpc_send_json(connection, flags, -request_number, state, NULL, NULL, NULL);
tf_ssb_connection_rpc_send_json(connection, flags, -request_number, NULL, state, NULL, NULL, NULL);
JS_FreeValue(context, joined);
JS_FreeValue(context, state);
@ -436,8 +437,8 @@ static void _tf_ssb_rpc_connection_blobs_get_callback(
}
/* TODO: Should we send the response in the callback? */
bool stored = true;
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream | k_ssb_rpc_flag_end_error, -request_number, (const uint8_t*)(stored ? "true" : "false"),
strlen(stored ? "true" : "false"), NULL, NULL, NULL);
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream | k_ssb_rpc_flag_end_error, -request_number, NULL,
(const uint8_t*)(stored ? "true" : "false"), strlen(stored ? "true" : "false"), NULL, NULL, NULL);
}
}
@ -470,7 +471,7 @@ static void _tf_ssb_rpc_connection_blobs_get(tf_ssb_connection_t* connection, co
JS_SetPropertyUint32(context, args, 0, JS_NewString(context, blob_id));
JS_SetPropertyStr(context, message, "args", args);
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), message,
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), "blobs.get", message,
_tf_ssb_rpc_connection_blobs_get_callback, _tf_ssb_rpc_connection_blobs_get_cleanup, get);
JS_FreeValue(context, message);
@ -525,14 +526,14 @@ static void _tf_ssb_rpc_connection_blobs_createWants_callback(
{
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, message, NULL, NULL, NULL);
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, message, NULL, NULL, NULL);
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);
}
}
@ -646,8 +647,8 @@ static void _tf_ssb_rpc_connection_tunnel_isRoom_callback(
JS_SetPropertyStr(context, message, "name", name);
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "source"));
JS_SetPropertyStr(context, message, "args", JS_NewArray(context));
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), message,
_tf_ssb_rpc_connection_room_attendants_callback, NULL, NULL);
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), "room.attendants",
message, _tf_ssb_rpc_connection_room_attendants_callback, NULL, NULL);
JS_FreeValue(context, message);
}
}
@ -744,7 +745,7 @@ static void _tf_ssb_connection_send_history_stream_after_work(tf_ssb_connection_
{
for (int i = 0; i < request->out_messages_count; i++)
{
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_json, request->request_number, (const uint8_t*)request->out_messages[i],
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_json, request->request_number, NULL, (const uint8_t*)request->out_messages[i],
strlen(request->out_messages[i]), NULL, NULL, NULL);
}
if (!request->out_finished)
@ -753,7 +754,7 @@ static void _tf_ssb_connection_send_history_stream_after_work(tf_ssb_connection_
}
else if (!request->live)
{
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json, request->request_number, (const uint8_t*)"false", strlen("false"), NULL, NULL, NULL);
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json, request->request_number, NULL, (const uint8_t*)"false", strlen("false"), NULL, NULL, NULL);
}
}
for (int i = 0; i < request->out_messages_count; i++)
@ -899,7 +900,7 @@ static void _tf_ssb_rpc_ebt_replicate_send_clock_after_work(tf_ssb_connection_t*
if (work->out_clock)
{
tf_ssb_connection_rpc_send(
connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_json, -work->request_number, (const uint8_t*)work->out_clock, strlen(work->out_clock), NULL, NULL, NULL);
connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_json, -work->request_number, NULL, (const uint8_t*)work->out_clock, strlen(work->out_clock), NULL, NULL, NULL);
tf_free(work->out_clock);
}
tf_free(work);
@ -1060,7 +1061,8 @@ static void _tf_ssb_rpc_send_ebt_replicate(tf_ssb_connection_t* connection)
JS_SetPropertyStr(context, message, "args", args);
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "duplex"));
int32_t request_number = tf_ssb_connection_next_request_number(connection);
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, request_number, message, _tf_ssb_rpc_ebt_replicate_client, NULL, NULL);
tf_ssb_connection_rpc_send_json(
connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, request_number, "ebt.replicate", message, _tf_ssb_rpc_ebt_replicate_client, NULL, NULL);
if (!tf_ssb_connection_get_ebt_request_number(connection))
{
tf_ssb_connection_set_ebt_request_number(connection, request_number);
@ -1076,7 +1078,7 @@ static void _tf_ssb_rpc_ebt_replicate_server(
return;
}
_tf_ssb_rpc_ebt_replicate(connection, flags, request_number, args, message, size, user_data);
tf_ssb_connection_add_request(connection, -request_number, _tf_ssb_rpc_ebt_replicate, NULL, NULL, NULL);
tf_ssb_connection_add_request(connection, -request_number, "ebt.replicate", _tf_ssb_rpc_ebt_replicate, NULL, NULL, NULL);
}
static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data)
@ -1091,8 +1093,8 @@ static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_chang
JS_SetPropertyStr(context, message, "name", name);
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "source"));
JS_SetPropertyStr(context, message, "args", JS_NewArray(context));
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), message,
_tf_ssb_rpc_connection_blobs_createWants_callback, NULL, NULL);
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), "blobs.createWants",
message, _tf_ssb_rpc_connection_blobs_createWants_callback, NULL, NULL);
JS_FreeValue(context, message);
if (tf_ssb_connection_is_client(connection))
@ -1103,8 +1105,8 @@ static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_chang
JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "isRoom"));
JS_SetPropertyStr(context, message, "name", name);
JS_SetPropertyStr(context, message, "args", JS_NewArray(context));
tf_ssb_connection_rpc_send_json(
connection, k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), message, _tf_ssb_rpc_connection_tunnel_isRoom_callback, NULL, NULL);
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), "tunnel.isRoom", message,
_tf_ssb_rpc_connection_tunnel_isRoom_callback, NULL, NULL);
JS_FreeValue(context, message);
_tf_ssb_rpc_send_ebt_replicate(connection);
@ -1126,7 +1128,8 @@ static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_chang
{
if (tf_ssb_connection_is_attendant(connections[i]))
{
tf_ssb_connection_rpc_send_json(connections[i], k_ssb_rpc_flag_stream, -tf_ssb_connection_get_attendant_request_number(connections[i]), left, NULL, NULL, NULL);
tf_ssb_connection_rpc_send_json(
connections[i], k_ssb_rpc_flag_stream, -tf_ssb_connection_get_attendant_request_number(connections[i]), NULL, left, NULL, NULL, NULL);
}
}
JS_FreeValue(context, left);
@ -1134,12 +1137,6 @@ static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_chang
}
}
typedef struct _delete_blobs_work_t
{
uv_work_t work;
tf_ssb_t* ssb;
} delete_blobs_work_t;
static void _tf_ssb_rpc_checkpoint(tf_ssb_t* ssb)
{
int64_t checkpoint_start_ms = uv_hrtime();
@ -1157,16 +1154,12 @@ static void _tf_ssb_rpc_checkpoint(tf_ssb_t* ssb)
tf_ssb_release_db_writer(ssb, db);
}
static void _tf_ssb_rpc_delete_blobs_work(uv_work_t* work)
static void _tf_ssb_rpc_delete_blobs_work(tf_ssb_t* ssb, void* user_data)
{
delete_blobs_work_t* delete = work->data;
tf_ssb_t* ssb = delete->ssb;
tf_ssb_record_thread_busy(ssb, true);
int64_t age = _get_global_setting_int64(ssb, "blob_expire_age_seconds", -1);
if (age <= 0)
{
_tf_ssb_rpc_checkpoint(ssb);
tf_ssb_record_thread_busy(ssb, false);
return;
}
int64_t start_ns = uv_hrtime();
@ -1208,28 +1201,16 @@ static void _tf_ssb_rpc_delete_blobs_work(uv_work_t* work)
tf_printf("Deleted %d blobs in %d ms.\n", deleted, (int)duration_ms);
_tf_ssb_rpc_checkpoint(ssb);
_tf_ssb_rpc_start_delete_blobs(ssb, deleted ? (int)duration_ms : (15 * 60 * 1000));
tf_ssb_record_thread_busy(ssb, false);
}
static void _tf_ssb_rpc_delete_blobs_after_work(uv_work_t* work, int status)
static void _tf_ssb_rpc_delete_blobs_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
delete_blobs_work_t* delete = work->data;
tf_ssb_unref(delete->ssb);
tf_free(delete);
tf_ssb_unref(ssb);
}
static void _tf_ssb_rpc_start_delete_callback(tf_ssb_t* ssb, void* user_data)
{
delete_blobs_work_t* work = tf_malloc(sizeof(delete_blobs_work_t));
*work = (delete_blobs_work_t) { .work = { .data = work }, .ssb = ssb };
tf_ssb_ref(ssb);
int r = uv_queue_work(tf_ssb_get_loop(ssb), &work->work, _tf_ssb_rpc_delete_blobs_work, _tf_ssb_rpc_delete_blobs_after_work);
if (r)
{
tf_printf("uv_queue_work: %s\n", uv_strerror(r));
tf_free(work);
tf_ssb_unref(ssb);
}
tf_ssb_run_work(ssb, _tf_ssb_rpc_delete_blobs_work, _tf_ssb_rpc_delete_blobs_after_work, NULL);
}
static void _tf_ssb_rpc_start_delete_blobs(tf_ssb_t* ssb, int delay_ms)

View File

@ -444,7 +444,7 @@ void tf_ssb_test_rooms(const tf_test_options_t* options)
JS_SetPropertyStr(context, message, "args", args);
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "duplex"));
tf_ssb_connection_rpc_send_json(connections[0], k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tunnel_request_number, message, NULL, NULL, NULL);
tf_ssb_connection_rpc_send_json(connections[0], k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tunnel_request_number, "tunnel.connect", message, NULL, NULL, NULL);
JS_FreeValue(context, message);
tf_ssb_connection_t* tun0 = tf_ssb_connection_tunnel_create(ssb1, id0, tunnel_request_number, id2);
@ -715,8 +715,8 @@ static void _close_callback(uv_timer_t* timer)
close_t* data = timer->data;
tf_printf("breaking %s %p\n", data->id, data->connection);
const char* message = "{\"name\":\"Error\",\"message\":\"whoops\",\"stack\":\"nah\"}";
tf_ssb_connection_rpc_send(
data->connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error, data->request_number, (const uint8_t*)message, strlen(message), NULL, NULL, NULL);
tf_ssb_connection_rpc_send(data->connection, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error, data->request_number, NULL, (const uint8_t*)message,
strlen(message), NULL, NULL, NULL);
uv_close((uv_handle_t*)timer, _timer_close);
}
@ -769,7 +769,7 @@ static void _ssb_test_room_broadcasts_visit(const char* host, const struct socka
JS_SetPropertyStr(context, message, "args", args);
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "duplex"));
tf_ssb_connection_rpc_send_json(tunnel, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tunnel_request_number, message, NULL, NULL, NULL);
tf_ssb_connection_rpc_send_json(tunnel, k_ssb_rpc_flag_stream | k_ssb_rpc_flag_new_request, tunnel_request_number, "tunnel.connect", message, NULL, NULL, NULL);
JS_FreeValue(context, message);
tf_printf("tunnel create ssb=%p portal=%s rn=%d target=%s\n", ssb, portal, (int)tunnel_request_number, target);

View File

@ -272,7 +272,7 @@ static void _tf_trace_begin_tagged(tf_trace_t* trace, const char* name, void* ta
char line[1024];
int p = snprintf(line, sizeof(line), "{\"ph\": \"B\", \"pid\": %d, \"tid\": %" PRId64 ", \"ts\": %" PRId64 ", \"name\": \"", getpid(), (int64_t)self, _trace_ts());
p += _tf_trace_escape_name(line + p, sizeof(line) - p, name);
p += _tf_trace_escape_name(line + p, sizeof(line) - p - 4, name);
p += snprintf(line + p, sizeof(line) - p, "\"},");
p = tf_min(p, tf_countof(line));
trace->callback(trace, line, p, trace->user_data);
@ -299,7 +299,7 @@ static void _tf_trace_end_tagged(tf_trace_t* trace, void* tag)
char line[1024];
int p = snprintf(line, sizeof(line), "{\"ph\": \"E\", \"pid\": %d, \"tid\": %" PRId64 ", \"ts\": %" PRId64 ", \"name\": \"", getpid(), (int64_t)pthread_self(), _trace_ts());
p += _tf_trace_escape_name(line + p, sizeof(line) - p, name);
p += _tf_trace_escape_name(line + p, sizeof(line) - p - 4, name);
p += snprintf(line + p, sizeof(line) - p, "\"},");
p = tf_min(p, tf_countof(line));
trace->callback(trace, line, p, trace->user_data);

View File

@ -1,2 +1,2 @@
#define VERSION_NUMBER "0.0.18"
#define VERSION_NAME "Celebrating totality for upwards of 3m1.4s."
#define VERSION_NUMBER "0.0.19-wip"
#define VERSION_NAME "Don't let your loyalty become a burden."