Compare commits

..

98 Commits

Author SHA1 Message Date
816c4f5f6b updated readme
I noticed luci was still running on the meshenger boxes after lazy install. This eats up a lot of resources and caused some trouble when trying to run other things on the box...
2015-09-25 22:42:22 +02:00
d974b08694 quick fix for too long user names 2015-05-22 17:56:48 +02:00
253990eaae added emoticon 🪨 2015-05-22 13:12:56 +02:00
1e63cc4f4d Merge branch 'master' of https://github.com/rscmbbng/meshenger 2015-05-22 13:09:21 +02:00
2ff471f4a7 added emoticon 🪨 2015-05-22 13:08:56 +02:00
jngrt
c51c092c87 emoticon remove 22px size css 2015-05-22 11:05:06 +02:00
1c8760c8c3 styled message-block margin for smaller screens 2015-05-21 21:47:53 +02:00
ae99ee803f added emoji + input sanitization 2015-05-21 17:17:31 +02:00
d5c5ccc4ba Merge branch 'master' of https://github.com/rscmbbng/meshenger 2015-05-21 16:02:51 +02:00
8bcfbb8070 added exception handling to announce() 2015-05-21 16:02:37 +02:00
19aeec6264 styled nodshops mobile 2015-05-21 15:03:36 +02:00
bc432d3dbc stylable nodes,hops info, minor esthetics 2015-05-21 14:58:00 +02:00
611734331f rounded images on mobile and stylable text messages 2015-05-21 14:18:17 +02:00
3658d3cf9e changes for alias 2015-05-21 14:05:45 +02:00
59f6370bb2 nodes that are missing for a while now get removed 2015-05-21 13:27:24 +02:00
20e3fcacf5 added proper alias support (via new json field) and split the js monolith into multiple files 2015-05-20 22:46:16 +02:00
61c33d2993 removed the possibility to send empty messages and photos 2015-05-20 21:18:13 +02:00
a8535433b5 resize photos 2015-05-20 18:28:52 +02:00
jngrt
1efcae7b48 emoticons, many bugfixes 2015-05-20 01:36:01 +02:00
jngrt
801b87590b fix photo messages outbox things 2015-05-19 23:41:50 +02:00
jngrt
7ec5f54422 photo submit fukkup 2015-05-19 23:14:22 +02:00
jngrt
c7c2170ee9 Merge branch 'master' of https://github.com/rscmbbng/meshenger 2015-05-19 23:13:44 +02:00
jngrt
ce1b342a5e photo submit fukkup 2015-05-19 23:13:41 +02:00
4e07207c5b added name field to photo ul page,correct id 2015-05-19 23:11:50 +02:00
b68bae4f42 added name field to photo ul page 2015-05-19 23:10:22 +02:00
b2a0b343b2 added more styling, canvas needs to be smaller 2015-05-19 22:40:50 +02:00
jngrt
79ffd9d369 photo page button stuff, js 2015-05-19 22:28:10 +02:00
2ead3f5aaa added more buttons, need more styling 2015-05-19 22:27:23 +02:00
jngrt
0128c61fea trigger file upload on state change 2015-05-19 21:56:01 +02:00
jngrt
1a590a5a41 Merge branch 'master' of https://github.com/rscmbbng/meshenger 2015-05-19 21:37:41 +02:00
481ceba7b4 buttons fixed, added back button 2015-05-19 21:37:05 +02:00
jngrt
0a38eee079 Merge branch 'master' of https://github.com/rscmbbng/meshenger 2015-05-19 21:36:29 +02:00
jngrt
b7fb1d4636 submit message return to overview 2015-05-19 21:36:23 +02:00
a3caf7e407 buttons fixed 2015-05-19 21:35:13 +02:00
jngrt
5de5ed9f1a init state 2015-05-19 21:07:49 +02:00
jngrt
a90c35451f split up index.html in 3 blocks 2015-05-19 20:58:43 +02:00
aba8149226 begin photo function 2015-05-19 20:24:39 +02:00
d6f081f8b0 built alias/nickname feature for nodes, in the process I broke the message color system.. can be fixed in main.js line 170 2015-05-19 14:26:48 +02:00
5e0fd9c935 reverted to previous fstab_extroot solution 2015-04-15 15:44:24 +02:00
3c71c68d70 expanded readme and cleaned scripts 2015-04-15 15:05:41 +02:00
8e0b6f8532 Update README.md 2015-04-15 14:41:38 +02:00
736d45d465 Update README.md 2015-04-15 14:41:10 +02:00
8f135c9cf1 Update README.md 2015-04-15 14:36:51 +02:00
c74673dbe3 added link to lazy instal howto 2015-04-15 14:29:42 +02:00
07503b5285 added information for how lazyinstall works 2015-04-15 13:50:14 +02:00
3d1652eaa8 udated the readme
started on how to use the lazy install
2015-04-15 13:41:03 +02:00
jngrt
0873ef597b added todo note 2014-11-07 16:50:34 +01:00
jngrt
90b5ee0273 solve log-config-file bug 2014-11-07 16:30:33 +01:00
jngrt
a37b519ddf Merge pull request #3 from rscmbbng/new-ui
New ui
2014-11-07 16:22:38 +01:00
jngrt
3b07132282 new UIyayayayay \o/\o/\o/ 2014-11-07 16:21:10 +01:00
jngrt
d19179cbf8 setup for new web ui 2014-11-07 01:40:19 +01:00
jngrt
df281b3fa9 Merge pull request #2 from rscmbbng/unthread-buildindex
Unthread buildindex
2014-11-06 22:02:15 +01:00
jngrt
842d656bd9 cleaned comments/logging 2014-11-06 21:54:57 +01:00
jngrt
e91905d63b build index successfully untangled 2014-11-06 21:34:56 +01:00
jngrt
0de4a6b878 error in clientserve main 2014-11-06 20:21:19 +01:00
jngrt
e336ba209e removed build_index thread, only called when update 2014-11-06 20:01:40 +01:00
jngrt
1dcfbee6b3 Merge pull request #1 from stuff2233/master
pull in logging from stuff2233
2014-11-06 16:07:55 +01:00
jngrt
70cbbcdcf0 Merge pull request #2 from rscmbbng/logging
Logging
2014-11-06 16:03:50 +01:00
jngrt
71bbea8d0c serve logging on /log for clients 2014-11-06 16:01:46 +01:00
jngrt
857b1c4d82 logging tweaks 2014-11-06 15:41:35 +01:00
jngrt
e4e8b7cf00 replaced stdout print with logging to file and stdout 2014-11-06 15:01:39 +01:00
dickreckard
943cbd3562 added line to prevent JSON errors to crash the whole thing 2014-11-06 11:59:54 +01:00
dickreckard
65720bf3b6 added autostart.wq 2014-11-05 20:11:33 +01:00
dickreckard
ed861b90da tempfix while hard links go to /root/meshenger 2014-11-05 18:00:07 +01:00
dickreckard
b55229eee8 dennis makes me hot 2014-11-05 17:42:51 +01:00
jngrt
17a82a94fc fixing date display in browser 2014-11-03 16:19:48 +01:00
jngrt
9af15c7399 Merge branch 'master' of https://github.com/jngrt/meshenger 2014-11-03 16:11:52 +01:00
38d6147381 Update README.md 2014-08-22 12:40:08 +02:00
810edef175 tips 2014-08-16 22:55:10 +02:00
6517baed2c added more instructions 2014-08-16 22:29:21 +02:00
b7459cfee3 added instructions for remote ssh access 2014-08-16 22:19:40 +02:00
3c90a86c5f markdown correction 2014-07-30 12:27:36 +02:00
a0369fff16 Added instructions
For running a python script as service/on boot
2014-07-30 12:25:22 +02:00
fb7708cf94 Update README.md
pff default is 1...
2014-07-27 22:41:09 +02:00
61a0f33939 Update README.md
small swap fstab error
2014-07-27 22:31:13 +02:00
5ce85ddac9 added sort interface 2014-05-30 16:15:50 +02:00
ffb4869020 fixed init script 2014-05-28 23:35:01 +02:00
dickreckard
04a6bb5c22 added SECURITY so that new lines doesnt allow code and doesnt break the whole things 2014-05-28 23:30:34 +02:00
ac745fde93 mistake corrected 2014-05-28 23:14:04 +02:00
c52e672aff Merge branch 'master' of git://github.com/stuff2233/meshenger 2014-05-28 23:05:11 +02:00
5e46d14d13 meshenger now changes the working directory to /meshenger/, fixed the lazyinstall 2014-05-28 23:01:03 +02:00
dd6a74d324 more cosmmosiisis 2014-05-28 22:16:09 +02:00
9ec75cca42 more cosmeticz 2014-05-28 21:55:27 +02:00
70740c3980 cosmeticaaa for firefox fix 2014-05-28 21:38:12 +02:00
45bd9f3bb0 corrected firewall overwrite 2014-05-28 21:33:59 +02:00
04a0cf548e updated lazyinstall, now includes hotspot, captive portal and init script 2014-05-28 21:25:36 +02:00
dickreckard
c750d062be webapp with user option and stuff 2014-05-28 21:22:11 +02:00
261621dd46 cosmetics 2014-05-28 21:00:11 +02:00
dickreckard
33a1e3f863 Merge branch 'master' of https://github.com/stuff2233/meshenger 2014-05-28 20:48:45 +02:00
dickreckard
722aa2a8d0 webapp 2014-05-28 20:48:08 +02:00
e8ef0c9a56 Merge branch 'master' of https://github.com/stuff2233/meshenger 2014-05-28 12:52:49 +02:00
0d4efad1ae cosmetic changes 2014-05-28 12:51:12 +02:00
5fe9e0b19b Update README.md 2014-05-28 12:35:39 +02:00
6bfd985333 Update README.md
added init script howto
2014-05-28 12:13:57 +02:00
dickreckard
fdc3d06675 fixed the webapp so it removes non-existing msgs from the cache 2014-05-28 12:02:01 +02:00
dickreckard
87ed307ac5 general cleanup of randomness 2014-05-28 11:09:01 +02:00
dickreckard
f12bd4dcec igg 2014-05-28 01:36:32 +02:00
dickreckard
6022f00690 keys| 2014-05-27 22:56:11 +02:00
32 changed files with 2031 additions and 208 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ msg/*
index
interfaceip6adress
nodes/*
*.log

218
README.md
View File

@ -1,4 +1,4 @@
meshenger
Meshenger
=========
Meshenger is a Forban-inspired messaging software used for a speculative broadcast communication project. The starting point is an electronic messaging system running on a wireless mesh network. The messages propagate through the network when devices that come in contact with each other synchronize their content. It is non-hierarchical, every node receives, relays and broadcasts messages.
@ -26,6 +26,18 @@ You are going to need to have an internet connection to your router, the easiest
Alternatively if you use OSX you can enable internet sharing (make sure to set your OSX machine as the gateway and DNS server for your router in /etc/config/network)
*If after flashing something goes wrong, you can reset the whole router in failsafe mode* see [here](http://wiki.openwrt.org/doc/howto/generic.failsafe).
### Lazy Install
Below you will find the manual configuration steps necessary to install Meshenger. We did however create some scripts to help you automate the process.
Read more [here](https://github.com/rscmbbng/meshenger/blob/master/lazyinstall/README.md)
To add: stop Luci from running!
`$ /etc/init.d/uhttpd disable`
Saves a lot of resources!
### System configuration
To use your router for Meshenger you're going to need to run the whole filesystem from a USB-Drive.
@ -274,8 +286,40 @@ Restart dnsmasq to apply the changes:
`$ /etc/init.d/dnsmasq restart`
Now all http requests will be directed to Meshenger!
Now all http requests will be directed to Meshenger! If it doesn't work, check your DNS settings of the client your're using (aka your computer, phone, fapfapfaplet). Make sure google's 8.8.8.8 is not there and set 192.168.2.1 as the dns server.
### Run meshenger on boot
Create a file in `$ /etc/init.d/` called meshenger and paste the script below:
```
#!/bin/sh /etc/rc.common
#meshenger startup script
START=101
#STOP=15
start() {
echo 'starting Meshenger'
/usr/bin/python /root/meshenger/main.py &
}
stop() {
echo 'Killing Meshenger'
killall python
}
```
Make the file executable
`$ chmod a+x /etc/init.d/meshenger`
Now you can start/stop meshenger as a service, to enable the meshenger as srevice on boot run
`$ /etc/init.d/meshenger enable`
To start/stop/disable replace 'enable' with start, stop or disable.
### Installing meshenger
@ -285,3 +329,173 @@ Get the dependencies and clone the git
`$ opkg install python git `
`$ git clone git://github.com/jngrt/meshenger.git `
### Running Meshenger on Boot
To launch the Meshenger script (or python script in this case), we have to run it as a 'Service'.
Befor we can do so we need to know some variable.
#### Path to python
Find out where your Python binary is located:
```$ which python```
This command outputs your path, for example: ` /usr/bin/python`, remember this path
#### Boot order
Alot of processes are started at boot, and we want to make sure our script runs after the system has booted completely. To find out the boot order, look in the rc.d folder:
```$ ls /etc/rc.d```
This will output a list of startup sctipts with prefixes like S10-, S20-, S30-. The numbers define the boot order, the higher, the later. Remember the highest 'S'(cript) number. We need to run our script after the last one.
#### Startup script
Create a new file in `/etc/init.d/`
```$ vi /etc/init.d/meshenger```
And paste the script below and correct your path to pyton and boot order number, both found above.
```
#!/bin/sh /etc/rc.common
#meshenger startup script
START=99 #the boot order number, note in our case the SAME number as the highest one found.
SERVICE_DAEMONIZE=1 #start as service, important!
start(){
sleep 10 #make sure booting is finished
echo 'starting meshenger'
/usr/bin/python /root/meshenger/main.py & #path to python binary, space, full path to your script
}
stop(){
echo 'stopping meshenger'
killall python
}
```
Make sure both your script (main.py) and the init.d script are executable!
```$ chmod +x /etc/init.d/meshenger```
```$ chmod +x /root/meshenger/main.py```
#### Enabling the service
Now we have to activate the script we just pasted and make it run as service, on every (re)boot.
```$ /etc/init.d/meshenger enable```
This creates a symlink in `/etc/rc.d` with the boot order number prefix you provided in the init.d script (S99-meshenger). You can also start and stop the service manually with:
```$ /etc/init.d/meshenger start```
```$ /etc/init.d/meshenger stop```
That's all, reboot and see if it works ( `$ ps | grep python` )!
### SSH Access from Internet (wan)
If you want, you can hook your box up to the internet and manage it remotely. This is not in the scope of this project but I'll share the steps with you:
#### Configure your firewall
```
$ vi /etc/config/firewall
config 'rule'
option 'src' 'wan'
option 'dest_port' '22'
option 'target' 'ACCEPT'
option 'proto' 'tcp'
$ /etc/init.d/firewall restart
```
Enable user iptable script:
```
$ vi vi /etc/firewall.user
iptables -t nat -A prerouting_wan -p tcp --dport 22 -j ACCEPT
iptables -A input_wan -p tcp --dport 22 -j ACCEPT
$ /etc/init.d/firewall restart
```
#### Configure open ssh service
Add the line below in your dropbear config
```
$ vi /etc/config/dropbear
option 'GatewayPorts' 'on'
$ /etc/init.d/dropbear restart
```
Now you can acces your router, via ssh, from the internet. Don't forget to add portforwarding on you're modem/router!
Next up, http access from the internet!
### HTTP Access from Internet (wan)
If you want, you can host Meshenger or your own homepage on the internet. This is not in the scope of this project but I'll share the steps with you:
#### Configure your firewall
Add the following lines in your firewall config, and restart the firewall:
```
$ vi /etc/config/firewall
config 'redirect'
option 'src' 'wan'
option 'src_dport' '80'
option 'dest' 'lan'
option 'dest_ip' '192.168.1.1'
option 'dest_port' '80'
option 'proto' 'tcp'
config 'rule'
option 'src' 'wan'
option 'dest_port' '80'
option 'target' 'ACCEPT'
option 'proto' 'tcp'
$ /etc/init.d/firewall restart
```
Now either run Meshenger, or run a simple http server (to share files, or whatever) from any directory with this Python oneliner:
``` python -m SimpleHTTPServer 80 ```
After forwarding the correct ports on your home router/modem (from any ip on port 80 to your openwrt box (lan) ip on port 80) your website will now be accessible from outside (wan) through your external IP!

View File

@ -1,3 +1,46 @@
This is a very primitive script to automate the openwrt setup.
It's probably best not used at this point.
However if you choose to use it make sure you have the following things set up:
- The OpenWRT router has a connection to the internet (by attaching it to another router via ethernet)
- You have a properly formatted usb drive (one ext4 partition, one 32mb swap partiton)
- Make sure the usb flash drive is inserted in the router
The lazy install scripts works by moving config files from this folder to replace the ones on the target router's filesystem.
Therefore you need to edit the following files to suit your own needs:
network file:
lines 11 and 13, the ip adress you want to use plus the ip adress of the gateway that provides this router with its internet connection.
wireless file:
line 11,the wireless SSID that the router will get
Once you've set all of this up copy the lazyinstall directory to your open-wrt router:
scp -r /path/to/meshenger/lazyinstall/ root@target.router.ip.adress:~/
ssh into the target router:
ssh root@target.router.ip.address
navigate to where you copied the lazyinstall directory:
cd ~/lazyinstall
first execute lazyinstall1, it will set up the ext_root on ths usb drive
./lazyinstall1.sh
once that's done reboot the router:
reboot -f
once it is up again, ssh back in. the router has now booted from the external usb drive. you can verify that by doing:
df -h
which should show you that rootfs is the size of your external usb drive
run lazyinstall2. this will copy all the config files, download python and git and clone the meshenger project. This can take a while.
After this is done you can reboot and you will have a fully functioning meshenger node!

38
lazyinstall/dhcp Normal file
View File

@ -0,0 +1,38 @@
config dnsmasq
option domainneeded 1
option boguspriv 1
option filterwin2k 0 # enable for dial on demand
option localise_queries 1
option rebind_protection 1 # disable if upstream must serve RFC1918 addresses
option rebind_localhost 1 # enable for RBL checking and similar services
#list rebind_domain example.lan # whitelist RFC1918 responses for domains
option local '/lan/'
option domain 'lan'
option expandhosts 1
option nonegcache 0
option authoritative 1
option readethers 1
option leasefile '/tmp/dhcp.leases'
option resolvfile '/tmp/resolv.conf.auto'
#list server '/mycompany.local/1.2.3.4'
#option nonwildcard 1
#list interface br-lan
#list notinterface lo
#list bogusnxdomain '64.94.110.11'
list server '//192.168.2.1'
config dhcp lan
option interface lan
option start 100
option limit 150
option leasetime 12h
config dhcp wan
option interface wan
option ignore 1
config dhcp hotspot
option 'interface' 'hotspot'
option 'start' '100'
option 'limit' '150'
option 'dynamicdhcp' '1'

34
lazyinstall/firewall Normal file
View File

@ -0,0 +1,34 @@
config 'zone'
option 'name' 'hotspot'
option 'input' 'ACCEPT'
option 'forward' 'ACCEPT' #was REJECT
option 'output' 'ACCEPT'
config 'rule'
option 'src' 'hotspot'
option 'dest_port' '53'
option 'proto' 'tcpudp'
option 'target' 'ACCEPT'
config 'rule'
option 'src' 'hotspot'
option 'src_port' '67-68'
option 'dest_port' '67-68'
option 'proto' 'udp'
option 'target' 'ACCEPT'
config 'rule'
option 'target' 'ACCEPT'
option 'src' 'hotspot' # guest wifi interface
option 'proto' 'tcp'
option '_name' 'Website' # this can maybe go?
option 'dest_port' '80'
config 'redirect'
option 'proto' 'tcp'
option '_name' 'Website' # this can maybe go?
option 'src' 'hotspot' # guest wifi interface
option 'src_dport' '80'
option 'dest_port' '80'
option 'dest_ip' '192.168.2.1' # ip of webserver

View File

@ -17,7 +17,7 @@ mount -t ext4 /dev/sda1 /mnt/sda1
echo 'Cleaning USB drive'
rm -r /mnt/sda1/
rm -r /mnt/sda1/*
sleep 4
@ -25,7 +25,7 @@ echo 'Copying filesystem to USB drive'
mkdir -p /tmp/cproot
mount --bind / /tmp/cproot
sleep 1
tar -C /tmp/cproot -cvf - . | tar -C /mnt/sda1 -xf -
tar -C /tmp/cproot -cvf - . | tar -C /mnt/sda1/ -xf -
sleep 1
umount /tmp/cproot

View File

@ -10,10 +10,18 @@ swapon /dev/sda2
mv fstab_extroot /etc/config/fstab
echo 'Configuring wireless and hotspot'
head -n 10 /etc/config/wireless >> tmp_wireless
cat wireless >> tmp_wireless
mv tmp_wireless /etc/config/wireless
cat firewall >>ls /etc/config/firewall
mv dhcp /etc/config/dhcp
echo "address=/#/192.168.2.1 " >> /etc/dnsmasq.conf
sleep 1
mv network /etc/config/network
@ -24,10 +32,13 @@ sed -i -e "s/option 'interfaces' 'mesh'/option 'interfaces' 'adhoc0'/g" /etc/con
opkg install python git
sleep 1
git clone git://github.com/jngrt/meshenger.git
git clone git://github.com/rscmbbng/meshenger /root/meshenger
mv uhttpd /etc/config/uhttpd
mv meshenger /etc/init.d/meshenger
/etc/init.d/meshenger enable
echo 'my ip address is:' #klopt nog niet
ifconfig br-lan | grep 'inet addr'

15
lazyinstall/meshenger Executable file
View File

@ -0,0 +1,15 @@
#!/bin/sh /etc/rc.common
#meshenger startup script
START=101
#STOP=15
start() {
echo 'starting Meshenger'
/usr/bin/python /root/meshenger/main.py &
}
stop() {
echo 'Killing Meshenger'
killall python
}

View File

@ -18,3 +18,9 @@ config interface 'mesh'
option mtu '1528'
option proto 'none'
config interface 'hotspot'
option 'iface' 'radio0' #use your existing wifi device (look in config/wireless below)
option 'proto' 'static'
option 'ipaddr' '192.168.2.1'
option 'netmask' '255.255.255.0'
option 'type' 'bridge'

View File

@ -1,4 +1,3 @@
config wifi-iface 'wmesh'
option device 'radio0'
option ifname 'adhoc0'
@ -6,3 +5,11 @@ config wifi-iface 'wmesh'
option mode 'adhoc'
option ssid 'mesh'
option bssid '66:66:66:66:66:66'
config wifi-iface
option 'device' 'radio0' #use your existing wifi device, look in the list above.
option 'ssid' 'meshenger_node' #use a unique name for your network?
option 'network' 'hotspot'
option 'mode' 'ap'
option 'encryption' 'none'
option 'isolate' '1'

4
log/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

240
main.py
View File

@ -1,25 +1,39 @@
#!/usr/bin/python
import socket, os, time, select, urllib, sys, threading, subprocess
import socket, os, time, select, urllib, sys, threading, json, logging, logging.config
os.chdir(os.path.dirname(__file__)) # change present working directory to the one where this file is
logging.config.fileConfig('pylog.conf')
logger = logging.getLogger('meshenger'+'.main')
class Meshenger:
devices = {} #the dictionary of all the nodes this this node has seen
devices = {} #the dictionary of all the nodes this this node has seen. each ip-entry has a tuple of 'foreign index update time' and time when last seen
devices_old = {}
serve_port = "13338"
announce_port = 13337
#own_ip = "0.0.0.0"
msg_dir = os.path.relpath('msg/')
exitapp = False #to kill all threads on
index_last_update = "0" #str(int(time.time()))
index_last_update = str(int(time.time()))
node_expiry = 15 #the time to wait before removing a node form the discovered nodelist
def __init__(self):
os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-lan/disable_ipv6")
os.system("echo 1 >> /proc/sys/net/ipv6/conf/br-hotspot/disable_ipv6")
self.own_ip = self.get_ip_adress()
self.own_ip = self.get_ip_adress().strip()
# this hash is needed in clientserve, so client can generate color
self.own_hash = self.hasj(self.own_ip)
if not os.path.exists(self.msg_dir):
os.mkdir(self.msg_dir)
print 'Making message directory'
logger.info('Making message directory')
self.init_index()
self.make_alias()
try:
d = threading.Thread(target=self.discover)
@ -38,63 +52,66 @@ class Meshenger:
c.daemon = True
c.start()
b = threading.Thread(target=self.build_index)
b.daemon = True
b.start()
#os.system("python meshenger_clientserve.py")
except (KeyboardInterrupt, SystemExit):
print 'exiting discovery thread'
logger.info('exiting discovery thread')
d.join()
a.join()
b.join()
n.join()
c.join()
sys.exit()
except Exception as e:
logger.warning( 'Main __init__ thread exception: %s', repr(e) )
except:
logger.warning( 'Main __init__ unknown thread exception')
while True:
print 'Entering main loop'
logger.debug('Entering main loop')
#
if len(self.devices) > 0:
print 'found', len(self.devices),'device(s)'
logger.info('found %s device(s)', len(self.devices))
for device in self.devices.keys():
nodepath = self.ip_to_hash_path(device) #make a folder for the node (nodes/'hash'/)
nodeupdatepath = os.path.join(self.ip_to_hash_path(device), 'lastupdate')
nodehash = self.hasj(device)
nodepath = os.path.join(os.path.abspath('nodes'), nodehash)
nodeupdatepath = os.path.join(nodepath, 'lastupdate')
print 'Checking age of foreign node index'
print self.devices[device], 'Foreign announce timestamp'
logger.info('Checking age of foreign node index')
logger.info('%s Foreign announce timestamp', self.devices[device])
try:
foreign_node_update = open(nodeupdatepath).read()
except:
foreign_node_update = 0 #means it was never seen before
print foreign_node_update, 'Locally stored timestamp for device'
logger.info('%s Locally stored timestamp for device', foreign_node_update)
if self.devices[device] > foreign_node_update:
print 'Foreign node"s index is newer, proceed to download index'
if not self.get_index(device, nodepath):
print 'index wget failed'
continue
print 'downloading messages'
if not self.get_messages(device, nodepath):
print 'getting messages failed'
continue
print 'updating node timestamp'
logger.info('Foreign node"s index is newer, proceed to download index')
self.get_index(device, nodepath)
logger.info('downloading messages')
self.get_messages(device, nodepath, nodehash)
self.build_index()
self.node_timestamp(device)
self.devices_old = dict(self.devices)
#check to see if a node has been missing for a while, if so remove it from self.devices
for device in self.devices_old.keys():
update_time = int(self.devices[device][1])
time_delta = int(time.time())- update_time
if time_delta >= self.node_expiry:
logger.info('Node '+device+' missing for '+str(self.node_expiry)+' seconds. Removing from list')
del self.devices[device]
time.sleep(5) #free process or ctrl+c
def node_timestamp(self, ip):
nodepath = os.path.abspath(os.path.join('nodes', self.hasj(ip)))
updatepath = os.path.join(nodepath, 'lastupdate')
with open(updatepath, 'wb') as lastupdate:
lastupdate.write(self.devices[ip])
lastupdate.write(self.devices[ip][0])
#return updatepath
@ -103,19 +120,22 @@ class Meshenger:
"""
Announce the node's existance to other nodes
"""
print 'Announcing'
logger.info('Announcing')
while not self.exitapp:
try:
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.sendto(self.index_last_update, ("ff02::1", self.announce_port))
sock.close()
#sock.close()
time.sleep(5)
except:
logger.warning('Failed to announce myself!')
def discover(self):
"""
Discover other devices by listening to the Meshenger announce port
"""
print 'Discovering'
logger.info('Discovering')
bufferSize = 1024 # whatever you need?
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
@ -124,19 +144,19 @@ Discover other devices by listening to the Meshenger announce port
while not self.exitapp:
result = select.select([s],[],[])[0][0].recvfrom(bufferSize)
ip = result[1][0]
print ip, "*"*45
#logger.debug('%s %s', ip, 'discovered')
node_path = os.path.join(os.path.abspath('nodes'), self.hasj(ip))
announce_time = str(int(time.time()))
if not os.path.exists(node_path) and ip != self.own_ip:
#loop for first timef
#loop for first time
self.ip_to_hash_path(ip) #make a folder /nodes/hash
self.devices[ip] = result[0]
self.devices[ip] = result[0], announce_time
#self.node_timestamp(ip) #make a local copy of the timestamp in /nodes/hash/updatetimestamp
print 'New node', ip
logger.info('New node %s', ip)
elif os.path.exists(node_path) and ip != self.own_ip:
print 'Known node', ip
self.devices[ip] = result[0]
logger.info('Known node %s', ip)
self.devices[ip] = result[0], announce_time
time.sleep(1)
@ -145,7 +165,7 @@ Discover other devices by listening to the Meshenger announce port
"""
Initialize the nodeserver
"""
print 'Serving to nodes'
logger.info('Serving to nodes')
import meshenger_nodeserve
meshenger_nodeserve.main()
@ -153,119 +173,91 @@ Initialize the nodeserver
"""
Initialize the clientserver
"""
print 'Serving to client'
logger.info('Serving to client')
import meshenger_clientserve
# set a reference to this object
meshenger_clientserve.meshenger = self
meshenger_clientserve.main()
# meshenger_clientserve.build_index_callback = self.build_index
def build_index(self):
def init_index(self):
"""
Make an index file of all the messages present on the node.
Save the time of the last update.
Initialize the index. Read from disk or create new.
"""
index_file = os.path.relpath( 'index' )
previous_index = []
if not os.path.exists( index_file ):
with open('index','wb') as index:
index.write('')
else:
previous_index = open( index_file ).read().split()
index_last_update_file = os.path.relpath( 'index_last_update' )
if os.path.exists( index_last_update_file ):
self.index_last_update = open( index_last_update_file ).read()
while not self.exitapp:
current_index = []
for root, folders, files in os.walk( self.msg_dir ):
if root == 'msg':
folders.sort()
else:
files.sort()
current_index += [root + '/' + f for f in files]
if current_index != previous_index:
with open( index_file, 'w' ) as f:
f.write( '\n'.join( current_index ))
self.index_last_update = str( int( time.time()))
print 'Index updated:', current_index
with open( os.path.relpath('index_last_update'), 'w') as f:
f.write( self.index_last_update )
previous_index = current_index
time.sleep( 5 )
"""
print 'Building own index for the first time\n'
logger.info('Checking own index for the first time\n')
if not os.path.exists('index'):
with open('index','wb') as index:
index.write('')
previous_index = []
self.previous_index = []
else:
previous_index = open('index').readlines()
self.previous_index = open('index').readlines()
self.build_index()
while not self.exitapp:
def build_index(self):
"""
Make an index file of all the messages present on the node.
Save the time of the last update.
"""
logger.debug('build_index')
current_index = os.listdir(self.msg_dir)
if current_index != previous_index:
if current_index != self.previous_index:
with open('index', 'wb') as index:
for message in os.listdir(self.msg_dir):
index.write(message)
index.write('\n')
self.index_last_update = str(int(time.time()))
print 'Index updated:', current_index
logger.info('Index updated: %s', current_index)
with open('index_last_update', 'wb') as indexupdate:
indexupdate.write(self.index_last_update) ### misschien moet dit index_last_update zijn
previous_index = current_index
time.sleep(5)
self.previous_index = current_index
def make_alias(self):
"""
See if the node already has an alias (nickname) if not just use the IP-Hash
"""
if not os.path.exists('alias'):
with open('alias','wb') as alias:
self.alias = self.own_hash
alias.write(self.own_hash)
else:
self.alias = open('alias','rb').read().replace('\n','') #should we replace the newline?
pass
logger.debug('Alias is "'+self.alias+'"')
def get_index(self,ip, path):
"""
Download the indices from other nodes.
"""
# time.sleep(0) # hack to prevent wget bug
# os.system('wget http://['+ip+'%adhoc0]:'+self.serve_port+'/index -O '+os.path.join(path,'index'))
command = 'wget http://['+ip+'%adhoc0]:'+self.serve_port+'/index -O '+os.path.join(path,'index')
print 'get_index: ', command
status = subprocess.call( command, shell=True )
return status == 0
Download the indices from other nodes.
"""
def get_messages(self, ip, path ):
os.system('wget http://['+ip+'%adhoc0]:'+self.serve_port+'/index -O '+os.path.join(path,'index'))
def get_messages(self, ip, path, hash):
"""
Get new messages from other node based on it's index file
"""
try:
with open(os.path.join(path,'index')) as index:
index = index.read().split('\n')
for message in index:
parts = message.split('/')
dirpath = os.path.join( parts[0], parts[1])
if not os.path.isdir( dirpath ):
os.mkdir( dirpath )
messagepath = os.path.relpath( message )
messagepath = os.path.join(os.path.abspath(self.msg_dir), message)
if not os.path.exists(messagepath):
print 'downloading', message, 'to', messagepath
command = 'wget http://['+ip+'%adhoc0]:' + self.serve_port + '/' + message+' -O ' + messagepath
status = subprocess.call( command, shell=True)
if status != 0:
return False
# succesfuly downloaded all messages, return true
return True
logger.info('downloading %s to %s', message, messagepath)
os.system('wget http://['+ip+'%adhoc0]:'+self.serve_port+'/msg/'+message+' -O '+messagepath)
with open(messagepath, 'r+') as f:
data=json.load(f)
data['hops']=str(int(data['hops'])+1)
data['node']=hash
f.seek(0)
json.dump(data, f)
except:
print 'Failed to download messages'
return False
logger.info('Failed to download messages')
pass
def ip_to_hash_path(self, ip):
"""
@ -280,6 +272,7 @@ Convert a node's ip into a hash and make a directory to store it's files
return nodepath
def hasj(self, ip):
"""
Convert a node's ip into a hash
@ -292,15 +285,16 @@ Convert a node's ip into a hash and make a directory to store it's files
"""
Hack to adhoc0's inet6 adress
"""
#TODO possibly check if it's an empty file
if not os.path.isfile('interfaceip6adress'):
os.system('ifconfig -a adhoc0 | grep inet6 > /root/meshenger/interfaceip6adress')
with open('interfaceip6adress', 'r') as a:
with open('/root/meshenger/interfaceip6adress', 'r') as a:
return a.read().split()[2].split('/')[0]
if __name__ == "__main__":
print "test"
logger.info("starting main...")
try:
meshenger = Meshenger()
except (KeyboardInterrupt, SystemExit):

View File

@ -8,42 +8,73 @@ from BaseHTTPServer import HTTPServer
import SimpleHTTPServer
import urlparse
import unicodedata
import logging, logging.config
logging.config.fileConfig('pylog.conf')
logger = logging.getLogger('meshenger'+'.clientserve')
class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
messageDir = "msg"
"""
Serve index and messages
"""
def do_GET(self):
"""
Serve index and messages
"""
if self.path.startswith( '/'+self.messageDir ):
parts = self.path.split('/');
if len( parts ) == 2:
self.wfile.write('give index')
elif len( parts ) == 3:
self.wfile.write('give msg')
elif self.path == '/index' or self.path.startswith( "/"+self.messageDir ):
if self.path == '/index' or self.path.startswith( "/"+self.messageDir ):
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
elif self.path == '/id':
if meshenger and meshenger.own_hash:
self.send_response(200)
self.send_header('Content-type', 'text-html')
self.end_headers()
self.wfile.write(meshenger.own_hash)
else:
self.send_error(404,'Id not yet available')
elif self.path == '/alias':
if meshenger and meshenger.alias:
self.send_response(200)
self.send_header('Content-type', 'text-html')
self.end_headers()
self.wfile.write(meshenger.alias)
else:
self.send_error(404,'Alias not yet available')
elif self.path == '/log':
self.send_response(200)
self.send_header('Content-type', 'text-html')
self.end_headers()
f = os.path.relpath('log/meshenger.log')
with open( f, 'r') as the_file:
self.wfile.write(the_file.read())
elif self.path.startswith('/web'):
f = os.path.relpath(self.path[1:])
if not os.path.exists( f ) or os.path.isdir( f ):
f = os.path.join('web', 'index.html')
self.path = '/'+f
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
elif self.path == '/old':
self.send_response(200) #serve the webapp on every url requested
self.send_header('Content-type', 'text/html')
self.end_headers()
f = os.path.join( "webapp.html")
f = os.path.relpath( 'webapp.html')
with open( f, 'r') as the_file:
self.wfile.write(the_file.read())
else:
self.path = '/' + os.path.join('web', 'index.html')
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
"""
Allow clients to post messages
"""
def do_POST(self):
"""
Allow clients to post messages
"""
if self.path == '/send':
logger.info('incoming message from client!')
length = int(self.headers['Content-Length'])
post_data = urlparse.parse_qs(self.rfile.read(length).decode('utf-8'))
@ -54,8 +85,18 @@ class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
self.end_headers()
self.wfile.write('message created')
def writeMessage(self, time, message):
#let main rebuild message index
try:
logger.debug('try to call meshenger.build_index: %s', repr(meshenger))
meshenger.build_index()
except:
logger.error('failed to call meshenger.build_index')
pass
def writeMessage(self, time, message):
"""
Write message to disk
"""
f = os.path.join( self.messageDir, time)
if os.path.isfile( f ):
return
@ -65,6 +106,7 @@ class ClientServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
class ClientServe():
def __init__(self, port):
logger.info('ClientServe.__init__')
server = HTTPServer( ('', port), ClientServeHandler)
server.serve_forever()

View File

@ -4,36 +4,56 @@ import logging
import cgi
import os
import socket
from BaseHTTPServer import HTTPServer
import BaseHTTPServer
import SimpleHTTPServer
import urlparse
import logging
import logging.config
logging.config.fileConfig('pylog.conf')
logger = logging.getLogger('meshenger'+'.nodeserve')
#GOTTMITTUNS
def _bare_address_string(self):
host, port = self.client_address[:2]
return '%s' % host
BaseHTTPServer.BaseHTTPRequestHandler.address_string = \
_bare_address_string
#END
class NodeServeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
messageDir = "/msg"
"""
Serve index and messages
"""
def do_GET(self):
"""
Serve index and messages
"""
if self.path == '/':
self.path = "/index"
if self.path == '/index' or self.path.startswith( self.messageDir ):
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
if self.path == '/alias' or self.path.startswith( self.messageDir ):
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
else:
self.send_response(404)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write('404 - Not Found')
class HTTPServerV6(HTTPServer):
class HTTPServerV6(BaseHTTPServer.HTTPServer):
address_family = socket.AF_INET6
class NodeServe():
def __init__(self, port):
logger.info('NodeServe.__init__')
server = HTTPServerV6(('::', port), NodeServeHandler)
server.serve_forever()

View File

@ -7,14 +7,6 @@
-perhaps replace the functionality of self.devices with something based on crawling nodes/hash/
-sometimes node tries to connect to itself (something wrong with receiving broadcast maybe??)
- HTTP calls the client needs:
- /msg/<uid> -> give list of all messages for this uid
- /msg/<uid>/<timestamp> -> get message for this timestamp
- /clients -> return list of all known clients (uid's + usernames)
- /getUID -> return uid for client ( unique id based on what? mac address? )
# Cryptoshit
encrypt / decrypt:
@ -22,5 +14,3 @@ http://www.davedoesdev.com/a-simple-and-consistent-wrapper-for-javascript-crypto
generating keys:
https://bitbucket.org/adrianpasternak/js-rsa-pem/wiki/Home

34
pylog.conf Normal file
View File

@ -0,0 +1,34 @@
[loggers]
keys=root,meshenger
[handlers]
keys=consoleHandler, fileHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_meshenger]
level=DEBUG
handlers=consoleHandler, fileHandler
qualname=meshenger
propagate=0
[handler_fileHandler]
class=handlers.RotatingFileHandler
level=INFO
args=('log/meshenger.log','a','maxBytes=10000','backupCount=5')
formatter=simpleFormatter
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=

BIN
web/.index.html.swo Normal file

Binary file not shown.

45
web/emoji.js Normal file
View File

@ -0,0 +1,45 @@
function parseEmoticons( inputStr ){
var emoticons = [
{
text: ':)',
image: '1.png'
},
{
text: '<iframe',
image: 'iframe-open.png'
},
{
text: '</iframe>',
image: 'iframe-close.png'
},
{
text: '<script',
image: 'script-open.gif'
},
{
text: '</script>',
image: 'script-close.gif'
},
{
text: '<style',
image: 'style-open.png'
},
{
text: '</style>',
image: 'style-close.png'
},
{
text:';)',
image: '2.png'
},
{
text:':rock:',
image: '3.png'
}
];
for ( var i = 0; i < emoticons.length; i++ ){
inputStr = inputStr.split(emoticons[i].text).join('<img class="emo" src="/web/emoticons/'+emoticons[i].image+'">');
}
return inputStr;
}

BIN
web/emoticons/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
web/emoticons/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
web/emoticons/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

70
web/index.html Executable file
View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<meta name="viewport" content="width=device-width, user-scalable=no">
<link href="/web/style.css" rel="stylesheet" type="text/css">
<title>meshenger</title>
</head>
<body>
<div id="message-page">
<button id="message-back" class="back-button">Back</button><br><br>
<form id="message-form" action="" accept-charset="utf-8">
<textarea id="name" rows="1" placeholder="Your Name"></textarea>
<textarea id="message" rows="2" placeholder="Your Message"></textarea>
<br>
<input type="submit" class="send-button" value="Send">
</form>
</div>
<div id="photo-page" class="photo-page">
<button id="photo-back" class="back-button">Back</button><br>
<input id="fileInput" accept="image/*" capture="camera" type="file">
<br>
<p>
<span id="progress">--</span>
<span id="total">--</span>
</p>
<!--Squashed source<br>-->
<img id="sourceImage" src="" alt="capture" height="200" width="200">
<!--Offset done<br>-->
<canvas id="canvas1" width="200" height="200"></canvas>
<!--Dithered<br>-->
<canvas id="canvas2" width="200" height="200"></canvas>
<!--Rotated<br>-->
<canvas id="canvas3" width="200" height="200"></canvas>
<br>
<button id="rot-left" class="rot-left">Rotate left</button>
<button id="rot-right" class="rot-right">Rotate right</button>
<br><br>
<textarea id="photo-name" rows="1" placeholder="Your Name"></textarea>
<br>
<button id="submit-photo" class="photo-buttons">Send</button>
</div>
<div id="overview-page"><br>
<div class="messages">
<ul id="inbox"></ul>
<ul id="outbox"></ul>
</div>
<div id="input-footer">
<button id="new-photo">Photo</button>
<button id="new-message">Message</button>
</div>
</div>
<script src="/web/main.js"></script>
<script src="/web/emoji.js"></script>
<script src="/web/photostuff.js"></script>
</body>
</html>

325
web/main.js Normal file
View File

@ -0,0 +1,325 @@
localStorage.clear();
//These need to be obtained from the node
var ownId, ownColor, ownAlias;
/*
* INIT
*/
document.addEventListener('DOMContentLoaded', function(){
function update(){
if ( !ownId ){
getOwnId();
}
if ( !ownAlias){
getOwnAlias();
}
checkInbox();
// also check for outbox items on interval,
// necessary in case connection is lost and messages are not yet sent
checkOutbox();
}
document.getElementById('message-form').onsubmit = onSubmitMessage;
//update everything to initialize
updateInboxView();
update();
//check for new messages every 7 seconds
window.setInterval( update, 7000 );
initState();
initPhotoStuff(); //all photostuff is found in photostuff.js
});
/*
* STATE CHANGES
*/
function initState(){
showOverview();
document.getElementById('new-photo').onclick = showNewPhoto;
document.getElementById('new-message').onclick = showNewMessage;
document.getElementById('message-back').onclick = showOverview;
document.getElementById('photo-back').onclick = showOverview;
}
function showNewPhoto(){
document.getElementById('photo-page').style.display = "block";
document.getElementById('overview-page').style.display = "none";
document.getElementById('message-page').style.display = "none";
document.getElementById('fileInput').click();
}
function showNewMessage(){
document.getElementById('photo-page').style.display = "none";
document.getElementById('overview-page').style.display = "none";
document.getElementById('message-page').style.display = "block";
}
function showOverview(){
document.getElementById('photo-page').style.display = "none";
document.getElementById('overview-page').style.display = "block";
document.getElementById('message-page').style.display = "none";
}
/*
* OUTBOX STUFF
*/
function onSubmitMessage(){
var msg = document.getElementById('message').value.replace(/\r?\n/g, "<br />");
if ( !msg || msg === "" ) { /* prevent sending of empty messages */
return false;
}
var namm = document.getElementById('name').value;
if ( !namm || namm === "" || namm.length > 20) { /* prevent too long usernames */
namm = "anonymous";
}
addOutboxItem( namm, "<p class='text-message'>"+msg+"</p>" );
return false;
}
function addOutboxItem( namm, message ){
var outStr = localStorage.getItem( 'outbox' ) || '';
var newMsgs = {};
var ddata = new Date().getTime();
var alias = ownAlias;
var contento = {
"time" : ddata,
"message" : message,
"name" : namm,
"node" : "local",
"alias": alias,
"hops" : "0"
};
newMsgs.message = contento;
localStorage.setItem( 'outbox', JSON.stringify(newMsgs) );
checkOutbox();
document.getElementById('message').value = '';
showOverview();
}
function checkOutbox() {
var outStr = localStorage.getItem( 'outbox' );
if ( ! outStr ) {
return;
}
var lines = outStr.split( /\n/ );
for ( var i in lines ) {
if ( lines[i].length === 0 ) {
continue;
}
var obj = JSON.parse(lines[i]);
var ts = obj.message.time;
delete obj.message.time;
var msg = JSON.stringify(obj.message);
sendMessage( ts, msg );
}
}
function sendMessage( timestamp, message ) {
var xhr = new XMLHttpRequest();
var data = 'time=' + encodeURIComponent( timestamp ) +
'&message=' + encodeURIComponent( message );
xhr.onreadystatechange = function(){
if ( xhr.readyState == 4){
if ( xhr.status == 200 ) {
removeOutboxItem( timestamp );
}
}
};
xhr.open('POST', 'send', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('If-Modified-Since', 'Sat, 1 Jan 2005 00:00:00 GMT');
xhr.send(data);
}
function removeOutboxItem( timestamp ) {
var outStr = localStorage.getItem( 'outbox' ) || '';
var lines = outStr.split( /\n/ );
for ( var i in lines ) {
var obj = JSON.parse(lines[i]);
var ts = obj.message.time;
if ( ts === timestamp ) {
lines.splice( i, 1 );
break;
}
}
var newOutStr = lines.join('\n');
localStorage.setItem('outbox', newOutStr);
}
/*
* INBOX STUFF
*/
function updateInboxView() {
var localStorageArray = [];
var contentString = '';
if (localStorage.length>0) {
for (i=0;i<localStorage.length;i++){
element=localStorage.getItem(localStorage.key(i));
if ( localStorage.key(i).length < 10 || element === 'outbox' ) {
continue;
}
try {
elementj = JSON.parse(element);
} catch (e) {
continue;
}
localStorageArray[i] = {
time:localStorage.key(i),
user:elementj.name,
message:elementj.message,
node:elementj.node,
hops:elementj.hops,
alias:elementj.alias
};
}
}
orderStorage = localStorageArray.sort(function(a,b) { return b.time - a.time; } );
for(var i in orderStorage) {
if ( i.length === 0 || i === 'outbox' ) {
continue;
}
var datereadable = getReadableDate( new Date(parseInt(orderStorage[i].time)) );
var color = getNodeColor( orderStorage[i].node );
contentString += '<li><div class="message-block" style="background-color:'+color+'">'+
'<div class="date-sender">On ' + datereadable +
' <b>' + parseEmoticons(orderStorage[i].user) +'</b> wrote:</div>' +
'<div class="message-text">' + parseEmoticons( orderStorage[i].message ) + '</div>' + //parseEmoticons is found in emoji.js
' <div class="nodehops"><div class="node '+orderStorage[i].node+'">from '+orderStorage[i].alias + '</div>' +
' <div class="hops '+orderStorage[i].hops+'">via '+orderStorage[i].hops+' nodes</div></div></div></li>';
}
document.getElementById( 'inbox' ).innerHTML = contentString;
}
function getReadableDate( date ) {
var day = date.getDate();
if (day < 10) day = '0' + day;
var month = date.getMonth()+1;
if (month < 10) month = '0' + month;
var year = date.getFullYear();
var hrs = date.getHours();
if (hrs < 10) hrs = '0' + hrs;
var min = date.getMinutes();
if (min < 10) min = '0' + min;
return day + '/' + month + '/' + year + ' ' + hrs + ':' + min;
}
function getNodeColor( nodeId ) {
if( nodeId === 'local' ) {
return ownColor || '#fff';
}
return colorLuminance(nodeId.substr(0,6), 0.5);
}
function colorLuminance(hex, lum) {
// validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, '');
if (hex.length < 6) {
hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
}
lum = lum || 0;
// convert to decimal and change luminosity
var rgb = "#", c, i;
for (i = 0; i < 3; i++) {
c = parseInt(hex.substr(i*2,2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
rgb += ("00"+c).substr(c.length);
}
return rgb;
}
function onMessageDownload( msg, filename ) {
if ( localStorage.getItem( filename ) === null ) {
localStorage.setItem( filename, msg );
}
updateInboxView();
}
function onIndex( index ) {
var lines = index.split( /\n/ );
var k,i,l,f;
for( k in localStorage){
l = 1;
for ( i in lines ) {
f = lines[i];
if (f == k){ l = 0; }
}
if (l == 1){
localStorage.removeItem(k);
}
}
updateInboxView();
for ( i in lines ) {
var fname = lines[i];
if ( localStorage.getItem( fname ) === null ) {
//localStorage.setItem( ts, lines[i].substr(lines[i].indexOf(' ')) );
downloadMessage( fname );
}
}
}
function downloadMessage(filename) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200){
onMessageDownload( xhr.responseText, filename );
}
};
xhr.open( "GET", 'msg/'+filename, true);
xhr.send();
}
function checkInbox() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200){
onIndex( xhr.responseText );
}
};
xhr.open( "GET", 'index', true);
xhr.send();
}
function getOwnId() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200){
ownId = xhr.responseText;
ownColor = getNodeColor( ownId );
}
};
xhr.open( "GET", 'id', true);
xhr.send();
}
function getOwnAlias() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4 && xhr.status == 200){
ownAlias = xhr.responseText;
}
};
xhr.open( "GET", 'alias', true);
xhr.send();
}

