Compare commits

...

160 Commits

Author SHA1 Message Date
0d9fac7363 Support ?filename= to download a blob with a given filename.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4246 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-22 23:02:36 +00:00
2fb91fccc0 Extra /.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4245 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-22 01:21:22 +00:00
24e1ab12ab Maybe you're not signed in.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4244 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-22 01:20:57 +00:00
10ea885d8d Show the username in the apps list.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4243 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-22 01:19:53 +00:00
ec65faa12d Assign all stock apps an emoji, show them in the app list, and let the editor set them.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4242 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-21 23:08:04 +00:00
53692a1ea8 Trying to make the apps like work better on a phone.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4241 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-21 16:54:06 +00:00
ebef51b4ea Continue trying to make the android build smaller.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4240 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-20 00:29:46 +00:00
a94d6f9271 Actually bind to whichever port.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4239 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-20 00:24:47 +00:00
3d2c88c201 Group contact messages, and try to fix some messages overflowing width.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4238 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-19 23:31:08 +00:00
bdeee7fc0e Trying mostly ineffectively to make android executables smaller.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4237 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-19 20:25:50 +00:00
33a037e0ea Move executables out of the way where android expects native libraries to be.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4236 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-19 13:12:51 +00:00
2dc2d9ebf6 Add appstore, so I can get apps more easily to my phone.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4235 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-19 13:07:33 +00:00
9748f0ed8b Clean up out slightly.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4234 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-18 12:31:58 +00:00
d6be2f7d54 Bind tildefriends HTTP to an arbitrary port, write it to a file, and have the Android activity notice that file write and load the correct URL.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4233 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-18 12:28:48 +00:00
63615747a7 Fix executable choosing for my phone, and fix broadcasting to each interface.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4232 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-18 01:26:34 +00:00
fbb657a85c Ugg, no actual change but I had to touch everything to get it working in the emulator again.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4231 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-17 23:48:54 +00:00
bdac0c7879 Whitespace.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4230 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-17 22:57:18 +00:00
54dde76a8a Optimize for size sometimes. APKs are part of all.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4229 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-16 00:44:22 +00:00
2bbe22bc7a Exclude some docs and things to get the release tar.xz back under 5MB.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4228 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-16 00:23:40 +00:00
ad8532f7ac Now actually include the code.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4227 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-15 23:57:35 +00:00
602941104e Support building both debug and release APKs. Release is too big.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4226 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-15 23:55:22 +00:00
d38b41687c Throw in the towel on swipe refresh and add a refresh button.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4225 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-15 23:08:57 +00:00
08125cd1e8 Fix the android code build again. Meh.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4224 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-15 22:14:21 +00:00
2ce2097a3f This works in the emulator.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4223 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-15 21:58:21 +00:00
a5da17e1b1 Use updated android tools? I don't know. Ugg.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4222 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-15 03:21:20 +00:00
2b0962f087 Add openssl for android x86_64, and build that executable into the APK as well. Not used yet.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4221 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-14 03:17:01 +00:00
37173cce4c Cut some things to make the APK smaller.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4220 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-14 02:39:25 +00:00
37edbd9824 Get forward and back gestures working, and hide the title bar. Hiding the action bar still eludes me.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4219 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-14 02:38:56 +00:00
a32bb02223 Various fixes I've accrued. Minor cleanups and more tracing in serialize. Turn off memory tracking. Fix Let's Encrypt.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4218 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-12 22:16:18 +00:00
2ab1b84432 sqlite-amalgamation-3410100.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4217 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-11 22:29:20 +00:00
52ae19220c Enable WebView prompts and localStorage and stuff.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4216 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-11 15:24:05 +00:00
10bfa65a4e Fixed apps not working most of the time. Ultimately, storing a pointer to the database using JS_NewInt64 was lossy and a bad idea. Also, remove use of JNI since we're only starting tildefriends as its own process now.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4215 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-11 13:57:17 +00:00
2a3b1a1e33 So close. We can do it without the .so.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4214 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-11 03:47:01 +00:00
f74f4f6da9 First signs of WebView working.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4213 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-11 02:37:27 +00:00
12a8b7a058 Fix other platforms.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4212 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-10 02:06:23 +00:00
400f07660f Whoa. Apps are running on android. Switched to a static build of OpenSSL 1.1.1t for simplicity.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4211 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-10 02:02:24 +00:00
d532795b7f Import stock apps from the apk.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4210 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-09 01:39:48 +00:00
6064ed6a3a Don't use Secure cookies if we're not using TLS.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4209 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-09 01:39:15 +00:00
2c1a43df2e Implement enough of the File JS API to serve some web pages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4208 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-09 01:03:35 +00:00
bf72782c9f Now we're running enough code to respond (incorrectly) to http requests.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4207 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-09 00:32:42 +00:00
63dcab30c3 Now we can run scripts from a .zip.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4206 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-08 23:59:11 +00:00
50e48af7c4 Add all the files I think I need to the .apk, and add zlib, so I can attempt to access them using minizip.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4205 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-08 17:46:19 +00:00
9127a18ff0 With approximately this code, I was able to establish an SHS connection with my phone.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4204 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-08 02:49:41 +00:00
61ff466908 Replace all printfs with tf_printf, which redirects to android logging. Change into the files directory so that sqlite can do its thing. Getting closer.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4203 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-07 17:50:17 +00:00
1c10768aa4 Fix overbuild in android deps.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4202 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-07 03:02:16 +00:00
992b123853 Didn't end up using this.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4201 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-05 02:54:29 +00:00
f736756b20 Make a JNI call.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4200 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-05 02:54:04 +00:00
28d73f5b37 Minimal build support for an android app. Written while the power was out.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4199 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-04 19:10:05 +00:00
262b0e5e52 Attempt to track CPU usage of libuv worker threads.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4198 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-03-01 01:36:26 +00:00
1e3807bcb9 Exposed functions to encrypt and decrypt private messages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4197 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-26 19:51:54 +00:00
2ed3295f77 sqlite-amalgamation-3410000.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4196 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-26 03:12:14 +00:00
8c9d687d50 Variety of minor fixes I've been running with. SSB web interface changes. calloc overallocation fix. Use sqlAsync. Probably some other things.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4195 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-23 01:29:54 +00:00
b8b694864e Whoops, overallocated.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4194 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-20 02:42:11 +00:00
961109635b Latest libsodium-1.0.18-stable.tar.gz.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4193 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-19 23:23:53 +00:00
86bc46a11e Track memory allocations with a linked list. This is only about 3x slower than without tracking instead of 5x and growing.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4192 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-19 22:28:36 +00:00
a6a6fe75ec Aha, one more leak in sqlAsync.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4191 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-19 13:51:06 +00:00
f55f863867 Some unused global variables.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4190 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-19 13:16:55 +00:00
4ce988d00b Memory leak in maskBytes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4189 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-19 02:01:59 +00:00
1548a8a852 One less alloc for setTimeout.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4188 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-19 01:28:14 +00:00
a9551b057b Trace more things. Add a CORS header for /mem so I can make an app to examine it. Fix a memory leak. Fix tf_realloc(NULL, 0).
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4187 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-18 23:43:00 +00:00
88c7d91858 Brute force memory tracking.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4186 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-18 21:00:39 +00:00
53cb80ebf7 Replace the sqlite allocator, and use our own tracking for stats. Want to use this to collect callstacks for all allocations.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4185 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-18 19:14:06 +00:00
1f67343d75 Make traces work with multiple threads, I think.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4184 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-18 00:51:22 +00:00
4bea8bb6ba sqlite thread safety and extended result codes, mainly.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4183 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-17 22:43:19 +00:00
8e1461b3f1 Catch more sqlite errors.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4182 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-17 02:04:48 +00:00
90b513d070 Fix syntax errors not propagating.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4181 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-17 01:42:56 +00:00
8a2d3d4669 Pass around SQL errors slightly better.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4180 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-16 00:06:45 +00:00
1741403206 More memory leaks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4179 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-15 02:59:46 +00:00
980db880cc Memory leak.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4178 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-15 02:56:01 +00:00
507a62539d Fix exporting.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4177 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-15 02:43:08 +00:00
6b5d73ed5c Vague attempt at some more cleanup, and stick pthread_self() in the traces.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4176 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-15 02:34:46 +00:00
1f77df7a90 Remove dependency on base64c. Use libsodium's. Also consolidate the calls, as the usage is quite special.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4175 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-14 03:15:24 +00:00
fa87462405 Finish writing this code. Yep.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4174 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-14 02:13:08 +00:00
a5f9f927e6 Fix some memory leaks I just introduced.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4173 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-08 01:50:47 +00:00
b35d74ce36 Allow running read-only sqlite queries from libuv worker threads. Needs so much more testing.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4172 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-08 01:29:44 +00:00
ac60be14a5 Sure, we can identify SVG files.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4171 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-07 23:39:04 +00:00
beda047eb0 Disable Nagle's algorithm before we start the TLS handshake. Just speculation that it will help with some responsiveness.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4170 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-06 02:29:00 +00:00
f6742bebf3 Tracing will continue until performance improves.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4169 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-03 15:06:18 +00:00
7f334ad783 Fine, only malloc_trim if it looks like we have it.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4168 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-03 14:20:26 +00:00
ffda896308 Finish import.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4167 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-03 14:09:53 +00:00
b2fbe9dfac Stale doc file. Fix hashtag links. Trace some GC stuff and try malloc_trim, whynot.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4166 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-03 14:01:05 +00:00
6d6c41bffa Oops. Cleanup.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4165 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-02 02:48:07 +00:00
e04d137af5 Refactored import and export. No user on disk.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4164 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-02 02:09:05 +00:00
ec52e62908 Move apps/cory/ => apps/. Going to change import and export to support this.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4163 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-02 00:18:22 +00:00
6104af0d70 Smaller docker image. Why not.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4162 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-01 23:47:07 +00:00
0ca05e297d No more global settings file.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4161 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-01 23:40:21 +00:00
e0dcec074c Add process name to trace.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4160 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-01 23:20:16 +00:00
a8cecb5c64 Fix trace producing invalid JSON.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4159 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-02-01 03:15:22 +00:00
582ee0e4d7 var => let
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4158 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-31 02:48:56 +00:00
0ba54c2b7b Update lit element. Better drafts. Compose content warnings.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4157 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-30 01:45:23 +00:00
3c288f7f68 Remove duplicate apps entries on import.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4156 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-29 01:58:57 +00:00
c692b1b1f8 Modernize. All core JS is modules. var => let.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4155 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-28 22:44:45 +00:00
7091b6e6a5 Move some things to C that probably should have never been in JS, especially sha1. Minor refactors, cleanup, and deletes along the way.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4154 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-28 21:59:36 +00:00
48cd08e095 Some emoji picker and drafts tweaks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4152 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-28 19:39:41 +00:00
ef7f9db9c4 Fix stats with multiple clients.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4151 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-28 00:14:56 +00:00
0092f24fb9 Fix votes multiplying, and make everything expand through the one true state.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4150 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-26 02:08:14 +00:00
f9db1a7acf Hoisting expanded state so that it plays better with stored drafts. Still learning to Lit Element.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4149 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-25 00:56:10 +00:00
da75ad9337 Fix buffer overflow.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4148 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-24 17:38:45 +00:00
7318ddd70e This might fix one disconnect issue, when a tunnel.connect error can't be forwarded?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4147 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-22 23:34:32 +00:00
ab75ec07f8 Added some storage+debugging to track what happens before we disconnect. Maybe I'll learn something.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4146 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-22 20:37:19 +00:00
0a6b842179 Fix linkifying urls with #fragments in them. Show when an about message is not about the author.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4145 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-22 17:25:37 +00:00
5d5ff121f9 Socket leak on accept.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4144 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-21 20:12:41 +00:00
adefa76dfd Fixed blocked users slipping through.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4143 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-21 19:30:00 +00:00
2420869e7f Some fixes for drafts on threads.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4142 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-21 19:12:55 +00:00
f841ca4399 Always bugged me that I don't show the total number of child messages, just the direct number.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4141 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-21 18:58:49 +00:00
433db904cd Some draft fixes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4140 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-21 01:39:00 +00:00
c067623740 Profile image update fix.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4139 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-21 00:21:26 +00:00
dab7050899 Experimenting with storing drafts. Fixed an old scary tfrpc bug which resulted in localStorageGet returning wrong values on subsequent calls.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4138 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-21 00:16:18 +00:00
77df158178 Don't create tunnel connections to targets we're already talking to. Policy is only one connection per id.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4137 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-19 00:02:31 +00:00
0af1bcf110 Audited message flags?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4136 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-18 23:43:49 +00:00
e05302ac99 Oops. This caused a double-reject.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4135 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-18 23:14:44 +00:00
ce6cc82d64 Some socket fixes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4134 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-18 23:03:17 +00:00
85a2bc3f0f Add a stat for blobs stored.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4133 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-18 22:52:54 +00:00
3285d93576 Expose stored connections on the connections tab. Still half-baked, but I'm going to use this.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4132 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-18 00:57:54 +00:00
0f11f497ed Expose stored connections to script, and only store connections that were explicitly requested.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4131 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-18 00:37:45 +00:00
45a5202456 Spelled this argument wrong.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4130 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-18 00:07:02 +00:00
ce0b4de5a1 Fix one lingering call to ssb.connectionSendJson.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4129 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-17 23:10:17 +00:00
134b2556ad Oh yeah, OpenSSL on windows, too, these days.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4128 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-17 22:56:36 +00:00
67d34bf70e Send history streams in batches. Should block the main thread less.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4127 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-17 02:17:29 +00:00
73863f9418 Minor error-sending cleanup. Produce callstacks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4126 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-15 21:23:28 +00:00
0cbc1a650b Change blob_wants from a table to a view. We can discover the information pretty fast, so let's not store extra data.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4125 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-14 23:25:56 +00:00
9248dfd97e Docs and emoji picker and probably some other random app updates.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4124 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-14 22:27:35 +00:00
b8f54f324f Avoid sending a superfluous response, I think?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4123 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-14 19:49:43 +00:00
3269c7ca45 Use tf_ssb_connection_rpc_send_json everywhere I can. Less code, and fixes some leaks.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4122 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-14 19:32:36 +00:00
8a1b4cceec Memory leak.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4121 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-14 14:05:31 +00:00
7cd925feca More message size fixing. Need to find the end of it.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4120 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-14 13:27:19 +00:00
f6ae15c4dc A variety of potential protocol/rpc fixes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4119 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-14 00:55:51 +00:00
6ed057089b Remove the pull/push/revert buttons that I haven't used in ages.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4118 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-12 00:57:56 +00:00
a5ba014736 401 Unauthorized is an error response we send.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4117 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-12 00:01:47 +00:00
4d4cc92150 Optionally enforce an HTTP => HTTPS redirect.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4116 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-11 23:39:42 +00:00
3b00b31e87 Fix ping units, and don't spam it.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4115 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-11 02:30:07 +00:00
3c687dc780 A room.attendants left message with no id crashes some other clients. :/
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4114 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-11 01:55:23 +00:00
987b2d539a Trying to understand what's up with rooms. Various minor fixes and improvements.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4113 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-11 01:43:35 +00:00
80a1e94da4 Simplify and fix ebt.replicate.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4112 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-09 22:37:34 +00:00
69253432b8 ssb.js is now entirely in C. Usual disclaimers about it not being amazingly well tested.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4111 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-08 20:01:35 +00:00
53e4f4341c createHistoryStream JS -> C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4110 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-08 17:45:15 +00:00
6ff33191bb Try to make the tests not mingle with other instances.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4109 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-08 13:48:28 +00:00
513eb88a53 -t rooms cleanup.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4108 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-08 00:44:36 +00:00
3506d9dec1 Rooms JS => C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4107 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-08 00:25:38 +00:00
c09e043812 blob wants from JS -> C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4106 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-05 00:52:23 +00:00
4c01f23ee8 blobs.createWants again without setTimeout to fix the test.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4105 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-04 23:11:49 +00:00
ff06e91ac8 Fix feed replication. Ugh, Cory.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4104 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-04 02:59:35 +00:00
8ed359327c Appease clang.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4103 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-03 00:49:21 +00:00
a66a70324d More blobs.get. Finally replicated again to manyverse.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4102 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-02 02:11:21 +00:00
67fbbd4a8d More generous receive buffer. Max RPC size is stored in two bytes. Double so that we have overhead for the header itself and another RPC.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4101 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-02 00:58:15 +00:00
235fc9b8f9 Oops.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4100 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-02 00:35:37 +00:00
f257cccded I think this fixes some blob replication bugs. Going to test more.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4099 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-02 00:33:11 +00:00
5342ddb2bd Fix an RPC stall? How did this ever work? How is it supposed to work?
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4098 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-01 22:42:31 +00:00
7cba1b21ad Fix HTTP request breakage.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4097 ed5197a5-7fde-0310-b194-c3ffbd925b24
2023-01-01 18:12:42 +00:00
120ed36552 Continuing to chip away at moving ssb.js to C. This time, following.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4096 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-31 21:44:48 +00:00
a9f6593979 Add replication to what -t bench measures. Add a bool to control printing RPC messages. Respond to ebt.replicate with messages that weren't requested.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4095 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-31 18:59:29 +00:00
ca6d042ed6 Use picohttpparser. No more messing around.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4094 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-31 16:47:10 +00:00
ae4c2aef69 + webp magic bytes.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4093 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-30 14:51:43 +00:00
ed1c85288c Exclude openssl binaries from the release .tar.xz.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4092 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-30 14:32:19 +00:00
71151a511d sqlite introduced an unused function, apparently. Ignore it.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4091 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-30 14:22:04 +00:00
7f35f01b88 sqlite-amalgamation-3400100.zip
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4090 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-30 13:59:05 +00:00
1d13c25ded tunnel.isRoom => C.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4089 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-30 01:23:44 +00:00
09ddfffa6b Add prebuild OpenSSL, and remove SCHANNEL code and whatever it was on MacOS. Build mingw for 64-bit.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4088 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-29 23:55:49 +00:00
d9aee6d05f Compile for android. Probably needs a bunch of work to run, but it's a step in a direction.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4087 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-29 21:58:54 +00:00
94d7d2e3e0 Formatting.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4086 ed5197a5-7fde-0310-b194-c3ffbd925b24
2022-12-29 17:01:27 +00:00
17793 changed files with 827692 additions and 7720 deletions

View File

@ -2,8 +2,10 @@ FROM bitnami/minideb:bullseye AS build
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
libssl-dev
gcc \
libc6-dev \
libssl-dev \
make
COPY . /app
RUN make -C /app -j $(nproc) release

235
Makefile
View File

