$ git clone https://socialnetwork.ion.nu/socialnetwork-web.git
commit f5241e0d27fbfc87ed6bcb89bbcfc89a39c25e18
Author: Alicia <...>
Date:   Mon May 29 18:16:26 2017 +0200

    Initial commit.

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8b8730a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,33 @@
+CFLAGS=$(shell pkg-config --cflags libsocial)
+LIBS=$(shell pkg-config --libs libsocial)
+EMSCRIPTVERSION=1.37.12
+GMPVERSION=6.1.2
+NETTLEVERSION=3.3
+GNUTLSVERSION=3.5.12
+SOCIALNETWORKREVISION=9e11beb56de1c867b1339fef577c97c2ac619d59
+
+all: webpeer socialnetwork/libsocial.js
+
+webpeer: daemon.o webpeer.o libwebsocket/libwebsocket.so
+ $(CC) -Wl,-R"$(shell pwd)/libwebsocket" $^ libwebsocket/libwebsocket.so $(LIBS) -o $@
+
+libwebsocket/libwebsocket.so:
+ make -C libwebsocket
+
+toolchain/usr/bin/emcc: emscripten-$(EMSCRIPTVERSION).tar.gz emscripten-fastcomp-$(EMSCRIPTVERSION).tar.gz emscripten-fastcomp-clang-$(EMSCRIPTVERSION).tar.gz
+ ./buildtoolchain.sh '$(EMSCRIPTVERSION)'
+
+socialnetwork/libsocial.js: toolchain/usr/bin/emcc
+ ./buildlibs.sh '$(GMPVERSION)' '$(NETTLEVERSION)' '$(GNUTLSVERSION)'
+ ./squeezeandcomment.sh '$@' 'This code was built from gmp $(GMPVERSION), nettle $(NETTLEVERSION), gnutls $(GNUTLSVERSION) and socialnetwork git revision $(SOCIALNETWORKREVISION) using emscripten (fastcomp) $(EMSCRIPTVERSION)'
+
+# Download all external sources we'll need
+download:
+ wget -c 'https://github.com/kripken/emscripten-fastcomp/archive/$(EMSCRIPTVERSION)/emscripten-fastcomp-$(EMSCRIPTVERSION).tar.gz'
+ wget -c 'https://github.com/kripken/emscripten-fastcomp-clang/archive/$(EMSCRIPTVERSION)/emscripten-fastcomp-clang-$(EMSCRIPTVERSION).tar.gz'
+ wget -c 'https://github.com/kripken/emscripten/archive/$(EMSCRIPTVERSION)/emscripten-$(EMSCRIPTVERSION).tar.gz'
+ wget -c 'https://ftp.gnu.org/gnu/gmp/gmp-$(GMPVERSION).tar.xz'
+ wget -c 'https://ftp.gnu.org/gnu/nettle/nettle-$(NETTLEVERSION).tar.gz'
+ wget -c 'ftp://ftp.gnutls.org/gcrypt/gnutls/v$(shell echo '$(GNUTLSVERSION)' | cut -d '.' -f 1-2)/gnutls-$(GNUTLSVERSION).tar.xz'
+ git clone https://ion.nu/git/socialnetwork || (cd socialnetwork && git pull origin master)
+ cd socialnetwork && git checkout $(SOCIALNETWORKREVISION)
diff --git a/adaptforjs.patch b/adaptforjs.patch
new file mode 100644
index 0000000..f4b14c7
--- /dev/null
+++ b/adaptforjs.patch
@@ -0,0 +1,272 @@
+diff --git a/Makefile b/Makefile
+index 62667e9..0450b1e 100644
+--- a/Makefile
++++ b/Makefile
+@@ -1,12 +1,11 @@
+-LIBS=$(shell pkg-config --libs gnutls)
+-CFLAGS=-g3 -Wall $(shell pkg-config --cflags gnutls)
++LIBS=$(shell pkg-config --libs gnutls nettle hogweed) -lgmp -O3
++CFLAGS=-O3 -Wall $(shell pkg-config --cflags gnutls)
+ PREFIX=/usr

+-all: socialtest libsocial.so libsocial.pc
++all: libsocial.js