225
web/photostuff.js Normal file
View File

@ -0,0 +1,225 @@
/*
* PHOTO STUFF
*/
var imgDim = 200;
var canvas1;
var context1;
var canvas2;
var context2;
var imgObj;
var fileReader;
var rotation = 0;
var sourceImage = document.getElementById('sourceImage'); // get reference to img
var fileInput = document.getElementById('fileInput'); // get reference to file select
var progressEl = document.getElementById('progress');
var totalEl = document.getElementById('total');
function initPhotoStuff(){
canvas1 = document.getElementById('canvas1');
context1 = canvas1.getContext('2d');
canvas2 = document.getElementById('canvas2');
context2 = canvas2.getContext('2d');
canvas3 = document.getElementById('canvas3');
context3 = canvas3.getContext('2d');
initCanvas(context1);
initCanvas(context2);
initCanvas(context3);
fileInput.onchange = onFileInputChange;
document.getElementById('rot-left').onclick = onRotateLeft;
document.getElementById('rot-right').onclick = onRotateRight;
document.getElementById('submit-photo').onclick = submitImage;
}
function submitImage(){
var image = new Image(); //create new image holder
var canvas = document.getElementById('canvas3'); // choose canvas element to convert
var dataURL = canvas.toDataURL(); // convert cabvas to data url we can handle
image.src = dataURL;
image.className = 'photo-message';
if ( !image.src || image.src === "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAACEUlEQVR4nO3TMQEAIAzAsPk3DQrWF45EQZ/OAVbzOgB+ZhAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBYBAIBoFgEAgGgWAQCAaBcAEODrNzcVUZ+gAAAABJRU5ErkJggg==" ) { /* prevent sending of empty messages (data url of blank, thus white, canvas)*/
return false;
}
var namm = document.getElementById('photo-name').value;
if( !namm || namm == "" ){
namm = "anonymous";
}
addOutboxItem( parseEmoticons(namm), image.outerHTML );
showOverview();
return false;
}
function initCanvas(context){
// Debug filling
context.fillStyle ="#dbbd7a";
context.fill();
context.beginPath();
context.rect(0,0, imgDim, imgDim);
context.fillStyle = 'white';
context.fill();
// context.lineWidth = 7;
// context.strokeStyle = 'black';
// context.stroke();
}
// create file reader
function onFileInputChange(){
var file = fileInput.files[0]; // get selected file (camera capture)
fileReader = new FileReader();
fileReader.onload = onFileReaderLoad; // add onload listener
fileReader.onprogress = onFileReaderProgress; // add progress listener
fileReader.readAsDataURL( file ); // get captured image as data URI
}
function onFileReaderProgress(e) {
if (e.lengthComputable) {
var progress = parseInt( ((e.loaded / e.total) * 100), 10 );
console.log(progress);
progressEl.innerHTML = e.loaded;
totalEl.innerHTML = e.total;
}
}
function onFileReaderLoad() {
imgObj = new Image();
imgObj.src = fileReader.result;
imgObj.onload = onImageLoad;
//for debugging: show image on screen, will be squashed
sourceImage.src = fileReader.result;
}
function onImageLoad() {
var w,h;
var xOffset = 0;
var yOffset = 0;
if ( imgObj.width > imgObj.height ) {
h = imgDim;
w = imgDim * imgObj.width / imgObj.height;
xOffset = (imgDim - w) / 2;
} else {
w = imgDim;
h = imgDim * imgObj.height / imgObj.width;
yOffset = (imgDim - h) / 2;
}
context1.drawImage(imgObj, xOffset, yOffset, w, h);
var imageData = context1.getImageData( 0, 0, canvas1.width, canvas1.height);
context2.putImageData( monochrome(imageData,128,"atkinson"), 0, 0);
drawRotated();
}
function onRotateRight(){
rotation += 90;
drawRotated();
}
function onRotateLeft(){
rotation -= 90;
drawRotated();
}
function drawRotated(){
context3.clearRect(0,0,canvas3.width,canvas3.height);
context3.save();
context3.translate(canvas3.width/2,canvas3.height/2);
context3.rotate(rotation * Math.PI / 180);
context3.translate(-canvas3.width/2,-canvas3.height/2);
context3.drawImage( canvas2, 0, 0 );
context3.restore();
}
var bayerThresholdMap = [
[ 15, 135, 45, 165 ],
[ 195, 75, 225, 105 ],
[ 60, 180, 30, 150 ],
[ 240, 120, 210, 90 ]
];
var lumR = [];
var lumG = [];
var lumB = [];
for (var i=0; i<256; i++) {
lumR[i] = i*0.299;
lumG[i] = i*0.587;
lumB[i] = i*0.114;
}
function monochrome(imageData, threshold, type){
var imageDataLength = imageData.data.length;
// Greyscale luminance (sets r pixels to luminance of rgb)
for (var i = 0; i <= imageDataLength; i += 4) {
imageData.data[i] = Math.floor(lumR[imageData.data[i]] + lumG[imageData.data[i+1]] + lumB[imageData.data[i+2]]);
}
var w = imageData.width;
var newPixel, err;
for (var currentPixel = 0; currentPixel <= imageDataLength; currentPixel+=4) {
if (type === "none") {
// No dithering
imageData.data[currentPixel] = imageData.data[currentPixel] < threshold ? 0 : 255;
} else if (type === "bayer") {
// 4x4 Bayer ordered dithering algorithm
var x = currentPixel/4 % w;
var y = Math.floor(currentPixel/4 / w);
var map = Math.floor( (imageData.data[currentPixel] + bayerThresholdMap[x%4][y%4]) / 2 );
imageData.data[currentPixel] = (map < threshold) ? 0 : 255;
} else if (type === "floydsteinberg") {
// FloydSteinberg dithering algorithm
newPixel = imageData.data[currentPixel] < 129 ? 0 : 255;
err = Math.floor((imageData.data[currentPixel] - newPixel) / 16);
imageData.data[currentPixel] = newPixel;
imageData.data[currentPixel + 4 ] += err*7;
imageData.data[currentPixel + 4*w - 4 ] += err*3;
imageData.data[currentPixel + 4*w ] += err*5;
imageData.data[currentPixel + 4*w + 4 ] += err*1;
} else {
// Bill Atkinson's dithering algorithm
newPixel = imageData.data[currentPixel] < 129 ? 0 : 255;
err = Math.floor((imageData.data[currentPixel] - newPixel) / 8);
imageData.data[currentPixel] = newPixel;
imageData.data[currentPixel + 4 ] += err;
imageData.data[currentPixel + 8 ] += err;
imageData.data[currentPixel + 4*w - 4 ] += err;
imageData.data[currentPixel + 4*w ] += err;
imageData.data[currentPixel + 4*w + 4 ] += err;
imageData.data[currentPixel + 8*w ] += err;
}
// Set g and b pixels equal to r
imageData.data[currentPixel + 1] = imageData.data[currentPixel + 2] = imageData.data[currentPixel];
}
// Alpha: make white pixels transparent!
var newColor = {r:0,g:0,b:0, a:0};
for ( i = 0, n = imageData.data.length; i <n; i += 4) {
var r = imageData.data[i],
g = imageData.data[i+1],
b = imageData.data[i+2];
// If its white, or close to white then change it
if(r >=200 && g >= 200 && b >= 200){
// Change the white to whatever.
imageData.data[i] = newColor.r;
imageData.data[i+1] = newColor.g;
imageData.data[i+2] = newColor.b;
imageData.data[i+3] = newColor.a;
}
}
return imageData;
}