@ -5,7 +5,7 @@ MAKEFLAGS += --no-builtin-rules
PROJECT = tildefriends
BUILD_DIR ?= out
BUILD_TYPES := debug release windebug winrelease
BUILD_TYPES := debug release windebug winrelease androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64
UNAME_M := $(shell uname -m)
CFLAGS += \
@ -16,17 +16,75 @@ CFLAGS += \
-MMD \
-ffunction-sections \
-fdata-sections \
-fno-omit-frame-pointer \
-fno-exceptions \
-g
LDFLAGS += -Wl,-gc-sections
LDFLAGS += -Wl,--gc-sections
debug windebug: CFLAGS += -Og
debug release: LDFLAGS += -rdynamic
release winrelease: CFLAGS += -DNDEBUG -O3
windebug winrelease: CC = i686-w64-mingw32-gcc-win32
ANDROID_SDK ?= ~/Android/Sdk
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/33.0.1
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-33
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/23.1.7779620
ANDROID_NDK_API_VERSION := 31
ANDROID_MIN_SDK_VERSION := 26
ANDROID_ARM64_TARGETS := \
out/androiddebug/tildefriends \
out/androidrelease/tildefriends
ANDROID_X86_64_TARGETS := \
out/androiddebug-x86_64/tildefriends \
out/androidrelease-x86_64/tildefriends
ANDROID_TARGETS := \
$(ANDROID_X86_64_TARGETS) \
$(ANDROID_ARM64_TARGETS)
DEBUG_TARGETS := \
out/debug/tildefriends \
out/windebug/tildefriends \
out/androiddebug/tildefriends \
out/androiddebug-x86_64/tildefriends
RELEASE_TARGETS := \
out/release/tildefriends \
out/winrelease/tildefriends \
out/androidrelease/tildefriends \
out/androidrelease-x86_64/tildefriends
ANDROID_RELEASE_TARGETS := $(filter-out $(DEBUG_TARGETS),$(ANDROID_TARGETS))
NONANDROID_RELEASE_TARGETS := $(filter-out $(ANDROID_ARM64_TARGETS),$(RELEASE_TARGETS))
NONANDROID_TARGETS := $(filter-out $(ANDROID_TARGETS),$(DEBUG_TARGETS) $(RELEASE_TARGETS))
$(NONANDROID_TARGETS): CFLAGS += -fno-omit-frame-pointer
$(NONANDROID_TARGETS): LDFLAGS += -rdynamic
$(ANDROID_TARGETS): CFLAGS += \
--sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
-fPIC \
-fomit-frame-pointer \
-fno-asynchronous-unwind-tables
$(ANDROID_TARGETS): LDFLAGS += --sysroot $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC
$(DEBUG_TARGETS): CFLAGS += -DDEBUG -Og
$(RELEASE_TARGETS): CFLAGS += -DNDEBUG
$(NONANDROID_RELEASE_TARGETS): CFLAGS += -O3
$(ANDROID_RELEASE_TARGETS): CFLAGS += -Os
windebug winrelease: CC = x86_64-w64-mingw32-gcc-win32
windebug winrelease: AS = $(CC)
windebug winrelease: CFLAGS += -D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00 -DNTDDI_VERSION=NTDDI_WIN10
windebug winrelease: LDFLAGS += -static
windebug winrelease: CFLAGS += \
-D_WIN32_WINNT=0x0A00 \
-DWINVER=0x0A00 \
-DNTDDI_VERSION=NTDDI_WIN10 \
-Ideps/openssl/mingw64/include
windebug winrelease: LDFLAGS += \
-static \
-lm \
-Ldeps/openssl/mingw64/lib
$(ANDROID_X86_64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := x86_64-linux-android
$(ANDROID_ARM64_TARGETS): ANDROID_NDK_TARGET_TRIPLE := aarch64-linux-android
$(ANDROID_TARGETS): CC = $(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/clang
$(ANDROID_TARGETS): AS = $(CC)
$(ANDROID_TARGETS): CFLAGS += \
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_NDK_API_VERSION) \
-Wno-unknown-warning-option
$(ANDROID_ARM64_TARGETS): CFLAGS += -Ideps/openssl/android/arm64-v8a/usr/local/include
$(ANDROID_ARM64_TARGETS): LDFLAGS += -Ldeps/openssl/android/arm64-v8a/usr/local/lib
$(ANDROID_X86_64_TARGETS): CFLAGS += -Ideps/openssl/android/x86_64/usr/local/include
$(ANDROID_X86_64_TARGETS): LDFLAGS += -Ldeps/openssl/android/x86_64/usr/local/lib
ifeq ($(UNAME_M),x86_64)
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
@ -36,7 +94,9 @@ endif
get_objs = \
$(foreach build_type,$(BUILD_TYPES),$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)))))) \
$(foreach build_type,debug release,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix))))) \
$(foreach build_type,windebug winrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_win)))))
$(foreach build_type,windebug winrelease,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_win))))) \
$(foreach build_type,androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_android))))) \
$(foreach build_type,androiddebug androidrelease androiddebug-x86_64 androidrelease-x86_64,$(addprefix $(BUILD_DIR)/$(build_type)/,$(addsuffix .o,$(basename $(value $(1)_unix)))))
APP_SOURCES := $(wildcard src/*.c)
APP_OBJS := $(call get_objs,APP_SOURCES)
@ -47,17 +107,15 @@ $(APP_OBJS): CFLAGS += \
-Ideps/libsodium \
-Ideps/libsodium/src/libsodium/include \
-Ideps/libuv/include \
-Ideps/zlib \
-Ideps/zlib/contrib/minizip \
-Ideps/picohttpparser \
-Ideps/quickjs \
-Ideps/sqlite \
-Ideps/valgrind \
-Ideps/xopt \
-Werror
BASE64C_SOURCES := deps/base64c/src/base64c.c
BASE64C_OBJS := $(call get_objs,BASE64C_SOURCES)
$(BASE64C_OBJS): CFLAGS += \
-Wno-sign-compare
BLOWFISH_SOURCES := \
deps/crypt_blowfish/crypt_blowfish.c \
deps/crypt_blowfish/crypt_gensalt.c \
@ -105,6 +163,9 @@ UV_SOURCES_unix := \
deps/libuv/src/unix/thread.c \
deps/libuv/src/unix/tty.c \
deps/libuv/src/unix/udp.c
UV_SOURCES_android := \
deps/libuv/src/unix/pthread-fixes.c \
deps/libuv/src/unix/random-getentropy.c
UV_SOURCES_win := \
deps/libuv/src/win/async.c \
deps/libuv/src/win/core.c \
@ -181,6 +242,7 @@ SODIUM_SOURCES := \
deps/libsodium/src/libsodium/randombytes/randombytes.c \
deps/libsodium/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c \
deps/libsodium/src/libsodium/sodium/core.c \
deps/libsodium/src/libsodium/sodium/codecs.c \
deps/libsodium/src/libsodium/sodium/runtime.c \
deps/libsodium/src/libsodium/sodium/utils.c
SODIUM_OBJS := $(call get_objs,SODIUM_SOURCES)
@ -197,22 +259,37 @@ SQLITE_SOURCES := deps/sqlite/sqlite3.c
SQLITE_OBJS := $(call get_objs,SQLITE_SOURCES)
$(SQLITE_OBJS): CFLAGS += \
-DSQLITE_DBCONFIG_DEFAULT_DEFENSIVE \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_DQS=0 \
-DSQLITE_ENABLE_MEMSYS5 \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_JSON1 \
-DSQLITE_MAX_LENGTH=5242880 \
-DSQLITE_MAX_SQL_LENGTH=100000 \
-DSQLITE_MAX_COLUMN=100 \
-DSQLITE_MAX_EXPR_DEPTH=40 \
-DSQLITE_MAX_COMPOUND_SELECT=300 \
-DSQLITE_MAX_VDBE_OP=25000 \
-DSQLITE_MAX_FUNCTION_ARG=8 \
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
-DSQLITE_MAX_ATTACHED=0 \
-DSQLITE_MAX_COLUMN=100 \
-DSQLITE_MAX_COMPOUND_SELECT=300 \
-DSQLITE_MAX_EXPR_DEPTH=40 \
-DSQLITE_MAX_FUNCTION_ARG=8 \
-DSQLITE_MAX_LENGTH=5242880 \
-DSQLITE_MAX_LIKE_PATTERN_LENGTH=50 \
-DSQLITE_MAX_VARIABLE_NUMBER=100 \
-DSQLITE_MAX_SQL_LENGTH=100000 \
-DSQLITE_MAX_TRIGGER_DEPTH=10 \
-DSQLITE_MAX_VARIABLE_NUMBER=100 \
-DSQLITE_MAX_VDBE_OP=25000 \
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_DESERIALIZE \
-DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_OMIT_TCL_VARIABLE \
-DSQLITE_PRAGMA_DEFAULT_WAL_SYNCHRONOUS=1 \
-DSQLITE_SECURE_DELETE \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_UNTESTABLE \
-DSQLITE_USE_ALLOCA \
-DHAVE_ISNAN \
-Wno-implicit-fallthrough \
-Wno-unused-but-set-variable
-Wno-unused-but-set-variable \
-Wno-unused-function \
-Wno-unused-variable
XOPT_SOURCES := deps/xopt/xopt.c
XOPT_OBJS := $(call get_objs,XOPT_SOURCES)
@ -222,25 +299,27 @@ $(filter $(BUILD_DIR)/win%,$(XOPT_OBJS)): CFLAGS += \
-DHAVE_VASNPRINTF \
-DHAVE_VASPRINTF \
-Dvsnprintf=rpl_vsnprintf
$(XOPT_OBJS): CFLAGS += \
-Wno-implicit-const-int-float-conversion
QUICKJS_SOURCES := \
deps/quickjs/cutils.c \
deps/quickjs/libbf.c \
deps/quickjs/libregexp.c \
deps/quickjs/libunicode.c \
deps/quickjs/quickjs-libc.c \
deps/quickjs/quickjs.c
QUICKJS_OBJS := $(call get_objs,QUICKJS_SOURCES)
$(QUICKJS_OBJS): CFLAGS += \
-DCONFIG_VERSION=\"$(shell cat deps/quickjs/VERSION)\" \
-DCONFIG_BIGNUM \
-DDUMP_LEAKS \
-D_GNU_SOURCE \
-Wno-sign-compare \
-Wno-enum-conversion \
-Wno-implicit-const-int-float-conversion \
-Wno-implicit-fallthrough \
-Wno-unused-variable \
-Wno-sign-compare \
-Wno-unused-but-set-variable \
-Wno-enum-conversion
-Wno-unused-variable
$(NONANDROID_TARGETS): CFLAGS += -DDUMP_LEAKS
LIBBACKTRACE_SOURCES := \
deps/libbacktrace/atomic.c \
@ -269,6 +348,24 @@ $(LIBBACKTRACE_OBJS): CFLAGS += \
-Wno-unused-function \
-DBACKTRACE_ELF_SIZE=64
PICOHTTPPARSER_SOURCES := \
deps/picohttpparser/picohttpparser.c
PICOHTTPPARSER_OBJS := $(call get_objs,PICOHTTPPARSER_SOURCES)
MINIUNZIP_SOURCES := \
deps/zlib/contrib/minizip/unzip.c \
deps/zlib/contrib/minizip/ioapi.c \
deps/zlib/adler32.c \
deps/zlib/crc32.c \
deps/zlib/inffast.c \
deps/zlib/inflate.c \
deps/zlib/inftrees.c \
deps/zlib/zutil.c
MINIUNZIP_OBJS := $(call get_objs,MINIUNZIP_SOURCES)
$(MINIUNZIP_OBJS): CFLAGS += \
-Ideps/zlib \
-Wno-maybe-uninitialized
LDFLAGS += \
-pthread \
-lm
@ -281,18 +378,29 @@ windebug winrelease: LDFLAGS += \
-lws2_32 \
-lkernel32 \
-liphlpapi \
-luserenv
-luserenv \
-lssl \
-lcrypto \
-lws2_32 \
-lcrypt32
$(ANDROID_TARGETS): LDFLAGS += \
-target $(ANDROID_NDK_TARGET_TRIPLE)$(ANDROID_NDK_API_VERSION) \
-ldl \
-llog \
-lssl \
-lcrypto
unix: debug release
win: windebug winrelease
all: $(BUILD_TYPES)
all: $(BUILD_TYPES) out/TildeFriends-debug.apk out/TildeFriends-release.apk
.PHONY: all win unix
ALL_APP_OBJS := \
$(APP_OBJS) \
$(BASE64C_OBJS) \
$(BLOWFISH_OBJS) \
$(LIBBACKTRACE_OBJS) \
$(MINIUNZIP_OBJS) \
$(PICOHTTPPARSER_OBJS) \
$(QUICKJS_OBJS) \
$(SODIUM_OBJS) \
$(SQLITE_OBJS) \
@ -308,7 +416,7 @@ $(1): $(BUILD_DIR)/$(1)/$(PROJECT)$(if $(filter win%,$(1)),.exe)
$(BUILD_DIR)/$(1)/$(PROJECT)$(if $(filter win%,$(1)),.exe): $(filter $(BUILD_DIR)/$(1)/%,$(ALL_APP_OBJS))
@echo [link] $$@
@$$(CC) -o $$@ $$^ $$(LDFLAGS)
@$$(CC) -o $$@ -Wl,-Map,$$@.map $$^ $$(LDFLAGS)
$(BUILD_DIR)/$(1)/%.o: %.c
@mkdir -p $$(dir $$@)
@ -323,6 +431,67 @@ endef
$(foreach build_type,$(BUILD_TYPES),$(eval $(call build_rules,$(build_type))))
# Android support.
out/res/layout_activity_main.xml.flat: src/android/res/layout/activity_main.xml
@mkdir -p $(dir $@)
@echo [aapt2] $@
@$(ANDROID_BUILD_TOOLS)/aapt2 compile -o out/res/ src/android/res/layout/activity_main.xml
out/apk/res.apk out/gen/com/unprompted/tildefriends/R.java: out/res/layout_activity_main.xml.flat src/android/AndroidManifest.xml
@mkdir -p $(dir $@)
@$(ANDROID_BUILD_TOOLS)/aapt2 link -I $(ANDROID_PLATFORM)/android.jar out/res/layout_activity_main.xml.flat --manifest src/android/AndroidManifest.xml -o out/apk/res.apk --java out/gen/
JAVA_FILES := out/gen/com/unprompted/tildefriends/R.java $(wildcard src/android/com/unprompted/tildefriends/*.java)
CLASS_FILES := $(foreach src,$(JAVA_FILES),out/classes/com/unprompted/tildefriends/$(notdir $(src:.java=.class)))
$(CLASS_FILES) &: $(JAVA_FILES)
@echo [javac] $(CLASS_FILES)
@javac --release 8 -Xlint:deprecation -classpath $(ANDROID_PLATFORM)/android.jar -d out/classes $(JAVA_FILES)
out/apk/classes.dex: $(CLASS_FILES)
@mkdir -p $(dir $@)
@echo [d8] $@
@$(ANDROID_BUILD_TOOLS)/d8 --$(BUILD_TYPE) --lib $(ANDROID_PLATFORM)/android.jar --output $(dir $@) out/classes/com/unprompted/tildefriends/*.class
PACKAGE_DIRS := \
apps/ \
core/ \
deps/codemirror/ \
deps/split/ \
deps/smoothie/
RAW_FILES := $(shell find $(PACKAGE_DIRS) -type f)
out/apk/TildeFriends-debug.unsigned.apk: BUILD_TYPE := debug
out/apk/TildeFriends-release.unsigned.apk: BUILD_TYPE := release
out/apk/TildeFriends-debug.unsigned.apk: out/apk/classes.dex out/androiddebug/tildefriends out/androiddebug-x86_64/tildefriends $(RAW_FILES) out/apk/res.apk
out/apk/TildeFriends-release.unsigned.apk: out/apk/classes.dex out/androidrelease/tildefriends out/androidrelease-x86_64/tildefriends $(RAW_FILES) out/apk/res.apk
out/%.unsigned.apk:
@mkdir -p $(dir $@) out/apk$(BUILD_TYPE)/bin/aarch64/ out/apk$(BUILD_TYPE)/bin/x86_64/
@echo [aapt] $@
@cp out/android$(BUILD_TYPE)/tildefriends out/apk$(BUILD_TYPE)/bin/aarch64/
@cp out/android$(BUILD_TYPE)-x86_64/tildefriends out/apk$(BUILD_TYPE)/bin/x86_64/
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk$(BUILD_TYPE)/bin/aarch64/tildefriends
@$(ANDROID_NDK)/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip out/apk$(BUILD_TYPE)/bin/x86_64/tildefriends
@cp out/apk/res.apk $@
@cp out/apk/classes.dex out/apk$(BUILD_TYPE)/
@cd out/apk$(BUILD_TYPE) && zip -u ../../$@ -q -9 -r . && cd ../../
@zip -u $@ -q -9 -r $(PACKAGE_DIRS) $(RAW_FILES)
out/%.apk: out/apk/%.unsigned.apk
@echo [apksigner] $(notdir $@)
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks keystore.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --out $@ $<
apk: out/TildeFriends-debug.apk
.PHONY: apk
apkgo: out/TildeFriends-debug.apk
@adb install $<
@adb shell am start com.unprompted.tildefriends/.MainActivity
.PHONY: apkgo
clean:
rm -rf $(BUILD_DIR)
.PHONY: clean

View File

@ -28,7 +28,7 @@ privileges. Further administration can be done at
<http://localhost:12345/~core/admin/`>.
## Documentation
There are the very beginnings of developer documentation in `apps/cory/docs/`
There are the very beginnings of developer documentation in `apps/docs/`
that can be read in-place or at <http://localhost:12345/~core/docs/>.
## License

4
apps/admin.json Normal file
View File

@ -0,0 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "🎛"
}

4
apps/api.json Normal file
View File

@ -0,0 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "📜"
}

4
apps/apps.json Normal file
View File

@ -0,0 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "💻"
}

77
apps/apps/app.js Normal file
View File

@ -0,0 +1,77 @@
async function fetch_info(apps) {
let result = {};
for (let [key, value] of Object.entries(apps)) {
let blob = await ssb.blobGet(value);
blob = blob ? utf8Decode(blob) : '{}';
result[key] = JSON.parse(blob);
}
return result;
}
async function main() {
var apps = await fetch_info(await core.apps());
var core_apps = await fetch_info(await core.apps('core'));
var doc = `<!DOCTYPE html>
<html>
<head>
<style>
.container {
display: grid;
grid-template-columns: repeat(auto-fill, 64px);
justify-content: space-around;
}
.app {
height: 96px;
width: 64px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
white-space: nowrap;
}
.app > a {
text-decoration: none;
max-width: 64px;
text-overflow: ellipsis ellipsis;
overflow: hidden;
}
</style>
</head>
<body style="background: #888">
<h1 id="apps_title">Apps</h1>
<div id="apps" class="container"></div>
<h1>Core Apps</h1>
<div id="core_apps" class="container"></div>
</body>
<script>
function populate_apps(id, name, apps) {
var list = document.getElementById(id);
for (let app of Object.keys(apps).sort()) {
let div = list.appendChild(document.createElement('div'));
div.classList.add('app');
let icon_a = document.createElement('a');
let icon = document.createElement('div');
icon.appendChild(document.createTextNode(apps[app].emoji || '📦'));
icon.style.fontSize = 'xxx-large';
icon_a.appendChild(icon);
icon_a.href = '/~' + name + '/' + app + '/';
icon_a.target = '_top';
div.appendChild(icon_a);
let a = document.createElement('a');
a.appendChild(document.createTextNode(app));
a.href = '/~' + name + '/' + app + '/';
a.target = '_top';
div.appendChild(a);
}
}
document.getElementById('apps_title').innerText = "~${escape(core.user.credentials?.session?.name || 'guest')}'s Apps";
populate_apps('apps', '${core.user.credentials?.session?.name}', ${JSON.stringify(apps)});
populate_apps('core_apps', 'core', ${JSON.stringify(core_apps)});
</script>
</html>`
app.setDocument(doc);
}
main();

3
apps/appstore.json Normal file
View File

@ -0,0 +1,3 @@
{
"type": "tildefriends-app"
}

55
apps/appstore/app.js Normal file
View File

@ -0,0 +1,55 @@
async function get_apps() {
let results = {};
await ssb.sqlStream(`
SELECT messages.*
FROM messages_fts('"application/tildefriends"')
JOIN messages ON messages.rowid = messages_fts.rowid
ORDER BY timestamp
`,
[],
function(row) {
let content = JSON.parse(row.content);
for (let mention of content.mentions) {
if (mention?.type === 'application/tildefriends') {
results[JSON.stringify([row.author, mention.name])] = {
message: row,
blob: mention.link,
name: mention.name,
};
}
}
});
return Object.values(results).sort((x, y) => y.message.timestamp - x.message.timestamp);
}
function render_app(app) {
return `
<div style="border: 2px solid white; display: inline-block; margin: 8px; padding: 8px">
<a href="/~cory/ssb/#${app.message.author}">@</a>
<a href="/~cory/ssb/#${app.message.id}">%</a>
<a href="/${app.blob}/">${app.name}</a>
</div>
`;
}
async function main() {
let apps = await get_apps();
app.setDocument(`
<html>
<head>
<base target="_top">
<style>
a:link { color: #bbf; }
a:visited { color: #ddd; }
a:hover { color: #ddf; }
</style>
</head>
<body style="color: #fff">
<h1>${apps.length} apps</h1>
${apps.map(render_app).join('\n')}
</body>
</html>
`);
}
main();

View File

@ -1 +0,0 @@
{"type":"tildefriends-app","files":{"app.js":"&uhGJsy5+qBgOgEgMqCTDasK+C+GWGptHKfPiAsD5eGA=.sha256","index.html":"&D3JwdPXy/QsLXkmwNDrBFXdzxfqO1/JGxfqEArnS5v4=.sha256","lit.min.js":"&3FfrVflmGr0n4lvN0GriN1Qz1lEw31SbZxRSJrcXR28=.sha256","script.js":"&TZ2ymD6cFVUjQleGcDslt8apjp7k3xLlfv2F8rQVM4I=.sha256"}}

View File

@ -1 +0,0 @@
{"type":"tildefriends-app","files":{"app.js":"&p35JmopfHf8hFh3Y9x6LrIxiUwaJZ5Nabzi2sVXpKoo=.sha256"}}

View File

@ -1 +0,0 @@
{"type":"tildefriends-app","files":{"app.js":"&qEJDfZ43KazIxiZl8OCKb2uaDOsPkxnIohEzQ1LLFpg=.sha256"}}

View File

@ -1,31 +0,0 @@
async function main() {
var apps = await core.apps();
var core_apps = await core.apps('core');
var doc = `<!DOCTYPE html>
<html>
<body style="background: #888">
<h1>Apps</h1>
<ul id="apps"></ul>
<h1>Core Apps</h1>
<ul id="core_apps"></ul>
</body>
<script>
function populate_apps(id, name, apps) {
var list = document.getElementById(id);
for (let app of Object.keys(apps).sort()) {
var li = list.appendChild(document.createElement('li'));
var a = document.createElement('a');
a.innerText = app;
a.href = '/~' + name + '/' + app + '/';
a.target = '_top';
li.appendChild(a);
}
}
populate_apps('apps', '${core.user.credentials?.session?.name}', ${JSON.stringify(apps)});
populate_apps('core_apps', 'core', ${JSON.stringify(core_apps)});
</script>
</html>`
app.setDocument(doc);
}
main();

View File

@ -1 +0,0 @@
{"type":"tildefriends-app","files":{"app.js":"&V5o5IM9/OUyIsVkjkMW/X0i/tflQOSVJuJBmHdMT9aM=.sha256"}}

View File

@ -1 +0,0 @@
{"type":"tildefriends-app","files":{"app.js":"&WEvJYebSMi5d2eXgUwJJmvR/Q4slFg3zHYB8Q2mXJII=.sha256","index.md":"&Pi0NTJn9/w76yIUKqRRuSvUPSpqkxdYynmjeOBbF3K8=.sha256","todo.md":"&d8Kq8yuOn8SL3tJVy9BiDXHAe/jverpBj5AMLWLtmFM=.sha256","structure.md":"&T+CBfT9XP6ooKFvD1ZCI9hsutqsNIamfBxtAho0HtlU=.sha256","guide.md":"&SgnGL0+rjetY2o9A2+lVRbNvHIkqKwMnZr9gXWneIlc=.sha256","ssb.md":"&ouqT3XzTGfBNpOP/uEdOw7K1F9BeLZgQCx24XTvhyXU=.sha256"}}

View File

@ -1,17 +0,0 @@
# ID Refactor
[Back to index](#index)
## Goals
- no way to get private key in javascript
- ssb.c syncs/broadcasts/... efficiently for everybody
## Schema
- separate table to discourage leakage
- `CREATE TABLE identities (user TEXT, public TEXT, secret TEXT);`
## API
- `ssb.createIdentity()` -> `id`
- `ssb.getIdentities()` => `[id, ...]`
- `ssb.deleteIdentity(id)`
- `ssb.post(id, ...)`
- `ssb.appendMessage(id, ...)`

View File

@ -1 +0,0 @@
{"type":"tildefriends-app","files":{"app.js":"&3d9ABFgRwQvWsYbFv/rzimtnLDnVrWlGtdw7serFIGw=.sha256"}}

View File

@ -1 +0,0 @@
{"type":"tildefriends-app","files":{"app.js":"&gxOJaVf/HdjVJVC9NvZ9n3/825OD1xMMHdF/dFQwe24=.sha256","lit-all.min.js":"&XKgdRySJuiZeZvchNFGjVWn0XOVhQFmG7/HTWYQ8s68=.sha256","index.html":"&TxhFekB9ov7tf/fmkAg7x5797i27oLidhgxEfDKC0T0=.sha256","script.js":"&G8puK9Q4MngHy3D4ppcKyT49WKbHD2OCeUcAw2ghTDE=.sha256","lit-all.min.js.map":"&lA9iFp1YbqSndxXZuwtgmrj7NDMkN71nJITbtjWL3VA=.sha256","tf-id-picker.js":"&maN8DUFrmRxW5nsVyOAMk5k1ekcz/pfzvSS99ac3jo8=.sha256","tf-app.js":"&7hclNu41CIoNk1JlXHiYmDPDyDIICZfMickJYtnF5eQ=.sha256","tf-message.js":"&oXFucwmn16nvKslQoGKTppO+71EoDZJE54z3WrlNUPI=.sha256","tf-user.js":"&bXTedgBudTQLXEBPY9R8OLfQ/ZLpo8YRU9Oq/wuGG3Y=.sha256","tf-utils.js":"&6RQUuxB3PkOhYEJr9+89Ptx7uijczjn0r035yCcQOQQ=.sha256","commonmark.min.js":"&bfBaMLU19d1p/vPBF9hlARqDX002KXG/UOfxOahZhe4=.sha256","tf-compose.js":"&y+Q47tdm60Od1UzuRu7OOLwineyQCL1LIb3KP5IwHTY=.sha256","emojis.json":"&h3P4pez+AI4aYdsN0dJ3pbUEFR0276t9AM20caj/W/s=.sha256","emojis.js":"&pqYLDE/13PyEt2ceeFqvnwZ8NqWfPfpDBt4vP8SeHbs=.sha256","tf-styles.js":"&LFeL/vWgrv4N8q/mBrQAnhbaOI+dXNJYvH9bn1bXSqQ=.sha256","tf-profile.js":"&vRKjsnYvOiHCQahzEfznCvP5YDwUPtltlpWf+pxwZ1Y=.sha256","commonmark-linkify.js":"&X+hNNkmSRvKY86khyAun+cXksquXbMakZdINbGbx30g=.sha256","tf-tab-search.js":"&ESt2vMG19sH5j6ungKua/ZuvIGslyuWyb3juXdOCecg=.sha256","tf-tab-news.js":"&F7T3LVS867x7vsKhYRR7eLNdCFZmrZ3JzEMfJEEKRm0=.sha256","tf-tab-connections.js":"&Ftt5RnkrhndV2lwC7XXUZX8JiUODqPjqEVgSTJQD6JU=.sha256","tf-news.js":"&gfG5LwXpugDkwDCOCOxQnNn0jLURZexSmvDu4SpQohA=.sha256","tribute.css":"&9FogMzZHKXCfGb7mlh7z+/wiNZzBsOB/tKoh6MfYJno=.sha256","tribute.esm.js":"&P1wKqCfYULpR/ahSB98JP8xaxfikuZwwtT6I/SAo7/Y=.sha256","commonmark-hashtag.js":"&H+V1OLA9GDdzycKclz276zAtSZLpT3rlNVa4+qQmp4o=.sha256"}}

File diff suppressed because one or more lines are too long

View File

@ -1,57 +0,0 @@
import {LitElement, html} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
class TfConnectionsElement extends LitElement {
static get properties() {
return {
broadcasts: {type: Array},
identities: {type: Array},
connections: {type: Array},
users: {type: Object},
}
}
constructor() {
super();
let self = this;
this.broadcasts = [];
this.identities = [];
this.connections = [];
this.users = {};
tfrpc.rpc.getAllIdentities().then(function(identities) {
self.identities = identities || [];
});
}
_emit_change() {
let changed_event = new Event('change', {
srcElement: this,
});
this.dispatchEvent(changed_event);
}
changed(event) {
this.selected = event.srcElement.value;
tfrpc.rpc.localStorageSet('whoami', this.selected);
this._emit_change();
}
render() {
return html`
<h2>Broadcasts</h2>
<ul>
${this.broadcasts.map(x => html`<li><tf-user id=${x.pubkey} .users=${this.users}></tf-user></li>`)}
</ul>
<h2>Connections</h2>
<ul>
${this.connections.map(x => html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`)}
</ul>
<h2>Local Accounts</h2>
<ul>
${this.identities.map(x => html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`)}
</ul>
`;
}
}
customElements.define('tf-connections', TfConnectionsElement);

View File

@ -1 +0,0 @@
{"type":"tildefriends-app","files":{"app.js":"&QUR1tKa15B5Or8AfPX/8Zs87teSeX0Mh/HF7PEPBom0=.sha256","index.html":"&QXhwvxhHc9fa8iL6088hGDu9FgWdY7wkXgvU2BMNv0A=.sha256","lit-core.min.js":"&tP9KhbgwF1chFqPtkNZ12Yx9AfkpnSjFiPcX5Pw5J9g=.sha256","script.js":"&KgOaUVjBM4MzSy7PpUVQHETuvgXAx2JGPJABksBg+QY=.sha256"}}

4
apps/db.json Normal file
View File

@ -0,0 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "💽"
}

4
apps/docs.json Normal file
View File

@ -0,0 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "📚"
}

View File

@ -2,9 +2,10 @@
Tilde Friends is a participating member of a greater social
network, [Secure Scuttlebutt](https://scuttlebutt.nz/),
augmenting it with a way to safely and securely write, share,
and run code.
adding a way to safely and securely write, share,
and run code in the form of server-side web applications.
- [Tilde Friends Vision](#vision)
- [Secure Scuttlebutt from Scratch](#ssb)
- [Structure](#structure)
- [Guide](#guide)

View File

@ -16,6 +16,8 @@ IPv4 addresses.
So be prepared to accept variations.
There also an undocumented "new" style of discovery message.
## Secret Handshake, Box Stream, and RPC Protocol
Now that two clients are aware of eachother, they need to complete a secret handshake.
The [programming guide](https://ssbc.github.io/scuttlebutt-protocol-guide/#handshake)
@ -26,12 +28,14 @@ The box stream and RPC protocol can both be implemented from the
without surprises.
## Synchronizing Data
So now you're discovering other clients on the local network, connecting, performing
a secret handshake, and making remote procedure calls over box streams. The next step
is to start synchronizing feeds over the network. The goal, after all, is to author
messages in your local append-only log and have them show up in distant clients, or
vice versa.
... `ebt.replicate` or `createHistoryStream` ...
## Rooms
TODO
## References
* [https://ssbc.github.io/scuttlebutt-protocol-guide/](https://ssbc.github.io/scuttlebutt-protocol-guide/)
* [https://dev.planetary.social/](https://dev.planetary.social/)
* [https://dev.planetary.social/](https://dev.planetary.social/)
* [https://dev.scuttlebutt.nz/#/golang/?id=muxrpc-endpoints](https://dev.scuttlebutt.nz/#/golang/?id=muxrpc-endpoints)

View File

@ -21,7 +21,7 @@ In combines the following key components:
are mediated through the core process.
When run with no arguments, it starts a web server on
[http://localhost:12345/](http://localhost:12345/) and an SSB server.
[http://localhost:12345/](http://localhost:12345/) and an SSB node.
## Web Interface
The Tilde Friends web server provides access to Tilde Friends applications,

View File

@ -5,8 +5,6 @@
- Sync status (problem feeds, messages/seconds stats, ...)
- app: wiki
- app: public blog
- app: build archive
- app: todo
- Content-Disposition: download
- remove SSB credentials
- export SSB credentials
@ -22,22 +20,23 @@
- fix weird HTTP warnings
- ssb from child process?
- channels
- image downsample
- placeholder/missing images
- no denial of service
- package standalone executable
- blob_wants 2.0
- editor without app iframe
- sequence_before_author -> flags
- linkify ssb: links
## MVP2
- perfect rooms support
- connections 2.0
- make a better connections API
## Maybe Done
- blob_wants 2.0
- image downsample
- app: todo
- app: build archive
- update README
- administrators config
- administrators config
- apps name characters
- initial: can't switch to account when there is only one
- get tarball under 5MB

62
apps/docs/vision.md Normal file
View File

@ -0,0 +1,62 @@
# Tilde Friends Vision
[Back to index](#index)
Tilde Friends is a tool for making and sharing.
It is both a peer-to-peer social network client, participating in Secure
Scuttlebutt, and an environment for creating and running web applications.
## Why
This is a thing that I wanted to exist and wanted to work on. No other reason.
There is not a business model. I believe it is interesting and unique.
## 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.
## Ways to Use Tilde Friends
1. **Social Network User**: This is a social network first. You are just here,
because your friends are. Or you like how we limit your message length or
short videos or whatever the trend is. If you are ambitious, you click links
and see interactive experiences (apps) that you wouldn't see elsewhere.
2. **Web Visitor**: You get links from a friend to meeting invites, polls, games,
lists, wiki pages, ..., and you interact with them as though they were
cloud-hosted by a megacorporation. They just work, and you don't think twice.
3. **Group leader**: You host or use a small public instance, installing apps for
a group of friends to use as web visitors.
4. **Developer**: You like to write code and make or improve apps for fun or to
solve problems. When you encounter a Tilde Friends app on a strange server,
you know you can trivially modify it or download it to your own instance.
## Future Goals / Endgame
1. Mobile apps. This can run on your old phone. Maybe you won't be hosting
the web interface publicly, but you can sync, install and edit apps, and
otherwise get the full experience from a tiny touch screen.
2. The universal application runtime. The web browser is the universal
platform, but even for the simplest application that you might want to host
for your friends, cloud hosting, containers, and complicated dependencies might
all enter the mix. Tilde Friends, though it is yet another thing to host,
includes everything you need out of the box to run a vast variety of interesting
apps.
Tilde Friends will be built out, gradually providing safe access to host
resources and client resources the same way web browsers extended access to
resources like GPU, persistent storage, cameras, ... over the years.
Not much effort has been put forward yet to having a robust, long-lasting API,
but since the client side longevity is already handled by web browsers, it
seems possible that the server-side API can be managed in a similar way.
3. An awesome development environment. Right now it runs JavaScript from the
first embeddable text editor I could poorly configure enough to edit code,
but it could incorporate a debugger, source control integration a la ssb-git,
merge tools, and transpiling from all sorts of different languages.

4
apps/follow.json Normal file
View File

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

View File

@ -15,7 +15,7 @@ async function following(db, id) {
f = {users: [], sequence: 0, version: k_version};
}
f.users = new Set(f.users);
await ssb.sqlStream(
await ssb.sqlAsync(
"SELECT "+
" sequence, "+
" json_extract(content, '$.contact') AS contact, "+
@ -73,7 +73,7 @@ async function getAbout(db, id) {
if (!f || f.version != k_version) {
f = {about: {}, sequence: 0, version: k_version};
}
await ssb.sqlStream(
await ssb.sqlAsync(
"SELECT "+
" sequence, "+
" content "+
@ -109,7 +109,7 @@ async function getAbout(db, id) {
async function getSize(db, id) {
let size = 0;
await ssb.sqlStream(
await ssb.sqlAsync(
"SELECT (SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1",
[id],
function (row) {

4
apps/ssb.json Normal file
View File

@ -0,0 +1,4 @@
{
"type": "tildefriends-app",
"emoji": "🐌"
}

View File

@ -30,12 +30,14 @@ tfrpc.register(async function getBroadcasts() {
tfrpc.register(async function getConnections() {
return ssb.connections();
});
tfrpc.register(async function connectionSendJson(id, message) {
return ssb.connectionSendJson(id, message);
tfrpc.register(async function getStoredConnections() {
return ssb.storedConnections();
});
tfrpc.register(async function createTunnel(portal, request_number, target) {
let t = ssb.createTunnel(portal, request_number, target);
return t;
tfrpc.register(async function forgetStoredConnection(connection) {
return ssb.forgetStoredConnection(connection);
});
tfrpc.register(async function createTunnel(portal, target) {
return ssb.createTunnel(portal, target);
});
tfrpc.register(async function connect(token) {
await ssb.connect(token);
@ -45,7 +47,7 @@ tfrpc.register(async function closeConnection(id) {
});
tfrpc.register(async function query(sql, args) {
let result = [];
await ssb.sqlStream(sql, args, function callback(row) {
await ssb.sqlAsync(sql, args, function callback(row) {
result.push(row);
});
return result;

View File

@ -39,7 +39,7 @@ function splitMatches(text, regexp) {
return result;
}
const regex = new RegExp("#[\\w-]+");
const regex = new RegExp("(?<!\w)#[\\w-]+");
function split(textNodes) {
const text = textNodes.map(n => n.literal).join("");

View File

@ -12,11 +12,6 @@ function get_emojis() {
export function picker(callback, anchor) {
get_emojis().then(function(json) {
let existing = document.getElementById('emoji_picker');
if (existing) {
existing.parentElement.removeChild(existing);
return;
}
let div = document.createElement('div');
div.id = 'emoji_picker';
div.style.color = '#000';
@ -24,20 +19,47 @@ export function picker(callback, anchor) {
div.style.border = '1px solid #000';
div.style.display = 'block';
div.style.position = 'absolute';
div.style.maxWidth = '16em';
div.style.minWidth = 'min(16em, 90vw)';
div.style.width = 'min(16em, 90vw)';
div.style.maxWidth = 'min(16em, 90vw)';
div.style.maxHeight = '16em';
div.style.overflow = 'scroll';
div.style.fontWeight = 'bold';
div.style.fontSize = 'xx-large';
let input = document.createElement('input');
input.type = 'text';
input.style.display = 'block';
input.style.boxSizing = 'border-box';
input.style.width = '100%';
input.style.margin = '0';
input.style.position = 'relative';
div.appendChild(input);
let list = document.createElement('div');
div.appendChild(list);
div.addEventListener('mousedown', function(event) {
event.stopPropagation();
});
function cleanup() {
console.log('emoji cleanup');
div.parentElement.removeChild(div);
window.removeEventListener('keydown', key_down);
console.log('removing click');
document.body.removeEventListener('mousedown', cleanup);
}
function key_down(event) {
if (event.key == 'Escape') {
cleanup();
}
}
function refresh() {
while (list.firstChild) {
list.removeChild(list.firstChild);
}
let search = input.value;
let any_at_all = false;
Object.entries(json).forEach(function(row) {
let header = document.createElement('div');
header.appendChild(document.createTextNode(row[0]));
@ -51,28 +73,26 @@ export function picker(callback, anchor) {
}
let emoji = document.createElement('span');
const k_size = '1.25em';
emoji.style.width = k_size;
emoji.style.maxWidth = k_size;
emoji.style.minWidth = k_size;
emoji.style.height = k_size;
emoji.style.maxHeight = k_size;
emoji.style.minHeight = k_size;
emoji.style.display = 'inline-block';
emoji.style.overflow = 'hidden';
emoji.style.cursor = 'pointer';
emoji.onclick = function() {
callback(entry);
div.parentElement.removeChild(div);
cleanup();
}
emoji.title = entry.name;
emoji.appendChild(document.createTextNode(entry.emoji));
list.appendChild(emoji);
any = true;
any_at_all = true;
}
if (!any) {
list.removeChild(header);
}
});
if (!any_at_all) {
list.appendChild(document.createTextNode('No matches found.'));
}
}
refresh();
input.oninput = refresh;
@ -81,5 +101,9 @@ export function picker(callback, anchor) {
div.style.top = '50%';
div.style.left = '50%';
div.style.transform = 'translate(-50%, -50%)';
input.focus();
console.log('adding click');
document.body.addEventListener('mousedown', cleanup);
window.addEventListener('keydown', key_down);
});
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -79,7 +79,7 @@ class TfElement extends LitElement {
WHERE author = ? AND
rowid > ? AND
rowid <= ? AND
json_extract(content, "$.type") = "contact"
json_extract(content, '$.type') = 'contact'
ORDER BY sequence
`,
[id, last_row_id, max_row_id]);
@ -109,8 +109,9 @@ class TfElement extends LitElement {
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
let contact = contacts[i];
let found = Object.keys(contact.following).filter(y => !contact.blocking[y]);
let deeper = depth > 1 ? await this.following_deep_internal(found, depth - 1, Object.assign({}, contact.blocking, blocking), last_row_id, following, max_row_id) : [];
let all_blocking = Object.assign({}, contact.blocking, blocking);
let found = Object.keys(contact.following).filter(y => !all_blocking[y]);
let deeper = depth > 1 ? await this.following_deep_internal(found, depth - 1, all_blocking, last_row_id, following, max_row_id) : [];
result[id] = [id, ...found, ...deeper];
}
return [...new Set(Object.values(result).flat())];
@ -132,7 +133,11 @@ class TfElement extends LitElement {
`, []))[0].max_row_id;
let result = await this.following_deep_internal(ids, depth, blocking, cache.last_row_id, cache.following, max_row_id);
cache.last_row_id = max_row_id;
await tfrpc.rpc.databaseSet('following', JSON.stringify(cache));
let store = JSON.stringify(cache);
/* 2023-02-20: Exceeding message size. */
//if (store.length < 512 * 1024) {
await tfrpc.rpc.databaseSet('following', store);
//}
return [result, cache.following];
}
@ -275,16 +280,6 @@ class TfElement extends LitElement {
}
}
add_fake_news() {
this.unread = [{
author: this.whoami,
placeholder: true,
id: '%fake_id',
text: 'text',
content: 'hello',
}, ...this.unread];
}
async set_tab(tab) {
this.tab = tab;
if (tab === 'news') {
@ -321,7 +316,6 @@ class TfElement extends LitElement {
return html`
${this.render_id_picker()}
${tabs}
<!-- <input type="button" value="Fake News" @click=${this.add_fake_news}></input> -->
${contents}
`;
}

View File

@ -1,4 +1,4 @@
import {LitElement, html} from './lit-all.min.js';
import {LitElement, html, unsafeHTML} from './lit-all.min.js';
import * as tfutils from './tf-utils.js';
import * as tfrpc from '/static/tfrpc.js';
import {styles} from './tf-styles.js';
@ -11,8 +11,8 @@ class TfComposeElement extends LitElement {
users: {type: Object},
root: {type: String},
branch: {type: String},
mentions: {type: Object},
apps: {type: Object},
drafts: {type: Object},
}
}
@ -23,16 +23,17 @@ class TfComposeElement extends LitElement {
this.users = {};
this.root = undefined;
this.branch = undefined;
this.mentions = {};
this.apps = undefined;
this.drafts = {};
}
changed(event) {
let edit = this.renderRoot.getElementById('edit');
let preview = this.renderRoot.getElementById('preview');
let text = edit.value;
process_text(text) {
if (!text) {
return '';
}
/* Update mentions. */
let draft = this.get_draft();
let updated = false;
for (let match of text.matchAll(/\[([^\[]+)]\(([@&%][^\)]+)/g)) {
let name = match[1];
let link = match[2];
@ -49,19 +50,53 @@ class TfComposeElement extends LitElement {
break;
}
}
if (!this.mentions[link]) {
this.mentions[link] = {
if (!draft.mentions) {
draft.mentions = {};
}
if (!draft.mentions[link]) {
draft.mentions[link] = {
link: link,
}
}
this.mentions[link].name = name.startsWith('@') ? name.substring(1) : name;
this.mentions = Object.assign({}, this.mentions);
draft.mentions[link].name = name.startsWith('@') ? name.substring(1) : name;
updated = true;
}
preview.innerHTML = tfutils.markdown(text);
if (updated) {
this.requestUpdate();
}
return tfutils.markdown(text);
}
convert_to_webp(buffer, type) {
input(event) {
let edit = this.renderRoot.getElementById('edit');
let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = this.process_text(edit.value);
let content_warning = this.renderRoot.getElementById('content_warning');
let content_warning_preview = this.renderRoot.getElementById('content_warning_preview');
if (content_warning && content_warning_preview) {
content_warning_preview.innerText = content_warning.value;
}
}
notify(draft) {
this.dispatchEvent(new CustomEvent('tf-draft', {
bubbles: true,
composed: true,
detail: {
id: this.branch,
draft: draft
},
}));
}
change() {
let draft = this.get_draft();
draft.text = this.renderRoot.getElementById('edit')?.value;
draft.content_warning = this.renderRoot.getElementById('content_warning')?.value;
this.notify(draft);
}
convert_to_format(buffer, type, mime_type) {
return new Promise(function(resolve, reject) {
let img = new Image();
img.onload = function() {
@ -73,7 +108,7 @@ class TfComposeElement extends LitElement {
canvas.height = img.height * scale;
let context = canvas.getContext('2d');
context.drawImage(img, 0, 0, canvas.width, canvas.height);
let data_url = canvas.toDataURL('image/webp');
let data_url = canvas.toDataURL(mime_type);
let result = atob(data_url.split(',')[1]).split('').map(x => x.charCodeAt(0));
resolve(result);
}
@ -88,27 +123,40 @@ class TfComposeElement extends LitElement {
async add_file(file) {
try {
let draft = this.get_draft();
let self = this;
let buffer = await file.arrayBuffer();
let type = file.type;
if (type.startsWith('image/')) {
buffer = await self.convert_to_webp(buffer, file.type);
type = 'image/webp';
let best_buffer;
let best_type;
for (let format of ['image/png', 'image/jpeg', 'image/webp']) {
let test_buffer = await self.convert_to_format(buffer, file.type, format);
if (!best_buffer || test_buffer.length < best_buffer.length) {
best_buffer = test_buffer;
best_type = format;
}
}
buffer = best_buffer;
type = best_type;
} else {
buffer = Array.from(new Uint8Array(buffer));
}
let id = await tfrpc.rpc.store_blob(buffer);
let name = type.split('/')[0] + ':' + file.name;
self.mentions[id] = {
if (!draft.mentions) {
draft.mentions = {};
}
draft.mentions[id] = {
link: id,
name: name,
type: type,
size: buffer.length ?? buffer.byteLength,
};
self.mentions = Object.assign({}, self.mentions);
let edit = self.renderRoot.getElementById('edit');
edit.value += `\n![${name}](${id})`;
self.changed();
self.change();
self.input();
} catch(e) {
alert(e?.message);
}
@ -130,6 +178,7 @@ class TfComposeElement extends LitElement {
submit() {
let self = this;
let draft = this.get_draft();
let edit = this.renderRoot.getElementById('edit');
let message = {
type: 'post',
@ -139,14 +188,18 @@ class TfComposeElement extends LitElement {
message.root = this.root;
message.branch = this.branch;
}
if (Object.values(this.mentions).length) {
message.mentions = Object.values(this.mentions);
if (Object.values(draft.mentions || {}).length) {
message.mentions = Object.values(draft.mentions);
}
if (draft.content_warning !== undefined) {
message.contentWarning = draft.content_warning;
}
console.log('Would post:', message);
tfrpc.rpc.appendMessage(this.whoami, message).then(function() {
edit.value = '';
self.mentions = {};
self.changed();
self.change();
self.notify(undefined);
self.requestUpdate();
}).catch(function(error) {
alert(error.message);
});
@ -155,8 +208,10 @@ class TfComposeElement extends LitElement {
discard() {
let edit = this.renderRoot.getElementById('edit');
edit.value = '';
this.changed();
this.dispatchEvent(new CustomEvent('tf-discard'));
this.change();
let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = '';
this.notify(undefined);
}
attach() {
@ -181,9 +236,21 @@ class TfComposeElement extends LitElement {
tribute.attach(this.renderRoot.getElementById('edit'));
}
updated() {
super.updated();
let edit = this.renderRoot.getElementById('edit');
if (this.last_updated_text !== edit.value) {
let preview = this.renderRoot.getElementById('preview');
preview.innerHTML = this.process_text(edit.value);
this.last_updated_text = edit.value;
}
}
remove_mention(id) {
delete this.mentions[id];
this.mentions = Object.assign({}, this.mentions);
let draft = this.get_draft();
delete draft.mentions[id];
this.notify(draft);
this.requestUpdate();
}
render_mention(mention) {
@ -216,7 +283,10 @@ class TfComposeElement extends LitElement {
};
}
}
this.mentions = Object.assign(this.mentions || {}, mentions);
let draft = this.get_draft();
draft.mentions = Object.assign(draft.mentions || {}, mentions);
this.requestUpdate();
this.notify(draft);
this.apps = null;
}
@ -244,14 +314,53 @@ class TfComposeElement extends LitElement {
}
}
set_content_warning(value) {
let draft = this.get_draft();
draft.content_warning = value;
this.notify(draft);
this.requestUpdate();
}
render_content_warning() {
let self = this;
let draft = this.get_draft();
if (draft.content_warning !== undefined) {
return html`
<div>
<input type="checkbox" id="cw" @change=${() => self.set_content_warning(undefined)} checked></input>
<label for="cw">CW</label>
<input type="text" id="content_warning" @input=${this.input} @change=${this.change} value=${draft.content_warning}></input>
</div>
`;
} else {
return html`
<input type="checkbox" id="cw" @change=${() => self.set_content_warning('')}></input>
<label for="cw">CW</label>
`;
}
}
get_draft() {
return this.drafts[this.branch || ''] || {};
}
render() {
let self = this;
let draft = self.get_draft();
let content_warning =
draft.content_warning !== undefined ?
html`<div id="content_warning_preview" class="content_warning">${draft.content_warning}</div>` :
undefined;
let result = html`
<div style="display: flex; flex-direction: row; width: 100%">
<textarea id="edit" @input=${this.changed} @paste=${this.paste} style="flex: 1 0 50%"></textarea>
<div id="preview" style="flex: 1 0 50%"></div>
<textarea id="edit" @input=${this.input} @change=${this.change} @paste=${this.paste} style="flex: 1 0 50%">${draft.text}</textarea>
<div style="flex: 1 0 50%">
${content_warning}
<div id="preview"></div>
</div>
</div>
${Object.values(this.mentions).map(x => self.render_mention(x))}
${Object.values(draft.mentions || {}).map(x => self.render_mention(x))}
${this.render_content_warning()}
${this.render_attach_app()}
<input type="button" value="Submit" @click=${this.submit}></input>
<input type="button" value="Attach" @click=${this.attach}></input>

View File

@ -10,12 +10,10 @@ class TfMessageElement extends LitElement {
whoami: {type: String},
message: {type: Object},
users: {type: Object},
reply: {type: Boolean},
drafts: {type: Object},
raw: {type: Boolean},
collapsed: {type: Boolean},
content_warning_expanded: {type: Boolean},
blog_data: {type: String},
blog_expanded: {type: Boolean},
expanded: {type: Object},
}
}
@ -27,13 +25,18 @@ class TfMessageElement extends LitElement {
this.whoami = null;
this.message = {};
this.users = {};
this.reply = false;
this.drafts = {};
this.raw = false;
this.collapsed = false;
this.expanded = {};
}
show_reply() {
this.reply = true;
let event = new CustomEvent('tf-draft', {bubbles: true, composed: true, detail: {id: this.message?.id, draft: ''}});
this.dispatchEvent(event);
}
discard_reply() {
this.dispatchEvent(new CustomEvent('tf-draft', {bubbles: true, composed: true, detail: {id: this.id, draft: undefined}}));
}
render_votes() {
@ -145,7 +148,7 @@ class TfMessageElement extends LitElement {
} else if (mention.link?.startsWith('&') &&
mention.name?.startsWith('video:')) {
return html`
<video controls style="max-height: 240px">
<video controls style="max-height: 240px; max-width: 128px">
<source src=${'/' + mention.link + '/view'}></source>
</video>
`;
@ -180,13 +183,33 @@ class TfMessageElement extends LitElement {
}
}
total_child_messages(message) {
if (!message.child_messages) {
return 0;
}
let total = message.child_messages.length;
for (let m of message.child_messages)
{
total += this.total_child_messages(m);
}
return total;
}
set_expanded(expanded, tag) {
this.dispatchEvent(new CustomEvent('tf-expand', {bubbles: true, composed: true, detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded}}));
}
toggle_expanded(tag) {
this.set_expanded(!this.expanded[(this.message.id || '') + (tag || '')], tag);
}
render_children() {
let self = this;
if (this.message.child_messages?.length) {
if (this.collapsed) {
return html`<input type="button" value=${this.message.child_messages?.length + ' More'} @click=${() => self.collapsed = false}></input>`;
if (!this.expanded[this.message.id]) {
return html`<input type="button" value=${this.total_child_messages(this.message) + ' More'} @click=${() => self.set_expanded(true)}></input>`;
} else {
return html`<input type="button" value="Collapse" @click=${() => self.collapsed = true}></input>${(this.message.child_messages || []).map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users}></tf-message>`)}`;
return html`<input type="button" value="Collapse" @click=${() => self.set_expanded(false)}></input>${(this.message.child_messages || []).map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded}></tf-message>`)}`;
}
}
}
@ -199,7 +222,7 @@ class TfMessageElement extends LitElement {
html`<input type="button" value="Raw" @click=${() => self.raw = true}></input>`;
function small_frame(inner) {
return html`
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; display: inline-block">
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); 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"><a tfarget="_top" href=${'#' + self.message.id}>%</a> ${new Date(self.message.timestamp).toLocaleString()}</span>
${raw_button}
@ -208,13 +231,20 @@ class TfMessageElement extends LitElement {
</div>
`
}
if (this.message.placeholder) {
if (this.message?.type === 'contact_group') {
return html`
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere">
${this.message.messages.map(x =>
html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded}></tf-message>`
)}
</div>`;
} else if (this.message.placeholder) {
return html`
<div style="border: 1px solid black; background-color: rgba(255, 255, 255, 0.1); margin-top: 8px; padding: 16px; overflow-wrap: anywhere">
<a target="_top" href=${'#' + this.message.id}>${this.message.id}</a> (placeholder)
<div>${this.render_votes()}</div>
${(this.message.child_messages || []).map(x => html`
<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} collapsed=true></tf-message>
<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded}></tf-message>
`)}
</div>`;
} else if (typeof(content?.type === 'string')) {
@ -227,25 +257,29 @@ class TfMessageElement extends LitElement {
}
if (content.image !== undefined) {
image = html`
<div><img src=${'/' + content.image + '/view'} style="width: 256px; height: auto"></img></div>
<div><img src=${'/' + (typeof(content.image?.link) == 'string' ? content.image.link : content.image) + '/view'} style="width: 256px; height: auto"></img></div>
`;
}
if (content.description !== undefined) {
description = html`
<div style="flex: 1 0 50%">
<div style="flex: 1 0 50%; overflow-wrap: anywhere">
<div>${unsafeHTML(tfutils.markdown(content.description))}</div>
</div>
`
}
let update = content.about == this.message.author ?
html`<div style="font-weight: bold">Updated profile.</div>` :
html`<div style="font-weight: bold">Updated profile for <tf-user id=${content.about} .users=${this.users}></tf-user>.</div>`;
return small_frame(html`
<div style="font-weight: bold">Updated profile.</div>
${update}
${name}
${image}
${description}
`);
} else if (content.type == 'contact') {
return small_frame(html`
return html`
<div>
<tf-user id=${this.message.author} .users=${this.users}></tf-user>
is
${
content.blocking === true ? 'blocking' :
@ -256,16 +290,16 @@ class TfMessageElement extends LitElement {
}
<tf-user id=${this.message.content.contact} .users=${this.users}></tf-user>
</div>
`);
`;
} else if (content.type == 'post') {
let reply = this.reply ? html`
let reply = (this.drafts[this.message?.id] !== undefined) ? html`
<tf-compose
?enabled=${this.reply}
whoami=${this.whoami}
.users=${this.users}
root=${this.message.content.root || this.message.id}
branch=${this.message.id}
@tf-discard=${() => this.reply = false}></tf-compose>
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}></tf-compose>
` : html`
<input type="button" value="Reply" @click=${this.show_reply}></input>
`;
@ -274,7 +308,7 @@ class TfMessageElement extends LitElement {
this.render_raw() :
unsafeHTML(tfutils.markdown(content.text));
let content_warning = html`
<div style="border: 1px solid #fff; border-radius: 1em; padding: 8px; margin: 4px" @click=${x => self.content_warning_expanded = !self.content_warning_expanded}>${content.contentWarning}</div>
<div class="content_warning" @click=${x => this.toggle_expanded(':cw')}>${content.contentWarning}</div>
`;
let content_html =
html`
@ -283,7 +317,7 @@ class TfMessageElement extends LitElement {
`;
let payload =
content.contentWarning ?
self.content_warning_expanded ?
self.expanded[(this.message.id || '') + ':cw'] ?
html`
${content_warning}
${content_html}
@ -323,12 +357,11 @@ class TfMessageElement extends LitElement {
`;
} else if (content.type === 'blog') {
let self = this;
console.log('requesting data');
tfrpc.rpc.get_blob(content.blog).then(function(data) {
self.blog_data = data;
});
let payload =
this.blog_expanded ?
this.expanded[(this.message.id || '') + ':blog'] ?
html`<div>${this.blog_data ? unsafeHTML(tfutils.markdown(this.blog_data)) : 'Loading...'}</div>` :
undefined;
let body = this.raw ?
@ -336,7 +369,7 @@ class TfMessageElement extends LitElement {
html`
<div
style="border: 1px solid #fff; border-radius: 1em; padding: 8px; margin: 4px; cursor: pointer"
@click=${x => self.blog_expanded = !self.blog_expanded}>
@click=${x => self.toggle_expanded(':blog')}>
<h2>${content.title}</h2>
<div style="display: flex; flex-direction: row">
<img src=/${content.thumbnail}/view></img>
@ -375,6 +408,11 @@ class TfMessageElement extends LitElement {
`;
} else if (content.type === 'pub') {
return small_frame(html`
<style>
span {
overflow-wrap: anywhere;
}
</style>
<span>
<div>
🍻 <tf-user .users=${this.users} id=${content.address.key}></tf-user>

View File

@ -9,6 +9,8 @@ class TfNewsElement extends LitElement {
users: {type: Object},
messages: {type: Array},
following: {type: Array},
drafts: {type: Object},
expanded: {type: Object},
}
}
@ -21,6 +23,8 @@ class TfNewsElement extends LitElement {
this.users = {};
this.messages = [];
this.following = [];
this.drafts = {};
this.expanded = {};
}
process_messages(messages) {
@ -77,6 +81,7 @@ class TfNewsElement extends LitElement {
}
for (let message of messages) {
message.votes = [];
message.parent_message = undefined;
message.child_messages = undefined;
}
@ -140,19 +145,38 @@ class TfNewsElement extends LitElement {
return recursive_sort(roots, true);
}
async load_and_render(messages) {
group_following(messages) {
let result = [];
let group = [];
for (let message of messages) {
if (message?.content?.type === 'contact') {
group.push(message);
} else {
if (group.length > 0) {
result.push({
type: 'contact_group',
messages: group,
});
group = [];
}
result.push(message);
}
}
return result;
}
load_and_render(messages) {
let messages_by_id = this.process_messages(messages);
let final_messages = this.finalize_messages(messages_by_id);
let final_messages = this.group_following(this.finalize_messages(messages_by_id));
return html`
<div style="display: flex; flex-direction: column">
${final_messages.map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} collapsed=true></tf-message>`)}
${final_messages.map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} .expanded=${this.expanded} collapsed=true></tf-message>`)}
</div>
`;
}
render() {
let messages = this.load_and_render(this.messages || []);
return html`${until(messages, html`<div>Loading placeholders...</div>`)}`;
return this.load_and_render(this.messages || []);
}
}

View File

@ -29,4 +29,11 @@ img {
color: #088;
background-color: #fff;
}
.content_warning {
border: 1px solid #fff;
border-radius: 1em;
padding: 8px;
margin: 4px;
}
`;

View File

@ -7,6 +7,7 @@ class TfTabConnectionsElement extends LitElement {
broadcasts: {type: Array},
identities: {type: Array},
connections: {type: Array},
stored_connections: {type: Array},
users: {type: Object},
}
}
@ -17,10 +18,14 @@ class TfTabConnectionsElement extends LitElement {
this.broadcasts = [];
this.identities = [];
this.connections = [];
this.stored_connections = [];
this.users = {};
tfrpc.rpc.getAllIdentities().then(function(identities) {
self.identities = identities || [];
});
tfrpc.rpc.getStoredConnections().then(function(connections) {
self.stored_connections = connections || [];
});
}
render_connection_summary(connection) {
@ -46,8 +51,7 @@ class TfTabConnectionsElement extends LitElement {
}
async _tunnel(portal, target) {
let request_number = await tfrpc.rpc.connectionSendJson(portal, {name: ['tunnel', 'connect'], args: [{portal: portal, target: target}], type: 'duplex'});
return tfrpc.rpc.createTunnel(portal, request_number, target);
return tfrpc.rpc.createTunnel(portal, target);
}
render_room_peer(connection) {
@ -70,6 +74,11 @@ class TfTabConnectionsElement extends LitElement {
`
}
async forget_stored_connection(connection) {
await tfrpc.rpc.forgetStoredConnection(connection);
this.stored_connections = (await tfrpc.rpc.getStoredConnections()) || [];
}
render() {
let self = this;
return html`
@ -92,6 +101,16 @@ class TfTabConnectionsElement extends LitElement {
</li>
`)}
</ul>
<h2>Stored Connections (WIP)</h2>
<ul>
${this.stored_connections.map(x => html`
<li>
<input type="button" @click=${() => self.forget_stored_connection(x)} value="Forget"></input>
<input type="button" @click=${() => tfrpc.rpc.connect(x)} value="Connect"></input>
${x.address}:${x.port} <tf-user id=${x.pubkey} .users=${self.users}></tf-user>
</li>
`)}
</ul>
<h2>Local Accounts</h2>
<ul>
${this.identities.map(x => html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`)}

View File

@ -10,6 +10,8 @@ class TfTabNewsFeedElement extends LitElement {
hash: {type: String},
following: {type: Array},
messages: {type: Array},
drafts: {type: Object},
expanded: {type: Object},
}
}
@ -22,6 +24,8 @@ class TfTabNewsFeedElement extends LitElement {
this.users = {};
this.hash = '#';
this.following = [];
this.drafts = {};
this.expanded = {};
}
async fetch_messages() {
@ -102,7 +106,7 @@ class TfTabNewsFeedElement extends LitElement {
alert(JSON.stringify(error, null, 2));
});
}
return html`<tf-news id="news" whoami=${this.whoami} .users=${this.users} .messages=${this.messages} .following=${this.following}></tf-news>`;
return html`<tf-news id="news" whoami=${this.whoami} .users=${this.users} .messages=${this.messages} .following=${this.following} .drafts=${this.drafts} .expanded=${this.expanded}></tf-news>`;
}
}
@ -114,6 +118,8 @@ class TfTabNewsElement extends LitElement {
hash: {type: String},
unread: {type: Array},
following: {type: Array},
drafts: {type: Object},
expanded: {type: Object},
}
}
@ -128,6 +134,11 @@ class TfTabNewsElement extends LitElement {
this.unread = [];
this.following = [];
this.cache = {};
this.drafts = {};
this.expanded = {};
tfrpc.rpc.localStorageGet('drafts').then(function(d) {
self.drafts = JSON.parse(d || '{}');
});
}
show_more() {
@ -156,6 +167,32 @@ class TfTabNewsElement extends LitElement {
return 'Show New: ' + Object.keys(counts).sort().map(x => (counts[x].toString() + ' ' + x + 's')).join(', ');
}
draft(event) {
let id = event.detail.id || '';
let previous = this.drafts[id];
if (event.detail.draft !== undefined) {
this.drafts[id] = event.detail.draft;
} else {
delete this.drafts[id];
}
/* Only trigger a re-render if we're creating a new draft or discarding an old one. */
if ((previous !== undefined) != (event.detail.draft !== undefined)) {
this.drafts = Object.assign({}, this.drafts);
}
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
}
on_expand(event) {
if (event.detail.expanded) {
let expand = {};
expand[event.detail.id] = true;
this.expanded = Object.assign({}, this.expanded, expand);
} else {
delete this.expanded[event.detail.id];
this.expanded = Object.assign({}, this.expanded);
}
}
render() {
let profile = this.hash.startsWith('#@') ?
html`<tf-profile id=${this.hash.substring(1)} whoami=${this.whoami} .users=${this.users}></tf-profile>` : undefined;
@ -163,9 +200,9 @@ class TfTabNewsElement extends LitElement {
<div><input type="button" value=${this.new_messages_text()} @click=${this.show_more}></input></div>
<a target="_top" href="#" ?hidden=${this.hash.length <= 1}>🏠Home</a>
<div>Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!</div>
<div><tf-compose whoami=${this.whoami} .users=${this.users}></tf-compose></div>
<div><tf-compose whoami=${this.whoami} .users=${this.users} .drafts=${this.drafts} @tf-draft=${this.draft}></tf-compose></div>
${profile}
<tf-tab-news-feed id="news" whoami=${this.whoami} .users=${this.users} .following=${this.following} hash=${this.hash}></tf-tab-news-feed>
<tf-tab-news-feed id="news" whoami=${this.whoami} .users=${this.users} .following=${this.following} hash=${this.hash} .drafts=${this.drafts} .expanded=${this.expanded} @tf-draft=${this.draft} @tf-expand=${this.on_expand}></tf-tab-news-feed>
`;
}
}

View File

@ -19,18 +19,23 @@ class TfUserElement extends LitElement {
}
render() {
let name = this.users?.[this.id]?.name;
name = name !== undefined ?
html`<a target="_top" href=${'#' + this.id}>${name}</a>` :
html`<a target="_top" href=${'#' + this.id}>${this.id}</a>`;
if (this.users[this.id]) {
let image = this.users[this.id].image;
image = typeof(image) == 'string' ? image : image?.link;
return html`
<div style="display: inline-block; font-weight: bold">
<img style="width: 2em; height: 2em; vertical-align: middle; border-radius: 50%" ?hidden=${image === undefined} src="${image ? '/' + image + '/view' : undefined}">
<a target="_top" href=${'#' + this.id}>${this.users[this.id].name ?? this.id}</a>
<img style="width: 2em; height: 2em; vertical-align: middle; border-radius: 50%" ?hidden=${image === undefined} src="${image ? '/' + image + '/view' : undefined}">
${name}
</div>`;
} else {
return html`
<div style="display: inline-block; font-weight: bold; word-wrap: anywhere">
<a target="_top" href=${'#' + this.id}>${this.id}</a>
<div style="display: inline-block; font-weight: bold">
${name}
</div>`;
}
}

View File

@ -5,8 +5,8 @@ export function markdown(md) {
var reader = new commonmark.Parser({safe: true});
var writer = new commonmark.HtmlRenderer();
var parsed = reader.parse(md || '');
parsed = hashtagify.transform(parsed);
parsed = linkify.transform(parsed);
parsed = hashtagify.transform(parsed);
var walker = parsed.walker();
var event, node;
while ((event = walker.next())) {

4
apps/todo.json Normal file
View File

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

View File

@ -22,14 +22,14 @@ App.prototype.readOutput = function(callback) {
App.prototype.makeFunction = function(api) {
let self = this;
let id = g_next_id++;
while (!id || g_calls[id]) {
id = g_next_id++;
}
let promise = new Promise(function(resolve, reject) {
g_calls[id] = {resolve: resolve, reject: reject};
});
let result = function() {
let id = g_next_id++;
while (!id || g_calls[id]) {
id = g_next_id++;
}
let promise = new Promise(function(resolve, reject) {
g_calls[id] = {resolve: resolve, reject: reject};
});
let message = {
message: 'tfrpc',
method: api[0],

View File

@ -1,9 +1,7 @@
import * as core from './core.js';
import * as http from './http.js';
import * as form from './form.js';
var gTokens = {};
var gDatabase = new Database("auth");
let gDatabase = new Database("auth");
const kRefreshInterval = 1 * 7 * 24 * 60 * 60 * 1000;
@ -76,7 +74,7 @@ function verifyPassword(password, hash) {
}
function hashPassword(password) {
var salt = bCrypt.gensalt(12);
let salt = bCrypt.gensalt(12);
return bCrypt.hashpw(password, salt);
}
@ -100,14 +98,14 @@ function makeAdministrator(name) {
}
function getCookies(headers) {
var cookies = {};
let cookies = {};
if (headers.cookie) {
var parts = headers.cookie.split(/,|;/);
for (var i in parts) {
var equals = parts[i].indexOf("=");
var name = parts[i].substring(0, equals).trim();
var value = parts[i].substring(equals + 1).trim();
let parts = headers.cookie.split(/,|;/);
for (let i in parts) {
let equals = parts[i].indexOf("=");
let name = parts[i].substring(0, equals).trim();
let value = parts[i].substring(equals + 1).trim();
cookies[name] = value;
}
}
@ -116,18 +114,18 @@ function getCookies(headers) {
}
function handler(request, response) {
var session = getCookies(request.headers).session;
let session = getCookies(request.headers).session;
if (request.uri == "/login") {
var sessionIsNew = false;
var loginError;
let sessionIsNew = false;
let loginError;
var formData = form.decodeForm(request.query);
let formData = form.decodeForm(request.query);
if (request.method == "POST" || formData.submit) {
sessionIsNew = true;
formData = form.decodeForm(utf8Decode(request.body), formData);
if (formData.submit == "Login") {
var account = gDatabase.get("user:" + formData.name);
let account = gDatabase.get("user:" + formData.name);
account = account ? JSON.parse(account) : account;
if (formData.register == "1") {
if (!account &&
@ -172,15 +170,15 @@ function handler(request, response) {
}
}
var cookie = `session=${session}; path=/; Max-Age=${kRefreshInterval}; Secure; SameSite=Strict`;
var entry = readSession(session);
let cookie = `session=${session}; path=/; Max-Age=${kRefreshInterval}; ${request.client.tls ? 'Secure; ' : ''}SameSite=Strict`;
let entry = readSession(session);
if (entry && formData.return) {
response.writeHead(303, {"Location": formData.return, "Set-Cookie": cookie});
response.end();
} else {
File.readFile("core/auth.html").then(function(data) {
var html = utf8Decode(data);
var contents = "";
let html = utf8Decode(data);
let contents = "";
if (entry) {
if (sessionIsNew) {
@ -217,7 +215,7 @@ function handler(request, response) {
contents += '</div>\n';
contents += '</form>';
}
var text = html.replace("<!--SESSION-->", contents);
let text = html.replace("<!--SESSION-->", contents);
response.writeHead(200, {"Content-Type": "text/html; charset=utf-8", "Set-Cookie": cookie, "Content-Length": text.length});
response.end(text);
}).catch(function(error) {
@ -226,7 +224,7 @@ function handler(request, response) {
});
}
} else if (request.uri == "/login/logout") {
response.writeHead(303, {"Set-Cookie": "session=; path=/; Secure; SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT", "Location": "/login" + (request.query ? "?" + request.query : "")});
response.writeHead(303, {"Set-Cookie": `session=; path=/; ${request.client.tls ? 'Secure; ' : ''}SameSite=Strict; expires=Thu, 01 Jan 1970 00:00:00 GMT`, "Location": "/login" + (request.query ? "?" + request.query : "")});
response.end();
} else {
response.writeHead(200, {"Content-Type": "text/plain; charset=utf-8", "Connection": "close"});
@ -235,8 +233,8 @@ function handler(request, response) {
}
function getPermissions(session) {
var permissions;
var entry = readSession(session);
let permissions;
let entry = readSession(session);
if (entry) {
permissions = getPermissionsForUser(entry.name);
permissions.authenticated = entry.name !== "guest";
@ -245,9 +243,9 @@ function getPermissions(session) {
}
function getPermissionsForUser(userName) {
var permissions = {};
let permissions = {};
if (core.globalSettings && core.globalSettings.permissions && core.globalSettings.permissions[userName]) {
for (var i in core.globalSettings.permissions[userName]) {
for (let i in core.globalSettings.permissions[userName]) {
permissions[core.globalSettings.permissions[userName][i]] = true;
}
}
@ -255,9 +253,9 @@ function getPermissionsForUser(userName) {
}
function query(headers) {
var session = getCookies(headers).session;
var entry;
var autologin = tildefriends.args.autologin;
let session = getCookies(headers).session;
let entry;
let autologin = tildefriends.args.autologin;
if (entry = autologin ? {name: autologin} : readSession(session)) {
return {
session: entry,

View File

@ -1,17 +1,14 @@
"use strict";
let gSocket;
let gCredentials;
let gPermissions;
let gCurrentFile;
let gFiles = {};
let gApp = {files: {}};
let gApp = {files: {}, emoji: '📦'};
let gEditor;
let gSplit;
let gGraphs = {};
let gTimeSeries = {};
let gParentApp;
let gOriginalInput;
let kErrorColor = "#dc322f";
@ -140,7 +137,7 @@ function showFiles() {
}
function trace() {
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}&title=Tilde%20Friends`);
window.open(`/speedscope/#profileURL=${encodeURIComponent('/trace')}`);
}
function stats() {
@ -222,6 +219,8 @@ function load(path) {
document.getElementById("editPane").style.display = 'flex';
}
gApp = json;
gApp.emoji = gApp.emoji || '📦';
document.getElementById('icon').value = gApp.emoji;
}
if (!isApp) {
document.getElementById("editPane").style.display = 'flex';
@ -251,8 +250,6 @@ function explodePath() {
function save(save_to) {
document.getElementById("save").disabled = true;
document.getElementById("push_to_parent").disabled = true;
document.getElementById("pull_from_parent").disabled = true;
if (gCurrentFile) {
gFiles[gCurrentFile].doc = gEditor.getDoc();
}
@ -298,6 +295,7 @@ function save(save_to) {
let app = {
type: "tildefriends-app",
files: Object.fromEntries(Object.keys(gFiles).map(x => [x, gFiles[x].id || gApp.files[x]])),
emoji: gApp.emoji || '📦',
};
Object.values(gFiles).forEach(function(file) { delete file.id; });
gApp = JSON.parse(JSON.stringify(app));
@ -323,8 +321,6 @@ function save(save_to) {
alert(error);
}).finally(function() {
document.getElementById("save").disabled = false;
document.getElementById("push_to_parent").disabled = false;
document.getElementById("pull_from_parent").disabled = false;
Object.values(gFiles).forEach(function(file) {
file.generation = file.doc.changeGeneration();
});
@ -332,6 +328,14 @@ function save(save_to) {
});
}
function changeIcon() {
let value = prompt('Enter a new app icon emoji:');
if (value !== undefined) {
gApp.emoji = value || '📦';
document.getElementById('icon').value = gApp.emoji;
}
}
function deleteApp() {
let name = document.getElementById("name");
let path = name && name.value ? name.value : url();
@ -348,16 +352,6 @@ function deleteApp() {
}
}
function pullFromParent() {
load(gParentApp ? gParentApp.path : null).then(x => save()).catch(function(error) {
alert(error)
});
}
function pushToParent() {
save(gParentApp ? gParentApp.path : null);
}
function url() {
let hash = window.location.href.indexOf('#');
let question = window.location.href.indexOf('?');
@ -402,7 +396,7 @@ function api_localStorageSet(key, value) {
window.localStorage.setItem('app:' + key, value);
}
function api_localStorageGet(key, value) {
function api_localStorageGet(key) {
return window.localStorage.getItem('app:' + key);
}
@ -535,11 +529,7 @@ function _receive_websocket_message(message) {
if (message && message.action == "session") {
setStatusMessage("🟢 Executing...", kStatusColor);
gCredentials = message.credentials;
gParentApp = message.parentApp;
updateLogin();
let parent_enabled = message.parentApp;
document.getElementById('push_to_parent').style.display = parent_enabled ? 'inline-block' : 'none';
document.getElementById('pull_from_parent').style.display = parent_enabled ? 'inline-block' : 'none';
} else if (message && message.action == 'permissions') {
gPermissions = message.permissions;
let permissions = document.getElementById('permissions_settings');
@ -565,6 +555,9 @@ function _receive_websocket_message(message) {
rpc_in: {group: 'rpc', name: 'in'},
rpc_out: {group: 'rpc', name: 'out'},
cpu_percent: {group: 'cpu', name: 'main'},
thread_percent: {group: 'cpu', name: 'work'},
arena_percent: {group: 'memory', name: 'm'},
js_malloc_percent: {group: 'memory', name: 'js'},
memory_percent: {group: 'memory', name: 'tot'},
@ -573,6 +566,9 @@ function _receive_websocket_message(message) {
tls_malloc_percent: {group: 'memory', name: 'tls'},
uv_malloc_percent: {group: 'memory', name: 'uv'},
messages_stored: {group: 'stored', name: 'messages'},
blobs_stored: {group: 'stored', name: 'blobs'},
socket_count: {group: 'socket', name: 'total'},
socket_open_count: {group: 'socket', name: 'open'},
@ -636,17 +632,19 @@ function _receive_websocket_message(message) {
message.message === 'tfrpc' &&
message.method) {
let api = k_api[message.method];
let id = message.id;
let params = message.params;
if (api) {
Promise.resolve(api.func(...message.params)).then(function(result) {
Promise.resolve(api.func(...params)).then(function(result) {
send({
message: 'tfrpc',
id: message.id,
id: id,
result: result,
});
}).catch(function(error) {
send({
message: 'tfrpc',
id: message.id,
id: id,
error: error,
});
});
@ -844,10 +842,12 @@ function message(event) {
function reconnect(path) {
let oldSocket = gSocket;
gSocket = null
oldSocket.onopen = null;
oldSocket.onclose = null;
oldSocket.onmessage = null;
oldSocket.close();
if (oldSocket) {
oldSocket.onopen = null;
oldSocket.onclose = null;
oldSocket.onmessage = null;
oldSocket.close();
}
connectSocket(path);
}
@ -966,6 +966,28 @@ window.addEventListener("load", function() {
window.addEventListener("message", message, false);
window.addEventListener("online", connectSocket);
document.getElementById("name").value = window.location.pathname;
document.getElementById('edit_link').addEventListener('click', function(event) {
event.preventDefault();
toggleEdit();
});
document.getElementById('show_permissions_link').addEventListener('click', () => showPermissions());
document.getElementById('files_hide').addEventListener('click', () => hideFiles());
document.getElementById('files_show').addEventListener('click', () => showFiles());
document.getElementById('closeStats').addEventListener('click', () => closeStats());
document.getElementById('closeEditor').addEventListener('click', () => closeEditor());
document.getElementById('save').addEventListener('click', () => save());
document.getElementById('icon').addEventListener('click', () => changeIcon());
document.getElementById('delete').addEventListener('click', () => deleteApp());
document.getElementById('trace_button').addEventListener('click', function(event) {
event.preventDefault();
trace();
});
document.getElementById('stats_button').addEventListener('click', function(event) {
event.preventDefault();
toggleStats();
});
document.getElementById('new_file_button').addEventListener('click', () => newFile());
document.getElementById('remove_file_button').addEventListener('click', () => removeFile());
for (let tag of document.getElementsByTagName('a')) {
if (tag.accessKey) {
tag.classList.add('tooltip_parent');

View File

@ -1,10 +1,11 @@
import * as auth from './auth.js';
import * as app from './app.js';
import * as auth from './auth.js';
import * as form from './form.js';
import * as httpd from './httpd.js';
var gProcessIndex = 0;
var gProcesses = {};
var gStatsTimer = false;
let gProcessIndex = 0;
let gProcesses = {};
let gStatsTimer = false;
const k_global_settings = {
index: {
@ -17,27 +18,35 @@ const k_global_settings = {
default_value: true,
description: 'Whether this instance should behave as a room.',
},
room_name: {
type: 'string',
default_value: 'tilde friends tunnel',
description: 'Name of the room.',
},
code_of_conduct: {
type: 'textarea',
default_value: undefined,
description: 'Code of conduct presented at sign-in.',
},
http_redirect: {
type: 'string',
default_value: undefined,
description: 'If connecting by HTTP and HTTPS is configured, Location header prefix (ie, "https://example.com")',
},
};
var gGlobalSettings = {
let gGlobalSettings = {
index: "/~core/apps/",
};
var kGlobalSettingsFile = "data/global/settings.json";
var kPingInterval = 60 * 1000;
let kPingInterval = 60 * 1000;
function printError(out, error) {
if (error.stackTrace) {
out.print(error.fileName + ":" + error.lineNumber + ": " + error.message);
out.print(error.stackTrace);
} else {
for (var i in error) {
for (let i in error) {
out.print(i);
}
out.print(error.toString());
@ -45,9 +54,9 @@ function printError(out, error) {
}
function invoke(handlers, argv) {
var promises = [];
let promises = [];
if (handlers) {
for (var i = 0; i < handlers.length; ++i) {
for (let i = 0; i < handlers.length; ++i) {
try {
promises.push(handlers[i](...argv));
} catch (error) {
@ -61,9 +70,9 @@ function invoke(handlers, argv) {
}
function broadcastEvent(eventName, argv) {
var promises = [];
for (var i in gProcesses) {
var process = gProcesses[i];
let promises = [];
for (let i in gProcesses) {
let process = gProcesses[i];
if (process.eventHandlers[eventName]) {
promises.push(invoke(process.eventHandlers[eventName], argv));
}
@ -72,14 +81,14 @@ function broadcastEvent(eventName, argv) {
}
function broadcast(message) {
var sender = this;
var promises = [];
for (var i in gProcesses) {
var process = gProcesses[i];
let sender = this;
let promises = [];
for (let i in gProcesses) {
let process = gProcesses[i];
if (process != sender
&& process.packageOwner == sender.packageOwner
&& process.packageName == sender.packageName) {
var from = getUser(process, sender);
let from = getUser(process, sender);
promises.push(postMessageInternal(from, process, message));
}
}
@ -109,9 +118,9 @@ function getApps(user, process) {
}
}
if (user) {
var db = new Database(user);
let db = new Database(user);
try {
var names = JSON.parse(db.get('apps'));
let names = JSON.parse(db.get('apps'));
return Object.fromEntries(names.map(name => [name, db.get('path:' + name)]));
} catch {
}
@ -126,21 +135,21 @@ function postMessageInternal(from, to, message) {
}
async function getSessionProcessBlob(blobId, session, options) {
var actualOptions = {timeout: kPingInterval};
let actualOptions = {timeout: kPingInterval};
if (options) {
for (var i in options) {
for (let i in options) {
actualOptions[i] = options[i];
}
}
return getProcessBlob(blobId, 'session_' + session, actualOptions);
}
let gManifestCache = {};
async function getProcessBlob(blobId, key, options) {
var process = gProcesses[key];
let process = gProcesses[key];
if (!process
&& !(options && "create" in options && !options.create)) {
let resolveReady;
let rejectReady;
try {
print("Creating task for " + blobId + " " + key);
process = {};
@ -155,8 +164,6 @@ async function getProcessBlob(blobId, key, options) {
process.lastPing = null;
process.timeout = options.timeout;
process.stats = false;
var resolveReady;
var rejectReady;
process.ready = new Promise(function(resolve, reject) {
resolveReady = resolve;
rejectReady = reject;
@ -167,7 +174,7 @@ async function getProcessBlob(blobId, key, options) {
process.task = null;
delete gProcesses[key];
};
var imports = {
let imports = {
'core': {
'broadcast': broadcast.bind(process),
'register': function(eventName, handler) {
@ -337,17 +344,30 @@ async function getProcessBlob(blobId, key, options) {
});
}
};
delete imports.ssb.addRpc;
imports.ssb.privateMessageEncrypt = function(id, recipients, message) {
if (process.credentials &&
process.credentials.session &&
process.credentials.session.name) {
return ssb.privateMessageEncrypt(process.credentials.session.name, id, recipients, message);
}
};
imports.ssb.privateMessageDecrypt = function(id, message) {
if (process.credentials &&
process.credentials.session &&
process.credentials.session.name) {
return ssb.privateMessageDecrypt(process.credentials.session.name, id, message);
}
};
if (process.credentials &&
process.credentials.session &&
process.credentials.session.name) {
imports.database = function(key) {
var db = new Database(process.credentials.session.name + ':' + key);
let db = new Database(process.credentials.session.name + ':' + key);
return Object.fromEntries(Object.keys(db).map(x => [x, db[x].bind(db)]));
};
imports.my_shared_database = function(packageName, key) {
var db = new Database(':shared:' + process.credentials.session.name + ':' + packageName + ':' + key);
let db = new Database(':shared:' + process.credentials.session.name + ':' + packageName + ':' + key);
return Object.fromEntries(Object.keys(db).map(x => [x, db[x].bind(db)]));
};
imports.databases = function() {
@ -356,7 +376,7 @@ async function getProcessBlob(blobId, key, options) {
}
if (options.packageOwner && options.packageName) {
imports.shared_database = function(key) {
var db = new Database(':shared:' + options.packageOwner + ':' + options.packageName + ':' + key);
let db = new Database(':shared:' + options.packageOwner + ':' + options.packageName + ':' + key);
return Object.fromEntries(Object.keys(db).map(x => [x, db[x].bind(db)]));
}
}
@ -371,14 +391,14 @@ async function getProcessBlob(blobId, key, options) {
process.task.setImports(imports);
process.task.activate();
let source = await getBlobOrContent(blobId);
var appSourceName = blobId;
var appSource = utf8Decode(source);
let appSourceName = blobId;
let appSource = utf8Decode(source);
try {
var appObject = JSON.parse(appSource);
let appObject = JSON.parse(appSource);
if (appObject.type == "tildefriends-app") {
appSourceName = 'app.js';
var id = appObject.files[appSourceName];
var blob = await getBlobOrContent(id);
let id = appObject.files[appSourceName];
let blob = await getBlobOrContent(id);
appSource = utf8Decode(blob);
await process.task.loadFile(['/tfrpc.js', await File.readFile('core/tfrpc.js')]);
await Promise.all(Object.keys(appObject.files).map(async function(f) {
@ -416,7 +436,7 @@ function setGlobalSettings(settings) {
}
}
var kStaticFiles = [
let kStaticFiles = [
{uri: '/', path: 'index.html', type: 'text/html; charset=UTF-8'},
{uri: '/style.css', type: 'text/css; charset=UTF-8'},
{uri: '/favicon.png', type: 'image/png'},
@ -427,8 +447,8 @@ var kStaticFiles = [
function startsWithBytes(data, bytes) {
if (data.byteLength >= bytes.length) {
var dataBytes = new Uint8Array(data.slice(0, bytes.length));
for (var i = 0; i < bytes.length; i++) {
let dataBytes = new Uint8Array(data.slice(0, bytes.length));
for (let i = 0; i < bytes.length; i++) {
if (dataBytes[i] != bytes[i] && bytes[i] !== null) {
return;
}
@ -438,10 +458,10 @@ function startsWithBytes(data, bytes) {
}
async function staticFileHandler(request, response, blobId, uri) {
for (var i in kStaticFiles) {
for (let i in kStaticFiles) {
if (uri === kStaticFiles[i].uri) {
var path = kStaticFiles[i].path || uri.substring(1);
var type = kStaticFiles[i].type || guessType(path);
let path = kStaticFiles[i].path || uri.substring(1);
let type = kStaticFiles[i].type || guessType(path);
let stat = await File.stat('core/' + path);
let id = `${stat.mtime}_${stat.size}`;
@ -450,7 +470,7 @@ async function staticFileHandler(request, response, blobId, uri) {
response.writeHead(304, {});
response.end();
} else {
var data = await File.readFile('core/' + path);
let data = await File.readFile('core/' + path);
response.writeHead(200, Object.assign(
{
'Content-Type': type,
@ -477,7 +497,7 @@ const k_mime_types = {
};
async function staticDirectoryHandler(request, response, directory, uri) {
var filename = uri || 'index.html';
let filename = uri || 'index.html';
if (filename.indexOf('..') != -1) {
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": "File not found".length});
response.end("File not found");
@ -492,7 +512,7 @@ async function staticDirectoryHandler(request, response, directory, uri) {
response.writeHead(304, {});
response.end();
} else {
var data = await File.readFile(directory + filename);
let data = await File.readFile(directory + filename);
response.writeHead(200, {
'Content-Type': k_mime_types[filename.split('.').pop()] || 'text/plain',
'Content-Length': data.byteLength,
@ -507,7 +527,7 @@ async function staticDirectoryHandler(request, response, directory, uri) {
}
async function wellKnownHandler(request, response, path) {
var data = await File.readFile("data/global/.well-known/" + path);
let data = await File.readFile("data/global/.well-known/" + path);
if (data) {
response.writeHead(200, {"Content-Type": "text/plain", "Content-Length": data.length});
response.end(data);
@ -532,6 +552,12 @@ function sendData(response, data, type, headers) {
startsWithBytes(data, [0x47, 0x49, 0x46, 0x38, 0x39, 0x61])) {
response.writeHead(200, Object.assign({"Content-Type": "image/gif", "Content-Length": data.byteLength}, headers || {}));
response.end(data);
} else if (startsWithBytes(data, [0x52, 0x49, 0x46, 0x46, null, null, null, null, 0x57, 0x45, 0x42, 0x50])) {
response.writeHead(200, Object.assign({"Content-Type": "image/webp", "Content-Length": data.byteLength}, headers || {}));
response.end(data);
} else if (startsWithBytes(data, [0x3c, 0x73, 0x76, 0x67])) {
response.writeHead(200, Object.assign({"Content-Type": "image/svg+xml", "Content-Length": data.byteLength}, headers || {}));
response.end(data);
} else if (startsWithBytes(data, [null, null, null, null, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32])) {
response.writeHead(200, Object.assign({"Content-Type": "audio/mpeg", "Content-Length": data.byteLength}, headers || {}));
response.end(data);
@ -566,12 +592,12 @@ function guessType(path) {
'js': 'text/javascript',
'svg': 'image/svg+xml',
};
var extension = path.split('.').pop();
let extension = path.split('.').pop();
return k_extension_to_type[extension];
}
async function blobHandler(request, response, blobId, uri) {
for (var i in kStaticFiles) {
for (let i in kStaticFiles) {
if (uri === kStaticFiles[i].uri && kStaticFiles[i].path) {
let stat = await File.stat('core/' + kStaticFiles[i].path);
let id = `${stat.mtime}_${stat.size}`;
@ -580,7 +606,7 @@ async function blobHandler(request, response, blobId, uri) {
response.writeHead(304, {});
response.end();
} else {
var data = await File.readFile('core/' + kStaticFiles[i].path);
let data = await File.readFile('core/' + kStaticFiles[i].path);
response.writeHead(200, Object.assign(
{
'Content-Type': kStaticFiles[i].type,
@ -600,52 +626,58 @@ async function blobHandler(request, response, blobId, uri) {
return;
}
var process;
let process;
if (uri == "/view") {
var data;
let data;
let match;
let query = form.decodeForm(request.query);
let headers = {};
if (query.filename && query.filename.match(/^[A-Za-z0-9\.-]*$/)) {
headers['Content-Disposition'] = `attachment; filename=${query.filename}`;
}
if (match = /^\/\~(\w+)\/(\w+)$/.exec(blobId)) {
var id = await new Database(match[1]).get('path:' + match[2]);
let id = await new Database(match[1]).get('path:' + match[2]);
if (id) {
if (request.headers['if-none-match'] === '"' + id + '"') {
response.writeHead(304, {});
response.writeHead(304, headers);
response.end();
} else {
data = await getBlobOrContent(id);
if (match[3]) {
var appObject = JSON.parse(data);
let appObject = JSON.parse(data);
data = appObject.files[match[3]];
}
sendData(response, data, undefined, {etag: '"' + id + '"'});
sendData(response, data, undefined, Object.assign({etag: '"' + id + '"'}, headers));
}
} else {
if (request.headers['if-none-match'] === '"' + blobId + '"') {
response.writeHead(304, {});
response.writeHead(304, headers);
response.end();
} else {
sendData(response, data, undefined, {etag: '"' + blobId + '"'});
sendData(response, data, undefined, Object.assign({etag: '"' + blobId + '"'}, headers));
}
}
} else {
if (request.headers['if-none-match'] === '"' + blobId + '"') {
response.writeHead(304, {});
response.writeHead(304, headers);
response.end();
} else {
data = await getBlobOrContent(blobId);
sendData(response, data, undefined, {etag: '"' + blobId + '"'});
sendData(response, data, undefined, Object.assign({etag: '"' + blobId + '"'}, headers));
}
}
} else if (uri == "/save") {
var match;
let match;
if (match = /^\/\~(\w+)\/(\w+)$/.exec(blobId)) {
let newBlobId = await ssb.blobStore(request.body);
var user = match[1];
var appName = match[2];
var credentials = auth.query(request.headers);
let user = match[1];
let appName = match[2];
let credentials = auth.query(request.headers);
if (credentials && credentials.session &&
(credentials.session.name == user ||
(credentials.permissions.administration && user == 'core'))) {
var database = new Database(user);
var apps = new Set();
let database = new Database(user);
let apps = new Set();
let apps_original = database.get('apps');
try {
apps = new Set(JSON.parse(apps_original));
@ -679,20 +711,20 @@ async function blobHandler(request, response, blobId, uri) {
} else if (uri == "/delete") {
let match;
if (match = /^\/\~(\w+)\/(\w+)$/.exec(blobId)) {
var user = match[1];
var appName = match[2];
var credentials = auth.query(request.headers);
let user = match[1];
let appName = match[2];
let credentials = auth.query(request.headers);
if (credentials && credentials.session &&
(credentials.session.name == user ||
(credentials.permissions.administration && user == 'core'))) {
var database = new Database(user);
var apps = new Set();
let database = new Database(user);
let apps = new Set();
try {
apps = new Set(JSON.parse(database.get('apps')));
} catch {
}
if (apps.delete(appName)) {
database.set('apps', JSON.stringify([...apps]));
database.set('apps', JSON.stringify([...apps].sort()));
}
database.remove('path:' + appName);
} else {
@ -705,12 +737,13 @@ async function blobHandler(request, response, blobId, uri) {
response.writeHead(200, {"Content-Type": "text/plain; charset=utf-8"});
response.end('OK');
} else {
var data;
var type;
var headers;
let data;
let type;
let headers;
let match;
if (match = /^\/\~(\w+)\/(\w+)$/.exec(blobId)) {
var db = new Database(match[1]);
var id = await db.get('path:' + match[2]);
let db = new Database(match[1]);
let id = await db.get('path:' + match[2]);
if (id) {
if (request.headers['if-none-match'] && request.headers['if-none-match'] == '"' + id + '"') {
headers = {
@ -720,7 +753,7 @@ async function blobHandler(request, response, blobId, uri) {
response.end();
} else {
data = utf8Decode(await getBlobOrContent(id));
var appObject = JSON.parse(data);
let appObject = JSON.parse(data);
data = appObject.files[uri.substring(1)];
data = await getBlobOrContent(data);
type = guessType(uri);
@ -735,7 +768,7 @@ async function blobHandler(request, response, blobId, uri) {
}
} else {
data = utf8Decode(await getBlobOrContent(blobId));
var appObject = JSON.parse(data);
let appObject = JSON.parse(data);
data = appObject.files[uri.substring(1)];
data = await getBlobOrContent(data);
headers = {
@ -755,41 +788,30 @@ ssb.addEventListener('connections', function() {
});
async function loadSettings() {
var data;
let data = {};
try {
var settings = new Database('core').get('settings');
let settings = new Database('core').get('settings');
if (settings) {
data = JSON.parse(settings);
}
} catch (error) {
print("Settings not found in database:", error);
}
if (!data) {
try {
data = JSON.parse(utf8Decode(await File.readFile(kGlobalSettingsFile)));
new Database('core').set('settings', JSON.stringify(data));
} catch (error) {
print("Unable to load settings from " + kGlobalSettingsFile + ":", error);
for (let [key, value] of Object.entries(k_global_settings)) {
if (data[key] === undefined) {
data[key] = value.default_value;
}
}
if (data) {
gGlobalSettings = data;
}
gGlobalSettings = data;
}
function sendStats() {
var any = false;
for (var process of Object.values(gProcesses)) {
if (process.app && process.stats) {
process.app.send({action: 'stats', stats: getStats()});
any = true;
let apps = Object.values(gProcesses).filter(process => process.app && process.stats).map(process => process.app);
if (apps.length) {
let stats = getStats();
for (let app of apps) {
app.send({action: 'stats', stats: stats});
}
}
if (any) {
setTimeout(sendStats, 1000);
} else {
gStatsTimer = false;
@ -807,7 +829,7 @@ function enableStats(process, enabled) {
loadSettings().then(function() {
httpd.all("/login", auth.handler);
httpd.all("", function(request, response) {
var match;
let match;
if (request.uri === "/" || request.uri === "") {
response.writeHead(303, {"Location": (request.client.tls ? 'https://' : 'http://') + request.headers.host + gGlobalSettings.index, "Content-Length": "0"});
return response.end();
@ -828,19 +850,31 @@ loadSettings().then(function() {
} else if (match = /^(.*)(\/(?:save|delete)?)$/.exec(request.uri)) {
return blobHandler(request, response, match[1], match[2]);
} else if (match = /^\/trace$/.exec(request.uri)) {
var data = trace();
let data = trace();
response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()});
return response.end(data);
} else if (match = /^\/disconnections$/.exec(request.uri)) {
let data = utf8Encode(JSON.stringify(disconnectionsDebug(), null, 2));
response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.byteLength.toString()});
return response.end(data);
} else if (match = /^\/debug$/.exec(request.uri)) {
var data = JSON.stringify(getDebug(), null, 2);
let data = JSON.stringify(getDebug(), null, 2);
response.writeHead(200, {"Content-Type": "application/json; charset=utf-8", "Content-Length": data.length.toString()});
return response.end(data);
} else if (match = /^\/mem$/.exec(request.uri)) {
let data = JSON.stringify(getAllocations(), null, 2);
response.writeHead(200, {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": data.length.toString(),
"Access-Control-Allow-Origin": "*",
});
return response.end(data);
} else if (request.uri == "/robots.txt") {
return blobHandler(request, response, null, request.uri);
} else if ((match = /^\/.well-known\/(.*)/.exec(request.uri)) && request.uri.indexOf("..") == -1) {
return wellKnownHandler(request, response, match[1]);
} else {
var data = "File not found.";
let data = "File not found.";
response.writeHead(404, {"Content-Type": "text/plain; charset=utf-8", "Content-Length": data.length.toString()});
return response.end(data);
}

View File

@ -1,7 +1,7 @@
function decode(encoded) {
var result = "";
for (var i = 0; i < encoded.length; i++) {
var c = encoded[i];
let result = "";
for (let i = 0; i < encoded.length; i++) {
let c = encoded[i];
if (c == "+") {
result += " ";
} else if (c == "%") {
@ -15,15 +15,15 @@ function decode(encoded) {
}
function decodeForm(encoded, initial) {
var result = initial || {};
let result = initial || {};
if (encoded) {
encoded = encoded.trim();
var items = encoded.split('&');
for (var i = 0; i < items.length; i++) {
var item = items[i];
var equals = item.indexOf('=');
var key = decode(item.slice(0, equals));
var value = decode(item.slice(equals + 1));
let items = encoded.split('&');
for (let i = 0; i < items.length; i++) {
let item = items[i];
let equals = item.indexOf('=');
let key = decode(item.slice(0, equals));
let value = decode(item.slice(equals + 1));
result[key] = value;
}
}

View File

@ -1,59 +0,0 @@
"use strict";
function parseUrl(url) {
// XXX: Hack.
var match = url.match(new RegExp("(\\w+)://([^/]+)?(.*)"));
return {
protocol: match[1],
host: match[2],
path: match[3],
port: match[1] == "http" ? 80 : 443,
};
}
function parseResponse(data) {
var firstLine;
var headers = {};
while (true) {
var endLine = data.indexOf("\r\n");
var line = data.substring(0, endLine);
if (!firstLine) {
firstLine = line;
} else if (!line.length) {
break;
} else {
var colon = line.indexOf(":");
headers[line.substring(colon)] = line.substring(colon + 1);
}
data = data.substring(endLine + 2);
}
return {body: data};
}
export function get(url) {
var parsed = parseUrl(url);
return new Promise(function(resolve, reject) {
var socket = new Socket();
var buffer = "";
return socket.connect(parsed.host, parsed.port).then(function() {
socket.read(function(data) {
if (data) {
buffer += data;
} else {
resolve(parseResponse(buffer));
}
});
if (parsed.port == 443) {
return socket.startTls();
}
}).then(function() {
socket.write(`GET ${parsed.path} HTTP/1.0\r\nHost: ${parsed.host}\r\nConnection: close\r\n\r\n`);
socket.shutdown();
}).catch(function(error) {
reject(error);
});
});
}

View File

@ -1,10 +1,8 @@
import * as sha1 from './sha1.js';
import * as core from './core.js';
"use strict";
var gHandlers = [];
var gSocketHandlers = [];
var gBadRequests = {};
let gHandlers = [];
let gSocketHandlers = [];
let gBadRequests = {};
const kRequestTimeout = 15000;
const kStallTimeout = 60000;
@ -17,8 +15,8 @@ function logError(error) {
}
function addHandler(handler) {
var added = false;
for (var i in gHandlers) {
let added = false;
for (let i in gHandlers) {
if (gHandlers[i].path == handler.path) {
gHandlers[i] = handler;
added = true;
@ -49,7 +47,7 @@ function registerSocketHandler(prefix, handler) {
function Request(method, uri, version, headers, body, client) {
this.method = method;
var index = uri.indexOf("?");
let index = uri.indexOf("?");
if (index != -1) {
this.uri = uri.slice(0, index);
this.query = uri.slice(index + 1);
@ -65,9 +63,9 @@ function Request(method, uri, version, headers, body, client) {
}
function findHandler(request) {
var matchedHandler = null;
for (var name in gHandlers) {
var handler = gHandlers[name];
let matchedHandler = null;
for (let name in gHandlers) {
let handler = gHandlers[name];
if (request.uri == handler.path || request.uri.slice(0, handler.path.length + 1) == handler.path + '/') {
matchedHandler = handler;
break;
@ -77,9 +75,9 @@ function findHandler(request) {
}
function findSocketHandler(request) {
var matchedHandler = null;
for (var name in gSocketHandlers) {
var handler = gSocketHandlers[name];
let matchedHandler = null;
for (let name in gSocketHandlers) {
let handler = gSocketHandlers[name];
if (request.uri == handler.path || request.uri.slice(0, handler.path.length + 1) == handler.path + '/') {
matchedHandler = handler;
break;
@ -89,27 +87,28 @@ function findSocketHandler(request) {
}
function Response(request, client) {
var kStatusText = {
let kStatusText = {
101: "Switching Protocols",
200: 'OK',
303: 'See other',
304: 'Not Modified',
400: 'Bad Request',
401: 'Unauthorized',
403: 'Forbidden',
404: 'File not found',
500: 'Internal server error',
};
var _started = false;
var _finished = false;
var _keepAlive = false;
var _chunked = false;
let _started = false;
let _finished = false;
let _keepAlive = false;
let _chunked = false;
return {
writeHead: function(status) {
if (_started) {
throw new Error("Response.writeHead called multiple times.");
}
var reason;
var headers;
let reason;
let headers;
if (arguments.length == 3) {
reason = arguments[1];
headers = arguments[2];
@ -117,11 +116,11 @@ function Response(request, client) {
reason = kStatusText[status];
headers = arguments[1];
}
var lowerHeaders = {};
var requestVersion = request.version.split("/")[1].split(".");
var responseVersion = (requestVersion[0] >= 1 && requestVersion[0] >= 1) ? "1.1" : "1.0";
var headerString = "HTTP/" + responseVersion + " " + status + " " + reason + "\r\n";
for (var i in headers) {
let lowerHeaders = {};
let requestVersion = request.version.split("/")[1].split(".");
let responseVersion = (requestVersion[0] >= 1 && requestVersion[0] >= 1) ? "1.1" : "1.0";
let headerString = "HTTP/" + responseVersion + " " + status + " " + reason + "\r\n";
for (let i in headers) {
headerString += i + ": " + headers[i] + "\r\n";
lowerHeaders[i.toLowerCase()] = headers[i];
}
@ -172,19 +171,16 @@ function Response(request, client) {
}
function handleRequest(request, response) {
var handler = findHandler(request);
let handler = findHandler(request);
print(request.client.peerName + " - - [" + new Date() + "] " + request.method + " " + request.uri + " " + request.version + " \"" + request.headers["user-agent"] + "\"");
if (handler) {
try {
var promise = handler.invoke(request, response);
if (promise) {
promise.catch(function(error) {
response.reportError(error);
request.client.close();
});
}
Promise.resolve(handler.invoke(request, response)).catch(function(error) {
response.reportError(error);
request.client.close();
});
} catch (error) {
response.reportError(error);
request.client.close();
@ -196,11 +192,11 @@ function handleRequest(request, response) {
}
function handleWebSocketRequest(request, response, client) {
var buffer = new Uint8Array(0);
var frame = new Uint8Array(0);
var frameOpCode = 0x0;
let buffer = new Uint8Array(0);
let frame;
let frameOpCode = 0x0;
var handler = findSocketHandler(request);
let handler = findSocketHandler(request);
if (!handler) {
client.close();
return;
@ -213,9 +209,9 @@ function handleWebSocketRequest(request, response, client) {
if (opCode == 0x1 && (typeof message == "string" || message instanceof String)) {
message = utf8Encode(message);
}
var fin = true;
var packet = [(fin ? (1 << 7) : 0) | (opCode & 0xf)];
var mask = false;
let fin = true;
let packet = [(fin ? (1 << 7) : 0) | (opCode & 0xf)];
let mask = false;
if (message.length < 126) {
packet.push((mask ? (1 << 7) : 0) | message.length);
} else if (message.length < (1 << 16)) {
@ -223,8 +219,8 @@ function handleWebSocketRequest(request, response, client) {
packet.push((message.length >> 8) & 0xff);
packet.push(message.length & 0xff);
} else {
var high = 0; //(message.length / (1 ** 32)) & 0xffffffff;
var low = message.length & 0xffffffff;
let high = 0; //(message.length / (1 ** 32)) & 0xffffffff;
let low = message.length & 0xffffffff;
packet.push((mask ? (1 << 7) : 0) | 127);
packet.push((high >> 24) & 0xff);
packet.push((high >> 16) & 0xff);
@ -236,7 +232,7 @@ function handleWebSocketRequest(request, response, client) {
packet.push(low & 0xff);
}
var array = new Uint8Array(packet.length + message.length);
let array = new Uint8Array(packet.length + message.length);
array.set(packet, 0);
array.set(message, packet.length);
try {
@ -252,53 +248,58 @@ function handleWebSocketRequest(request, response, client) {
client.read(function(data) {
if (data) {
var newBuffer = new Uint8Array(buffer.length + data.length);
let newBuffer = new Uint8Array(buffer.length + data.length);
newBuffer.set(buffer, 0);
newBuffer.set(data, buffer.length);
buffer = newBuffer;
while (buffer.length >= 2) {
var bits0 = buffer[0];
var bits1 = buffer[1];
let bits0 = buffer[0];
let bits1 = buffer[1];
if (bits1 & (1 << 7) == 0) {
// Unmasked message.
client.close();
}
var opCode = bits0 & 0xf;
var fin = bits0 & (1 << 7);
var payloadLength = bits1 & 0x7f;
var maskStart = 2;
let opCode = bits0 & 0xf;
let fin = bits0 & (1 << 7);
let payloadLength = bits1 & 0x7f;
let maskStart = 2;
if (payloadLength == 126) {
payloadLength = 0;
for (var i = 0; i < 2; i++) {
for (let i = 0; i < 2; i++) {
payloadLength <<= 8;
payloadLength |= buffer[2 + i];
}
maskStart = 4;
} else if (payloadLength == 127) {
payloadLength = 0;
for (var i = 0; i < 8; i++) {
for (let i = 0; i < 8; i++) {
payloadLength <<= 8;
payloadLength |= buffer[2 + i];
}
maskStart = 10;
}
var havePayload = buffer.length >= payloadLength + 2 + 4;
let havePayload = buffer.length >= payloadLength + 2 + 4;
if (havePayload) {
var mask = buffer.slice(maskStart, maskStart + 4);
var dataStart = maskStart + 4;
var decoded = new Array(payloadLength);
var payload = buffer.slice(dataStart, dataStart + payloadLength);
let mask =
buffer[maskStart + 0] |
buffer[maskStart + 1] << 8 |
buffer[maskStart + 2] << 16 |
buffer[maskStart + 3] << 24;
let dataStart = maskStart + 4;
let payload = buffer.slice(dataStart, dataStart + payloadLength);
let decoded = maskBytes(payload, mask);
buffer = buffer.slice(dataStart + payloadLength);
for (var i = 0; i < payloadLength; i++) {
decoded[i] = payload[i] ^ mask[i % 4];
}
var newBuffer = new Uint8Array(frame.length + decoded.length);
newBuffer.set(frame, 0);
newBuffer.set(decoded, frame.length);
frame = newBuffer;
if (frame) {
let newBuffer = new Uint8Array(frame.length + decoded.length);
newBuffer.set(frame, 0);
newBuffer.set(decoded, frame.length);
frame = newBuffer;
} else {
frame = decoded;
}
if (opCode) {
frameOpCode = opCode;
@ -311,7 +312,7 @@ function handleWebSocketRequest(request, response, client) {
opCode: frameOpCode,
});
}
frame = new Uint8Array(0);
frame = undefined;
}
} else {
break;
@ -339,31 +340,15 @@ function handleWebSocketRequest(request, response, client) {
}
function webSocketAcceptResponse(key) {
var kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
var kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var hex = sha1.hash(key + kMagic)
var binary = "";
for (var i = 0; i < hex.length; i += 6) {
var characters = hex.substring(i, i + 6);
if (characters.length < 6) {
characters += "0".repeat(6 - characters.length);
}
var value = parseInt(characters, 16);
for (var bit = 0; bit < 8 * 3; bit += 6) {
if (i * 8 / 2 + bit >= 8 * hex.length / 2) {
binary += kAlphabet.charAt(64);
} else {
binary += kAlphabet.charAt((value >> (18 - bit)) & 63);
}
}
}
return binary;
let kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
let kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
return base64Encode(sha1Digest(key + kMagic));
}
function badRequest(client, reason) {
var now = new Date();
var count = 0;
var old = gBadRequests[client.peerName];
let now = new Date();
let count = 0;
let old = gBadRequests[client.peerName];
if (!old) {
gBadRequests[client.peerName] = {
expire: new Date(now.getTime() + 1 * 60 * 1000),
@ -381,9 +366,9 @@ function badRequest(client, reason) {
}
function allowRequest(client) {
var old = gBadRequests[client.peerName];
let old = gBadRequests[client.peerName];
if (old) {
var now = new Date();
let now = new Date();
if (old.expire < now) {
delete gBadRequests[client.peerName];
return true;
@ -404,15 +389,15 @@ function handleConnection(client) {
}
client.info = 'accepted';
var inputBuffer = new Uint8Array(0);
var request;
var headers = {};
var lineByLine = true;
var bodyToRead = -1;
var body;
var requestCount = -1;
var readCount = 0;
var isWebsocket = false;
let inputBuffer = new Uint8Array(0);
let request;
let headers = {};
let parsing_header = true;
let bodyToRead = -1;
let body;
let requestCount = -1;
let readCount = 0;
let isWebsocket = false;
function resetTimeout(requestIndex) {
if (isWebsocket) {
@ -430,7 +415,7 @@ function handleConnection(client) {
}
}, kRequestTimeout);
} else {
var lastReadCount = readCount;
let lastReadCount = readCount;
setTimeout(function() {
if (readCount == lastReadCount) {
client.info = 'stalled';
@ -447,10 +432,9 @@ function handleConnection(client) {
resetTimeout(++requestCount);
function reset() {
inputBuffer = new Uint8Array(0);
request = undefined;
headers = {};
lineByLine = true;
parsing_header = true;
bodyToRead = -1;
body = undefined;
client.info = 'reset';
@ -459,8 +443,8 @@ function handleConnection(client) {
function finish() {
client.info = 'finishing';
var requestObject = new Request(request[0], request[1], request[2], headers, body, client);
var response = new Response(requestObject, client);
let requestObject = new Request(request[0], request[1], request[2], headers, body, client);
let response = new Response(requestObject, client);
try {
handleRequest(requestObject, response)
if (client.isConnected) {
@ -472,71 +456,6 @@ function handleConnection(client) {
}
}
function handleLine(line, length) {
if (bodyToRead == -1) {
line = utf8Decode(line);
if (!request) {
if (!line) {
badRequest(client, 'Empty request.');
return false;
}
request = line.split(' ');
if (request.length != 3 || !request[2].startsWith('HTTP/1.')) {
badRequest(client, 'Bad request.');
request = null;
return false;
}
return true;
} else if (line) {
var colon = line.indexOf(':');
var key = line.slice(0, colon).trim();
var value = line.slice(colon + 1).trim();
headers[key.toLowerCase()] = value;
return true;
} else {
if (headers["content-length"] != undefined) {
bodyToRead = parseInt(headers["content-length"]);
lineByLine = false;
if (bodyToRead > 16 * 1024 * 1024) {
badRequest(client, 'Request too large: ' + bodyToRead + '.');
return false;
}
body = new Uint8Array(bodyToRead);
client.info = 'waiting for body';
resetTimeout(requestCount);
return true;
} else if (headers["connection"]
&& headers["connection"].toLowerCase().split(",").map(x => x.trim()).indexOf("upgrade") != -1
&& headers["upgrade"]
&& headers["upgrade"].toLowerCase() == "websocket") {
isWebsocket = true;
client.info = 'websocket';
var requestObject = new Request(request[0], request[1], request[2], headers, body, client);
var response = new Response(requestObject, client);
handleWebSocketRequest(requestObject, response, client);
/* Prevent the timeout from disconnecting us. */
requestCount++;
return false;
} else {
finish();
return false;
}
}
} else {
var offset = body.length - bodyToRead;
if (line.length > body.length - offset) {
line = line.slice(0, body.length - offset);
}
body.set(line, offset);
bodyToRead -= line.length;
if (bodyToRead <= 0) {
finish();
}
}
}
client.noDelay = true;
client.onError(function(error) {
logError(client.peerName + " - - [" + new Date() + "] " + error);
});
@ -547,36 +466,82 @@ function handleConnection(client) {
if (bodyToRead != -1 && !isWebsocket) {
resetTimeout(requestCount);
}
const kMaxLineLength = 4096;
var newBuffer = new Uint8Array(inputBuffer.length + data.length);
let newBuffer = new Uint8Array(inputBuffer.length + data.length);
newBuffer.set(inputBuffer, 0);
newBuffer.set(data, inputBuffer.length);
inputBuffer = newBuffer;
var newLine = '\n'.charCodeAt(0);
var carriageReturn = '\r'.charCodeAt(0);
if (parsing_header)
{
let result = parseHttp(inputBuffer, inputBuffer.length - data.length);
if (result) {
if (typeof result === 'number') {
if (result == -2) {
/* More. */
} else {
badRequest(client, 'Bad request.');
return;
}
} else if (typeof result === 'object') {
request = [
result.method,
result.path,
`HTTP/1.${result.minor_version}`,
];
var more = true;
while (more) {
if (lineByLine) {
more = false;
var end = inputBuffer.indexOf(newLine);
var realEnd = end;
if (end > 0 && inputBuffer[end - 1] == carriageReturn) {
--end;
}
if (end > kMaxLineLength || end == -1 && inputBuffer.length > kMaxLineLength) {
badRequest(client, 'Request too long.');
return;
}
if (end != -1) {
var line = inputBuffer.slice(0, end);
inputBuffer = inputBuffer.slice(realEnd + 1);
more = handleLine(line, realEnd + 1);
headers = Object.fromEntries(Object.entries(result.headers).map(x => [x[0].toLowerCase(), x[1]]));
parsing_header = false;
inputBuffer = inputBuffer.slice(result.bytes_parsed);
if (!client.tls && tildefriends.https_port && core.globalSettings.http_redirect && !result.path.startsWith('/.well-known/')) {
let requestObject = new Request(request[0], request[1], request[2], headers, body, client);
let response = new Response(requestObject, client);
response.writeHead(303, {"Location": `${core.globalSettings.http_redirect}${result.path}`, "Content-Length": "0"});
response.end();
return;
}
if (headers["content-length"] != undefined) {
bodyToRead = parseInt(headers["content-length"]);
if (bodyToRead > 16 * 1024 * 1024) {
badRequest(client, 'Request too large: ' + bodyToRead + '.');
return;
}
body = new Uint8Array(bodyToRead);
client.info = 'waiting for body';
resetTimeout(requestCount);
} else if (headers["connection"]
&& headers["connection"].toLowerCase().split(",").map(x => x.trim()).indexOf("upgrade") != -1
&& headers["upgrade"]
&& headers["upgrade"].toLowerCase() == "websocket") {
isWebsocket = true;
client.info = 'websocket';
let requestObject = new Request(request[0], request[1], request[2], headers, body, client);
let response = new Response(requestObject, client);
handleWebSocketRequest(requestObject, response, client);
/* Prevent the timeout from disconnecting us. */
requestCount++;
} else {
finish();
}
}
}
}
if (!parsing_header && inputBuffer.length)
{
let offset = body.length - bodyToRead;
let length = Math.min(inputBuffer.length, body.length - offset);
if (inputBuffer.length > body.length - offset) {
body.set(inputBuffer.slice(0, length), offset);
inputBuffer = inputBuffer.slice(length);
} else {
more = handleLine(inputBuffer, inputBuffer.length);
inputBuffer = new Uint8Array(0);
body.set(inputBuffer, offset);
inputBuffer = inputBuffer.slice(inputBuffer.length);
}
bodyToRead -= length;
if (bodyToRead <= 0) {
finish();
}
}
} else {
@ -586,32 +551,47 @@ function handleConnection(client) {
});
}
var kBacklog = 8;
var kHost = "0.0.0.0"
let kBacklog = 8;
let kHost = "0.0.0.0"
var socket = new Socket();
socket.bind(kHost, tildefriends.http_port).then(function() {
var listenResult = socket.listen(kBacklog, function() {
socket.accept().then(handleConnection).catch(function(error) {
logError("[" + new Date() + "] accept error " + error);
let socket = new Socket();
socket.bind(kHost, tildefriends.http_port).then(function(port) {
print("bound to", port);
print("checking", tildefriends.args.out_http_port_file);
if (tildefriends.args.out_http_port_file) {
print("going to write the file");
File.writeFile(tildefriends.args.out_http_port_file, port.toString() + '\n').then(function(r) {
print("wrote port file", tildefriends.args.out_http_port_file, r);
}).catch(function() {
print("failed to write port file");
});
}
let listenResult = socket.listen(kBacklog, async function() {
try {
let client = await socket.accept();
client.noDelay = true;
handleConnection(client);
} catch (error) {
logError("[" + new Date() + "] accept error " + error);
}
});
}).catch(function(error) {
logError("[" + new Date() + "] bind error " + error);
});
if (tildefriends.https_port) {
var tls = {};
var secureSocket = new Socket();
let tls = {};
let secureSocket = new Socket();
secureSocket.bind(kHost, tildefriends.https_port).then(function() {
return secureSocket.listen(kBacklog, async function() {
try {
var client = await secureSocket.accept();
let client = await secureSocket.accept();
client.noDelay = true;
client.tls = true;
const kCertificatePath = "data/httpd/certificate.pem";
const kPrivateKeyPath = "data/httpd/privatekey.pem";
var stat = await Promise.all([
let stat = await Promise.all([
await File.stat(kCertificatePath),
await File.stat(kPrivateKeyPath),
]);
@ -621,8 +601,8 @@ if (tildefriends.https_port) {
tls.keyStat.mtime != stat[1].mtime ||
tls.keyStat.size != stat[1].size) {
print("Reloading " + kCertificatePath + " and " + kPrivateKeyPath);
var privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
var certificate = utf8Decode(await File.readFile(kCertificatePath));
let privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
let certificate = utf8Decode(await File.readFile(kCertificatePath));
tls.context = new TlsContext();
tls.context.setPrivateKey(privateKey);

View File

@ -11,8 +11,8 @@
<span>😎</span>
<a accesskey="h" data-tip="Open home app." href="/" style="color: #fff">Tilde Friends</a>
<a accesskey="a" data-tip="Open apps list." href="/~core/apps/">apps</a>
<a accesskey="e" data-tip="Toggle the app editor." href="#" onclick="event.preventDefault(); toggleEdit()">edit</a>
<a accesskey="p" data-tip="View and change permissions." href="#" onclick="showPermissions()">🎛️</a>
<a accesskey="e" data-tip="Toggle the app editor." href="#" id="edit_link">edit</a>
<a accesskey="p" data-tip="View and change permissions." href="#" id="show_permissions_link">🎛️</a>
<span id="status"></span>
<span id="requests"></span>
<span id="permissions_settings"></span>
@ -22,34 +22,32 @@
<div id="content" class="hbox" style="flex: 1 1; width: 100%">
<div id="statsPane" class="vbox" style="display: none; flex 1 1">
<div class="hbox">
<input type="button" id="closeStats" name="closeStats" value="Close" onclick="closeStats()">
<input type="button" id="closeStats" name="closeStats" value="Close">
</div>
<div id="graphs" class="vbox" style="height: 100%"></div>
</div>
<div id="editPane" class="vbox" style="display: none">
<div class="navigation hbox">
<input type="button" id="closeEditor" name="closeEditor" value="Close" onclick="closeEditor()">
<input type="button" id="save" name="save" value="Save" onclick="save()">
<input type="button" id="closeEditor" name="closeEditor" value="Close">
<input type="button" id="save" name="save" value="Save">
<input type="button" id="icon" name="icon" value="📦">
<input type="text" id="name" name="name" style="flex: 1 1; min-width: 1em"></input>
<input type="button" id="push_to_parent" value="Push to Parent" onclick="pushToParent()">
<input type="button" id="pull_from_parent" value="Pull from Parent" onclick="pullFromParent()">
<input type="button" id="revert" name="revert" value="Revert" onclick="revert()">
<input type="button" id="delete" name="delete" value="Delete" onclick="deleteApp()">
<input type="button" onclick="event.preventDefault(); trace()" value="Trace">
<input type="button" onclick="event.preventDefault(); toggleStats()" value="Stats">
<input type="button" id="delete" name="delete" value="Delete">
<input type="button" id="trace_button" value="Trace">
<input type="button" id="stats_button" value="Stats">
</div>
<div class="hbox" style="height: 100%">
<div id="filesPane">
<div class="hbox">
<span id="files_header">Files</span>
<span id="files_hide" onclick="hideFiles()">«</span>
<span id="files_show" onclick="showFiles()">»</span>
<span id="files_hide">«</span>
<span id="files_show">»</span>
</div>
<div id="files_content">
<ul id="files"></ul>
<br>
<div><button onclick="newFile()">New File</button></div>
<div><button onclick="removeFile()">Remove File</button></div>
<div><button id="new_file_button">New File</button></div>
<div><button id="remove_file_button">Remove File</button></div>
</div>
</div>
<div id="docPane" style="display: flex; flex: 1 1 50%; flex-flow: column">
@ -65,6 +63,6 @@
</div>
<script src="/split/split.min.js"></script>
<script src="/smoothie/smoothie.js"></script>
<script src="/static/client.js"></script>
<script src="/static/client.js" type="module"></script>
</body>
</html>

View File

@ -1,160 +0,0 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* SHA-1 implementation in JavaScript (c) Chris Veness 2002-2014 / MIT Licence */
/* */
/* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */
/* http://csrc.nist.gov/groups/ST/toolkit/examples.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* jshint node:true *//* global define, escape, unescape */
'use strict';
/**
* SHA-1 hash function reference implementation.
*
* @namespace
*/
var Sha1 = {};
/**
* Generates SHA-1 hash of string.
*
* @param {string} msg - (Unicode) string to be hashed.
* @returns {string} Hash of msg as hex character string.
*/
Sha1.hash = function(msg) {
// convert string to UTF-8, as SHA only deals with byte-streams
msg = msg.utf8Encode();
// constants [§4.2.1]
var K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ];
// PREPROCESSING
msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
// convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + 1 + appended length
var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints
var M = new Array(N);
for (var i=0; i<N; i++) {
M[i] = new Array(16);
for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
(msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
} // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
}
// add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
// note: most significant word would be (len-1)*8 >>> 32, but since JS converts
// bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]);
M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
// set initial hash value [§5.3.1]
var H0 = 0x67452301;
var H1 = 0xefcdab89;
var H2 = 0x98badcfe;
var H3 = 0x10325476;
var H4 = 0xc3d2e1f0;
// HASH COMPUTATION [§6.1.2]
var W = new Array(80); var a, b, c, d, e;
for (var i=0; i<N; i++) {
// 1 - prepare message schedule 'W'
for (var t=0; t<16; t++) W[t] = M[i][t];
for (var t=16; t<80; t++) W[t] = Sha1.ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
// 2 - initialise five working variables a, b, c, d, e with previous hash value
a = H0; b = H1; c = H2; d = H3; e = H4;
// 3 - main loop
for (var t=0; t<80; t++) {
var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
var T = (Sha1.ROTL(a,5) + Sha1.f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
e = d;
d = c;
c = Sha1.ROTL(b, 30);
b = a;
a = T;
}
// 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
H0 = (H0+a) & 0xffffffff;
H1 = (H1+b) & 0xffffffff;
H2 = (H2+c) & 0xffffffff;
H3 = (H3+d) & 0xffffffff;
H4 = (H4+e) & 0xffffffff;
}
return Sha1.toHexStr(H0) + Sha1.toHexStr(H1) + Sha1.toHexStr(H2) +
Sha1.toHexStr(H3) + Sha1.toHexStr(H4);
};
/**
* Function 'f' [§4.1.1].
* @private
*/
Sha1.f = function(s, x, y, z) {
switch (s) {
case 0: return (x & y) ^ (~x & z); // Ch()
case 1: return x ^ y ^ z; // Parity()
case 2: return (x & y) ^ (x & z) ^ (y & z); // Maj()
case 3: return x ^ y ^ z; // Parity()
}
};
/**
* Rotates left (circular left shift) value x by n positions [§3.2.5].
* @private
*/
Sha1.ROTL = function(x, n) {
return (x<<n) | (x>>>(32-n));
};
/**
* Hexadecimal representation of a number.
* @private
*/
Sha1.toHexStr = function(n) {
// note can't use toString(16) as it is implementation-dependant,
// and in IE returns signed numbers when used on full words
var s="", v;
for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
return s;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** Extend String object with method to encode multi-byte string to utf8
* - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */
if (typeof String.prototype.utf8Encode == 'undefined') {
String.prototype.utf8Encode = function() {
return unescape( encodeURIComponent( this ) );
};
}
/** Extend String object with method to decode utf8 string to multi-byte */
if (typeof String.prototype.utf8Decode == 'undefined') {
String.prototype.utf8Decode = function() {
try {
return decodeURIComponent( escape( this ) );
} catch (e) {
return this; // invalid UTF-8? return as-is
}
};
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (typeof module != 'undefined' && module.exports) module.exports = Sha1; // CommonJs export
if (typeof define == 'function' && define.amd) define([], function() { return Sha1; }); // AMD
export let hash = Sha1.hash;

View File

@ -1,370 +0,0 @@
"use strict";
var g_wants_requests = {};
var g_database = new Database('core');
let g_attendants = {};
const k_use_create_history_stream = false;
const k_blobs_concurrent_target = 8;
const k_settings = JSON.parse(g_database.get('settings') ?? '{"room": true}');
function following(db, id) {
var o = db.get(id + ":following");
const k_version = 5;
var f = o ? JSON.parse(o) : o;
if (!f || f.version != k_version) {
f = {users: [], sequence: 0, version: k_version};
}
f.users = new Set(f.users);
ssb.sqlStream(
"SELECT "+
" sequence, "+
" json_extract(content, '$.contact') AS contact, "+
" json_extract(content, '$.following') AS following "+
"FROM messages "+
"WHERE "+
" author = ?1 AND "+
" sequence > ?2 AND "+
" json_extract(content, '$.type') = 'contact' "+
"UNION SELECT MAX(sequence) AS sequence, NULL, NULL FROM messages WHERE author = ?1 "+
"ORDER BY sequence",
[id, f.sequence],
function(row) {
if (row.following) {
f.users.add(row.contact);
} else {
f.users.delete(row.contact);
}
f.sequence = row.sequence;
});
f.users = Array.from(f.users).sort();
var j = JSON.stringify(f);
if (o != j) {
db.set(id + ":following", j);
}
return f.users;
}
function followingDeep(db, seed_ids, depth) {
if (depth <= 0) {
return seed_ids;
}
var f = seed_ids.map(x => following(db, x));
var ids = [].concat(...f);
var x = followingDeep(db, [...new Set(ids)].sort(), depth - 1);
x = [...new Set([].concat(...x, ...seed_ids))].sort();
return x;
}
function get_latest_sequence_for_author(author) {
var sequence = 0;
ssb.sqlStream(
'SELECT MAX(sequence) AS sequence FROM messages WHERE author = ?1',
[author],
function(row) {
if (row.sequence) {
sequence = row.sequence;
}
});
return sequence;
}
function storeMessage(message) {
var payload = message.message.value ? message.message.value : message.message;
if (typeof(payload) == 'object') {
ssb.storeMessage(payload);
}
}
function tunnel_attendants(request) {
if (request.message.type !== 'state') {
throw Error('Unexpected type: ' + request.message.type);
}
let state = new Set(request.message.ids);
for (let id of state) {
request.add_room_attendant(id);
}
request.more(function attendants(message) {
if (message.message.type === 'joined') {
request.add_room_attendant(message.message.id);
state.add(message.message.id);
} else if (message.message.type === 'left') {
request.remove_room_attendant(message.message.id);
state.delete(message.message.id);
} else {
throw Error('Unexpected type: ' + message.type);
}
});
}
function send_blobs_create_wants(connection) {
connection.send_json({'name': ['blobs', 'createWants'], 'type': 'source', 'args': []}, function on_blob_create_wants(message) {
if (message.message?.name === 'Error') {
return;
}
Object.keys(message.message).forEach(function(id) {
if (message.message[id] < 0) {
if (g_wants_requests[connection.id]) {
delete connection.active_blob_wants[id];
var blob = ssb.blobGet(id);
if (blob) {
var out_message = {};
out_message[id] = blob.byteLength;
g_wants_requests[connection.id].send_json(out_message);
}
}
} else {
var received_bytes = 0;
var expected_bytes = message.message[id];
var buffer = new Uint8Array(expected_bytes);
connection.send_json({'name': ['blobs', 'get'], 'type': 'source', 'args': [id]}, function(message) {
if (message.flags & 0x4 /* end */) {
delete connection.active_blob_wants[id];
} else {
buffer.set(new Uint8Array(message.message, 0, message.message.byteLength), received_bytes);
received_bytes += message.message.byteLength;
if (received_bytes == expected_bytes) {
ssb.blobStore(buffer);
}
}
});
if (g_wants_requests[connection.id] && Object.keys(connection.active_blob_wants).length < k_blobs_concurrent_target) {
requestMoreBlobs(g_wants_requests[connection.id]);
}
}
});
});
}
ssb.addEventListener('connections', function on_connections_changed(change, connection) {
if (change == 'add') {
connection.active_blob_wants = {};
var sequence = get_latest_sequence_for_author(connection.id);
if (k_use_create_history_stream) {
connection.send_json({'name': ['createHistoryStream'], 'type': 'source', 'args': [{'id': connection.id, 'seq': sequence, 'live': true, 'keys': false}]}, storeMessage);
var identities = ssb.getAllIdentities();
followingDeep(g_database, identities, 2).then(function(ids) {
for (let id of ids) {
if (identities.indexOf(id) != -1) {
continue;
}
var sequence = get_latest_sequence_for_author(id);
connection.send_json({'name': ['createHistoryStream'], 'type': 'source', 'args': [{'id': id, 'seq': sequence, 'live': true, 'keys': false}]}, storeMessage);
}
});
} else {
if (connection.is_client) {
connection.send_json({"name": ["ebt", "replicate"], "args": [{"version": 3, "format": "classic"}], "type": "duplex"}, ebtReplicateClient);
connection.send_json_async({'name': ['tunnel', 'isRoom'], 'args': []}, function tunnel_is_room(request) {
if (request.message) {
request.connection.send_json({'name': ['room', 'attendants'], 'args': [], 'type': 'source'}, tunnel_attendants);
}
});
}
send_blobs_create_wants(connection);
}
} else if (change == 'remove') {
print('REMOVE', connection.id);
notify_attendant_changed(connection.id, 'left');
delete g_attendants[connection.id];
delete g_wants_requests[connection.id];
} else {
print('CHANGE', change);
}
});
function blob_want_discovered(request, id) {
if (!request || !request.connection || Object.keys(request.connection.active_blob_wants).length > k_blobs_concurrent_target) {
return;
}
var message = {};
message[id] = -1;
request.send_json(message);
request.connection.active_blob_wants[id] = true;
}
function requestMoreBlobs(request) {
ssb.sqlStream(
'SELECT id FROM blob_wants LIMIT ' + k_blobs_concurrent_target,
[],
row => blob_want_discovered(request, row.id));
}
ssb.addRpc(['blobs', 'createWants'], function(request) {
g_wants_requests[request.connection.id] = request;
ssb.addEventListener('blob_want_added', id => blob_want_discovered(request, id));
requestMoreBlobs(request);
});
ssb.addRpc(['tunnel', 'isRoom'], function(request) {
if (k_settings.room) {
request.send_json({"name": "tilde friends tunnel", "membership": false, "features": ["tunnel", "room1"]});
} else {
request.send_json(false);
}
});
function notify_attendant_changed(id, type) {
if (!id) {
print(`notify_attendant_changed called with id=${id}`);
return;
}
for (let r of Object.values(g_attendants)) {
try {
r.send_json({
type: type,
id: id,
});
} catch (e) {
print(`Removing ${id} from g_attendants in ${type}.`, e);
delete g_attendants[id];
}
}
}
ssb.addRpc(['room', 'attendants'], function(request) {
let ids = Object.keys(g_attendants).sort();
request.send_json({
type: 'state',
ids: ids,
});
notify_attendant_changed(request.connection.id, 'joined');
g_attendants[request.connection.id] = request;
});
function ebtReplicateSendClock(request, have) {
var identities = ssb.getAllIdentities();
var message = {};
var last_sent = request.connection.sent_clock || {};
var ids = followingDeep(g_database, identities, 2).concat([request.connection.id]);
if (!Object.keys(last_sent).length) {
for (let id of ids) {
message[id] = get_latest_sequence_for_author(id);
}
}
for (let id of Object.keys(have)) {
if (message[id] === undefined) {
var sequence = get_latest_sequence_for_author(id);
message[id] = sequence ? sequence : -1;
}
}
var to_send = {}
var offset = Math.floor(Math.random() * ids.length);
for (var i = 0; i < ids.length; i++) {
var id = ids[(i + offset) % ids.length];
if (last_sent[id] === undefined || message[id] > last_sent[id]) {
last_sent[id] = to_send[id] = message[id] === -1 ? -1 : message[id] << 1;
}
if (Object.keys(to_send).length >= 32) {
request.send_json(to_send);
to_send = {};
}
}
request.connection.sent_clock = last_sent;
if (Object.keys(to_send).length) {
request.send_json(to_send);
}
}
function formatMessage(row) {
if (row.sequence_before_author) {
return {
previous: row.previous,
sequence: row.sequence,
author: row.author,
timestamp: row.timestamp,
hash: row.hash,
content: JSON.parse(row.content),
signature: row.signature,
};
} else {
return {
previous: row.previous,
author: row.author,
sequence: row.sequence,
timestamp: row.timestamp,
hash: row.hash,
content: JSON.parse(row.content),
signature: row.signature,
};
}
}
function ebtReplicateRegisterMessageCallback(request) {
ssb.addEventListener('message', function(message_id) {
ssb.sqlStream(
'SELECT previous, author, id, sequence, timestamp, hash, content, signature FROM messages WHERE id = ?1',
[message_id],
function (row) {
request.send_json(formatMessage(row));
});
});
}
function ebtReplicateCommon(request) {
if (request.message.author) {
storeMessage(request);
} else {
ebtReplicateSendClock(request, request.message);
for (let id of Object.keys(request.message)) {
if (request.message[id] >= 0 && (request.message[id] & 1) == 0) {
ssb.sqlStream(
'SELECT previous, author, id, sequence, timestamp, hash, content, signature FROM messages WHERE author = ?1 AND sequence >= ?2 ORDER BY sequence',
[id, request.message[id] >> 1],
function (row) {
request.send_json(formatMessage(row));
});
}
}
}
}
function ebtReplicateClient(request) {
if (request.message?.name !== 'Error') {
if (!request.connection.message_registered) {
ebtReplicateRegisterMessageCallback(request);
request.connection.message_registered = true;
}
ebtReplicateCommon(request);
}
}
function ebtReplicateServer(request) {
ebtReplicateRegisterMessageCallback(request);
ebtReplicateSendClock(request, {});
request.more(ebtReplicateCommon);
}
ssb.addRpc(['ebt', 'replicate'], ebtReplicateServer);
ssb.addRpc(['createHistoryStream'], function(request) {
var id = request.args[0].id;
var seq = request.args[0].seq;
var keys = request.args[0].keys || request.args[0].keys === undefined;
function sendMessage(row) {
if (keys) {
var message = {
key: row.id,
value: formatMessage(row),
timestamp: row.timestamp,
};
} else {
var message = formatMessage(row);
}
request.send_json(message);
}
ssb.sqlStream(
'SELECT previous, author, id, sequence, timestamp, hash, content, signature, sequence_before_author FROM messages WHERE author = ?1 AND sequence >= ?2 ORDER BY sequence',
[id, seq ?? 0],
sendMessage);
ssb.addEventListener('message', function(message_id) {
ssb.sqlStream(
'SELECT previous, author, id, sequence, timestamp, hash, content, signature, sequence_before_author FROM messages WHERE id = ?1 AND author = ?2',
[message_id, id],
function (row) {
sendMessage(row);
});
});
});

View File

@ -14,7 +14,7 @@ if (k_is_browser) {
function make_rpc(target, prop, receiver) {
return function() {
let id = g_next_id++;
while (!id || g_calls[id]) {
while (!id || g_calls[id] !== undefined) {
id = g_next_id++;
}
let promise = new Promise(function(resolve, reject) {
@ -39,38 +39,35 @@ function send(response) {
function call_rpc(message) {
if (message && message.message === 'tfrpc') {
let method = g_api[message.method];
let id = message.id;
if (message.method) {
let method = g_api[message.method];
if (method) {
let response = {message: 'tfrpc', id: message.id};
try {
Promise.resolve(method(...message.params)).then(function(result) {
response.result = result;
send(response);
send({message: 'tfrpc', id: id, result: result});
}).catch(function(error) {
response.error = error;
send(response);
send({message: 'tfrpc', id: id, error: error});
});
} catch (error) {
response.error = error;
send(response);
send({message: 'tfrpc', id: id, error: error});
}
} else {
throw new Error(message.method + ' not found.');
}
} else if (message.error !== undefined) {
if (g_calls[message.id]) {
g_calls[message.id].reject(message.error);
delete g_calls[message.id];
if (g_calls[id]) {
g_calls[id].reject(message.error);
delete g_calls[id];
} else {
throw new Error(message.id + ' not found to reply.');
throw new Error(id + ' not found to reply.');
}
} else {
if (g_calls[message.id]) {
g_calls[message.id].resolve(message.result);
delete g_calls[message.id];
if (g_calls[id]) {
g_calls[id].resolve(message.result);
delete g_calls[id];
} else {
throw new Error(message.id + ' not found to reply.');
throw new Error(id + ' not found to reply.');
}
}
}

View File

@ -1,106 +0,0 @@
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# http://www.gnu.org/software/automake
Makefile
Makefile.in
/ar-lib
/mdate-sh
/py-compile
/test-driver
/ylwrap
# http://www.gnu.org/software/autoheader
config.h
# http://www.gnu.org/software/autoconf
autom4te.cache
/autoscan.log
/autoscan-*.log
/aclocal.m4
/compile
/config.guess
/config.h.in
/config.log
/config.status
/config.sub
/configure
/configure.scan
/depcomp
/install-sh
/missing
/stamp-h1
# https://www.gnu.org/software/libtool/
/ltmain.sh
# http://www.gnu.org/software/texinfo
/texinfo.tex
# http://www.gnu.org/software/m4/
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4
# vim
*.swp
# project specific
test/gen
test/test[0-9]*
test/.deps

29
deps/base64c/LICENSE vendored
View File

@ -1,29 +0,0 @@
BSD 3-Clause License
Copyright (c) 2018, Sean Hanna
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,2 +0,0 @@
AUTOMAKE_OPTIONS = foreign
SUBDIRS = src test

View File

@ -1,60 +0,0 @@
# base64c
This is primarily just a fork of a base64 decoder from the FreeBSD codebase. It has received a few modifications:
* removed all allocations, you are expected to pass in a buffer that has sufficient space and you will get an error (-1) if you run out of space
* replaced a dynamically generated lookup table with a hardcoded lookup table
* wrote my own unit tests, i'm sure there are tests for freebsd somewhere but i didn't find them
# Embedding
This code is primarily intended to be dropped into an existing code base ( or perhaps using submodules). To do that:
* grab include/base64c.h
* grab src/base64c.h
# Usage
Call base64c_encoding_length() to calculate how big a buffer you need to encode a string. It's somewhere around 4 times the size of the input string. This length includes a null terminator.
```c
char input_string[256];
size_t new_len = base64c_encoding_length( strlen(input_string));
unsigned char *buffer = (unsigned char*)malloc(new_len);
```
Call base64c_encode() to actually encode your input string as base64. It will write to the buffer and return how many characters were written. If there was an error it will return -1.
```c
size_t output_length = base64c_encode(input_string, strlen(input_string), buffer, new_len);
if (output_length == -1) {
int x = 1/0; // ERROR!
}
```
Call base64c_decoding_length() to calculate how big a buffer you need to decode. It comes out to about half the size. This number isn't always exact, but it is close to within a byte or two.
```c
size_t decode_len = base64c_decoding_length( strlen(buffer) );
unsigned char *decoded = (unsigned char*)malloc( decode_len );
```
Call base64c_decode() to decode an encoded base64 string. It will write to the buffer and return how many characters were written. IF there was an error it will return -1. If the string contains invalid number of characters, or has any characters that are not part of the base64 character set an error will be returned.
# Building
You need to bootstrap all the autoconf tools by running ./autogen.sh
You need to have autoconf installed to do this.
Once bootstrapped run ./configure
# Tests
There are tests in the test/ subfolder. They will be built automatically. There is no special test runner. You can run each of the test cases manually to check whether the code is working properly.
# References
(http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c)
(https://github.com/freebsd/freebsd/blob/master/contrib/wpa/src/utils/base64.c)

View File

@ -1,3 +0,0 @@
#!/bin/sh
aclocal && automake --gnu --add-missing && autoconf

View File

@ -1,22 +0,0 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
BASE64C_VERSION=0.5
AC_PREREQ([2.69])
AC_INIT(base64c, 0.5, hannasm@gmail.com)
AM_INIT_AUTOMAKE(base64c, 0.5)
AC_CONFIG_SRCDIR([include/base64c.h])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_SIZE_T
# Checks for library functions.
AC_OUTPUT(Makefile src/Makefile test/Makefile)

View File

@ -1,42 +0,0 @@
#ifndef base64cC_H
#define base64cC_H
#include <stddef.h>
#include <stdint.h>
/**
* base64c_encoding_length - calculate length to allocate for encode
* @len: Length of input string
* Returns: number of bytes required to base64c encode, this includes room for '\0' terminator
*/
size_t base64c_encoding_length(size_t len);
/**
* base64c_decoding_length - calculate length to allocate for decode
* @len: Length of (base64 encoded) input string
* Returns: maximum number of bytes required to decode
*/
size_t base64c_decoding_length(size_t inlen);
/**
* base64c_encode - base64c encode
* @src: Data to be encoded
* @len: Length of the data to be encoded
* @out: Mutable output buffer destination, all encoded bytes will be written to the destination
* @out_len: length of output buffer
* Returns: number of bytes written, or -1 if there was an error
*/
size_t base64c_encode(const unsigned char *src, size_t len, unsigned char* out, const size_t out_len);
/**
* base64c_decode - base64c decode
* @src: Data to be decoded
* @len: Length of the data to be decoded
* @out_len: Pointer to output length variable
* Returns: Allocated buffer of out_len bytes of decoded data,
* or %NULL on failure
*
* Caller is responsible for freeing the returned buffer.
*/
size_t base64c_decode(const unsigned char *src, size_t len, unsigned char *out, const size_t out_len);
#endif

View File

@ -1,3 +0,0 @@
CFLAGS = --pednatic -Wall -stdc99 -O2
LDFLAGS =

View File

@ -1,139 +0,0 @@
#include <string.h>
#include <stdio.h>
/*
* Base64 encoding/decoding (RFC1341)
* Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
static const unsigned char base64c_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const unsigned char base64c_dtable[256] = {
/*000*/0x80,/*001*/0x80,/*002*/0x80,/*003*/0x80,/*004*/0x80,/*005*/0x80,/*006*/0x80,/*007*/0x80,/*008*/0x80,/*009*/0x80,/*010*/0x80,/*011*/0x80,/*012*/0x80,/*013*/0x80,/*014*/0x80,/*015*/0x80,/*016*/0x80,/*017*/0x80,/*018*/0x80,/*019*/0x80,
/*020*/0x80,/*021*/0x80,/*022*/0x80,/*023*/0x80,/*024*/0x80,/*025*/0x80,/*026*/0x80,/*027*/0x80,/*028*/0x80,/*029*/0x80,/*030*/0x80,/*031*/0x80,/*032*/0x80,/*033*/0x80,/*034*/0x80,/*035*/0x80,/*036*/0x80,/*037*/0x80,/*038*/0x80,/*039*/0x80,
/*040*/0x80,/*041*/0x80,/*042*/0x80,/*043*/0x3e,/*044*/0x80,/*045*/0x80,/*046*/0x80,/*047*/0x3f,/*048*/0x34,/*049*/0x35,/*050*/0x36,/*051*/0x37,/*052*/0x38,/*053*/0x39,/*054*/0x3a,/*055*/0x3b,/*056*/0x3c,/*057*/0x3d,/*058*/0x80,/*059*/0x80,
/*060*/0x80,/*061*/0x00,/*062*/0x80,/*063*/0x80,/*064*/0x80,/*065*/0x00,/*066*/0x01,/*067*/0x02,/*068*/0x03,/*069*/0x04,/*070*/0x05,/*071*/0x06,/*072*/0x07,/*073*/0x08,/*074*/0x09,/*075*/0x0a,/*076*/0x0b,/*077*/0x0c,/*078*/0x0d,/*079*/0x0e,
/*080*/0x0f,/*081*/0x10,/*082*/0x11,/*083*/0x12,/*084*/0x13,/*085*/0x14,/*086*/0x15,/*087*/0x16,/*088*/0x17,/*089*/0x18,/*090*/0x19,/*091*/0x80,/*092*/0x80,/*093*/0x80,/*094*/0x80,/*095*/0x80,/*096*/0x80,/*097*/0x1a,/*098*/0x1b,/*099*/0x1c,
/*100*/0x1d,/*101*/0x1e,/*102*/0x1f,/*103*/0x20,/*104*/0x21,/*105*/0x22,/*106*/0x23,/*107*/0x24,/*108*/0x25,/*109*/0x26,/*110*/0x27,/*111*/0x28,/*112*/0x29,/*113*/0x2a,/*114*/0x2b,/*115*/0x2c,/*116*/0x2d,/*117*/0x2e,/*118*/0x2f,/*119*/0x30,
/*120*/0x31,/*121*/0x32,/*122*/0x33,/*123*/0x80,/*124*/0x80,/*125*/0x80,/*126*/0x80,/*127*/0x80,/*128*/0x80,/*129*/0x80,/*130*/0x80,/*131*/0x80,/*132*/0x80,/*133*/0x80,/*134*/0x80,/*135*/0x80,/*136*/0x80,/*137*/0x80,/*138*/0x80,/*139*/0x80,
/*140*/0x80,/*141*/0x80,/*142*/0x80,/*143*/0x80,/*144*/0x80,/*145*/0x80,/*146*/0x80,/*147*/0x80,/*148*/0x80,/*149*/0x80,/*150*/0x80,/*151*/0x80,/*152*/0x80,/*153*/0x80,/*154*/0x80,/*155*/0x80,/*156*/0x80,/*157*/0x80,/*158*/0x80,/*159*/0x80,
/*160*/0x80,/*161*/0x80,/*162*/0x80,/*163*/0x80,/*164*/0x80,/*165*/0x80,/*166*/0x80,/*167*/0x80,/*168*/0x80,/*169*/0x80,/*170*/0x80,/*171*/0x80,/*172*/0x80,/*173*/0x80,/*174*/0x80,/*175*/0x80,/*176*/0x80,/*177*/0x80,/*178*/0x80,/*179*/0x80,
/*180*/0x80,/*181*/0x80,/*182*/0x80,/*183*/0x80,/*184*/0x80,/*185*/0x80,/*186*/0x80,/*187*/0x80,/*188*/0x80,/*189*/0x80,/*190*/0x80,/*191*/0x80,/*192*/0x80,/*193*/0x80,/*194*/0x80,/*195*/0x80,/*196*/0x80,/*197*/0x80,/*198*/0x80,/*199*/0x80,
/*200*/0x80,/*201*/0x80,/*202*/0x80,/*203*/0x80,/*204*/0x80,/*205*/0x80,/*206*/0x80,/*207*/0x80,/*208*/0x80,/*209*/0x80,/*210*/0x80,/*211*/0x80,/*212*/0x80,/*213*/0x80,/*214*/0x80,/*215*/0x80,/*216*/0x80,/*217*/0x80,/*218*/0x80,/*219*/0x80,
/*220*/0x80,/*221*/0x80,/*222*/0x80,/*223*/0x80,/*224*/0x80,/*225*/0x80,/*226*/0x80,/*227*/0x80,/*228*/0x80,/*229*/0x80,/*230*/0x80,/*231*/0x80,/*232*/0x80,/*233*/0x80,/*234*/0x80,/*235*/0x80,/*236*/0x80,/*237*/0x80,/*238*/0x80,/*239*/0x80,
/*240*/0x80,/*241*/0x80,/*242*/0x80,/*243*/0x80,/*244*/0x80,/*245*/0x80,/*246*/0x80,/*247*/0x80,/*248*/0x80,/*249*/0x80,/*250*/0x80,/*251*/0x80,/*252*/0x80,/*253*/0x80,/*254*/0x80,/*255*/0x00,
};
size_t base64c_encoding_length(size_t len) {
size_t olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */
olen++; /* nul termination */
if (olen < len)
return 0; /* integer overflow */
return olen;
}
size_t base64c_encode(const unsigned char *src, size_t len,
unsigned char* out, const size_t out_len)
{
unsigned char *pos;
const unsigned char *end, *in;
const unsigned char *out_end = out + out_len;
end = src + len;
in = src;
pos = out;
if (out_len < base64c_encoding_length(len)) { return -1; }
while (end - in >= 3 ) {
*pos++ = base64c_table[in[0] >> 2];
*pos++ = base64c_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
*pos++ = base64c_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
*pos++ = base64c_table[in[2] & 0x3f];
in += 3;
}
if (end - in) {
*pos++ = base64c_table[in[0] >> 2];
if (end - in == 1) {
*pos++ = base64c_table[(in[0] & 0x03) << 4];
*pos++ = '=';
} else {
*pos++ = base64c_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
*pos++ = base64c_table[(in[1] & 0x0f) << 2];
}
*pos++ = '=';
}
*pos = '\0';
return out_len - (out_end-pos);
}
size_t base64c_decoding_length(size_t inlen) {
return inlen / 4 * 3;
}
size_t base64c_decode(const unsigned char *src, size_t len, unsigned char *out, const size_t out_len)
{
if (out == NULL) { return 0; }
if (out_len <= 0) { return 0; }
unsigned char *pos, block[4], tmp;
size_t i, count;
int pad = 0;
if (len == 0 ){
*out = '\0';
return 1;
}
if (len % 4) {
return -1;
}
pos = out;
count = 0;
for (i = 0; i < len; i++) {
if (src[i] == '=') { pad++; }
tmp = base64c_dtable[src[i]];
if (tmp == 0x80) { return -1; }
block[count] = tmp;
count++;
if (count == 4) {
switch (pad) {
case 0:
if ((pos - out) + 3 > out_len) {
return -1;
}
*pos++ = (block[0] << 2) | (block[1] >> 4);
*pos++ = (block[1] << 4) | (block[2] >> 2);
*pos++ = (block[2] << 6) | block[3];
break;
case 1:
if ((pos - out) + 2 > out_len || i + 1 > len) {
return -1;
}
*pos++ = (block[0] << 2) | (block[1] >> 4);
*pos++ = (block[1] << 4) | (block[2] >> 2);
break;
case 2:
if ((pos - out) + 1 > out_len || i + 1 > len) {
return -1;
}
*pos++ = (block[0] << 2) | (block[1] >> 4);
break;
default:
break;
}
count = 0;
}
}
return pos - out;
}

View File

@ -1,16 +0,0 @@
CFLAGS = --pedantic -Wall -std=c99 -g -ggdb
LDFLAGS =
bin_PROGRAMS = test001 test002 test003 test004 \
test005 test006 test007 test008 \
gen
test001_SOURCES = test001.c ../src/base64c.c
test002_SOURCES = test002.c ../src/base64c.c
test003_SOURCES = test003.c ../src/base64c.c
test004_SOURCES = test004.c ../src/base64c.c
test005_SOURCES = test005.c ../src/base64c.c
test006_SOURCES = test006.c ../src/base64c.c
test007_SOURCES = test007.c ../src/base64c.c
test008_SOURCES = test008.c ../src/base64c.c
gen_SOURCES = gen.c

View File

@ -1,22 +0,0 @@
#include <stdio.h>
#include <string.h>
static const unsigned char base64_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int main() {
unsigned char out[256];
memset(out, 0x80, 255);
for (int i = 0; i < 64; i++) {
out[base64_table[i]] = i;
}
out['='] = 0;
printf("static const unsigned char base64c_dtable[256] = {");
for (int i = 0; i < 256; i++) {
if (i% 20==0) { printf("\n"); }
printf("/*%03d*/0x%02x,", i, out[i]);
}
printf("\n};");
}

View File

@ -1,36 +0,0 @@
#include "../include/base64c.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
unsigned char in[12] = "Hello World";
size_t in_len = 11;
unsigned char enc[32];
size_t enc_len = 32;
unsigned char out[12];
size_t out_len = 12;
printf("Encoding %lu - %s\n", in_len, in);
size_t enc_result = base64c_encode(in, in_len, enc, enc_len);
printf("Encoded %lu - %s\n", enc_result, enc);
size_t dec_result = base64c_decode(enc, enc_result, out, out_len);
if ((long)dec_result < 0) {
printf("Decode failed with code %ld\n", (long)dec_result);
return 1;
}
printf("Decoded %lu - %s\n", dec_result, out);
if (dec_result != in_len) {
printf("in length %ld not equal to out length %ld", in_len, dec_result);
return 3;
}
if (strncmp((char*)in, (char*)out, in_len)) {
printf("roundtrip encoding failed\n");
return 2;
}
}

View File

@ -1,37 +0,0 @@
#include "../include/base64c.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
unsigned char in[11] = "Hello Worl";
size_t in_len = 10;
unsigned char enc[32];
size_t enc_len = 32;
unsigned char out[12];
size_t out_len = 12;
printf("Encoding %lu - %s\n", in_len, in);
size_t enc_result = base64c_encode(in, in_len, enc, enc_len);
printf("Encoded %lu - %s\n", enc_result, enc);
size_t dec_result = base64c_decode(enc, enc_result, out, out_len);
if ((long)dec_result < 0) {
printf("Decode failed with code %ld\n", (long)dec_result);
return 1;
}
printf("Decoded %lu - %s\n", dec_result, out);
if (dec_result != in_len) {
printf("in length %ld not equal to out length %ld", in_len, dec_result);
return 3;
}
if (strncmp((char*)in, (char*)out, in_len)) {
printf("roundtrip encoding failed\n");
return 2;
}
}

View File

@ -1,37 +0,0 @@
#include "../include/base64c.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
unsigned char in[10] = "Hello Wor";
size_t in_len = 9;
unsigned char enc[32];
size_t enc_len = 32;
unsigned char out[12];
size_t out_len = 12;
printf("Encoding %lu - %s\n", in_len, in);
size_t enc_result = base64c_encode(in, in_len, enc, enc_len);
printf("Encoded %lu - %s\n", enc_result, enc);
size_t dec_result = base64c_decode(enc, enc_result, out, out_len);
if ((long)dec_result < 0) {
printf("Decode failed with code %ld\n", (long)dec_result);
return 1;
}
printf("Decoded %lu - %s\n", dec_result, out);
if (dec_result != in_len) {
printf("in length %ld not equal to out length %ld", in_len, dec_result);
return 3;
}
if (strncmp((char*)in, (char*)out, in_len)) {
printf("roundtrip encoding failed\n");
return 2;
}
}

View File

@ -1,37 +0,0 @@
#include "../include/base64c.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
unsigned char in[10] = "Hello Wo";
size_t in_len = 8;
unsigned char enc[32];
size_t enc_len = 32;
unsigned char out[12];
size_t out_len = 12;
printf("Encoding %lu - %s\n", in_len, in);
size_t enc_result = base64c_encode(in, in_len, enc, enc_len);
printf("Encoded %lu - %s\n", enc_result, enc);
size_t dec_result = base64c_decode(enc, enc_result, out, out_len);
if ((long)dec_result < 0) {
printf("Decode failed with code %ld\n", (long)dec_result);
return 1;
}
printf("Decoded %lu - %s\n", dec_result, out);
if (dec_result != in_len) {
printf("in length %ld not equal to out length %ld", in_len, dec_result);
return 3;
}
if (strncmp((char*)in, (char*)out, in_len)) {
printf("roundtrip encoding failed\n");
return 2;
}
}

View File

@ -1,36 +0,0 @@
#include "../include/base64c.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
unsigned char in[13] = "Hello Worlds";
size_t in_len = 12;
unsigned char enc[32];
size_t enc_len = 32;
unsigned char out[13];
size_t out_len = 13;
printf("Encoding %lu - %s\n", in_len, in);
size_t enc_result = base64c_encode(in, in_len, enc, enc_len);
printf("Encoded %lu - %s\n", enc_result, enc);
size_t dec_result = base64c_decode(enc, enc_result, out, out_len);
if ((long)dec_result < 0) {
printf("Decode failed with code %ld\n", (long)dec_result);
return 1;
}
printf("Decoded %lu - %s\n", dec_result, out);
if (dec_result != in_len) {
printf("in length %ld not equal to out length %ld", in_len, dec_result);
return 3;
}
if (strncmp((char*)in, (char*)out, in_len)) {
printf("roundtrip encoding failed\n");
return 2;
}
}

View File

@ -1,36 +0,0 @@
#include "../include/base64c.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
unsigned char in[14] = "Hello Worldsy";
size_t in_len = 13;
unsigned char enc[32];
size_t enc_len = 32;
unsigned char out[15];
size_t out_len = 15;
printf("Encoding %lu - %s\n", in_len, in);
size_t enc_result = base64c_encode(in, in_len, enc, enc_len);
printf("Encoded %lu - %s\n", enc_result, enc);
size_t dec_result = base64c_decode(enc, enc_result, out, out_len);
if ((long)dec_result < 0) {
printf("Decode failed with code %ld\n", (long)dec_result);
return 1;
}
printf("Decoded %lu - %s\n", dec_result, out);
if (dec_result != in_len) {
printf("in length %ld not equal to out length %ld", in_len, dec_result);
return 3;
}
if (strncmp((char*)in, (char*)out, in_len)) {
printf("roundtrip encoding failed\n");
return 2;
}
}

View File

@ -1,36 +0,0 @@
#include "../include/base64c.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
unsigned char in[15] = "Hello World of";
size_t in_len = 14;
unsigned char enc[32];
size_t enc_len = 32;
unsigned char out[15];
size_t out_len = 15;
printf("Encoding %lu - %s\n", in_len, in);
size_t enc_result = base64c_encode(in, in_len, enc, enc_len);
printf("Encoded %lu - %s\n", enc_result, enc);
size_t dec_result = base64c_decode(enc, enc_result, out, out_len);
if ((long)dec_result < 0) {
printf("Decode failed with code %ld\n", (long)dec_result);
return 1;
}
printf("Decoded %lu - %s\n", dec_result, out);
if (dec_result != in_len) {
printf("in length %ld not equal to out length %ld", in_len, dec_result);
return 3;
}
if (strncmp((char*)in, (char*)out, in_len)) {
printf("roundtrip encoding failed\n");
return 2;
}
}

View File

@ -1,37 +0,0 @@
#include "../include/base64c.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
unsigned char in[10] = "H";
size_t in_len = 1;
unsigned char enc[32];
size_t enc_len = 32;
unsigned char out[12];
size_t out_len = 12;
printf("Encoding %lu - %s\n", in_len, in);
size_t enc_result = base64c_encode(in, in_len, enc, enc_len);
printf("Encoded %lu - %s\n", enc_result, enc);
size_t dec_result = base64c_decode(enc, enc_result, out, out_len);
if ((long)dec_result < 0) {
printf("Decode failed with code %ld\n", (long)dec_result);
return 1;
}
printf("Decoded %lu - %s\n", dec_result, out);
if (dec_result != in_len) {
printf("in length %ld not equal to out length %ld", in_len, dec_result);
return 3;
}
if (strncmp((char*)in, (char*)out, in_len)) {
printf("roundtrip encoding failed\n");
return 2;
}
}

View File

@ -12,13 +12,14 @@ jobs:
tcc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Update packages list
run: sudo apt-get update
- name: Install dependencies
run: sudo apt-get install -y build-essential libtool autoconf automake tcc
run: |
sudo apt-get install -y build-essential libtool autoconf automake tcc
- name: Autogen
run: ./autogen.sh -s
@ -31,10 +32,40 @@ jobs:
make uninstall
make distclean
zig:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Update packages list
run: sudo apt-get update
- name: Install dependencies
run: |
curl -sL -o - https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz | tar xJ -f - -C /opt/
sudo mv /opt/zig-* /opt/zig
- name: Autogen
run: ./autogen.sh -s
- name: Compilation with zig
run: |
export PATH=/opt/zig:$PATH
zig build
zig build -Dtarget=x86_64-linux
zig build -Dtarget=aarch64-linux
zig build -Dtarget=x86_64-windows
zig build -Dtarget=aarch64-windows
zig build -Dtarget=x86_64-macos
zig build -Dtarget=aarch64-macos
zig build -Dtarget=wasm32-wasi
zig build -Drelease-fast
rm -fr zig-cache zig-out
regular:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Update packages list
run: sudo apt-get update
@ -63,7 +94,7 @@ jobs:
check-globals:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Update packages list
run: sudo apt-get update
@ -81,7 +112,7 @@ jobs:
other-comp:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Update packages list
run: sudo apt-get update
@ -107,7 +138,7 @@ jobs:
other-arch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Update packages list
run: sudo apt-get update
@ -123,3 +154,28 @@ jobs:
env CPPFLAGS="-DDEV_MODE=1" ./configure --disable-dependency-tracking --host=powerpc-linux-gnu
make -j $(nproc)
make clean > /dev/null
android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Update packages list
run: sudo apt-get update
- name: Install base dependencies
run: sudo apt-get install -y libtool autoconf automake unzip
- name: Autogen
run: ./autogen.sh -s
- name: Install Android NDK
run: |
mkdir /tmp/android && cd /tmp/android
curl -o ndk.zip -L https://dl.google.com/android/repository/android-ndk-r25b-linux.zip
unzip ndk.zip && rm -f *.zip && mv android-ndk* ndk
- name: Android compilation
run: |
env ANDROID_NDK_HOME=/tmp/android/ndk ./dist-build/android-x86.sh
env ANDROID_NDK_HOME=/tmp/android/ndk ./dist-build/android-armv8-a.sh

View File

@ -12,12 +12,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: cpp
@ -27,4 +27,4 @@ jobs:
make -j $(nproc) check
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

Some files were not shown because too many files have changed in this diff Show More