+-libsocial.so: CFLAGS+=-fPIC
+-libsocial.so: social.o peer.o update.o udpstream.o
+- $(CC) -shared $^ $(LIBS) -o $@
++libsocial.js: social.o peer.o update.o udpstream.o
++ $(CC) -shared $^ $(LIBS) -s EXPORTED_FUNCTIONS="['_social_init','_peer_handlesocket','_peer_new_unique']" -s RESERVED_FUNCTION_POINTERS=1 -o $@

+ libsocial.pc:
+  echo 'prefix=$(PREFIX)' > libsocial.pc
+diff --git a/peer.c b/peer.c
+index 7d02247..e918944 100644
+--- a/peer.c
++++ b/peer.c
+@@ -211,19 +211,14 @@ void peer_init(const char* keypath)
+   if(!privkey)
+   {
+     gnutls_x509_privkey_init(&privkey);
+-    struct stat st;
++//    struct stat st;
+     char loadfailed=1;
+-    if(!stat(keypath, &st))
+     {
+       gnutls_datum_t keydata;
+-      keydata.size=st.st_size;
+-      keydata.data=malloc(st.st_size);
+-      int f=open(keypath, O_RDONLY);
+-      read(f, keydata.data, st.st_size);
+-      close(f);
++      keydata.size=strlen(keypath);
++      keydata.data=keypath;
+       // TODO: Allow encrypted keys, using _import2()
+       if(!gnutls_x509_privkey_import2(privkey, &keydata, GNUTLS_X509_FMT_PEM, 0, GNUTLS_PKCS_PLAIN)){loadfailed=0;}
+-      free(keydata.data);
+     }
+     if(loadfailed)
+     {
+@@ -232,12 +227,13 @@ void peer_init(const char* keypath)
+       gnutls_x509_privkey_generate(privkey, GNUTLS_PK_RSA, 3072, 0);
+ // printf("Done\n");
+       // TODO: Allow exporting encrypted key, using _export2_pkcs8() I think
+-      gnutls_datum_t keydata;
+-      gnutls_x509_privkey_export2(privkey, GNUTLS_X509_FMT_PEM, &keydata);
+-      int f=open(keypath, O_WRONLY|O_TRUNC|O_CREAT, 0600);
+-      write(f, keydata.data, keydata.size);
+-      close(f);
+-      gnutls_free(keydata.data);
++// TODO: Return this to the user, somewhere (or to the server, if password-protecting it is secure enough for that and the user registers a username to tie it to)
++//      gnutls_datum_t keydata;
++//      gnutls_x509_privkey_export2(privkey, GNUTLS_X509_FMT_PEM, &keydata);
++//      int f=open(keypath, O_WRONLY|O_TRUNC|O_CREAT, 0600);
++//      write(f, keydata.data, keydata.size);
++//      close(f);
++//      gnutls_free(keydata.data);
+     }
+     size_t size=ID_SIZE;
+     gnutls_x509_privkey_get_key_id(privkey, GNUTLS_KEYID_USE_SHA256, peer_id, &size);
+@@ -390,7 +386,7 @@ void peer_bootstrap(int sock, const char* peerlist)
+ #define readordie(x,y,z) {ssize_t r=gnutls_record_recv(x->tls,y,z); if(z && r<1){peer_disconnect(x, 0); continue;}}
+ void peer_handlesocket(int sock) // Incoming data
+ {
+-  udpstream_readsocket(sock); // If it locks up here we're probably missing a bootstrap node
++//  udpstream_readsocket(sock); // If it locks up here we're probably missing a bootstrap node
+   struct peer* peer;
+   while((peer=findpending()))
+   {
+diff --git a/social.c b/social.c
+index 6c05f24..171e7a8 100644
+--- a/social.c
++++ b/social.c
+@@ -66,6 +66,7 @@ static void user_save(struct user* user)

+ static void user_load(struct user* user)
+ {
++/*
+   // Load user data (only pubkey atm), but spare pubkey if it's already set
+   if(!user->pubkey)
+   {
+@@ -96,6 +97,7 @@ static void user_load(struct user* user)
+     read(f, buf, size);
+     social_update_parse(user, buf, size);
+   }
++*/
+ }

+ static struct user* user_new(const unsigned char id[ID_SIZE])
+diff --git a/udpstream.c b/udpstream.c
+index ba57da5..3b232f9 100644
+--- a/udpstream.c
++++ b/udpstream.c
+@@ -61,6 +61,7 @@ struct udpstream
+   unsigned int buflen;
+   unsigned char state;
+   time_t timestamp;
++  uint16_t wsseq;
+ // TODO: add void pointer to keep relevant application data? plus a function to free it if the connection is closed or abandoned as stale
+ };

+@@ -83,6 +84,7 @@ static struct udpstream* stream_new(int sock, struct sockaddr* addr, socklen_t a
+   stream->buflen=0;
+   stream->state=0; // Start new streams as invalid, need to init
+   stream->timestamp=time(0);
++  stream->wsseq=0;
+   ++streamcount;
+   streams=realloc(streams, sizeof(void*)*streamcount);
+   streams[streamcount-1]=stream;
+@@ -102,6 +104,7 @@ struct udpstream* udpstream_find(struct sockaddr* addr, socklen_t addrlen)
+   return 0;
+ }

++/*
+ static ssize_t stream_send(struct udpstream* stream, uint8_t type, uint16_t seq, uint32_t size, const void* buf)
+ {
+ // TODO: Include a checksum in the header?
+@@ -137,6 +140,7 @@ static void udpstream_requestresend(struct udpstream* stream, uint16_t seq)
+   if(!missedcount){return;}
+   stream_send(stream, TYPE_RESEND, 0, missedcount*sizeof(uint16_t), missed);
+ }
++*/

+ static void stream_free(struct udpstream* stream)
+ {
+@@ -167,10 +171,12 @@ struct udpstream* udpstream_new(int sock, struct sockaddr* addr, socklen_t addrl
+ {
+   struct udpstream* stream=stream_new(sock, addr, addrlen);
+   stream->state=STATE_INIT; // If we're creating the stream we're the ones initializing it
+-  stream_send(stream, TYPE_INIT, 0, 0, 0);
++// TODO: Notify webpeer so it'll initiate the connection? I guess the TLS handshake takes care of that
++//  stream_send(stream, TYPE_INIT, 0, 0, 0);
+   return stream;
+ }

++/*
+ void udpstream_readsocket(int sock)
+ {
+   time_t now=time(0);
+@@ -288,14 +294,16 @@ fprintf(stderr, "TODO: resend packets\n");
+     }
+   }
+ }
++*/

+ struct udpstream* udpstream_poll(void)
+ {
+-  time_t now=time(0);
++//  time_t now=time(0);
+   unsigned int i;
+   for(i=0; i<streamcount; ++i)
+   {
+     // Check for state changes
++// TODO: Webpeer needs to send status changes like this over the websocket and libsocialjs.js needs to pass it along to libsocial
+     if(streams[i]->state&STATE_CLOSED){return streams[i];}
+     // Check for the next packet in the order
+     unsigned int i2;
+@@ -303,6 +311,7 @@ struct udpstream* udpstream_poll(void)
+     {
+       if(streams[i]->recvpackets[i2].seq==streams[i]->inseq){return streams[i];}
+     }
++/*
+     // Send ping if it's been 20 seconds without any data, unless we already sent one
+     if(streams[i]->timestamp+20<now && !(streams[i]->state&STATE_PING))
+     {
+@@ -320,6 +329,7 @@ struct udpstream* udpstream_poll(void)
+         return streams[i];
+       }
+     }
++*/
+   }
+   return 0;
+ }
+@@ -355,8 +365,10 @@ ssize_t udpstream_read(struct udpstream* stream, void* buf, size_t size)
+   return -1;
+ }

++void(*websockproxy_write)(struct sockaddr*, socklen_t, const void*, size_t);
+ ssize_t udpstream_write(struct udpstream* stream, const void* buf, size_t size)
+ {
++/*
+   if(stream->state&(STATE_CLOSED|STATE_CLOSING)){return 0;} // EOF, TODO: -1 and EBADFD for STATE_CLOSING?
+ // TODO: abort and return negative if sentpacketcount is too high? EWOULDBLOCK?
+   ++stream->sentpacketcount;
+@@ -367,6 +379,8 @@ ssize_t udpstream_write(struct udpstream* stream, const void* buf, size_t size)
+   memcpy(stream->sentpackets[stream->sentpacketcount-1].buf, buf, size);
+   stream_send(stream, TYPE_PAYLOAD, stream->outseq, size, buf);
+   ++stream->outseq;
++*/
++  websockproxy_write(&stream->addr, stream->addrlen, buf, size);
+   return size;
+ }

+@@ -380,11 +394,31 @@ int udpstream_getsocket(struct udpstream* stream){return stream->sock;}

+ void udpstream_close(struct udpstream* stream)
+ {
+-  if(stream->state&STATE_CLOSED) // Closed by peer, just free it
+-  {
++//  if(stream->state&STATE_CLOSED) // Closed by peer, just free it
++//  {
+     stream_free(stream);
++/*
+   }else{
+     stream->state|=STATE_CLOSING;
+     stream_send(stream, TYPE_CLOSE, 0, 0, 0);
+   }
++*/
++}
++
++void websockproxy_setwrite(void(*writefunc)(struct sockaddr*, socklen_t, const void*, size_t))
++{
++  websockproxy_write=writefunc;
++}
++void websockproxy_read(struct sockaddr* addr, socklen_t addrlen, const void* buf, size_t payloadsize)
++{
++  // Find the stream
++  struct udpstream* stream=udpstream_find(addr, addrlen);
++  if(!stream){stream=stream_new(-1, addr, addrlen);}
++  // Add to list of parsed packets
++  ++stream->recvpacketcount;
++  stream->recvpackets=realloc(stream->recvpackets, sizeof(struct packet)*stream->recvpacketcount);
++  stream->recvpackets[stream->recvpacketcount-1].seq=(stream->wsseq++);
++  stream->recvpackets[stream->recvpacketcount-1].buf=malloc(payloadsize);
++  stream->recvpackets[stream->recvpacketcount-1].buflen=payloadsize;
++  memcpy(stream->recvpackets[stream->recvpacketcount-1].buf, buf, payloadsize);
+ }
+diff --git a/update.c b/update.c
+index aa69d92..8ad78dc 100644
+--- a/update.c
++++ b/update.c
+@@ -26,6 +26,7 @@
+ #include "social.h"
+ #include "update.h"

++/*
+ static void mkdirp(char* path)
+ {
+   char* next=path;
+@@ -38,6 +39,7 @@ static void mkdirp(char* path)
+     slash[0]='/';
+   }
+ }
++*/

+ void social_update_write(struct buffer* buf, struct update* update)
+ {
+@@ -109,6 +111,7 @@ void social_update_sign(struct update* update)

+ void social_update_save(struct user* user, struct update* update)
+ {
++/*
+   char path[strlen(social_prefix)+strlen("/updates/0")+ID_SIZE*2];
+   sprintf(path, "%s/updates/"PEERFMT, social_prefix, PEERARG(user->id));
+   mkdirp(path);
+@@ -124,6 +127,7 @@ void social_update_save(struct user* user, struct update* update)
+   buffer_deinit(buf);
+   close(f);
+ // TODO: Is it bad to close and reopen a file in rapid succession? if it is maybe we should implement some kind of cache for cases where we're saving many updates fast, like receiving someone else's updates for the first time
++*/
+ }

+ struct update* social_update_getfield(struct user* user, const char* name)
diff --git a/buildlibs.sh b/buildlibs.sh
new file mode 100755
index 0000000..7191795
--- /dev/null
+++ b/buildlibs.sh
@@ -0,0 +1,63 @@
+#!/bin/sh -e
+#   socialnetwork-web, peer-to-peer social network web client
+#   Copyright (C) 2017  alicia@ion.nu
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU Affero General Public License version 3
+#   as published by the Free Software Foundation.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU Affero General Public License for more details.
+#
+#   You should have received a copy of the GNU Affero General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+prefix="`pwd`/toolchain/usr"
+export PATH="${prefix}/bin:${PATH}"
+export PKG_CONFIG_PATH="${prefix}/lib/pkgconfig"
+export CFLAGS='-O3' # TODO: Make sure these didn't break anything
+export LDFLAGS='-O3'
+GMPVERSION="$1"
+NETTLEVERSION="$2"
+GNUTLSVERSION="$3"
+
+cd toolchain
+# gmp
+tar -xJf "../gmp-${GMPVERSION}.tar.xz"
+cd "gmp-${GMPVERSION}"
+mkdir -p build
+cd build
+emconfigure ../configure --prefix="$prefix" --disable-static --enable-shared --disable-assembly
+make
+make install
+cd ../..
+# nettle
+tar -xzf "../nettle-${NETTLEVERSION}.tar.gz"
+cd "nettle-${NETTLEVERSION}"
+# Remove emscripten-incompatible options
+sed -i -e 's/ -ggdb3//' configure
+# Work around broken macros
+sed -i -e 's/#if !/#ifndef /' camellia-internal.h camellia-absorb.c
+sed -i -e 's/#if /#ifdef /' camellia-crypt-internal.c
+# Work around emscripten limitations with fopen in configure
+sed -i -e 's/FILE *\* *f *= *fopen *( *\("[^"]*"\)/#include <unistd.h>\n#include <fcntl.h>\nint fd=open(\1, O_WRONLY|O_CREAT|O_TRUNC, 0644);\nFILE*f=fdopen(fd/' configure
+mkdir -p build
+cd build
+emconfigure ../configure --prefix="$prefix" --disable-static --enable-shared --disable-assembler --disable-documentation LDFLAGS="-L${prefix}/lib -I${prefix}/include" CFLAGS="-I${prefix}/include"
+sed -i -e 's/^#define SIZEOF[^ ]* $/&4/' config.h
+make GMP_NUMB_BITS=32
+make install
+cd ../..
+# gnutls
+tar -xJf "../gnutls-${GNUTLSVERSION}.tar.xz"
+cd "gnutls-${GNUTLSVERSION}"
+mkdir -p build
+cd build
+emconfigure ../configure --prefix="$prefix" --disable-static --enable-shared --with-included-libtasn1 --disable-hardware-acceleration --disable-dtls-srtp-support --disable-srp-authentication --disable-psk-authentication --disable-openpgp-authentication --disable-ocsp --disable-openssl-compatibility --disable-nls --disable-libdane --without-tpm --without-p11-kit --disable-tests --disable-cxx --disable-tools --with-included-unistring --with-zlib LDFLAGS="-L${prefix}/lib"
+make
+make install defexec_DATA=
+cd ../../../socialnetwork
+git apply ../adaptforjs.patch
+emmake make
diff --git a/buildtoolchain.sh b/buildtoolchain.sh
new file mode 100755
index 0000000..d0c0f4c
--- /dev/null
+++ b/buildtoolchain.sh
@@ -0,0 +1,44 @@
+#!/bin/sh -e
+#   socialnetwork-web, peer-to-peer social network web client
+#   Copyright (C) 2017  alicia@ion.nu
+#
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU Affero General Public License version 3
+#   as published by the Free Software Foundation.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU Affero General Public License for more details.
+#
+#   You should have received a copy of the GNU Affero General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+EMSCRIPTVERSION="$1"
+mkdir -p toolchain
+cd toolchain
+prefix="`pwd`/usr"
+export PATH="${prefix}/bin:${PATH}"
+# Set up pkg-config path
+mkdir -p usr/lib
+ln -sf "../../emscripten-${EMSCRIPTVERSION}/system/local/lib/pkgconfig" usr/lib
+# Build emscripten LLVM/Clang
+tar -xzf "../emscripten-fastcomp-${EMSCRIPTVERSION}.tar.gz"
+cd "emscripten-fastcomp-${EMSCRIPTVERSION}"
+tar -xzf "../../emscripten-fastcomp-clang-${EMSCRIPTVERSION}.tar.gz"
+mv "emscripten-fastcomp-clang-${EMSCRIPTVERSION}" tools/clang
+mkdir -p build
+cd build
+cmake -DCMAKE_INSTALL_PREFIX="${prefix}" -DLLVM_TARGETS_TO_BUILD="X86;JSBackend" ..
+make
+make install
+cd ../..
+# Install the emscripten tools/wrappers
+tar -xzf "../emscripten-${EMSCRIPTVERSION}.tar.gz"
+cd "emscripten-${EMSCRIPTVERSION}"
+for prog in `ls | grep '^em' | grep -v '\.'`; do
+  ln -sf "`pwd`/${prog}" "${prefix}/bin"
+done
+cd ..
+emcc -v
+mkdir -p "emscripten-${EMSCRIPTVERSION}/system/local/lib/pkgconfig"
diff --git a/daemon.c b/daemon.c
new file mode 100644
index 0000000..2a766a2
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,44 @@
+/*
+    webpeer, a websocket-udpstream proxy for socialnetwork-web
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    as published by the Free Software Foundation.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <unistd.h>
+#include <netinet/in.h>
+extern void webpeer(int sock);
+
+int main()
+{
+  int sock=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+  struct sockaddr_in addr;
+  addr.sin_family=AF_INET;
+  addr.sin_addr.s_addr=0;
+  addr.sin_port=htons(5000);
+  while(bind(sock, (struct sockaddr*)&addr, sizeof(addr))){perror("bind"); sleep(10);}
+  listen(sock, 10);
+  while(1)
+  {
+    struct sockaddr addr;
+    socklen_t socklen=sizeof(addr);
+    int client=accept(sock, &addr, &socklen);
+    if(!fork())
+    {
+      close(sock);
+      webpeer(client);
+      _exit(1);
+    }
+    close(client);
+  }
+}
diff --git a/libsocialjs.js b/libsocialjs.js
new file mode 100644
index 0000000..eb75271
--- /dev/null
+++ b/libsocialjs.js
@@ -0,0 +1,58 @@
+/*
+    socialnetwork-web, peer-to-peer social network web client
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    as published by the Free Software Foundation.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+var connection;
+function websockwrite(addr, addrlen, buf, size)
+{
+  addr=new Blob(Module.HEAP8.subarray(addr, addr+addrlen));
+  buf=new Blob(Module.HEAP8.subarray(buf, buf+size));
+  connection.send(addr);
+  connection.send(buf);
+}
+
+var websockproxy_read;
+var peer_new_unique;
+
+var websockproxy_to=false;
+var firstpacket=true;
+function handlenet(data)
+{
+  var f=new FileReader();
+  f.readAsArrayBuffer(data.data);
+  data=new Int8Array(f.result);
+  if(firstpacket) // Bootstrap
+  {
+    firstpacket=false;
+    // TODO: Read lengths and pass subarrays until we reach the end of the array. length=data[pos]*256+data[pos+1]
+    peer_new_unique(-1, data, data.length);
+    return;
+  }
+  if(websockproxy_to)
+  {
+    websockproxy_read(websockproxy_to, websockproxy_to.length, data, data.length);
+    _peer_handlesocket(-1); // Handle whatever we just received
+  }else{websockproxy_to=data;}
+}
+function init(privkey)
+{
+  websockproxy_read=Module.cwrap('websockproxy_read', null, ['array', 'number', 'array', 'number']);
+  peer_new_unique=Module.cwrap('peer_new_unique', 'array', ['number', 'array', 'number']);
+  _websockproxy_setwrite(Runtime.addFunction(websockwrite));
+  connection=new WebSocket('wss://127.0.0.1:5000/', 'socialwebsock-0.1');
+  connection.onmessage=handlenet;
+  connection.onclose=function(){alert('The connection to the server was lost.');};
+  Module.ccall('social_init', null, ['string', 'string'], [privkey, '']);
+}
diff --git a/squeezeandcomment.sh b/squeezeandcomment.sh
new file mode 100755
index 0000000..c8bd3df
--- /dev/null
+++ b/squeezeandcomment.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+file="$1"
+comment="$2"
+base="`basename "$file"`"
+sed -i -e '/^$/d;/^\/\//d' "$file" # Probably safe
+tr -d '\r\n' < "$file" > ".tmp.${base}" # Not so safe
+mv ".tmp.${base}" "$file"
+sed -i -e "1i// ${comment}" "$file" # 100% safe
diff --git a/webpeer.c b/webpeer.c
new file mode 100644
index 0000000..06bbe25
--- /dev/null
+++ b/webpeer.c
@@ -0,0 +1,94 @@
+/*
+    webpeer, a websocket-udpstream proxy for socialnetwork-web
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    as published by the Free Software Foundation.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <stdio.h>
+#include <string.h>
+#include <poll.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <libsocial/udpstream.h>
+#include "libwebsocket/websock.h"
+
+const char* verifyproto(const char* path, const char* host, char* protocol, const char* origin)
+{
+  if(strcmp(path, "/")){return 0;}
+  if(strcmp(protocol, "socialwebsock-0.1")){return 0;}
+  return "socialwebsock-0.1";
+}
+
+void webpeer(int sock)
+{
+// printf("Handling connection...\n");
+  websock_conn* conn=websock_new(sock, 1, "cert.pem", "priv.pem");
+  if(!websock_handshake_server(conn, verifyproto, 0)){printf("Handshake failed\n"); return;}
+  int udpsock=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+// TODO: First send a list of bootstrap peers, should this be binary too?
+//  websock_write(conn, "127.0.0.1:4000", 14, WEBSOCK_TEXT);
+  struct sockaddr_in bootstrapaddr={
+    .sin_family=AF_INET,
+    .sin_addr.s_addr=0x100007f,
+    .sin_port=htons(4000),
+    0
+  };
+  websock_write(conn, &bootstrapaddr, sizeof(bootstrapaddr), WEBSOCK_BINARY);
+  // Listen to client and udpstream socket and pass messages back and forth (client handles the TLS within the udpstream)
+  struct pollfd pfd[]={{.fd=sock, .events=POLLIN, .revents=0}, {.fd=udpsock, .events=POLLIN, .revents=0}};
+  while(1)
+  {
+    poll(pfd, 2, -1);
+    if(pfd[0].revents)
+    {
+printf("Handling websocket input...\n");
+      pfd[0].revents=0;
+      // Read the address
+      struct websock_head head;
+      if(!websock_readhead(conn, &head)){break;}
+      socklen_t addrlen=head.length;
+      char addr[addrlen];
+      while(!websock_readcontent(conn, addr, &head));
+      // Read the payload
+      if(!websock_readhead(conn, &head)){break;}
+      char buf[head.length];
+      while(!websock_readcontent(conn, buf, &head));
+      struct udpstream* stream=udpstream_find((struct sockaddr*)addr, addrlen);
+      if(!stream) // TODO: Check against blocklist (don't let people use this service to flood others over UDP), also add a brief block for the entire host until we know this connection is ok
+      {
+        stream=udpstream_new(udpsock, (struct sockaddr*)addr, addrlen);
+      }
+// printf("Writing %u bytes\n", head.length);
+      udpstream_write(stream, buf, head.length);
+    }
+    if(pfd[1].revents)
+    {
+printf("Handling udpsocket input...\n");
+      pfd[1].revents=0;
+      udpstream_readsocket(udpsock);
+      struct udpstream* stream;
+      while((stream=udpstream_poll()))
+      {
+        char buf[2048];
+        ssize_t len=udpstream_read(stream, buf, 2048);
+        if(len<1){udpstream_close(stream); continue;}
+        struct sockaddr addr;
+        socklen_t addrlen=sizeof(addr);
+        udpstream_getaddr(stream, &addr, &addrlen);
+        websock_write(conn, &addr, addrlen, WEBSOCK_BINARY);
+        websock_write(conn, buf, len, WEBSOCK_BINARY);
+      }
+    }
+  }
+// TODO: At end of session, update the list of bootstrap peers
+}
diff --git a/websocial.html b/websocial.html
new file mode 100644
index 0000000..e60a707
--- /dev/null
+++ b/websocial.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+  <title>SocialNetwork web client</title>
+  <script src="libsocial.js"></script>
+  <script src="libsocialjs.js"></script>
+</head>
+<body>
+  <textarea id="privkey" placeholder="Paste your private key here"></textarea><br />
+  <button onclick="init(document.getElementById('privkey').value);">Log in</button>
+</body>
+</html>