523
web/style.css Executable file
View File

@ -0,0 +1,523 @@
body, html{
margin: 0;
padding: 0;
}
body{
font-size:20px;
text-shadow: 1px 1px #ddd;
font-family: times, 'times new roman', helvetica,serif;
line-height:1.5em;
color:#444;
background:#fff;
padding:20px;
}
/* resize images on mobile */
@media all and (max-width: 768px) {
.photo-message{ /* rounded corner imgs...*/
width:100%;
height:100%;
overflow:hidden;
line-height: 0;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
}
img.photo-message{
width:100%;
line-height: 0;
vertical-align:middle; /* otherwise produces bottom border the size of the body 'line-height' */
/* disable anti aliasing */
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: pixelated;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
.nodehops{
position: absolute;
float: right;
right:20px;
text-align: right;
padding: 0px;
margin-top:-50px;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
background:#fff;
}
}
/* resize images on desktop */
@media all and (min-width: 768px) {
img.photo-message{
width:50%;
display: block;
margin-left: auto;
margin-right: auto;
/* disable anti aliasing */
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: pixelated;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
img.photo-message{
/* disable anti aliasing */
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: pixelated;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
.photo-message{ /* rounded corner imgs on desktop...*/
padding:30px;
width:100%;
height:100%;
overflow:hidden;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
}
.nodehops{
position: absolute;
float: right;
right:20px;
text-align: right;
padding: 4px;
margin-top:-60px;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
background:#fff;
}
}
img.emo {
}
.date-sender .emo {
width:22px;
height:22px;
}
p.text-message{
padding: 30px;
padding-top: 50px;
}
#input-footer{
position: fixed;
top:0px;
width:100%;
margin:10px;
}
#new-photo{
display:inline;
float:left;
width:30%;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
#new-message{
margin-right: 60px;
display:inline;
width:30%;
float:right;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
.back-button{
display:inline;
float:left;
width:100%;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
.rot-left{
display:inline;
float:left;
width:50%;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
.rot-right{
display:inline;
width:50%;
float:right;
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
.photo-buttons {
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
width:100%;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
/* hide calculation canvases */
#sourceImage{
display: none;
}
#canvas1{
display: none;
}
#canvas2{
display: none;
}
#progress{
display: none;
}
#total{
display: none;
}
#message-form textarea{
-webkit-appearance: none;
-moz-appearance: none;
display: block;
outline:none;
width: 98%;
margin: 0 auto;
line-height: 40px;
border:none;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
font-family: inherit;
font-size: 20px;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
#photo-name{ /* text field */
-webkit-appearance: none;
-moz-appearance: none;
display: block;
outline:none;
width: 98%;
margin: 0 auto;
line-height: 40px;
border:none;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
font-family: inherit;
font-size: 20px;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
#message-form .send-button {
-webkit-appearance: none;
-moz-appearance: none;
outline: none;
border:0;
-webkit-border-radius: 13px;
-moz-border-radius: 13px;
border-radius: 13px;
height:40px;
width:100%;
display: block;
font-family: inherit;
font-size: 20px;
font-weight:bold;
text-shadow: 0 0 23px #888;
background-color:#fff;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
}
select{
width:100%;
display: block;
margin-left: auto;
margin-right: auto;
margin: 1.5em 0;
font-family: inherit;
font-size: inherit;
font-size: 20px;
}
h2{
margin: 0 0 20px;
font-size: 40px;
text-shadow: 0px 0px 23px #bbb;
}
hr{
height:10px;
border: 10;
width:87%;
background-image: url(css/bg1.gif);
}
.messages ul{
padding:0px;
margin: 0;
}
.messages li{
list-style: none;
margin: 0;
}
.messages .message-block {
margin-top:30px;
word-wrap:break-word;
overflow: visible;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
background:#fff;
}
.photoBox {
width: 100%;
margin: 0 auto;
line-height: 40px;
display: none;
position: absolute;
background-color:#fff;
z-index:1002;
overflow: visible;
background:#fff;
}
.node, .hops {
font-size:10px;
display: inline-block;
word-wrap:break-word;
overflow: visible;
}
.message-text {
font-size: 25px;
}
.date-sender {
position: absolute;
font-size: 15px;
padding: 4px;
word-wrap:break-word;
overflow: visible;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
background:#fff;
}
#outbox{
display:none;
}
#header{
width:100%;
text-align: center;
}

