;XP7*ii2MAR}W)8QE
zM@Zlu?vRaqWXP?q6?@hu`zq2AVDHh7ShV3
zA8v2l&qUT+zF6*K9U7Rt7Vx?{U;fYu$R@)nLe@m*($Etb4wXC;kQWL`kVJF#8}tNrCUIri
zj(9^tWR!qJY+tL7awtahWT2zP9bQxPVR@t^&4Lfd^T&SZ4U(A~aWc$YXVfusKyxi2
zEWKzW!y_q{`1D26^}I{t-%tsdH1pw^20
w%rr8ASV=`Bot};FN^+F|1ROQ*x*-AK91vuZOTthMfByufte^ocmV52@Kd8?HPyhe`
literal 0
HcmV?d00001
diff --git a/coder-apps/pi/coder/static/media/newapp_tip.png b/coder-apps/pi/coder/static/media/newapp_tip.png
new file mode 100644
index 0000000000000000000000000000000000000000..e6e44db7dcd622a37482d68827c2f8aab7cfa1e8
GIT binary patch
literal 10125
zcmaKSby!>d(k&E<6$#K{r4Tf@2MHxef1r
zOOz9j`MnR1x04Gx8Vw`q?P6}}VC@dHu(m}yOEB(#?qCEWtt1%rz^Z(zE--65q=K)j
zwU#eJ+tSy;Qp}1`5(<3o4M77qS-YD9y`3DL-5}l)jQ`+5(A)QBUPjhX
zUsW9lL%CW5!90T8mVEsDKw)7Xej%{1Fh3VifRA5*mk<3G=H?fIfQ2CfBEWyY7}2Y_
zTG>D};d1|83%w`7Xy@+k0^#NL^77*G668U-+Vb*?iHY4~2ncYaJ-FR`oZZd6xt-mZ
z{(}IwcC&Ovy0{}z&cJ&_a|@J*y96UT(|?uV1KM&u1N&j(F
zRsH|FIywE1x0}1B_5bGk{{(i^_HnW1)wFg)dAM4l8)w6Gzmy9E=4x&3j&jvTp&b9i
zqPiW*9pz?+ask4$gn?|T=9Wn3djtDF5vr;XMQ1m6b7xCyMYseb8iNOkw1UV8@d=B-
zq(xj!$PQZVPh9LiYF2es6@87Xj|2-Fx{~F7SPKNiswf|SE|6@Yy=YI2lgp1z!
zAL&~=qxJ5J7WPli#xo2IraMKrw6^!$jxn;9cFF^KyfiV<{%BV`;+fTl^Z-&bzI3Xm
zeNtSSkt}gOSa}
z=boOio^AUL`<*rV+W6XBwr%^a5S{@H`@Le;onp@T`-I
zj07qO!5No#+J^f@a#UP2wG%PRY2A;kLGsx~yH*8B8ml)>vQswsEB;;mf)#%OaG12M
z3v|EV9Yn8}hzZdqsObIfabyXicRQj6or%s?TL~Gp9(yMwnL8pFN$f1i*NK6N4%tS4
zbmhTp6>`b($qKl@aQ`EOF8f&auXR=l;=n8-G%lJ8QKhWlrX$K=;iFW0-n1zCKRC;@
zCd*cD>>)F_!^@zY@2+(t{g48!^!zP&@~928EK^y!ky_M~-s7m@MAS=Oyaj%J;DrYm
zK30-qrVfF_m@N|Q1xt|5u1C)fnG^*1vIv{FBrgbce+d(s7sJl}&!$5x@%V)S9lTH|&dla|LZ^R7#v32CByal2
z)=+z|K#V+s67uX~!vXHq0Pe6hL?9E7GeH4DDIJ|)+6kUjoN7&8c@>{C95OgU
z(bZT56Hl4^_=B|3x)YEy(=al*SNQyWIu(fFMxeHK!;D@@cV=Tf@UZ@)YGp8t
z3b9V=NOvDvJk`nw)tt%hNN)>xe}19%P?!`=K^+F|*A6ntajUaK84w8X82=y0ATxm0
zv0?rc&}HAaV)4E)Wlw>WDvI7xzF}Sbtwf~~>_(5A00RY`7TQvh^6
zoIqL7CQO1=0=C%w`Zy0Q!UX>KS2)K-kPxTzv4BFpSkQgoq&?r+md;IVfzvv5n3i{p
zOIa3h3_xvObY8Fr>ymgl24tuBC5cu!BRMbt`&uYH0;%|+)a(zs03wqkwlxeXlmDYeNx-^63`I+$4-c0VX(XY3qkxx
zIDXIqRElTo@efZsq#bn|b5fxhT!DsPyzRmNK&SV|vKR)kN?`iGR}%WBGcnA1p9V!|
zl{29SDn6IAj9N)}ZRYtuzIe;s3#Fz}YFGGmUD~hD@C6Up5-b1`*e
zC86}Owx0gSrUa?pUunzILYyMVZrwn~ZypWbXO3lt
zO|S)h1%y5_mEONGShaMJdldc|ADtKGX@&0bYci6c*ZLySYD`_gdp%DQiH1yzUCpzS!F
zWfL@@pMZU!@cT_s5WTKeR6r*230OIv3uboLxt#QIpi0FP`41IBPJgw(+AfEkiQC!Fh#
z3{w>NDF4hO=*ahLy6*N}jtoFBR5-b&2<+x#N*XK#?vK^tr^ofGPq4mc=3$it;8`??|VXN%KA-+W~
z1t}ixt}~gCR1HFf9(BT7PVD(FzReG<6{Q}p?8l{>_54viR2*&I{zd%vcxU|1iyirl
zKt+H7Gr=yx-DC1ay|wg
z#sc_e_S&NX!9pSUSqg$;BQ+2CC3my%K65mXg_pcQQLYf_x%-ZL6t03542-i1yX0ft
z-j7sb-2D>NR25NH2F<;(x0l|Yt-38#Rg!xe`qNLx5Av83EwA-3qd6T;%$Mj(U||TM
zTYZkow38ewEaf%MXWel-#y^@)c-ZwYMcX!e_;zyg()iu!LmLB1{0R62J%(b8x|4O3
zJ!@%2keN+|;-nlS(vS>77C%VA@=WEi?7(=+Y5;8W9h5fRoEb=_s>+AE%f>2iYZ;-wmZ8*}H`GRkicZ*zz)TLggg(Lqok;d4it
zLd@V8z1WRKM75`Y+O><@qecowkRgdmb&irSkpA4;M2~E)Dsu#}MQuZ$yq+Xq7(7Ao
zfm^@u?@dLQ3Ms@ja&s*3llEr=PUsKY#!O?*zh}f#F~kqoVTD`rA(#b5Y*15fXy3RH
zBMx3%ndk8*0=ZAAocQK6U!h{Y!hYm=9{C-zixMj(U3t*R3c38Y
z@V*R1R8#J+Tc1lxd&^*c*wH0_#Hn?y+xiN(J*Uk+4E*HqL}aX?BIC8y^sCKv$3q_@
z1wL#I8eq<^o|=w_s<^?gsSx{i6vq`E5jgOW7TIqSSNx!T^8Mc|T>!YRRMKoA>(tun
zF{FF4F0!y?%JnkT&QS;-HkC&q`5{4Z48IU6mF1(_zex(>jiiVCOq2hzrCb=?B%s}x
zuEsVNQ$PU&WUSU^YEZQpaxCQ?XWEO&<3PrORN{YbxPo4BFHue`kxY|^MG4-9QK{5!
zk-7kR-H!4TNmSm4;o_X4ujG`3n+%IP>sqVFTc_dWlU0gs_>2x_lLXzXBan;Oq*Coc
zWcsnFLt*d_*TSR@SJ%+d6=gsyyfjnYg{nI(w7?Y`$2lz*ZmKFP1SBWa%Hv#G@hd*}
z=fjp3qVxWCR+)MnlVmF9BXi9>>QdrU9f`vnnw*g)c9ESV_^jRbB9%{UXih&tc#Lf>rKITPWECGm6JVjWIA
z^Ry{moRkp)K30h`+F4gdP$XL6Sx6y8BlvD3ljx()HfZgz$2!NNPcrY=p}kngUjaRx
zFm%B`Umj^4R!UDgB6O66fKT3eo!2EfypA&sq53U#@RLbN^lhb&j>o7e7>=dc_4*rc
z1j*MTRu~`@3w@uqu({s1S}I1n^QbVG1P3<#YJZ*eaDosogvuzW2B&+uIl_y=vVr4S
z@H-%l9nW#jb<%ludv=uqAoRuOuGVr?q!$$mVX0KikZG_QdI*(P4Flp$Su9t!O!a%E
zcn>71ve@p4f>WR>i41sO9zQN>G1C$Uc%m34=c3?7e2@{T~S99Qqupchsrci$4+sjZ3P&qGO|`ibk2I|rBB
zHSz+TLAL&Ji9_sUZp3|&I84gv%sw7WDkTpeQG9DaPFNjp4%OLDRhZmKC(X78_%)31
z_tADQW%5fM*?IYs!DBIOvumOT)<0%EwrH(@y3y!N91n}D?X_R+DY#|uTZY9@@ynd<
z&Qp@A`uTm%e)*Mp@ydC=iQ5;8gjxXpHTEf`;&~X(7bowC+Gq1FD(tT}5V_CADqzdlZ>vA8WZefYYM3^uFj_zvz3|Q(cdq;kT@gQxo(2y=o9KsihT7zAk_LK&CnZee
zinoXGmz`S>{uSz02jY)GG;oX&PuBz?J{ug8u|nDCb+3*qV=EbB_c7Tqkn^&2nlS~K
z&-shxTM3G`U|6>60Q=a7pbXPA(?Fk^VV9o)XZ<>Q#wjzT>U&ru&jCmT!?4(|Y=xvc
zfxV8q+cGFl{OTJ@lbM^*wFSSky_8T~lG8cfDpT%Xa*HpopQ2hxcX4+~NYq-n57do(
zHd5Lm-Wla+n2OWw5-<244~^ZvmAv(yty@!Y+f_W{<)XxXaxH9FN$BM{i(=&Q0J2^eR6Z+{Z_~77$jnwwH1hpP-6(~WojZFjdrB4?H|*EXlIAWxXa842zppVX5_fN>ek5+#n&V_=
zw!Wyj&;w&f(Eayl(F?<&=aWQN+5SiUW1av7T5sTZ_WbERgGD!^(l2T8rW@$uN&bZ2
zI9e5H=D_O7^VquQiZo{<+p4}dma;p<3oaVz0jr#F+o|hLyIuw1RpotYkd^pwS7lH?aEau|Ih(P3
z8{3|xi@j@e@_TGZ2q0kep6NWmtLY(3>v*bZ;O*_ylF1>%dz=lrxCTB+f(DXS@Ysc^
z|6y++D#^Q4)wjO8OCOcPnPPmrT`kih2LvXI{PY)``$;F@7@EhyylA+u^_z+PjndV!
ztSD-!b?=kRc1UOcivf^cxk~(?mYT#9Qo=dxjT8>fJ|=ZHonV6riW%*Lk(hqJ-B@=Q
z*dZ&v#E~~vJ}mW9M(w_G*W|)8anF;vxaQUIV&%^Pl)qbFmO8h=lQtJ@Y?vD#E{&VN
zSdNVlVQ3(5zcu`mU9#i5XIktk4fGDpTAOb$jDNJ(cHylQyHMO_vlHGPTT!4
z;iWh|isbkx&wDj;EEa}a&+uYflZX$^B
z9-m2SR7!?Ei3qAa2Vx!;-*2r?i;VU&BtQ;3@B&XP&;2t2P6OQB3jPlZeM*z|yTemU
z4Rs0B>+-?d5r=WMb8!zf3Szh(oE}InU7wH8VWf-wUOr#c>k27BGGA0xadVw>Z{4|i
z5YNxk{b;ZTi6u-J`DMDUO*>i}EL(_af3g7(yBzjXdQJDrEfOM6@vjWI&a2CKU9Wbg
zn1j+6m}V#_gZG>i&eXl1RcISs-EIE46*2dN{dAs$t!|o+NasyRUmrhF_w{Gc{mNdp+76wyC;wMzHxC05(?F;IC+0d3ORQ%
z!45aD5h7Gxb)uH+NI@RgaygjbsYoh7^v1#A2J1>w+~qmSQ&S?LHHcbclG-%F&cy+#$X`-+-syo4NLXFJ
z%nYePYMzn560K4RwH~TB7#w+itkUn+TO-CEO$)F|P@WhzH%?e2;*cbhV_?AwIu3T>
z%MyJ~DHP^n!8E|rtTuP5^5}WYh5#$04~^?(GOV?&`;;>!VXRIhH}G;xeo;9~0MOAv
zC97mlf1)<1oaAw}4$taR84E2Lqum0Q%=;{L{CpXe+p}P!uZm
z-Tvg;`l)No#5?Hwj+~(ZIE`X*q~+?bMnVTgsc@M-SzO#<$ad9ls^cfg;(D~4x>XU7
z_Y!!ivc$JB%!TD8)iWe+pmKDuxfD~6WC2w5yGpEoNli%Uy#+6Qav{>72HJ+|zvIf6)
z<$mCxG%#|0YAd#4j*d;NKPqn`e6;(ONH%v)=>r)XE`C{+N~<{^J3zn9*W-lRMi~~)
zR$Mm^mrus*Iy+F+*)u8zWJ&{7nPZh?z#g)n8B7KL)R}jO**!g)Ev)vWRoF<;GufKD
z-8e1(MRP@vI@h%~c=0Rmr|N1-ygRScu;%rx;Z?r>MBL88@9-669<^Pq(yVnbOXfFu
z%Rj2!W^wf&hu+35QCh#QFPZ?}dUkhP))L1ja}zx)GrVUDPgS0n{OL+(yC|wW{;&NX
znqi8jrZ)7IlB4NWOMrdLSLXMGJv1gHc#}=ier2rRcyehb6fmX*-8R2}zw%z_>AonK
zO-Ge1o%WM1$N5P6yB-yi4%tuHeAhHXVgrt{PM8}v`PAJD@l5jWvV_eL?y>-O5TJiQ@Snwu^MoBG?a*$BgoOM=}m
z0&y$BT|{qc@@obi(LAx?5*LO-iI*I%r1RBjImr9NH$0dj(feLMz|J|e_&J9
z>mQ6oLDEWu8$P=*fRyp>?hS9Q#Qbp=X-g^uvefK*-&j4W?m`=8)7{joUX2Vvp7vK#
z=Oj;!3K2Ry9n1~CS%W5>xApvGb*ex8(A0e7h%9LMP2$u-EcLQ0Q~qt&`0fvzDd#X7
zw>L>BWh^}zHcCfIl3b1_DmtpvFFj_%GB|UX*rSc~iH9S<+x^bpKreKhI&e#wTWNGN
zXpPO~G%({&ACVdfrnaAg(uV(?kB>s{YN&jPSW1qVx
zjCN)0vyOkAt36n!Y(+omTcO|Ka@e+YeRU5I^KnWCxn%96PMlpl_O>OW9jFo}iyqh{
z;V0EY_ts4gKlro(rVUTUFB+46MYW0oWS&%TX_~pC`fvG`~FmBKpe-p<8={7os37
zAnApE4#*NZmKcFErlSVA2(}l+mIf57l>CHTWOA@+5{+SqrxXJMXL?VW@fd@x_*kAj
z>UjnpNKUnr;)&S1LV;?OMhN`SF`X13Q`e)~88Ux=p0>{aE6-X&!$d4VcByB^21WNh4oxo?!sTz?*e^zLfU9
z%~11j^-tNPj-!{UV~8lhWL3PC0aJ55`J`s=9{$+=+gjA5lL+LZ@O+XCsi8eoBBd1t
zbR~LT>5>MlA@r|zC?4L)YJ7h1%)T`C<&lqK-e_w+P3(n}p^qd!qgQP$#d?VhBEuwq*9j;vxWN<(Z_FEroQH&Z1m1K`4)>+)6Mv0_ShAShmqX$gh3tdJZ5)umkf%G)SY
z)qzkm5YbV(wfm@$p9uR9Y>PWbl;(moA!E$kZVxv55$$ylVpfZD
zN{lq4J7{O5!MYX_OtF&^ee_173wultJJf7$N{R2R3@7yO9Hh7_01hKj}37uRzdrBNiOOn8Nm&d5i$
zJOUhM_;aOG&%f~frQ2F7_|r-ri#cr3GuJxZKZKxQ!zaZ~AOzrAnDYI_?Vd=Fex0JE
z8<{5@Wbgy`*dH?%m>KV~32B+@CqgV0%;Q-$mBx3n$tU
z?%HeS{94CCP$=IVieQh$U}M?x{E#=S-UL8J*jA(*X7l9h%M2pEVjSboULhzXl^#ym
zHzgH(G1s*)d(cjSJ<-*Cl%7vwgro){1sg!D?H0y3?z!4n^#--%X5s8PP#|Xk}EL)
zV-VnIGn^Y~d>mrqbK@r5%)ubprAXFE*JZZcD!5xoramY#Z#AgQGOI`Nh=In5U{~Wo
zR~aC?1r=d>;ZbidAfDnKN0=Ihq$tE6t0;LAa?1+ID1IGUwl;g|S3yRu82+0xwin8O
zS^uIRJ9D5?i37z}9;8l;^G<+mTHHada7*V^8`6cD>C!tfkx=df--lz-!^oDB?N>}G
zL0^hZMN7jKlzB$=`Ht#Uhv=~TyP88378+N(Z%E3@Cwp0+io&&`{JBUW5!NAzgwVI7
z9j@JuECm^#o^@|IG1C@ef7nqM@R7YP0|-&Z5E`VvqKvdYAXOzhd~Gug0Jll;OeDMK
z-*Bmu6T*d*e6OL*d0o{`ai*!C`
zRA&-He?{f-U>;>C43(Cf8_Jhnsx0euv5j*4Eth&>P-HVk$CBLL^+Cp!VeI6|Mf!w~
zp_!}nv6w-mUA8@RSH@JWsDYH}LHq;SnyZszCl+%5pB0-Ux_^rtHDqQ>u4t2U&r=xj
z-@qsAj`-^xLdZshp4drI+2mCTnD}Rp09I=K+Ti2mf$`lH*6wBa$0cN6DZnp<*)w*u
zV|!#y-J!D&PE24Dvw4{%klqf0JqaU}s
z(6{ON5#|&tM7!Qw92lK*8IX3{VT#{l)g0()C)NaH0F#joUBzBcU6qc=|QE=*yc?_#*~
zZj41j@a#uPoh?(LGlz3n%3UgSR
z)MN_M&D1Z6QgRan=KJV|d2VWC@Ae7lW>Tp#s5A_4ZiYK!wg@BMQ6+mY&y0Lefhp1A
zV*OMs3wH1Q7Ltv&i
P{~)I*i-1?ln1%cwqFIYv
literal 0
HcmV?d00001
diff --git a/coder-apps/pi/coder/static/media/settings_tip.png b/coder-apps/pi/coder/static/media/settings_tip.png
new file mode 100644
index 0000000000000000000000000000000000000000..6c813c3308b2406c1f5a35ff1477eedd9232a5ac
GIT binary patch
literal 7218
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
GNB$oUXgS6J
literal 0
HcmV?d00001
diff --git a/coder-base/views/apps/coder/index.html b/coder-apps/pi/coder/views/index.html
similarity index 100%
rename from coder-base/views/apps/coder/index.html
rename to coder-apps/pi/coder/views/index.html
diff --git a/coder-base/apps/wifi/app.js b/coder-apps/pi/wifi/app/app.js
similarity index 100%
rename from coder-base/apps/wifi/app.js
rename to coder-apps/pi/wifi/app/app.js
diff --git a/coder-base/apps/wifi/meta.json b/coder-apps/pi/wifi/app/meta.json
similarity index 100%
rename from coder-base/apps/wifi/meta.json
rename to coder-apps/pi/wifi/app/meta.json
diff --git a/coder-base/static/apps/wifi/css/index.css b/coder-apps/pi/wifi/static/css/index.css
similarity index 100%
rename from coder-base/static/apps/wifi/css/index.css
rename to coder-apps/pi/wifi/static/css/index.css
diff --git a/coder-base/static/apps/wifi/js/index.js b/coder-apps/pi/wifi/static/js/index.js
similarity index 100%
rename from coder-base/static/apps/wifi/js/index.js
rename to coder-apps/pi/wifi/static/js/index.js
diff --git a/coder-base/views/apps/wifi/index.html b/coder-apps/pi/wifi/views/index.html
similarity index 100%
rename from coder-base/views/apps/wifi/index.html
rename to coder-apps/pi/wifi/views/index.html
diff --git a/coder-base/apps/.gitignore b/coder-base/apps/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-base/config.js.default b/coder-base/config.js.default
new file mode 100644
index 00000000..19052a53
--- /dev/null
+++ b/coder-base/config.js.default
@@ -0,0 +1,28 @@
+
+exports.listenIP = null; //Defaults to *
+exports.listenPort = '8081'; //the SSL port things run on
+exports.httpListenPort = '8080'; //this will all be redirected to SSL
+exports.cacheApps = true;
+exports.httpVisiblePort = '80'; //forwarded http port the user sees
+exports.httpsVisiblePort = '443'; //forwarded https port the user sees
+
+
+//SSL Info
+exports.country = "US";
+exports.state = "New York";
+exports.locale = "New York";
+exports.commonName = "coder.local";
+exports.subjectAltName = "DNS:192.168.0.1";
+
+
+//Experimental
+//
+//Status Server
+// This can be used in conjundtion with the sample findcoder
+// appengine project. It allows multiple Coders on the same
+// NAT network to be discoverable. Coder devices will ping the
+// external server with their internal IP, and the server
+// will list the devices for any requesting machine that
+// originates from the same external IP.
+exports.statusServer = '[yourpingserver].appspot.com';
+exports.enableStatusServer = false;
diff --git a/coder-base/config.js.localhost b/coder-base/config.js.localhost
new file mode 100644
index 00000000..3aa5890b
--- /dev/null
+++ b/coder-base/config.js.localhost
@@ -0,0 +1,28 @@
+
+exports.listenIP = '127.0.0.1'; //Defaults to *
+exports.listenPort = '8081'; //the SSL port things run on
+exports.httpListenPort = '8080'; //this will all be redirected to SSL
+exports.cacheApps = true;
+exports.httpVisiblePort = '8080'; //forwarded http port the user sees
+exports.httpsVisiblePort = '8081'; //forwarded https port the user sees
+
+
+//SSL Info
+exports.country = "US";
+exports.state = "New York";
+exports.locale = "New York";
+exports.commonName = "coder.local";
+exports.subjectAltName = "DNS:192.168.0.1";
+
+
+//Experimental
+//
+//Status Server
+// This can be used in conjundtion with the sample findcoder
+// appengine project. It allows multiple Coders on the same
+// NAT network to be discoverable. Coder devices will ping the
+// external server with their internal IP, and the server
+// will list the devices for any requesting machine that
+// originates from the same external IP.
+exports.statusServer = '[yourpingserver].appspot.com';
+exports.enableStatusServer = false;
diff --git a/coder-base/localserver.js b/coder-base/localserver.js
new file mode 100644
index 00000000..1ef8034f
--- /dev/null
+++ b/coder-base/localserver.js
@@ -0,0 +1,177 @@
+/**
+ * Coder for Raspberry Pi
+ * A simple platform for experimenting with web stuff.
+ * http://goo.gl/coder
+ *
+ * Copyright 2013 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+var express = require('express');
+var io = require('socket.io');
+var net = require('http');
+var http = require('http');
+var https = require('https');
+var crypto = require('crypto');
+var path = require('path');
+var config = require('./config');
+var fs = require('fs');
+var util = require('util');
+var cons = require('consolidate');
+var params = require('express-params');
+var querystring = require('querystring');
+
+var loadApp = function( loadpath ) {
+
+ var userapp = null;
+ if ( config.cacheApps ) {
+ userapp = require(loadpath);
+ } else {
+
+ var cached = require.cache[loadpath + '.js'];
+ if ( cached ) {
+ userapp = require(loadpath);
+ if ( userapp.on_destroy ) {
+ userapp.on_destroy();
+ }
+ delete require.cache[loadpath + ".js"];
+ }
+ userapp = require(loadpath);
+ }
+ return userapp;
+};
+
+
+var apphandler = function( req, res, appdir ) {
+
+ var appname = req.params[0];
+ var apppath = req.params[1];
+ var modpath = appdir + appname;
+ var userapp = loadApp( modpath + "/app" );
+
+
+ util.log( "GET: " + apppath + " " + appname );
+
+ //Redirect to sign-in for unauthenticated users
+ publicAllowed = ["auth"]; //apps that are exempt from any login (should only be auth)
+ auth = require(appdir + "auth" + "/app");
+ user = auth.isAuthenticated(req, res);
+ if ( !user && publicAllowed.indexOf( appname ) < 0) {
+ util.log("redirect: " + "http://" + getHost(req) + ":" + config.httpVisiblePort + '/app/auth');
+ res.redirect("http://" + getHost(req) + ":" + config.httpVisiblePort + '/app/auth' );
+ return;
+ }
+
+
+ if ( !apppath ) {
+ apppath = "/";
+ } else {
+ apppath = "/" + apppath;
+ }
+
+ userapp.settings.appname = appname;
+ userapp.settings.viewpath="apps/" + appname;
+ userapp.settings.appurl="/app/" + appname;
+ userapp.settings.staticurl = "/static/apps/" + appname;
+ userapp.settings.device_name = auth.getDeviceName();
+ userapp.settings.coder_owner = auth.getCoderOwner();
+ userapp.settings.coder_color = auth.getCoderColor();
+ if ( userapp.settings.device_name === "" ) {
+ userapp.settings.device_name = "Coder";
+ }
+ if ( userapp.settings.coder_color === "" ) {
+ userapp.settings.coder_color = "#3e3e3e";
+ }
+
+ var routes = [];
+ if ( req.route.method === 'get' ) {
+ routes = userapp.get_routes;
+ } else if ( req.route.method === 'post' ) {
+ routes = userapp.post_routes;
+ }
+
+ if ( routes ) {
+ var found = false;
+ for ( var i in routes ) {
+ route = routes[i];
+ if ( route['path'] instanceof RegExp ) {
+ var m = route['path'].exec( apppath );
+ if ( m ) {
+ userapp[route['handler']]( req, res, m );
+ found = true;
+ break;
+ }
+
+ } else if ( route['path'] === apppath ) {
+ userapp[route['handler']]( req, res );
+ found = true;
+ break;
+ }
+
+ }
+
+ if ( !found ) {
+ res.status( 404 );
+ res.render('404', {
+ title: 'error'
+ });
+ }
+ }
+};
+
+
+var startLocal = function() {
+ http.createServer(localapp).listen( config.httpListenPort, '127.0.0.1' );
+};
+
+var getHost = function( req ) {
+ var host = req.connection.address().address;
+ if ( typeof req.headers.host !== "undefined" ) {
+ host = req.headers.host;
+ if ( host.match(/:/g) ) {
+ host = host.slice( 0, host.indexOf(":") );
+ }
+ }
+ return host;
+};
+
+//Traffic on 127.0.0.1 (localhost) only
+var localapp = express();
+params.extend( localapp );
+localapp.engine( 'html', cons.mustache );
+localapp.set( 'view engine', 'html' );
+localapp.set( 'views', __dirname + '/views' );
+localapp.use( express.bodyParser() );
+localapp.use( express.cookieParser() );
+localapp.use( express.session({
+ secret: crypto.randomBytes(16).toString('utf-8'),
+ store: new express.session.MemoryStore()
+}));
+localapp.use( '/static', express.static( __dirname + '/static' ) );
+localapp.get( '/', function( req, res ) {
+ util.log( 'GET: /' );
+ res.redirect( '/app/auth' );
+});
+localapp.all( /^\/app\/(\w+)\/(.*)$/, function( req, res ) { apphandler( req, res, __dirname + '/apps/'); } );
+localapp.all( /^\/app\/(\w+)\/$/, function( req, res ) { apphandler( req, res, __dirname + '/apps/'); } );
+localapp.all( /^\/app\/(\w+)$/, function( req, res ) { apphandler( req, res, __dirname + '/apps/'); } );
+
+
+startLocal();
+
+process.on('uncaughtException', function(err) {
+ console.log('WARNING: unhandled exception: ' + err );
+});
+
diff --git a/coder-base/package.json b/coder-base/package.json
index a107a3d6..a773197a 100644
--- a/coder-base/package.json
+++ b/coder-base/package.json
@@ -10,6 +10,7 @@
"consolidate": "0.8.0",
"socket.io": "0.9.13",
"express-params": "0.0.3",
- "bcrypt": "0.7.4"
+ "bcrypt": "0.7.4",
+ "connect": "*"
}
}
diff --git a/coder-base/server.js b/coder-base/server.js
index 160663f7..32b6c614 100644
--- a/coder-base/server.js
+++ b/coder-base/server.js
@@ -70,7 +70,8 @@ var apphandler = function( req, res, appdir ) {
auth = require(appdir + "auth" + "/app");
user = auth.isAuthenticated(req, res);
if ( !user && publicAllowed.indexOf( appname ) < 0) {
- res.redirect("https://" + getHost(req) + ":" + config.httpsVisiblePort + '/app/auth' );
+ util.log( "redirect: " + "https://" + getHost(req) + ":" + config.httpsVisiblePort + '/app/auth' );
+ res.redirect("https://" + getHost(req) + ":" + config.httpsVisiblePort + '/app/auth' );
return;
}
@@ -234,6 +235,9 @@ var getHost = function( req ) {
var host = req.connection.address().address;
if ( typeof req.headers.host !== "undefined" ) {
host = req.headers.host;
+ if ( host.match(/:/g) ) {
+ host = host.slice( 0, host.indexOf(":") );
+ }
}
return host;
};
@@ -265,7 +269,7 @@ var redirectapp = express();
params.extend( redirectapp );
redirectapp.engine( 'html', cons.mustache );
redirectapp.all( /.*/, function( req, res ) {
- util.log( 'redirect: ' + getHost(req) + " " + config.httpsVisiblePort + " " + req.url );
+ util.log( 'redirect: ' + getHost(req) + ":" + config.httpsVisiblePort + " " + req.url );
res.redirect("https://" + getHost(req) + ":" + config.httpsVisiblePort + req.url);
});
diff --git a/coder-base/static/apps/.gitignore b/coder-base/static/apps/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-base/sudo_scripts/reboot b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/reboot
similarity index 100%
rename from coder-base/sudo_scripts/reboot
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/reboot
diff --git a/coder-base/sudo_scripts/setpipass b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/setpipass
similarity index 100%
rename from coder-base/sudo_scripts/setpipass
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/setpipass
diff --git a/coder-base/sudo_scripts/wpa_cli_apscan b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_apscan
similarity index 100%
rename from coder-base/sudo_scripts/wpa_cli_apscan
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_apscan
diff --git a/coder-base/sudo_scripts/wpa_cli_scan b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_scan
similarity index 100%
rename from coder-base/sudo_scripts/wpa_cli_scan
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_scan
diff --git a/coder-base/sudo_scripts/wpa_cli_scanresults b/raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_scanresults
similarity index 100%
rename from coder-base/sudo_scripts/wpa_cli_scanresults
rename to raspbian-addons/home/coder/coder-dist/coder-base/sudo_scripts/wpa_cli_scanresults
From 302812abf74487390b803d082f6510fdc9f4237a Mon Sep 17 00:00:00 2001
From: Jason Striegel
Date: Tue, 22 Oct 2013 20:38:00 -0700
Subject: [PATCH 04/37] force adding empty views directory to git
---
coder-base/views/apps/.gitignore | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 coder-base/views/apps/.gitignore
diff --git a/coder-base/views/apps/.gitignore b/coder-base/views/apps/.gitignore
new file mode 100644
index 00000000..e69de29b
From bce619090f26311296d0f088e1d4b1dc5c3dc418 Mon Sep 17 00:00:00 2001
From: Jason Striegel
Date: Wed, 23 Oct 2013 00:11:03 -0400
Subject: [PATCH 05/37] Update README.md
---
README.md | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 55cb19f9..4298e1e6 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,10 @@ http://goo.gl/coder
### What You'll Find Here
#### coder-base
-The Coder node.js server and applications that come pre-installed
+The Coder node.js server and application files
+
+#### coder-apps
+The Coder applications that come pre-installed in the Coder distribution
#### raspbian-addons
Modifications and additions to the stock raspian configuration and init structure
From 59b284c7978ac8cd77d647ad7176d2b41a582fd4 Mon Sep 17 00:00:00 2001
From: Jason Striegel
Date: Sat, 30 Nov 2013 14:01:03 -0500
Subject: [PATCH 06/37] allowing pi sudo scripts to be executed
---
raspbian-addons/etc/sudoers | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/raspbian-addons/etc/sudoers b/raspbian-addons/etc/sudoers
index ba5fa1c7..7caa1d4a 100644
--- a/raspbian-addons/etc/sudoers
+++ b/raspbian-addons/etc/sudoers
@@ -27,4 +27,4 @@ root ALL=(ALL:ALL) ALL
#includedir /etc/sudoers.d
pi ALL=(ALL) NOPASSWD: ALL
-coder ALL= NOPASSWD: /home/coder/coder-dist/coder-base/sudo_scripts/setpipass
+coder ALL= NOPASSWD: /home/coder/coder-dist/coder-base/sudo_scripts/*
From b17850741b3ca289167e08e6b04d9ed0cf48270e Mon Sep 17 00:00:00 2001
From: Jason Striegel
Date: Sat, 30 Nov 2013 17:56:59 -0500
Subject: [PATCH 07/37] node location moved. fixed pi app install script
---
coder-apps/install_pi.sh | 6 +++---
raspbian-addons/etc/init.d/coder-daemon | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/coder-apps/install_pi.sh b/coder-apps/install_pi.sh
index dbc92e7e..9f9f4583 100755
--- a/coder-apps/install_pi.sh
+++ b/coder-apps/install_pi.sh
@@ -19,7 +19,7 @@ base=$1
./install_common.sh $base
-./install_app.sh auth $base ./common/
-./install_app.sh coder $base ./common/
-./install_app.sh wifi $base ./common/
+./install_app.sh auth $base ./pi/
+./install_app.sh coder $base ./pi/
+./install_app.sh wifi $base ./pi/
diff --git a/raspbian-addons/etc/init.d/coder-daemon b/raspbian-addons/etc/init.d/coder-daemon
index cf31ab81..34fc9940 100755
--- a/raspbian-addons/etc/init.d/coder-daemon
+++ b/raspbian-addons/etc/init.d/coder-daemon
@@ -17,7 +17,7 @@
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="Coder Server"
NAME="coder-daemon"
-DAEMON=/usr/bin/nodejs
+DAEMON=/opt/node/bin/node
DAEMON_ARGS="/home/coder/coder-dist/coder-base/server.js"
DAEMON_PATH="/home/coder/coder-dist/coder-base/"
PIDFILE=/var/run/$NAME.pid
From 012518dc78d84e72bb12295e1d6a5da206c5dbf6 Mon Sep 17 00:00:00 2001
From: Jason Striegel
Date: Sun, 1 Dec 2013 00:44:24 -0500
Subject: [PATCH 08/37] first pass at integrating socketio
---
coder-apps/common/coderlib/app/meta.json | 2 +-
coder-apps/common/coderlib/static/js/index.js | 98 +++++++++++++
coder-apps/tests/sockettest/app/app.js | 77 ++++++++++
coder-apps/tests/sockettest/app/meta.json | 8 ++
.../tests/sockettest/static/css/index.css | 4 +
.../tests/sockettest/static/js/index.js | 28 ++++
.../tests/sockettest/static/media/.gitignore | 0
coder-apps/tests/sockettest/views/index.html | 33 +++++
coder-base/package.json | 3 +-
coder-base/server.js | 135 +++++++++++++++++-
10 files changed, 380 insertions(+), 8 deletions(-)
create mode 100644 coder-apps/tests/sockettest/app/app.js
create mode 100644 coder-apps/tests/sockettest/app/meta.json
create mode 100644 coder-apps/tests/sockettest/static/css/index.css
create mode 100644 coder-apps/tests/sockettest/static/js/index.js
create mode 100644 coder-apps/tests/sockettest/static/media/.gitignore
create mode 100644 coder-apps/tests/sockettest/views/index.html
diff --git a/coder-apps/common/coderlib/app/meta.json b/coder-apps/common/coderlib/app/meta.json
index 1f9e7f00..8f9d24aa 100644
--- a/coder-apps/common/coderlib/app/meta.json
+++ b/coder-apps/common/coderlib/app/meta.json
@@ -1,6 +1,6 @@
{
"created": "2013-03-05",
- "modified": "2013-07-17",
+ "modified": "2013-12-01",
"color": "#3e3e3e",
"author": "Jason Striegel",
"name": "CoderLib",
diff --git a/coder-apps/common/coderlib/static/js/index.js b/coder-apps/common/coderlib/static/js/index.js
index 2df15182..1b44b6fb 100644
--- a/coder-apps/common/coderlib/static/js/index.js
+++ b/coder-apps/common/coderlib/static/js/index.js
@@ -23,10 +23,16 @@ if (typeof console === "undefined" || typeof console.log === "undefined") {
console.log = function() {};
}
+var socketReady = false;
+var onSocketReady = null;
+
+
var Coder = {
coderlib_url: "/app/coderlib",
appname: '',
appurl: '',
+ socket: null,
+ socketListeners: {},
listApps: function( callback ) {
$.get( Coder.coderlib_url + "/api/app/list", function(data){
@@ -50,6 +56,72 @@ var Coder = {
});
});
},
+
+ connectSocket: function( callback ) {
+ var doConnect = function() {
+ Coder.socket = io.connect(window.location.protocol + window.location.hostname + ':' + window.location.port +'/' );
+ Coder.socket.on('connect', function() {
+ console.log('socket connected');
+
+ if ( callback ) {
+ callback();
+ }
+ });
+ Coder.socket.on('SERVERLOG', function(d) {
+ console.log('SERVERLOG: ' + JSON.stringify(d));
+ });
+ Coder.socket.on('appdata', function(data) {
+ console.log('appdata received');
+ console.log( data );
+ if ( data && data.key !== undefined ) {
+ if ( Coder.socketListeners[data.key] !== undefined ) {
+ for ( var x=0; x
+
+
+ Coder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hello World
+
Your html goes here.
+
+
+
+
\ No newline at end of file
diff --git a/coder-base/package.json b/coder-base/package.json
index a773197a..e42be37a 100644
--- a/coder-base/package.json
+++ b/coder-base/package.json
@@ -11,6 +11,7 @@
"socket.io": "0.9.13",
"express-params": "0.0.3",
"bcrypt": "0.7.4",
- "connect": "*"
+ "connect": "*",
+ "cookie": "*"
}
}
diff --git a/coder-base/server.js b/coder-base/server.js
index 32b6c614..122daaea 100644
--- a/coder-base/server.js
+++ b/coder-base/server.js
@@ -20,7 +20,7 @@
var express = require('express');
-var io = require('socket.io');
+var socketio = require('socket.io');
var net = require('http');
var http = require('http');
var https = require('https');
@@ -33,7 +33,8 @@ var cons = require('consolidate');
var params = require('express-params');
var querystring = require('querystring');
var path = require('path');
-
+var cookie = require('cookie');
+var connect = require('connect');
var loadApp = function( loadpath ) {
var userapp = null;
@@ -136,6 +137,7 @@ var startSSLRedirect = function() {
http.createServer( redirectapp ).listen( config.httpListenPort, config.listenIP );
};
+var server;
var startSSL = function() {
privateKeyFile=path.normalize('certs/server.key');
@@ -150,7 +152,9 @@ var startSSL = function() {
}
if ( privateKey !== "" && certificate !== "" ) {
- https.createServer({ key: privateKey, cert: certificate }, sslapp).listen( config.listenPort, config.listenIP );
+ server = https.createServer({ key: privateKey, cert: certificate }, sslapp);
+ server.listen( config.listenPort, config.listenIP );
+ initSocketIO( server );
} else {
var spawn = require('child_process').spawn;
@@ -174,13 +178,110 @@ var startSSL = function() {
var loadServer = function() {
privateKey = fs.readFileSync(privateKeyFile).toString();
certificate = fs.readFileSync(certificateFile).toString();
- https.createServer({ key: privateKey, cert: certificate }, sslapp).listen( config.listenPort, config.listenIP );
+ server = https.createServer({ key: privateKey, cert: certificate }, sslapp);
+ server.listen( config.listenPort, config.listenIP );
+ initSocketIO( server );
};
genSelfSignedCert(privateKeyFile, certificateFile);
}
};
+var io;
+var initSocketIO = function( server ) {
+ io = socketio.listen( server );
+ io.set('log level', 1); //this can cause a recursion problem since we are piping log info to a socket
+
+ // sync session data with socket
+ // via https://github.com/DanielBaulig/sioe-demo/blob/master/app.js
+ io.set('authorization', function (handshake, accept) {
+ if (!handshake.headers.cookie) {
+ console.log('no cookie sent with socket connection');
+ return accept('No cookie transmitted.', false);
+ }
+
+ handshake.cookie = cookie.parse(handshake.headers.cookie);
+ handshake.sessionID = connect.utils.parseSignedCookie(handshake.cookie['connect.sid'], storesecret);
+
+ if (handshake.cookie['connect.sid'] == handshake.sessionID) {
+ return accept('Cookie is invalid', false );
+ }
+
+ handshake.sessionStore = sslapp.sessionStore;
+
+ if (!handshake.sessionID) {
+ return accept('Session cookie could not be found', false);
+ }
+
+ handshake.sessionStore.get(handshake.sessionID, function (err, session) {
+ if (err) {
+ console.log( 'error loading session' );
+ return accept('Error', false);
+ }
+
+ var s = handshake.session = new express.session.Session(handshake, session );
+ return accept(null, true);
+ });
+ });
+
+ io.sockets.on('connection', function (socket) {
+ // socket.emit('news', { hello: 'world' });
+ // socket.on('my other event', function (data) {
+ // console.log(data);
+ // });
+
+ var sess = socket.handshake.session;
+
+ socket.on('appdata', function(data) {
+ if ( !sess.authenticated ) {
+ return;
+ }
+ if ( data.appid !== undefined && data.appid.match(/^\w+$/) && data.key !== undefined ) {
+ var appname = data.appid;
+ var userapp = loadApp( __dirname + '/apps/' + appname + "/app" );
+ var auth = require( __dirname + "/apps/auth/app" );
+ userapp.settings.appname = appname;
+ userapp.settings.viewpath="apps/" + appname;
+ userapp.settings.appurl="/app/" + appname;
+ userapp.settings.staticurl = "/static/apps/" + appname;
+ userapp.settings.device_name = auth.getDeviceName();
+ userapp.settings.coder_owner = auth.getCoderOwner();
+ userapp.settings.coder_color = auth.getCoderColor();
+ if ( userapp.settings.device_name === "" ) {
+ userapp.settings.device_name = "Coder";
+ }
+ if ( userapp.settings.coder_color === "" ) {
+ userapp.settings.coder_color = "#3e3e3e";
+ }
+
+ var route;
+ var key = data.key;
+ var routes = userapp.socketio_routes;
+ if ( routes ) {
+ var found = false;
+ for ( var i in routes ) {
+ route = routes[i];
+ if ( route['key'] instanceof RegExp ) {
+ var m = route['path'].exec( key );
+ if ( m ) {
+ userapp[route['handler']]( socket, data.data, m );
+ found = true;
+ break;
+ }
+
+ } else if ( route['key'] === key ) {
+ userapp[route['handler']]( socket, data.data );
+ found = true;
+ break;
+ }
+
+ }
+ }
+ }
+ });
+ });
+};
+
var pingEnabled = config.enableStatusServer;
var pingStatusServer = function() {
var server = config.statusServer;
@@ -244,15 +345,18 @@ var getHost = function( req ) {
//HTTPS handles all normal traffic
var sslapp = express();
+var storesecret = crypto.randomBytes(16).toString('utf-8');
params.extend( sslapp );
+sslapp.sessionStore = new express.session.MemoryStore();
sslapp.engine( 'html', cons.mustache );
sslapp.set( 'view engine', 'html' );
sslapp.set( 'views', __dirname + '/views' );
sslapp.use( express.bodyParser() );
sslapp.use( express.cookieParser() );
sslapp.use( express.session({
- secret: crypto.randomBytes(16).toString('utf-8'),
- store: new express.session.MemoryStore()
+ secret: storesecret,
+ key: 'connect.sid',
+ store: sslapp.sessionStore
}));
sslapp.use( '/static', express.static( __dirname + '/static' ) );
sslapp.get( '/', function( req, res ) {
@@ -264,6 +368,7 @@ sslapp.all( /^\/app\/(\w+)\/$/, function( req, res ) { apphandler( req, res, __
sslapp.all( /^\/app\/(\w+)$/, function( req, res ) { apphandler( req, res, __dirname + '/apps/'); } );
+
//HTTP is all redirected to HTTPS
var redirectapp = express();
params.extend( redirectapp );
@@ -281,3 +386,21 @@ process.on('uncaughtException', function(err) {
console.log('WARNING: unhandled exception: ' + err );
});
+// Allow front end console to receive server logs over a socket connection.
+// Note that util.log will still only go to stdout
+var origlog = console.log;
+console.log = function(d) {
+ origlog.call( console, d );
+ if ( io ) {
+ io.set('log level', 1);
+ var clients = io.sockets.clients();
+ for ( var x=0; x
Date: Tue, 3 Dec 2013 16:31:41 -0500
Subject: [PATCH 09/37] slight socket.io library refarctoring
---
coder-apps/common/coderlib/app/meta.json | 2 +-
coder-apps/common/coderlib/static/js/index.js | 181 +++++++++---------
.../{sockettest => socket_test}/app/app.js | 12 +-
.../{sockettest => socket_test}/app/meta.json | 4 +-
.../static/css/index.css | 0
.../tests/socket_test/static/js/index.js | 16 ++
.../static/media/.gitignore | 0
.../views/index.html | 0
.../tests/sockettest/static/js/index.js | 28 ---
coder-base/server.js | 108 ++++++-----
10 files changed, 182 insertions(+), 169 deletions(-)
rename coder-apps/tests/{sockettest => socket_test}/app/app.js (87%)
rename coder-apps/tests/{sockettest => socket_test}/app/meta.json (62%)
rename coder-apps/tests/{sockettest => socket_test}/static/css/index.css (100%)
create mode 100644 coder-apps/tests/socket_test/static/js/index.js
rename coder-apps/tests/{sockettest => socket_test}/static/media/.gitignore (100%)
rename coder-apps/tests/{sockettest => socket_test}/views/index.html (100%)
delete mode 100644 coder-apps/tests/sockettest/static/js/index.js
diff --git a/coder-apps/common/coderlib/app/meta.json b/coder-apps/common/coderlib/app/meta.json
index 8f9d24aa..20c91bb2 100644
--- a/coder-apps/common/coderlib/app/meta.json
+++ b/coder-apps/common/coderlib/app/meta.json
@@ -1,6 +1,6 @@
{
"created": "2013-03-05",
- "modified": "2013-12-01",
+ "modified": "2013-12-03",
"color": "#3e3e3e",
"author": "Jason Striegel",
"name": "CoderLib",
diff --git a/coder-apps/common/coderlib/static/js/index.js b/coder-apps/common/coderlib/static/js/index.js
index 1b44b6fb..c11123c5 100644
--- a/coder-apps/common/coderlib/static/js/index.js
+++ b/coder-apps/common/coderlib/static/js/index.js
@@ -23,16 +23,13 @@ if (typeof console === "undefined" || typeof console.log === "undefined") {
console.log = function() {};
}
-var socketReady = false;
-var onSocketReady = null;
+
var Coder = {
coderlib_url: "/app/coderlib",
appname: '',
appurl: '',
- socket: null,
- socketListeners: {},
listApps: function( callback ) {
$.get( Coder.coderlib_url + "/api/app/list", function(data){
@@ -57,71 +54,86 @@ var Coder = {
});
},
- connectSocket: function( callback ) {
- var doConnect = function() {
- Coder.socket = io.connect(window.location.protocol + window.location.hostname + ':' + window.location.port +'/' );
- Coder.socket.on('connect', function() {
- console.log('socket connected');
-
- if ( callback ) {
- callback();
- }
- });
- Coder.socket.on('SERVERLOG', function(d) {
- console.log('SERVERLOG: ' + JSON.stringify(d));
- });
- Coder.socket.on('appdata', function(data) {
- console.log('appdata received');
- console.log( data );
- if ( data && data.key !== undefined ) {
- if ( Coder.socketListeners[data.key] !== undefined ) {
- for ( var x=0; x
Date: Tue, 3 Dec 2013 19:55:32 -0500
Subject: [PATCH 10/37] working on gpio and web sockets
---
coder-apps/tests/gpio_test/app/app.js | 151 ++++++++++++++++++
coder-apps/tests/gpio_test/app/meta.json | 8 +
.../tests/gpio_test/static/css/index.css | 4 +
coder-apps/tests/gpio_test/static/js/index.js | 69 ++++++++
.../tests/gpio_test/static/media/.gitignore | 0
coder-apps/tests/gpio_test/views/index.html | 36 +++++
.../coder/coder-dist/coder-base/package.json | 18 +++
7 files changed, 286 insertions(+)
create mode 100644 coder-apps/tests/gpio_test/app/app.js
create mode 100644 coder-apps/tests/gpio_test/app/meta.json
create mode 100644 coder-apps/tests/gpio_test/static/css/index.css
create mode 100644 coder-apps/tests/gpio_test/static/js/index.js
create mode 100644 coder-apps/tests/gpio_test/static/media/.gitignore
create mode 100644 coder-apps/tests/gpio_test/views/index.html
create mode 100644 raspbian-addons/home/coder/coder-dist/coder-base/package.json
diff --git a/coder-apps/tests/gpio_test/app/app.js b/coder-apps/tests/gpio_test/app/app.js
new file mode 100644
index 00000000..dc6f7bd5
--- /dev/null
+++ b/coder-apps/tests/gpio_test/app/app.js
@@ -0,0 +1,151 @@
+var gpio = require("gpio");
+gpio.logging = true;
+
+var gpioID = 7; //pin 26, bottom right header
+var gpioDevice;
+var connected = false; //ensure only one process talks to us at a time.
+
+exports.settings={};
+//These are dynamically updated by the runtime
+//settings.appname - the app id (folder) where your app is installed
+//settings.viewpath - prefix to where your view html files are located
+//settings.staticurl - base url path to static assets /static/apps/appname
+//settings.appurl - base url path to this app /app/appname
+//settings.device_name - name given to this coder by the user, Ie."Billy's Coder"
+//settings.coder_owner - name of the user, Ie. "Suzie Q."
+//settings.coder_color - hex css color given to this coder.
+
+exports.get_routes = [
+ { path:'/', handler:'index_handler' },
+];
+
+exports.post_routes = [
+];
+
+exports.socketio_routes = [
+ { key:'connect', handler:'on_socket_connect' },
+ { key:'gpio', handler:'on_socket_gpio' },
+];
+
+
+var connections = {};
+
+exports.index_handler = function( req, res ) {
+ var tmplvars = {};
+ tmplvars['static_url'] = exports.settings.staticurl;
+ tmplvars['app_name'] = exports.settings.appname;
+ tmplvars['app_url'] = exports.settings.appurl;
+ tmplvars['device_name'] = exports.settings.device_name;
+
+ res.render( exports.settings.viewpath + '/index', tmplvars );
+};
+
+
+var enableGPIO = function() {
+ console.log("Enabling GPIO " + gpioID );
+ gpioDevice = gpio.export( gpioID, {
+ ready: function() {
+ //Pause briefly after pin is exported.
+ //There seems to be an error if you try to immediately access it.
+ setTimeout( function() {
+ console.log("GPIO value: on");
+ gpioDevice.setDirection("out");
+ gpioDevice.set(1, function() {
+ console.log("GPIO should be on");
+ });
+ //blinkLED();
+ }, 100 );
+ }
+ });
+};
+var disableGPIO = function() {
+ console.log("Disabling GPIO" + gpioID );
+ gpioDevice.removeAllListeners();
+ gpioDevice.reset();
+ gpioDevice.unexport();
+};
+
+var ledval = 0;
+var blinkLED = function() {
+ if ( !connected ) {
+ return;
+ }
+
+ gpioDevice.set( ledval );
+ if ( ledval == 0 ) {
+ ledval = 1;
+ } else {
+ ledval = 0;
+ }
+
+ //run this method again after half a second
+ setTimeout( blinkLED, 500 );
+};
+
+
+
+exports.on_socket_connect = function( socket, data ) {
+ console.log( 'socket connect from ID: ' + socket.socketID );
+ console.log( data );
+
+ if ( !connected ) {
+ enableGPIO();
+ connected = true;
+ }
+
+ connections[socket.socketID] = {
+ socket: socket,
+ name: data.name,
+ id: socket.socketID
+ };
+ socket.on('disconnect', function() {
+ console.log( 'socket disconnect from ID: ' + socket.socketID );
+ delete connections[socket.socketID];
+
+ //Free up the GPIO when the last socket disconnects
+ if ( Object.keys( connections ).length <= 0 ) {
+ disableGPIO();
+ connected = false;
+ }
+ });
+
+};
+
+
+exports.on_socket_gpio = function( socket, data ) {
+ switch (data.command) {
+ case "set":
+ setGPIO( data.value );
+ break;
+ case "direction":
+ setDirection( data.direction );
+ break;
+ case "value":
+ sendValue( socket );
+ break;
+ }
+};
+
+var setGPIO = function( val ) {
+ val = parseInt( val );
+ if ( val != 0 ) {
+ val = 1;
+ }
+ gpioDevice.set( val );
+};
+var setDirection = function( dir ) {
+ if ( dir !== "in") {
+ dir = "out";
+ }
+ gpioDevice.setDirection( dir );
+};
+var sendValue = function( socket ) {
+ socket.emit( "apdata", {
+ key: "gpiovalue",
+ data: gpioDevice.value
+ });
+};
+
+exports.on_destroy = function() {
+};
+
diff --git a/coder-apps/tests/gpio_test/app/meta.json b/coder-apps/tests/gpio_test/app/meta.json
new file mode 100644
index 00000000..916925d4
--- /dev/null
+++ b/coder-apps/tests/gpio_test/app/meta.json
@@ -0,0 +1,8 @@
+{
+ "created": "2013-11-30",
+ "modified": "2013-12-03",
+ "color": "#2ecc71",
+ "author": "",
+ "name": "GPIO Test",
+ "hidden": false
+}
\ No newline at end of file
diff --git a/coder-apps/tests/gpio_test/static/css/index.css b/coder-apps/tests/gpio_test/static/css/index.css
new file mode 100644
index 00000000..408ef623
--- /dev/null
+++ b/coder-apps/tests/gpio_test/static/css/index.css
@@ -0,0 +1,4 @@
+
+.pagecontent {
+ padding: 24px;
+}
\ No newline at end of file
diff --git a/coder-apps/tests/gpio_test/static/js/index.js b/coder-apps/tests/gpio_test/static/js/index.js
new file mode 100644
index 00000000..8cb76a31
--- /dev/null
+++ b/coder-apps/tests/gpio_test/static/js/index.js
@@ -0,0 +1,69 @@
+
+$(document).ready( function() {
+
+ //This code will run after your page loads
+ Coder.socketConnection.init(function(){
+
+ addOutputMessage( "Connected with ID: " + Coder.socketConnection.socketID );
+ addOutputMessage( "Click ON or OFF to enable blinking" );
+
+ Coder.socketConnection.sendData( 'connect', {'name':'testing'} );
+
+ Coder.socketConnection.addListener( 'Received gpio value', function( d ){
+ console.log("gpio value: " + d);
+ });
+
+
+ // Blink every 100ms if enabled is on
+ // enabled is set below by a button click.
+ var blinkval = 0;
+ setInterval( function() {
+
+ if ( blinkval ) {
+ blinkval = 0;
+ } else {
+ blinkval = 1;
+ }
+
+ if ( enabled ) {
+
+ Coder.socketConnection.sendData( 'gpio', {
+ command: "set",
+ value: blinkval
+ });
+ }
+ }, 100 );
+
+
+
+ //Enable or disable the blinker
+ var enabled = false;
+ $("#on").click( function() {
+ Coder.socketConnection.sendData( 'gpio', {
+ command: "set",
+ value: 1
+ });
+ enabled = true;
+ });
+ $("#off").click( function() {
+ Coder.socketConnection.sendData( 'gpio', {
+ command: "set",
+ value: 0
+ });
+ enabled = false;
+ });
+
+ });
+
+ addOutputMessage( "Connecting... see the debug console for log messages." );
+
+});
+
+
+
+var addOutputMessage = function( text ) {
+ var $output = $("#output");
+ $output.prepend( $("").text( text ) );
+ console.log( text );
+}
+
diff --git a/coder-apps/tests/gpio_test/static/media/.gitignore b/coder-apps/tests/gpio_test/static/media/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/coder-apps/tests/gpio_test/views/index.html b/coder-apps/tests/gpio_test/views/index.html
new file mode 100644
index 00000000..a2effaa2
--- /dev/null
+++ b/coder-apps/tests/gpio_test/views/index.html
@@ -0,0 +1,36 @@
+
+
+
+ Coder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
GPIO Test
+
ON
+
OFF
+
+
+
+
+
+
\ No newline at end of file
diff --git a/raspbian-addons/home/coder/coder-dist/coder-base/package.json b/raspbian-addons/home/coder/coder-dist/coder-base/package.json
new file mode 100644
index 00000000..08dbdee7
--- /dev/null
+++ b/raspbian-addons/home/coder/coder-dist/coder-base/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "coder-base",
+ "description": "kid-friendly web programming environment for pi",
+ "version": "0.0.1",
+ "private": true,
+ "dependencies": {
+ "express": "3.1.0",
+ "redis": "0.8.2",
+ "mustache": "0.7.2",
+ "consolidate": "0.8.0",
+ "socket.io": "0.9.13",
+ "express-params": "0.0.3",
+ "bcrypt": "0.7.4",
+ "connect": "*",
+ "cookie": "*",
+ "gpio": "*",
+ }
+}
From 693385e5cc1c63905047e831afa65fe87505c7e6 Mon Sep 17 00:00:00 2001
From: Jason Striegel
Date: Tue, 3 Dec 2013 19:57:11 -0500
Subject: [PATCH 11/37] gpio/sockets test support
---
coder-apps/tests/socket_test/static/js/index.js | 14 ++++++++++++--
coder-apps/tests/socket_test/views/index.html | 7 ++++---
.../home/coder/coder-dist/coder-base/package.json | 2 +-
3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/coder-apps/tests/socket_test/static/js/index.js b/coder-apps/tests/socket_test/static/js/index.js
index 1ac1cfbf..34cbb575 100644
--- a/coder-apps/tests/socket_test/static/js/index.js
+++ b/coder-apps/tests/socket_test/static/js/index.js
@@ -3,14 +3,24 @@ $(document).ready( function() {
//This code will run after your page loads
Coder.socketConnection.init(function(){
- console.log("connected with ID: " + Coder.socketConnection.socketID);
- Coder.socketConnection.sendData( 'connect', {'name':'jimmy'} );
+ addOutputMessage( "Connected with ID: " + Coder.socketConnection.socketID );
+
+
+ Coder.socketConnection.sendData( 'connect', {'name':'testing'} );
Coder.socketConnection.addListener( 'message', function( d ){
console.log("message from: " + d.name + " : " + d.message);
});
});
+ addOutputMessage( "Connecting... see the debug console for log messages." );
+
});
+var addOutputMessage = function( text ) {
+ var $output = $("#output");
+ $output.prepend( $("").text( text ) );
+ console.log( text );
+}
+
diff --git a/coder-apps/tests/socket_test/views/index.html b/coder-apps/tests/socket_test/views/index.html
index 3c200c8c..7fab1e9e 100644
--- a/coder-apps/tests/socket_test/views/index.html
+++ b/coder-apps/tests/socket_test/views/index.html
@@ -25,9 +25,10 @@
-
Hello World
-
Your html goes here.
-
+
Socket Test
+
+
+