View File

@ -7,35 +7,181 @@
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<meta name="viewport" content="width=device-width, user-scalable=no">
<style>
body, html{margin: 0;padding: 0;}
body{
font-size:20px;
text-shadow: 1px 1px orange;
font-family: times, 'times new roman', helvetica,serif;
line-height:1.5em;
color:#444;
background:#fff;
background-image: url(css/marble.jpg);
}
textarea{
-webkit-appearance: none; -moz-appearance: none;
display: block;
margin-left: auto;
margin-right: auto;
width: 90%; height: 50px;
line-height: 40px; font-size: 17px;
border: 1px solid #bbb;
-webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px;
background-image: url(css/aqua.jpg);
font-family: inherit;
font-size: inherit;
font-size: 20px;
}
button {
display: block;
margin-left: auto;
margin-right: auto;
margin: 1.5em 0;
font-family: inherit;
font-size: inherit;
font-size: 20px;
}
select{
width:100%;
display: block;
margin-left: auto;
margin-right: auto;
margin: 1.5em 0;
font-family: inherit;
font-size: inherit;
font-size: 20px;
}
h2{
font-size: 40px;
}
hr{
height:10px;
border: 10;
width:87%;
background-image: url(css/bg1.gif);
}
ul{
width:95%;
//margin-left: 0px;
padding-left:30px;
}
li{
word-wrap:break-word;
list-style: none;
overflow: visible;
padding:30px;
-moz-box-shadow:0 0 23px #bbb;
-webkit-box-shadow:0 0 23px #bbb;
box-shadow:0 0 23px #bbb;
-webkit-border-top-left-radius:123px;
-webkit-border-top-right-radius:123px;
-webkit-border-bottom-right-radius:123px;
-webkit-border-bottom-left-radius:123px;
-moz-border-radius-bottomright:123px;
-moz-border-radius-bottomleft:123px;
-moz-border-top-left-radius:123px;
-moz-border-top-right-radius:123px;
border-top-left-radius:123px;
border-top-right-radius:123px;
border-bottom-right-radius:123px;
border-bottom-left-radius:123px;
left:-35px;
width:100%;
//overflow:hidden;
position:relative;
background:#fff;
//background:-webkit-gradient(linear, left top, left bottom, from(#fff), to(#eee));
//background:-moz-linear-gradient(top, #fff, #eee);
}
#outbox{
display:none;
}
#header{
width:100%;
text-align: center;
}
#name{
}
.hops .node{
// display:hidden;
}
</style>
<title>meshed up</title>
</head>
<body>
<h2>message</h2>
<textarea id="message" rows="3" style="width:100%"></textarea>
<div id="header">
<h2>Meshenger</h2>
</div>
<textarea id="name" rows="1" placeholder="Your Name (leave empty for anonymous)"></textarea>
<textarea id="message" rows="3" placeholder="Your Message"></textarea>
<button style="width:100%" id="send">Send</button>
<h2>outbox</h2>
<ul id="outbox"></ul>
<h2>inbox</h2>
<select id="messagesort" name="messageSort">
<option value="dateReceived">Sort by Date Received</option>
<option value="dateSend">Sort by Date Send</option>
</select>
<ul id="inbox"></ul>
<!--<h2>outbox</h2>-->
<ul id="outbox"></ul>
<script type="text/javascript">
localStorage.clear();
/*
* OUTBOX STUFF
*/
document.getElementById( 'send' ).onclick = function() {
var outStr = localStorage.getItem( 'outbox' ) || '';
outStr += new Date().getTime() + ' ' + document.getElementById('message').value + '\n';
localStorage.setItem( 'outbox', outStr );
if (document.getElementById('name').value == ""){
var namm= "anonymous";
}
else{
var namm= document.getElementById('name').value;
}
var mess = document.getElementById('message').value.replace(/\r?\n/g, "<br />");
var newMsgs ={};
var ddata= new Date().getTime();
var contento = {
"time" : ddata,
"message" : mess,
"name" : namm,
"node" : "local",
"hops" : "0"
}
newMsgs.message = contento;
localStorage.setItem( 'outbox', JSON.stringify(newMsgs) );
updateOutboxView();
checkOutbox();
document.getElementById('message').value = '';
//localStorage.setItem(
// new Date().getTime(),
// document.getElementById('message').value );
//updateList();
};
function checkOutbox() {
var outStr = localStorage.getItem( 'outbox' );
@ -47,8 +193,10 @@ function checkOutbox() {
if ( lines[i].length === 0 ) {
continue;
}
var ts = lines[ i ].substr( 0, lines[ i ].indexOf( ' ' ));
var msg = lines[ i ].substr( lines[ i ].indexOf( ' ' ));
var obj = JSON.parse(lines[i]);
var ts = obj.message.time;
delete obj.message.time;
var msg = JSON.stringify(obj.message);
sendMessage( ts, msg );
}
}
@ -74,7 +222,8 @@ function removeOutboxItem( timestamp ) {
var outStr = localStorage.getItem( 'outbox' ) || '';
var lines = outStr.split( /\n/ );
for ( var i in lines ) {
var ts = lines[ i ].substr( 0, lines[ i ].indexOf( ' ' ));
var obj = JSON.parse(lines[i]);
var ts = obj.message.time;
if ( ts === timestamp ) {
lines.splice( i, 1 );
break;
@ -92,36 +241,54 @@ function updateOutboxView() {
if ( lines[ i ].length === 0 ) {
continue;
}
var ts = lines[ i ].substr( 0, lines[ i ].indexOf( ' ' ));
var msg = lines[ i ].substr( lines[ i ].indexOf( ' ' ));
var obj = JSON.parse(lines[i]);
var ts = obj.message.time;
delete obj.message.time;
var msg = JSON.stringify(obj.message);
contentString += '<li><b>' + ts + ' </b>' + msg + '</li>';
}
document.getElementById( 'outbox' ).innerHTML = contentString;
}
function doStuffForEach( entriesString, stuffFunction ) {
if ( ! entriesString ) {
return;
}
var lines = entriesString.split( /\n/ );
for ( var i in lines ) {
var ts = lines[ i ].substr( 0, lines[ i ].indexOf( ' ' ));
var msg = lines[ i ].substr( lines[ i ].indexOf( ' ' ) + 1 );
stuffFunction( ts, msg );
}
}
/*
* INBOX STUFF
*/
function updateInboxView() {
function updateInboxView() {
var localStorageArray = new Array();
var contentString = '';
for(var i in localStorage)
if (localStorage.length>0) {
for (i=0;i<localStorage.length;i++){
element=localStorage.getItem(localStorage.key(i));
if ( localStorage.key(i).length < 10 || element === 'outbox' ) {
continue;
}
// alert(element);
try {
elementj = JSON.parse(element);
} catch (e) {
continue;
}
localStorageArray[i] = { time:localStorage.key(i), user:elementj.name, message:elementj.message, node:elementj.node, hops:elementj.hops };
}
}
orderStorage = localStorageArray.sort(function(a,b) { return b.time - a.time } );
for(var i in orderStorage)
{
if ( i.length === 0 || i === 'outbox' ) {
continue;
}
contentString += '<li><b>' + i + ' </b>' + localStorage[i] + '</li>';
var date = new Date(parseInt(orderStorage[i].time));
// date.setHours(date.getHours() + 2);
var datereadable = date.getDate()+"/"+(date.getMonth()+1)+"/"+date.getFullYear()+" "+date.getHours()+":"+date.getMinutes();
contentString += '<li><b>' + datereadable + ' </b>' + ' <i>'+ orderStorage[i].user +'</i><br/> '+orderStorage[i].message+' <span class="node '+orderStorage[i].node+'">'+orderStorage[i].node+'</span> <span class="hops '+orderStorage[i].hops+'">'+orderStorage[i].hops+'</span></li>';
}
document.getElementById( 'inbox' ).innerHTML = contentString;
}
@ -134,13 +301,28 @@ function onMessageDownload( msg, filename ) {
}
function onIndex( index ) {
var lines = index.split( /\n/ );
for(var k in localStorage){
var l = 1;
for ( var i in lines ) {
var f = lines[i];
if (f == k){ l = 0; }
}
if (l == 1){
localStorage.removeItem(k);
}
}
updateInboxView();
for ( var i in lines ) {
var fname = lines[i];
if ( localStorage.getItem( fname ) === null ) {
//localStorage.setItem( ts, lines[i].substr(lines[i].indexOf(' ')) );
downloadMessage( fname );
}
}
}
function downloadMessage(filename) {
var xhr = new XMLHttpRequest();
@ -173,7 +355,7 @@ updateOutboxView();
window.setInterval( function(){
checkInbox();
checkOutbox();
}, 10000 );
}, 7000 );
</script>