├── .gitignore ├── README.md ├── Sports Stress Ball, Mini Foam Sports Ball, Foam Sports Ball for School Carnival Reward, Party Bag Gift Fillers (Colorful Soccer Ball, 30 Pieces).desktop ├── design ├── assembly2.scad ├── electronics-deck2.scad ├── electronics-deck2.stl ├── lower-chassis2.scad ├── lower-chassis2.stl ├── motor-cover.scad ├── motor-cover.stl ├── phone-mount.scad └── phone-mount.stl ├── esp32 └── tethys.ino ├── images ├── charging-usb.jpg ├── charging-wireless.jpg ├── drv8833-cheap-0.jpg ├── drv8833-cheap-1.jpg ├── robot.jpg ├── tinypico-drv8833-combo.jpg └── video.gif └── server ├── package-lock.json ├── package.json ├── root └── etc │ └── nginx │ ├── nginx.conf │ └── sites-available │ └── default ├── run ├── server-io.js ├── server.js └── static ├── index.html ├── join ├── bg.jpg ├── css │ ├── index.css │ ├── material.blue_grey-indigo.min.css │ ├── material.css │ ├── material.min.css │ └── material.min.css.map ├── index.html └── js │ ├── algorithms.js │ ├── control.js │ ├── index.js │ ├── jquery-3.4.1.min.js │ ├── material.js │ ├── material.min.js │ ├── material.min.js.map │ ├── socket.io.slim.js │ └── ui.js ├── robot ├── css │ ├── index.css │ ├── material.blue_grey-indigo.min.css │ ├── material.css │ ├── material.min.css │ └── material.min.css.map ├── index.html └── js │ ├── BluetoothTerminal.js │ ├── algorithms.js │ ├── bluetooth-main.js │ ├── gpsNode.js │ ├── imuNode.js │ ├── index.js │ ├── jquery-3.4.1.min.js │ ├── material.js │ ├── material.min.js │ ├── material.min.js.map │ ├── roslite.bundle.js │ ├── roslite.bundle.min.js │ ├── socket.io.slim.js │ └── ui.js └── socket.io.slim.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BotParty 2 | 3 | A mini telepresence robot. At about $130 total for all parts (including the pictured used Pixel 1 phone), the goal of this robot is to be as inexpensive as possible for a complete telepresence solution. 4 | 5 | ![robot](/images/video.gif?raw=true "robot") 6 | 7 | ![robot](/images/robot.jpg?raw=true "robot") 8 | 9 | Completed robot. 10 | 11 | ![robot](/images/charging-wireless.jpg?raw=true "robot") 12 | 13 | If you buy a 5V2A Qi charging sticker, you can charge the robot wirelessly just by driving on top of a wireless charger. Simple and no docking alignment fuss! 14 | 15 | ![robot](/images/charging-usb.jpg?raw=true "robot") 16 | 17 | You can use Anker Powerline micro-USB cables to charge the robot without taking it apart. *Not all micro-USB cables* will clear the outer edge of the sprocket. 18 | 19 | ![robot](/images/tinypico-drv8833-combo.jpg?raw=true "robot") 20 | 21 | The motors are driven by a DRV8833 and a TinyPICO ESP32 microcontroller. The phone talks to the ESP32 over BLE. The ESP32 is super feature packed and also has Wi-Fi, capacitive touch sensing, I2C, SPI, UART, ADC, DAC, and lots of other features so you can build off this design and add whatever sensors and actuators you please. Build a security robot, a robot that roams around sensing VOC gas levels, and more! 22 | 23 | ## Assembly 24 | 25 | ### 3D printed parts 26 | 27 | See .stl files in design/. I recommend printing in PETG. PLA sucks. 28 | 29 | ### Mechanical parts 30 | 31 | * [M3 short heat-set inserts](https://www.mcmaster.com/94180a331), M3, OD=5.6mm, L=3.8mm -- [Possible alternative](https://www.ebay.com/sch/i.html?_from=R40&_trksid=m570.l1313&_nkw=initeq+m3+long&_sacat=0) 32 | * [M3 long heat-set inserts](https://www.mcmaster.com/94180a333), M3, OD=5.6mm, L=6.4mm -- [Possible alternative](https://www.ebay.com/sch/i.html?_from=R40&_trksid=m570.l1313&_nkw=initeq+m3+short&_sacat=0) 33 | * [Pololu 22T track set](https://www.pololu.com/product/3030) 34 | * [M3 L=5mm countersunk screws](https://www.mcmaster.com/92125a125) 35 | * [M3 L=5mm socket head screws](https://www.mcmaster.com/91292A110) 36 | 37 | ### Electronics 38 | * [Used Google Pixel 1](https://www.ebay.com/sch/i.html?_from=R40&_trksid=p2334524.m570.l1313.TR9.TRC1.A0.H0.Xpixel+1.TRS2&_nkw=pixel+1&_sacat=0&LH_TitleDesc=0&_osacat=0&_odkw=pixel+1+unlocked) -- You can get them used or "refurbished" for under $65. The charging cord on the portable charger below just barely fits so if you buy any bigger phone than the Pixel 1, you may need an extension on the charging cable. I do **not** recommend using an iPhone as Apple is consistently behind the times in terms of in-browser WebRTC support and doesn't even have bluetooth support from the browser. If you use an iPhone you will have to write an iOS native app from scratch. 39 | * 2 x [Micro metal gearmotors](https://www.pololu.com/category/60/micro-metal-gearmotors) -- If you buy from Pololu, HPCB or HP 75:1 is proboblay the best for this use case but you can go up or down to 30:1 to 150:1 depending on your needs. Get single shaft if you don't plan on even putting encoders in, get dual-shaft if you think you might want encoders later. Get MP instead of HP/HPCB if you want a cheaper motor. Or search eBay/AliExpress for MUCH cheaper alternatives although many suppliers don't provide specs on current and stall torque making it hard to choose. There are also sometimes people on eBay reselling authentic used Pololu motors at cheaper prices. 40 | * Portable charger: [YPLANG 9000mAh](https://www.amazon.com/Powerbank-9000mAh-Portable-Charger-External/dp/B07JMTSPC3) or [BeeFix 9000mAh](https://www.amazon.com/Portable-Charger-9000mAh-External-Battery/dp/B07SNV2B42/) or [Elephant Story 9000mAh](http://www.elephantstory.net/product/ds01/) -- They are the same charger under 2 different brand names). Make sure you get the version with 1 USB-C out + 1 lightning+microUSB combination out, and not their older version with 1 microUSB + 1 lightning. The chassis and 3D printed parts are designed to match this charger, which allows charging+discharging at the same time, and has two charging ports, one of which can be used for a Qi charger sticker. 41 | * [2A Qi fast charging receiver USB-C](https://www.aliexpress.com/item/4000239832349.html?spm=a2g0o.productlist.0.0.35b368d0CEJ0jH&algo_pvid=8c201542-8113-476a-9f66-177729616d19&algo_expid=8c201542-8113-476a-9f66-177729616d19-2&btsid=0be3746c15870616127626599eba46&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_) if you want wireless charging where the robot can just drive up onto a charger (optional) 42 | * 1 x DRV8833 carrier [No-name cheap 5 for $8 carrier](https://www.amazon.com/KOOBOOK-DRV8833-Module-Bridge-Controller/dp/B07S4FVY9M/) or [Pololu 1 for $4.95 DRV8833 carrier](https://www.pololu.com/product/2130) or any other DRV8833 carrier 43 | * [TinyPICO](https://www.adafruit.com/product/4335) or other ESP32 board of your choice 44 | * [24 AWG wire](https://www.amazon.com/gp/product/B07G2BWBX8/) 45 | * [1x2 Dupont connector housing](https://www.pololu.com/product/1901) and [crimps](https://www.pololu.com/product/1930) or just get a [kit like this](https://www.amazon.com/gp/product/B078RRPRQZ/). [This crimper](https://www.amazon.com/Crimping-0-08-1-0-18-28AWG-Ratcheting-Connector/dp/B01N1RFZZ4/) has worked well for me for Dupont crimps. 46 | * [JST-EH](https://www.digikey.com/catalog/en/partgroup/eh-series/) connectors for ESP32 board and ESP-DRV8833 connection (or use whatever you prefer). [This crimper](https://www.amazon.com/Engineers-Precision-Crimping-Pliers-Pa-09/dp/B002AVVO7K/) has worked well for me for JST-EH. 47 | 48 | ## Code 49 | 50 | **esp32/** contains firmware for a TinyPICO. (a) Follow TinyPICO's instructions to enable the Arduino IDE (b) install the analogWrite library for ESP32 and then (c) compile it with Arduino. You can then visit the [ESP32 web terminal](https://dheera.github.io/esp32terminal/) to test connectivity. Steps to test: 51 | 52 | - Use desktop Chrome on a laptop that has Bluetooth, or Chrome on Android also works. Chrome on iOS will not work. 53 | - Go to chrome://flags/#enable-experimental-web-platform-features and enable. 54 | - Go to the ESP32 web terminal and Connect to "tethys-255" which should be your newly flashed TinyPICO. 55 | - If you connected successfully, you will see "Z" being output every second. That is a heartbeat from the firmware. 56 | - Type "G 0 255 0 255" to drive the motors, and you can try other values as well. They are just PWM values for 2 H-bridges. The motors will stop after 1 second; this is expected as the ESP32 firmware imposes a 1 second heartbeat on incoming commands. 57 | - Type "S 4" to set the robot number to 4 or whatever number you want <=255. This is a persistent setting. After a power cycle (unplug and replug the TinyPICO) the device will show up as "tethys-4". This allows you to prevent from connecting the wrong robot phone to the wrong robot ESP32. 58 | 59 | **server/** contains server code. Run with node and proxy node through nginx for https (see nginx.conf and sites-available/default). Hacky, to be improved. 60 | 61 | You MUST go to chrome://flags/#enable-experimental-web-platform-features on the robot and enable it! This is required for Chrome to be able to talk to BLE devices from a webpage. Only the robot needs this, not your participants. 62 | 63 | https://yourserver/robot/#room on the robot with Chrome. You need to set a robot id and a passcode. Then every time you load the page on the robot you need to press the bluetooth button and select the "tethys-###" device. Unfortunately this is a limitation of the web bluetooth API and it cannot auto-connect without a user gesture. 64 | 65 | https://yourserver/join/#room is for participants. Chrome on Linux/MacOS/Windows should all work. Issues have been reported on Safari and iOS and I need to get a hold of some Apple hardware to debug these. 66 | 67 | WebRTC data channels [don't seem to renegotiate](https://stackoverflow.com/questions/61179293/renegotiating-sdp-withaudiovideodata-webrtc) so I use socket.io for driving and WebRTC for audio/video. Clunky but WebRTC documentation is lacking and all the examples suck at illustrating proper SDP renegotiation. 68 | 69 | -------------------------------------------------------------------------------- /Sports Stress Ball, Mini Foam Sports Ball, Foam Sports Ball for School Carnival Reward, Party Bag Gift Fillers (Colorful Soccer Ball, 30 Pieces).desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Name=Link to Sports Stress Ball, Mini Foam Sports Ball, Foam Sports Ball for School Carnival Reward, Party Bag Gift Fillers (Colorful Soccer Ball, 30 Pieces) 4 | Type=Link 5 | URL=https://www.amazon.com/Sports-Stress-Carnival-Fillers-Colorful/dp/B0811SFFH8/ref=sr_1_15?dchild=1&keywords=stress+ball+pack+soccer+football&qid=1587106299&sr=8-15 6 | Icon=text-html 7 | -------------------------------------------------------------------------------- /design/assembly2.scad: -------------------------------------------------------------------------------- 1 | breakaway=0; 2 | 3 | import("lower-chassis2.stl"); 4 | 5 | translate([41.5+breakaway,0,16+breakaway]) 6 | import("motor-cover.stl"); 7 | 8 | translate([0-2*breakaway,0,20.0+2*breakaway]) 9 | import("electronics-deck2.stl"); -------------------------------------------------------------------------------- /design/electronics-deck2.scad: -------------------------------------------------------------------------------- 1 | batt_l = 67; 2 | batt_w = 110; 3 | batt_h = 18; 4 | 5 | w_stretch = 6; 6 | w_off = batt_l/2 - 16; 7 | 8 | pi_l = 65; 9 | pi_w = 56; 10 | pi_hole_space_l = 58; 11 | pi_hole_space_w = 49; 12 | 13 | wheel_dist_l = 48; 14 | m3_insert_d = 4.8; 15 | 16 | t = 2; 17 | 18 | 19 | h=9.5+t*2+2; 20 | 21 | module m3insertstandoff() { 22 | difference() { 23 | cylinder(d1=8,d2=8,h=8,$fn=32); 24 | cylinder(d=m3_insert_d,h=10,$fn=32); 25 | } 26 | } 27 | 28 | translate([0,0,2]) { 29 | for(i=[-40:40:40]) 30 | translate([-15,i,0]) { 31 | m3insertstandoff(); 32 | } 33 | 34 | 35 | for(i=[-40:40:40]) 36 | translate([40,i,0]) { 37 | m3insertstandoff(); 38 | } 39 | } 40 | 41 | difference() { 42 | translate([10+t/2,0,0]) 43 | cube_center([batt_l+2*t+20+t,batt_w+2*t,4]); 44 | 45 | for(x=[-25:10:35]) { 46 | for(y=[-40:10:40]) { 47 | if(!(x==35 && (abs(y)==40||y==0))) { 48 | translate([x+0.5,y,0]) 49 | cylinder(d=2,h=5,$fn=16); 50 | } 51 | } } 52 | 53 | difference() { 54 | translate([-batt_l/23.5+11.5,0,2]) 55 | cube_center([batt_l+13,batt_w,4.5]); 56 | for(s=[-1:1:1]) 57 | translate([0,s*batt_w/2,0]) 58 | cube_center([100,8,4]); 59 | } 60 | 61 | translate([+batt_l/2+2*t+12.5,0,0]) { 62 | for(s=[-1:1:1]) 63 | translate([3,s*(batt_w/2-15),0]) { 64 | cylinder(d=3.1,h=3.001,$fn=32); 65 | translate([0,0,1]) 66 | cylinder(d1=3.1,d2=6,h=3.001,$fn=32); 67 | } 68 | } 69 | 70 | for(i=[-60:15:60]) 71 | translate([-batt_l/2-t+1,-7.5+i,0]) 72 | cube_center([2,10.5,5]); 73 | 74 | translate([-batt_l/2-t+1,0,1.7]) 75 | cube_center([2,100,5]); 76 | 77 | for(s=[-1:2:1]) 78 | translate([0.5+s*((batt_l+1)/2-6),batt_w/2-20,0]) 79 | power_cutout(); 80 | 81 | for(s=[-1:2:1]) 82 | translate([batt_l/2+w_off-9.5,s*(24.5-3.5-2+4),0]) 83 | motorwire_cutout(); 84 | 85 | translate([0.5,batt_w/2-9/2-13.5,0]) 86 | indicator_cutout(); 87 | 88 | 89 | /* 90 | translate([1,0,1]) 91 | rotate([0,0,90]) 92 | pi_standoffs_holes(); 93 | 94 | translate([1,0,2]) 95 | rotate([0,0,90]) 96 | pi_dip(); 97 | */ 98 | 99 | } 100 | 101 | 102 | /* 103 | translate([1,0,4]) 104 | rotate([0,0,90]) 105 | pi_standoffs(); 106 | 107 | translate([1,22,2]) 108 | dcdc_standoffs(); 109 | 110 | translate([20,0,2]) 111 | rotate([0,0,-90]) 112 | bno055_standoffs(h=5); 113 | 114 | 115 | translate([-4,-23,2]) 116 | rotate([0,0,0]) 117 | ina219_standoffs(h=5); 118 | 119 | translate([-9.5,0,2]) 120 | rotate([0,0,180]) 121 | powerboost_standoffs(); 122 | */ 123 | 124 | module pi_dip() { 125 | difference() { 126 | cube_center([pi_l,pi_w,5]); 127 | translate([-pi_hole_space_l/2,pi_hole_space_w/2]) 128 | cylinder(d=10,h=5,$fn=32); 129 | translate([pi_hole_space_l/2,pi_hole_space_w/2]) 130 | cylinder(d=10,h=5,$fn=32); 131 | translate([-pi_hole_space_l/2,-pi_hole_space_w/2]) 132 | cylinder(d=10,h=5,$fn=32); 133 | translate([pi_hole_space_l/2,-pi_hole_space_w/2]) 134 | cylinder(d=10,h=5,$fn=32); 135 | } 136 | } 137 | 138 | module breadboard() { 139 | for (i = [-10:4:10]) { 140 | for (j = [-38:4:38]) { 141 | translate([i,j,0]) 142 | cylinder(d=1.8,h=10,$fn=8); 143 | } 144 | } 145 | } 146 | 147 | module motorwire_cutout() { 148 | cube_center([5,16,10]); 149 | } 150 | 151 | module indicator_cutout() { 152 | cube_center([15,9,10]); 153 | } 154 | 155 | module power_cutout() { 156 | cube_center([8,14,10]); 157 | } 158 | 159 | module pi_cutout() { 160 | minkowski() { 161 | cube_center([pi_l-4,pi_w-4,10]); 162 | cylinder(d=4,$fn=16); 163 | } 164 | } 165 | 166 | module dcdc_standoffs() { 167 | translate([-36/2,11/2,0]) 168 | standoff2(); 169 | translate([36/2,-11/2,0]) 170 | standoff2(); 171 | difference() { 172 | cube_center([40,15,0.3]); 173 | cube_center([39.4,14.4,1]); 174 | } 175 | } 176 | 177 | module ina219_standoffs(h=3) { 178 | translate([-20/2,17/2,0]) 179 | standoff2(h=h); 180 | translate([20/2,-17/2,0]) 181 | standoff2(h=h); 182 | translate([-20/2,-17/2,0]) 183 | standoff2(h=h); 184 | translate([20/2,17/2,0]) 185 | standoff2(h=h); 186 | difference() { 187 | cube_center([25,22,0.3]); 188 | cube_center([24.4,21.4,1]); 189 | } 190 | } 191 | 192 | module bno055_standoffs(h=3) { 193 | translate([-21.5/2,15/2,0]) 194 | standoff2(h=h); 195 | translate([21.5/2,-15/2,0]) 196 | standoff2(h=h); 197 | translate([-21.5/2,-15/2,0]) 198 | standoff2(h=h); 199 | translate([21.5/2,15/2,0]) 200 | standoff2(h=h); 201 | difference() { 202 | cube_center([26,20,0.3]); 203 | cube_center([25.4,19.4,1]); 204 | } 205 | } 206 | 207 | module powerboost_standoffs() { 208 | translate([31.5/2,17.5/2,0]) 209 | standoff2(); 210 | translate([31.5/2,-17.5/2,0]) 211 | standoff2(); 212 | translate([-31.5/2,-13/2,0]) 213 | standoff2(); 214 | translate([-31.5/2,13/2,0]) 215 | standoff2(); 216 | difference() { 217 | cube_center([35,22,0.3]); 218 | cube_center([34.4,21.4,1]); 219 | } 220 | } 221 | 222 | module standoff2(h=3.5) { 223 | difference() { 224 | cylinder(d1=4.5,d2=3.5,h=h,$fn=32); 225 | cylinder(d=1.8,h=5,$fn=32); 226 | } 227 | } 228 | 229 | module pi_standoffs() { 230 | translate([-pi_hole_space_l/2,-pi_hole_space_w/2]) 231 | standoff(); 232 | translate([pi_hole_space_l/2,-pi_hole_space_w/2]) 233 | standoff(); 234 | translate([-pi_hole_space_l/2,pi_hole_space_w/2]) 235 | standoff(); 236 | translate([pi_hole_space_l/2,pi_hole_space_w/2]) 237 | standoff(); 238 | } 239 | 240 | module pi_standoffs_holes() { 241 | translate([-pi_hole_space_l/2,-pi_hole_space_w/2]) 242 | cylinder(d=m3_insert_d,h=5,$fn=32); 243 | translate([pi_hole_space_l/2,-pi_hole_space_w/2]) 244 | cylinder(d=m3_insert_d,h=5,$fn=32); 245 | translate([-pi_hole_space_l/2,pi_hole_space_w/2]) 246 | cylinder(d=m3_insert_d,h=5,$fn=32); 247 | translate([pi_hole_space_l/2,pi_hole_space_w/2]) 248 | cylinder(d=m3_insert_d,h=5,$fn=32); 249 | } 250 | 251 | module standoff(h=3.5) { 252 | difference() { 253 | cylinder(d1=8,d2=8,h=h,$fn=32); 254 | cylinder(d=m3_insert_d,h=h,$fn=32); 255 | } 256 | } 257 | 258 | module cube_center(dims) { 259 | translate([-dims[0]/2, -dims[1]/2, 0]) 260 | cube(dims); 261 | } 262 | 263 | 264 | module triangle() { 265 | scale([1,1,1]) 266 | rotate([0,-90,90]) 267 | linear_extrude(height = batt_w+2*t, center = true, convexity = 10, twist = 0) 268 | polygon(points=[[0,0],[batt_h+t,8],[batt_h+t,0]]); 269 | } 270 | 271 | module triangle2() { 272 | scale([1,1,1]) 273 | rotate([0,-90,0]) 274 | linear_extrude(height = 16, center = true, convexity = 10, twist = 0) 275 | polygon(points=[[0,0],[5,7],[5,0]]); 276 | } 277 | -------------------------------------------------------------------------------- /design/lower-chassis2.scad: -------------------------------------------------------------------------------- 1 | batt_l = 67; 2 | batt_w = 110; 3 | batt_h = 18; 4 | 5 | w_stretch = 0; 6 | w_off = batt_l/2 - 16; 7 | 8 | /* 9 | translate([-8,batt_w/2+10,11]) 10 | rotate([90,0,0]) 11 | cylinder(d=3,$fn=32); 12 | translate([-8+48+2,batt_w/2+10,11]) 13 | rotate([90,0,0]) 14 | cylinder(d=3,$fn=32); 15 | */ 16 | 17 | 18 | /* 19 | translate([-8.5+2,batt_w/2+15,10]) 20 | rotate([90,0,0]) 21 | cylinder(d=35); 22 | translate([-8.5+50,batt_w/2+15,10]) 23 | rotate([90,0,0]) 24 | cylinder(d=35); 25 | */ 26 | 27 | 28 | pi_l = 65; 29 | pi_w = 56; 30 | pi_hole_space_l = 58; 31 | pi_hole_space_w = 49; 32 | 33 | wheel_dist_l = 48; 34 | m3_insert_d = 4.8; 35 | 36 | t = 2; 37 | 38 | //h=9.5+t*2+2; 39 | 40 | top_space = 4; 41 | h=12; 42 | 43 | 44 | /* 45 | translate([0,0,35]) 46 | rotate([0,0,90]) 47 | cube_center([105,65,17]); 48 | */ 49 | /* 50 | translate([batt_l/2+20,0,30]) 51 | cube_center([8.5,143.8,69.5]); 52 | */ 53 | 54 | translate([batt_l/2+0.5+5+1.5*t,0,0]) 55 | cube_center([15,batt_w+2*t,6]); 56 | 57 | difference() { 58 | cube_center([batt_l+2*t,batt_w+2*t,batt_h + t]); 59 | 60 | translate([0.5,0,t]) 61 | cube_center([batt_l+1,batt_w,batt_h * 2]); 62 | 63 | translate([0,0,t-0.5]) 64 | cube_center([batt_l-10,batt_w-10,batt_h]); 65 | 66 | for(j=[-21:7:21]) { 67 | for(i=[-42:7:42]) { 68 | translate([j,i,0]) 69 | cube_center([4,4,batt_h]); 70 | } 71 | } 72 | 73 | 74 | translate([15,100,2+9-8/2]) 75 | cube_center([15,100,8]); 76 | 77 | translate([-wheel_dist_l/2+w_off-w_stretch,0,(h+t)/2+batt_h+t-h-top_space]) 78 | rotate([90,0,0]) 79 | cylinder(d=m3_insert_d, h=500, center=true, $fn=32); 80 | } 81 | 82 | difference() { 83 | wheelbars(); 84 | scale([1,1,-1]) 85 | cube_center([200,200,100]); 86 | } 87 | 88 | module cube_center(dims) { 89 | translate([-dims[0]/2, -dims[1]/2, 0]) 90 | cube(dims); 91 | } 92 | 93 | 94 | 95 | module wheelbars() { 96 | 97 | //h=15.5; 98 | 99 | translate([0,0,batt_h+t - h - top_space]) 100 | difference() { 101 | union() { 102 | 103 | translate([0,batt_w/2+t+3.5,0]) 104 | translate([6+t/2,0,0]) 105 | cube_center([batt_l + 2*t + 12 + t, 7, h]); 106 | translate([0,-(batt_w/2+t+3.5),0]) 107 | translate([6+t/2,0,0]) 108 | cube_center([batt_l + 2*t + 12 + t, 7, h]); 109 | 110 | translate([batt_l/2 + 6 + t*1.5 ,0,0]) 111 | cube_center([16+t-4,batt_w + 2*t, h]); 112 | 113 | translate([-wheel_dist_l/2+w_off-w_stretch,batt_w/2+t-2,-5]) 114 | triangle2(); 115 | translate([wheel_dist_l/2+w_off,batt_w/2+t,-5]) 116 | triangle2(); 117 | 118 | translate([-wheel_dist_l/2+w_off-w_stretch,-(batt_w/2+t)+2,-5]) 119 | scale([1,-1,1]) 120 | triangle2(); 121 | translate([wheel_dist_l/2+w_off,-(batt_w/2+t),-5]) 122 | scale([1,-1,1]) 123 | triangle2(); 124 | 125 | } 126 | 127 | translate([w_off-w_stretch/2,batt_w/2 + 7, 0]) 128 | cube_center([32+w_stretch, 10, h]); 129 | 130 | translate([w_off-w_stretch/2,-(batt_w/2 + 7), 0]) 131 | cube_center([32+w_stretch, 10, h]); 132 | 133 | translate([w_off-14.5-32.5-w_stretch,batt_w/2 + 7, 0]) 134 | cube_center([30, 10, h]); 135 | 136 | translate([w_off-14.5-32.5-w_stretch,-(batt_w/2 + 7), 0]) 137 | cube_center([30, 10, h]); 138 | 139 | 140 | translate([-32.5,-(batt_w/2 + 7+ 5), 0]) 141 | cube_center([80, 10, h]); 142 | 143 | translate([-32.5,(batt_w/2 + 7+ 5), 0]) 144 | cube_center([80, 10, h]); 145 | 146 | translate([-wheel_dist_l/2+w_off-w_stretch,0,(h+t)/2]) 147 | rotate([90,0,0]) 148 | cylinder(d=m3_insert_d, h=200, center=true, $fn=32); 149 | 150 | 151 | translate([wheel_dist_l/2+w_off-2,0,h-6]) 152 | cylinder(d=m3_insert_d,h=100,$fn=32); 153 | 154 | // motor mount cutouts 155 | for(sign=[-1:2:1]) 156 | translate([wheel_dist_l/2+w_off,sign*(batt_w/2 + t +7 - 38/2),t]) 157 | rotate([0,0,sign*90+90]) 158 | motor_mount_cutout(); 159 | 160 | } 161 | 162 | } 163 | 164 | module motor_mount_cutout() { 165 | difference() { 166 | union() { 167 | cube_center([12,38.001,9.5+5]); 168 | //translate([0,-38/2+24+5/2,0]) 169 | //cube_center([14,5,9.5+5]); 170 | 171 | translate([0,24.5-3.5,0]) 172 | cube_center([12,16,15]); 173 | } 174 | translate([-4,-38/2+1+3.5/2,0]) 175 | cube_center([3,3.5,0.5]); 176 | translate([-5.75,-38/2+1+3.5/2,0]) 177 | cube_center([0.5,3.5,2]); 178 | 179 | translate([-4,-38/2+5.75+2/2,0]) 180 | cube_center([3,2,0.5]); 181 | translate([-5.75,-38/2+5.75+2/2,0]) 182 | cube_center([0.5,2,2]); 183 | 184 | translate([0,-38/2+24+0.3/2,0]) 185 | cube_center([14,0.3,0.5]); 186 | 187 | translate([-12/2+1/2,-38/2+9+5/2,0]) 188 | cube_center([1,5,1]); 189 | translate([12/2-1/2,-38/2+9+5/2,0]) 190 | cube_center([1,5,1]); 191 | } 192 | } 193 | 194 | translate([+batt_l/2+2*t+12,0,0]) 195 | { 196 | difference() { 197 | scale([-1,1,1]) triangle(); 198 | translate([3.5,batt_w/2-4,batt_h+t-top_space - 6.5]) 199 | cylinder(d=m3_insert_d,h=100,$fn=32); 200 | translate([3.5,-batt_w/2+4,batt_h+t-top_space - 6.5]) 201 | cylinder(d=m3_insert_d,h=100,$fn=32); 202 | } 203 | } 204 | 205 | module triangle() { 206 | scale([1,1,1]) 207 | rotate([0,-90,90]) 208 | linear_extrude(height = batt_w+2*t, center = true, convexity = 10, twist = 0) 209 | polygon(points=[[0,0],[12,8],[batt_h+t-top_space,8],[batt_h+t-top_space,0]]); 210 | } 211 | 212 | module triangle2() { 213 | scale([1,1,1]) 214 | rotate([0,-90,0]) 215 | linear_extrude(height = 16, center = true, convexity = 10, twist = 0) 216 | polygon(points=[[0,0],[5,7],[5,0]]); 217 | } 218 | 219 | 220 | for(q=[0:1:1]) 221 | translate([w_off +17.5+ q*13,0,batt_h + t - top_space]) 222 | difference() { 223 | cube_center([1,batt_w+2*7+2*t,1]); 224 | //for(sign=[-1:2:1]) 225 | //translate([0,sign*(batt_w/2-17.5),0]) 226 | //cube_center([5,5,5]); 227 | } 228 | 229 | 230 | translate([-batt_l/2-t/2,0,batt_h+t]) { 231 | difference() { 232 | cube_center([2,batt_w+2*t,4]); 233 | for(i=[-45:15:45]) { 234 | translate([0,-i,0]) 235 | cube_center([2,5,2]); 236 | } 237 | 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /design/motor-cover.scad: -------------------------------------------------------------------------------- 1 | batt_l = 68; 2 | batt_w = 110; 3 | batt_h = 18; 4 | 5 | w_stretch = 2; 6 | w_off = batt_l/2 - 16; 7 | 8 | pi_l = 65; 9 | pi_w = 56; 10 | pi_hole_space_l = 58; 11 | pi_hole_space_w = 49; 12 | 13 | wheel_dist_l = 48; 14 | m3_insert_d = 4.8; 15 | 16 | t = 2; 17 | 18 | top_space = 4; 19 | h=12; 20 | 21 | rotate([180,0,0]) { 22 | 23 | difference() { 24 | union() { 25 | cube_center([16,batt_w+7+7 + 2*t,1]); 26 | cube_center([16,batt_w + 4 + 4 + 2*t,top_space]); 27 | 28 | translate([16/2+8/2,0,0]) 29 | cube_center([8,batt_w+2*t,top_space]); 30 | 31 | translate([0,-batt_w/2-t-4,1]) 32 | rotate([0,0,90]) 33 | scale([3,16,3]) 34 | triangle(); 35 | 36 | translate([0,batt_w/2+t+4,1]) 37 | rotate([0,0,90]) 38 | scale([-3,16,3]) 39 | triangle(); 40 | 41 | } 42 | 43 | translate([-7,0,0,]) 44 | cube_center([2,batt_w+2*t+0.2,10]); 45 | 46 | translate([-6.5,0,0]) 47 | cube_center([1.2,150,1.2]); 48 | 49 | translate([6.5,0,0]) 50 | cube_center([1.2,150,1.2]); 51 | 52 | translate([11.5,-batt_w/2+4,0]) { 53 | translate([0,0,1]) 54 | cylinder(d1=3.1,d2=6.1,h=3.001,$fn=32); 55 | translate([0,0,0]) 56 | cylinder(d1=3.1,d2=3.1,h=3,$fn=32); 57 | } 58 | 59 | translate([11.5,batt_w/2-4,0]) { 60 | translate([0,0,1]) 61 | cylinder(d1=3.1,d2=6.1,h=3.001,$fn=32); 62 | translate([0,0,0]) 63 | cylinder(d1=3.1,d2=3.1,h=3,$fn=32); 64 | } 65 | 66 | translate([-2,0,0]) { 67 | translate([0,0,1]) 68 | cylinder(d1=3.1,d2=6.1,h=3.001,$fn=32); 69 | translate([0,0,0]) 70 | cylinder(d1=3.1,d2=3.1,h=3,$fn=32); 71 | } 72 | 73 | translate([11.5,-batt_w/2+15,0]) 74 | cylinder(d=m3_insert_d,h=5,$fn=32); 75 | translate([11.5,0,0]) 76 | cylinder(d=m3_insert_d,h=5,$fn=32); 77 | translate([11.5,batt_w/2-15,0]) 78 | cylinder(d=m3_insert_d,h=5,$fn=32); 79 | 80 | translate([0,-24.5+3.5+2-4,0]) 81 | cube_center([12,16,0.75]); 82 | translate([0,-24.5+3.5+2-4,0]) 83 | cube_center([5,16,4]); 84 | 85 | translate([0,24.5-3.5-2+4,0]) 86 | cube_center([12,16,0.75]); 87 | translate([0,24.5-3.5-2+4,0]) 88 | cube_center([5,16,4]); 89 | 90 | } 91 | 92 | 93 | translate([-12/2+3/2,batt_w/2+7+t-3.5/2-1,-.5]) 94 | cube_center([3,3.5,0.5]); 95 | 96 | translate([-12/2+3/2,batt_w/2+7+t-2/2-5.75,-.5]) 97 | cube_center([3,2,0.5]); 98 | 99 | translate([12/2-1/2,batt_w/2+7+t-3.5/2-9,-1]) 100 | cube_center([1,3.5,1]); 101 | 102 | translate([-12/2+1/2,batt_w/2+7+t-3.5/2-9,-1]) 103 | cube_center([1,3.5,1]); 104 | 105 | rotate([0,0,180]) { 106 | translate([-12/2+3/2,batt_w/2+7+t-3.5/2-1,-.5]) 107 | cube_center([3,3.5,0.5]); 108 | 109 | translate([-12/2+3/2,batt_w/2+7+t-2/2-5.75,-.5]) 110 | cube_center([3,2,0.5]); 111 | 112 | translate([12/2-1/2,batt_w/2+7+t-3.5/2-9,-1]) 113 | cube_center([1,3.5,1]); 114 | 115 | translate([-12/2+1/2,batt_w/2+7+t-3.5/2-9,-1]) 116 | cube_center([1,3.5,1]); 117 | } 118 | 119 | } 120 | 121 | module cube_center(dims) { 122 | translate([-dims[0]/2, -dims[1]/2, 0]) 123 | cube(dims); 124 | } 125 | 126 | module triangle() { 127 | rotate([0,-90,90]) 128 | linear_extrude(height = 1, center = true, convexity = 10, twist = 0) 129 | polygon(points=[[0,0],[0,1],[1,0]]); 130 | } 131 | -------------------------------------------------------------------------------- /design/phone-mount.scad: -------------------------------------------------------------------------------- 1 | translate([0,8,0]) 2 | difference() { 3 | cube_center([102,8,8.5]); 4 | translate([0,-2,9.0]) 5 | scale([1,1,0.5]) 6 | rotate([0,90,0]) 7 | cylinder(center=true,d=12,h=200,$fn=32); 8 | translate([0,-5.5,6]) 9 | cube_center([102,8,10]); 10 | } 11 | 12 | difference() { 13 | for(s=[-1:2:1]) { 14 | translate([s*20,0,0]) 15 | rotate([9.0,0,0]) 16 | cube_center([10,8,40]); 17 | } 18 | 19 | scale([1,1,-1]) 20 | cube_center([100,100,100]); 21 | } 22 | 23 | 24 | difference() { 25 | union() { 26 | cube_center([102,10,6]); 27 | translate([0,-60/2,0]) 28 | cube_center([4,55,6]); 29 | translate([0,-55,0]) 30 | cylinder(d=9,h=6,$fn=64); 31 | } 32 | for(i=[-40:40:40]) { 33 | translate([i,0,0]) 34 | cylinder(d=3.2,h=50,$fn=32); 35 | translate([i,0,2]) 36 | cylinder(d=6.5,h=50,$fn=32); 37 | } 38 | translate([0,-55,0]) 39 | cylinder(d=3.2,h=50,$fn=32); 40 | translate([0,-55,2]) 41 | cylinder(d=6.5,h=50,$fn=32); 42 | } 43 | 44 | module cube_center(dims,r=0) { 45 | if(r==0) { 46 | translate([-dims[0]/2, -dims[1]/2, 0]) 47 | cube(dims); 48 | } else { 49 | 50 | minkowski() { 51 | translate([-dims[0]/2+r, -dims[1]/2+r, 0]) 52 | cube([dims[0]-2*r,dims[1]-2*r,dims[2]]); 53 | cylinder(r=r,h=0.00001,$fn=32); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /esp32/tethys.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #define EEPROM_SIZE 1 14 | 15 | #define BAUDRATE 115200 16 | 17 | 18 | #define SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e" 19 | #define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" 20 | #define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" 21 | 22 | BLEServer *pServer; 23 | BLEService *pService; 24 | BLECharacteristic *pTxCharacteristic; 25 | BLECharacteristic *pRxCharacteristic; 26 | 27 | bool deviceConnected = false; 28 | std::string rxValue = ""; 29 | 30 | void ble_send(std::string msg) { 31 | Serial.print("# sending: "); 32 | Serial.println(msg.c_str()); 33 | pTxCharacteristic->setValue((byte*)msg.c_str(), msg.size()); 34 | pTxCharacteristic->notify(); 35 | } 36 | 37 | int pinMapping[4] = {22,21,32,33}; 38 | 39 | unsigned long last_command_time = 0; 40 | 41 | void on_ble_receive(std::string msg) { 42 | // command format: 3 bytes: 43 | // ['P'] [channel] [value] 44 | // sets PWM of channel to value 45 | 46 | char str[256]; 47 | strcpy(str, msg.c_str()); 48 | const char* delim = " "; 49 | char* token; 50 | 51 | token = strtok(str, delim); 52 | 53 | if(strcmp(token,"G")==0) { 54 | token = strtok(NULL, delim); 55 | int channel = 0; 56 | while(token != NULL) { 57 | Serial.print("channel "); 58 | Serial.print(channel); 59 | Serial.print(" -> "); 60 | int value = atoi(token); 61 | Serial.println(value); 62 | analogWrite(pinMapping[channel], value); 63 | 64 | token = strtok(NULL, delim); 65 | channel++; 66 | } 67 | 68 | last_command_time = millis(); 69 | } else if(strcmp(token, "S")==0) { 70 | token = strtok(NULL, delim); 71 | unsigned char robot_id = atoi(token); 72 | EEPROM.write(0, robot_id); 73 | EEPROM.commit(); 74 | Serial.print("set robot id -> "); 75 | Serial.println((int)robot_id); 76 | } 77 | 78 | } 79 | 80 | 81 | class MyServerCallbacks : public BLEServerCallbacks 82 | { 83 | void onConnect(BLEServer *pServer) 84 | { 85 | Serial.println("# connected"); 86 | deviceConnected = true; 87 | } 88 | void onDisconnect(BLEServer *pServer) 89 | { 90 | Serial.println("# disonnected"); 91 | deviceConnected = false; 92 | } 93 | }; 94 | 95 | class MyCallbacks : public BLECharacteristicCallbacks 96 | { 97 | void onWrite(BLECharacteristic *pCharacteristic) 98 | { 99 | Serial.print("# received: "); 100 | rxValue = pCharacteristic->getValue(); 101 | Serial.println(rxValue.c_str()); 102 | on_ble_receive(rxValue); 103 | } 104 | }; 105 | 106 | void setup() { 107 | 108 | 109 | 110 | for(int channel=0;channel<4;channel++) { 111 | pinMode(pinMapping[channel], INPUT); 112 | digitalWrite(pinMapping[channel], LOW); 113 | } 114 | 115 | EEPROM.begin(EEPROM_SIZE); 116 | 117 | int bot_num = EEPROM.read(0); 118 | 119 | Serial.begin(BAUDRATE); 120 | 121 | char robot_name[256]; 122 | sprintf(robot_name, "tethys-%d", bot_num); 123 | BLEDevice::init(robot_name); 124 | pServer = BLEDevice::createServer(); 125 | pServer->setCallbacks(new MyServerCallbacks()); 126 | 127 | pService = pServer->createService(SERVICE_UUID); 128 | 129 | // TX 130 | pTxCharacteristic = pService->createCharacteristic( 131 | CHARACTERISTIC_UUID_TX, 132 | BLECharacteristic::PROPERTY_NOTIFY 133 | ); 134 | pTxCharacteristic->addDescriptor(new BLE2902()); 135 | 136 | // RX 137 | pRxCharacteristic = pService->createCharacteristic( 138 | CHARACTERISTIC_UUID_RX, 139 | BLECharacteristic::PROPERTY_WRITE 140 | ); 141 | pRxCharacteristic->setCallbacks(new MyCallbacks()); 142 | 143 | pService->start(); 144 | pServer->getAdvertising()->start(); 145 | 146 | Serial.println("# waiting for client connection ..."); 147 | 148 | 149 | } 150 | 151 | void loop() { 152 | static unsigned long millis_last = 0; 153 | static unsigned long reset_last = 0; 154 | 155 | if (millis() - last_command_time >= 1000) { 156 | if(millis() - reset_last >= 1000) { 157 | reset_last = millis(); 158 | for(int channel=0;channel<4;channel++) { 159 | analogWrite(pinMapping[channel], 0); 160 | } 161 | } 162 | } 163 | 164 | if (millis() - millis_last >= 1000) { 165 | millis_last = millis(); 166 | 167 | if (deviceConnected) { 168 | char msg[] = "Z\n"; 169 | ble_send(msg); 170 | 171 | Serial.println(touchRead(4)); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /images/charging-usb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dheera/robot-botparty/8988b9896cb919899c0b8f6d8cc7fb83ee04bcdf/images/charging-usb.jpg -------------------------------------------------------------------------------- /images/charging-wireless.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dheera/robot-botparty/8988b9896cb919899c0b8f6d8cc7fb83ee04bcdf/images/charging-wireless.jpg -------------------------------------------------------------------------------- /images/drv8833-cheap-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dheera/robot-botparty/8988b9896cb919899c0b8f6d8cc7fb83ee04bcdf/images/drv8833-cheap-0.jpg -------------------------------------------------------------------------------- /images/drv8833-cheap-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dheera/robot-botparty/8988b9896cb919899c0b8f6d8cc7fb83ee04bcdf/images/drv8833-cheap-1.jpg -------------------------------------------------------------------------------- /images/robot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dheera/robot-botparty/8988b9896cb919899c0b8f6d8cc7fb83ee04bcdf/images/robot.jpg -------------------------------------------------------------------------------- /images/tinypico-drv8833-combo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dheera/robot-botparty/8988b9896cb919899c0b8f6d8cc7fb83ee04bcdf/images/tinypico-drv8833-combo.jpg -------------------------------------------------------------------------------- /images/video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dheera/robot-botparty/8988b9896cb919899c0b8f6d8cc7fb83ee04bcdf/images/video.gif -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@hapi/bourne": { 8 | "version": "2.0.0", 9 | "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", 10 | "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==" 11 | }, 12 | "@types/color-name": { 13 | "version": "1.1.1", 14 | "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", 15 | "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" 16 | }, 17 | "accepts": { 18 | "version": "1.3.7", 19 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 20 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 21 | "requires": { 22 | "mime-types": "~2.1.24", 23 | "negotiator": "0.6.2" 24 | } 25 | }, 26 | "after": { 27 | "version": "0.8.2", 28 | "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", 29 | "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" 30 | }, 31 | "ansi-styles": { 32 | "version": "3.2.1", 33 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 34 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 35 | "requires": { 36 | "color-convert": "^1.9.0" 37 | } 38 | }, 39 | "args": { 40 | "version": "5.0.1", 41 | "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", 42 | "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", 43 | "requires": { 44 | "camelcase": "5.0.0", 45 | "chalk": "2.4.2", 46 | "leven": "2.1.0", 47 | "mri": "1.1.4" 48 | }, 49 | "dependencies": { 50 | "chalk": { 51 | "version": "2.4.2", 52 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 53 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 54 | "requires": { 55 | "ansi-styles": "^3.2.1", 56 | "escape-string-regexp": "^1.0.5", 57 | "supports-color": "^5.3.0" 58 | } 59 | } 60 | } 61 | }, 62 | "array-flatten": { 63 | "version": "1.1.1", 64 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 65 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 66 | }, 67 | "arraybuffer.slice": { 68 | "version": "0.0.7", 69 | "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", 70 | "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" 71 | }, 72 | "async-limiter": { 73 | "version": "1.0.1", 74 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 75 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" 76 | }, 77 | "atomic-sleep": { 78 | "version": "1.0.0", 79 | "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", 80 | "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" 81 | }, 82 | "backo2": { 83 | "version": "1.0.2", 84 | "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", 85 | "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" 86 | }, 87 | "base64-arraybuffer": { 88 | "version": "0.1.5", 89 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 90 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" 91 | }, 92 | "base64id": { 93 | "version": "2.0.0", 94 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", 95 | "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" 96 | }, 97 | "better-assert": { 98 | "version": "1.0.2", 99 | "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", 100 | "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", 101 | "requires": { 102 | "callsite": "1.0.0" 103 | } 104 | }, 105 | "blob": { 106 | "version": "0.0.5", 107 | "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", 108 | "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" 109 | }, 110 | "body-parser": { 111 | "version": "1.19.0", 112 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 113 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 114 | "requires": { 115 | "bytes": "3.1.0", 116 | "content-type": "~1.0.4", 117 | "debug": "2.6.9", 118 | "depd": "~1.1.2", 119 | "http-errors": "1.7.2", 120 | "iconv-lite": "0.4.24", 121 | "on-finished": "~2.3.0", 122 | "qs": "6.7.0", 123 | "raw-body": "2.4.0", 124 | "type-is": "~1.6.17" 125 | } 126 | }, 127 | "bytes": { 128 | "version": "3.1.0", 129 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 130 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 131 | }, 132 | "callsite": { 133 | "version": "1.0.0", 134 | "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", 135 | "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" 136 | }, 137 | "camelcase": { 138 | "version": "5.0.0", 139 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", 140 | "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" 141 | }, 142 | "chalk": { 143 | "version": "3.0.0", 144 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", 145 | "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", 146 | "requires": { 147 | "ansi-styles": "^4.1.0", 148 | "supports-color": "^7.1.0" 149 | }, 150 | "dependencies": { 151 | "ansi-styles": { 152 | "version": "4.2.1", 153 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", 154 | "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", 155 | "requires": { 156 | "@types/color-name": "^1.1.1", 157 | "color-convert": "^2.0.1" 158 | } 159 | }, 160 | "color-convert": { 161 | "version": "2.0.1", 162 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 163 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 164 | "requires": { 165 | "color-name": "~1.1.4" 166 | } 167 | }, 168 | "color-name": { 169 | "version": "1.1.4", 170 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 171 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 172 | }, 173 | "has-flag": { 174 | "version": "4.0.0", 175 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 176 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 177 | }, 178 | "supports-color": { 179 | "version": "7.1.0", 180 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", 181 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", 182 | "requires": { 183 | "has-flag": "^4.0.0" 184 | } 185 | } 186 | } 187 | }, 188 | "color-convert": { 189 | "version": "1.9.3", 190 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 191 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 192 | "requires": { 193 | "color-name": "1.1.3" 194 | } 195 | }, 196 | "color-name": { 197 | "version": "1.1.3", 198 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 199 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 200 | }, 201 | "component-bind": { 202 | "version": "1.0.0", 203 | "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", 204 | "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" 205 | }, 206 | "component-emitter": { 207 | "version": "1.2.1", 208 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 209 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" 210 | }, 211 | "component-inherit": { 212 | "version": "0.0.3", 213 | "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", 214 | "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" 215 | }, 216 | "content-disposition": { 217 | "version": "0.5.3", 218 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 219 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 220 | "requires": { 221 | "safe-buffer": "5.1.2" 222 | } 223 | }, 224 | "content-type": { 225 | "version": "1.0.4", 226 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 227 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 228 | }, 229 | "cookie": { 230 | "version": "0.4.0", 231 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 232 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 233 | }, 234 | "cookie-signature": { 235 | "version": "1.0.6", 236 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 237 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 238 | }, 239 | "dateformat": { 240 | "version": "3.0.3", 241 | "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", 242 | "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" 243 | }, 244 | "debug": { 245 | "version": "2.6.9", 246 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 247 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 248 | "requires": { 249 | "ms": "2.0.0" 250 | } 251 | }, 252 | "depd": { 253 | "version": "1.1.2", 254 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 255 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 256 | }, 257 | "destroy": { 258 | "version": "1.0.4", 259 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 260 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 261 | }, 262 | "ee-first": { 263 | "version": "1.1.1", 264 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 265 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 266 | }, 267 | "encodeurl": { 268 | "version": "1.0.2", 269 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 270 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 271 | }, 272 | "end-of-stream": { 273 | "version": "1.4.4", 274 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 275 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 276 | "requires": { 277 | "once": "^1.4.0" 278 | } 279 | }, 280 | "engine.io": { 281 | "version": "3.4.0", 282 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.0.tgz", 283 | "integrity": "sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==", 284 | "requires": { 285 | "accepts": "~1.3.4", 286 | "base64id": "2.0.0", 287 | "cookie": "0.3.1", 288 | "debug": "~4.1.0", 289 | "engine.io-parser": "~2.2.0", 290 | "ws": "^7.1.2" 291 | }, 292 | "dependencies": { 293 | "cookie": { 294 | "version": "0.3.1", 295 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 296 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 297 | }, 298 | "debug": { 299 | "version": "4.1.1", 300 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 301 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 302 | "requires": { 303 | "ms": "^2.1.1" 304 | } 305 | }, 306 | "ms": { 307 | "version": "2.1.2", 308 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 309 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 310 | } 311 | } 312 | }, 313 | "engine.io-client": { 314 | "version": "3.4.0", 315 | "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz", 316 | "integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==", 317 | "requires": { 318 | "component-emitter": "1.2.1", 319 | "component-inherit": "0.0.3", 320 | "debug": "~4.1.0", 321 | "engine.io-parser": "~2.2.0", 322 | "has-cors": "1.1.0", 323 | "indexof": "0.0.1", 324 | "parseqs": "0.0.5", 325 | "parseuri": "0.0.5", 326 | "ws": "~6.1.0", 327 | "xmlhttprequest-ssl": "~1.5.4", 328 | "yeast": "0.1.2" 329 | }, 330 | "dependencies": { 331 | "debug": { 332 | "version": "4.1.1", 333 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 334 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 335 | "requires": { 336 | "ms": "^2.1.1" 337 | } 338 | }, 339 | "ms": { 340 | "version": "2.1.2", 341 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 342 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 343 | }, 344 | "ws": { 345 | "version": "6.1.4", 346 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", 347 | "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", 348 | "requires": { 349 | "async-limiter": "~1.0.0" 350 | } 351 | } 352 | } 353 | }, 354 | "engine.io-parser": { 355 | "version": "2.2.0", 356 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", 357 | "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", 358 | "requires": { 359 | "after": "0.8.2", 360 | "arraybuffer.slice": "~0.0.7", 361 | "base64-arraybuffer": "0.1.5", 362 | "blob": "0.0.5", 363 | "has-binary2": "~1.0.2" 364 | } 365 | }, 366 | "escape-html": { 367 | "version": "1.0.3", 368 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 369 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 370 | }, 371 | "escape-string-regexp": { 372 | "version": "1.0.5", 373 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 374 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 375 | }, 376 | "etag": { 377 | "version": "1.8.1", 378 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 379 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 380 | }, 381 | "express": { 382 | "version": "4.17.1", 383 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 384 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 385 | "requires": { 386 | "accepts": "~1.3.7", 387 | "array-flatten": "1.1.1", 388 | "body-parser": "1.19.0", 389 | "content-disposition": "0.5.3", 390 | "content-type": "~1.0.4", 391 | "cookie": "0.4.0", 392 | "cookie-signature": "1.0.6", 393 | "debug": "2.6.9", 394 | "depd": "~1.1.2", 395 | "encodeurl": "~1.0.2", 396 | "escape-html": "~1.0.3", 397 | "etag": "~1.8.1", 398 | "finalhandler": "~1.1.2", 399 | "fresh": "0.5.2", 400 | "merge-descriptors": "1.0.1", 401 | "methods": "~1.1.2", 402 | "on-finished": "~2.3.0", 403 | "parseurl": "~1.3.3", 404 | "path-to-regexp": "0.1.7", 405 | "proxy-addr": "~2.0.5", 406 | "qs": "6.7.0", 407 | "range-parser": "~1.2.1", 408 | "safe-buffer": "5.1.2", 409 | "send": "0.17.1", 410 | "serve-static": "1.14.1", 411 | "setprototypeof": "1.1.1", 412 | "statuses": "~1.5.0", 413 | "type-is": "~1.6.18", 414 | "utils-merge": "1.0.1", 415 | "vary": "~1.1.2" 416 | } 417 | }, 418 | "fast-redact": { 419 | "version": "2.0.0", 420 | "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-2.0.0.tgz", 421 | "integrity": "sha512-zxpkULI9W9MNTK2sJ3BpPQrTEXFNESd2X6O1tXMFpK/XM0G5c5Rll2EVYZH2TqI3xRGK/VaJ+eEOt7pnENJpeA==" 422 | }, 423 | "fast-safe-stringify": { 424 | "version": "2.0.7", 425 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", 426 | "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" 427 | }, 428 | "finalhandler": { 429 | "version": "1.1.2", 430 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 431 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 432 | "requires": { 433 | "debug": "2.6.9", 434 | "encodeurl": "~1.0.2", 435 | "escape-html": "~1.0.3", 436 | "on-finished": "~2.3.0", 437 | "parseurl": "~1.3.3", 438 | "statuses": "~1.5.0", 439 | "unpipe": "~1.0.0" 440 | } 441 | }, 442 | "flatstr": { 443 | "version": "1.0.12", 444 | "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", 445 | "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" 446 | }, 447 | "forwarded": { 448 | "version": "0.1.2", 449 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 450 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 451 | }, 452 | "fresh": { 453 | "version": "0.5.2", 454 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 455 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 456 | }, 457 | "has-binary2": { 458 | "version": "1.0.3", 459 | "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", 460 | "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", 461 | "requires": { 462 | "isarray": "2.0.1" 463 | } 464 | }, 465 | "has-cors": { 466 | "version": "1.1.0", 467 | "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", 468 | "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" 469 | }, 470 | "has-flag": { 471 | "version": "3.0.0", 472 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 473 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 474 | }, 475 | "http-errors": { 476 | "version": "1.7.2", 477 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 478 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 479 | "requires": { 480 | "depd": "~1.1.2", 481 | "inherits": "2.0.3", 482 | "setprototypeof": "1.1.1", 483 | "statuses": ">= 1.5.0 < 2", 484 | "toidentifier": "1.0.0" 485 | } 486 | }, 487 | "iconv-lite": { 488 | "version": "0.4.24", 489 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 490 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 491 | "requires": { 492 | "safer-buffer": ">= 2.1.2 < 3" 493 | } 494 | }, 495 | "indexof": { 496 | "version": "0.0.1", 497 | "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", 498 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" 499 | }, 500 | "inherits": { 501 | "version": "2.0.3", 502 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 503 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 504 | }, 505 | "ipaddr.js": { 506 | "version": "1.9.1", 507 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 508 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 509 | }, 510 | "isarray": { 511 | "version": "2.0.1", 512 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 513 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" 514 | }, 515 | "jmespath": { 516 | "version": "0.15.0", 517 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 518 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 519 | }, 520 | "joycon": { 521 | "version": "2.2.5", 522 | "resolved": "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz", 523 | "integrity": "sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ==" 524 | }, 525 | "leven": { 526 | "version": "2.1.0", 527 | "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", 528 | "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" 529 | }, 530 | "media-typer": { 531 | "version": "0.3.0", 532 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 533 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 534 | }, 535 | "merge-descriptors": { 536 | "version": "1.0.1", 537 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 538 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 539 | }, 540 | "methods": { 541 | "version": "1.1.2", 542 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 543 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 544 | }, 545 | "mime": { 546 | "version": "1.6.0", 547 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 548 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 549 | }, 550 | "mime-db": { 551 | "version": "1.43.0", 552 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", 553 | "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" 554 | }, 555 | "mime-types": { 556 | "version": "2.1.26", 557 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", 558 | "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", 559 | "requires": { 560 | "mime-db": "1.43.0" 561 | } 562 | }, 563 | "mri": { 564 | "version": "1.1.4", 565 | "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", 566 | "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==" 567 | }, 568 | "ms": { 569 | "version": "2.0.0", 570 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 571 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 572 | }, 573 | "negotiator": { 574 | "version": "0.6.2", 575 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 576 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 577 | }, 578 | "object-component": { 579 | "version": "0.0.3", 580 | "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", 581 | "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" 582 | }, 583 | "on-finished": { 584 | "version": "2.3.0", 585 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 586 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 587 | "requires": { 588 | "ee-first": "1.1.1" 589 | } 590 | }, 591 | "once": { 592 | "version": "1.4.0", 593 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 594 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 595 | "requires": { 596 | "wrappy": "1" 597 | } 598 | }, 599 | "parseqs": { 600 | "version": "0.0.5", 601 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", 602 | "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", 603 | "requires": { 604 | "better-assert": "~1.0.0" 605 | } 606 | }, 607 | "parseuri": { 608 | "version": "0.0.5", 609 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", 610 | "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", 611 | "requires": { 612 | "better-assert": "~1.0.0" 613 | } 614 | }, 615 | "parseurl": { 616 | "version": "1.3.3", 617 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 618 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 619 | }, 620 | "path-to-regexp": { 621 | "version": "0.1.7", 622 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 623 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 624 | }, 625 | "pino": { 626 | "version": "6.2.0", 627 | "resolved": "https://registry.npmjs.org/pino/-/pino-6.2.0.tgz", 628 | "integrity": "sha512-UzrsiT5Wyscw7dxHa8Ec8G2kY45mwFk7rrZhMkCMg8s9F8VWDVj+WFcaSIKproTDyxlqerMaHw+11jlNXgeiCg==", 629 | "requires": { 630 | "fast-redact": "^2.0.0", 631 | "fast-safe-stringify": "^2.0.7", 632 | "flatstr": "^1.0.12", 633 | "pino-std-serializers": "^2.4.2", 634 | "quick-format-unescaped": "^4.0.1", 635 | "sonic-boom": "^1.0.0" 636 | } 637 | }, 638 | "pino-pretty": { 639 | "version": "4.0.0", 640 | "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-4.0.0.tgz", 641 | "integrity": "sha512-YLy/n3dMXYWOodSm530gelkSAJGmEp29L9pqiycInlIae5FEJPWAkMRO3JFMbIFtjD2Ve4SH2aBcz2aRreGpBQ==", 642 | "requires": { 643 | "@hapi/bourne": "^2.0.0", 644 | "args": "^5.0.1", 645 | "chalk": "^3.0.0", 646 | "dateformat": "^3.0.3", 647 | "fast-safe-stringify": "^2.0.7", 648 | "jmespath": "^0.15.0", 649 | "joycon": "^2.2.5", 650 | "pump": "^3.0.0", 651 | "readable-stream": "^3.6.0", 652 | "split2": "^3.1.1", 653 | "strip-json-comments": "^3.0.1" 654 | } 655 | }, 656 | "pino-std-serializers": { 657 | "version": "2.4.2", 658 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.4.2.tgz", 659 | "integrity": "sha512-WaL504dO8eGs+vrK+j4BuQQq6GLKeCCcHaMB2ItygzVURcL1CycwNEUHTD/lHFHs/NL5qAz2UKrjYWXKSf4aMQ==" 660 | }, 661 | "proxy-addr": { 662 | "version": "2.0.6", 663 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 664 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 665 | "requires": { 666 | "forwarded": "~0.1.2", 667 | "ipaddr.js": "1.9.1" 668 | } 669 | }, 670 | "pump": { 671 | "version": "3.0.0", 672 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 673 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 674 | "requires": { 675 | "end-of-stream": "^1.1.0", 676 | "once": "^1.3.1" 677 | } 678 | }, 679 | "qs": { 680 | "version": "6.7.0", 681 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 682 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 683 | }, 684 | "quick-format-unescaped": { 685 | "version": "4.0.1", 686 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz", 687 | "integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==" 688 | }, 689 | "range-parser": { 690 | "version": "1.2.1", 691 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 692 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 693 | }, 694 | "rate-limiter-flexible": { 695 | "version": "2.1.3", 696 | "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.1.3.tgz", 697 | "integrity": "sha512-xpUP6qDd4QTgg61D/OBekrpQralUJnfSfYweS/Msmz1d3qpg+XThOvLjgpY+7NgoakMk9mBlNdJlSmP/sXBS5Q==" 698 | }, 699 | "raw-body": { 700 | "version": "2.4.0", 701 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 702 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 703 | "requires": { 704 | "bytes": "3.1.0", 705 | "http-errors": "1.7.2", 706 | "iconv-lite": "0.4.24", 707 | "unpipe": "1.0.0" 708 | } 709 | }, 710 | "readable-stream": { 711 | "version": "3.6.0", 712 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 713 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 714 | "requires": { 715 | "inherits": "^2.0.3", 716 | "string_decoder": "^1.1.1", 717 | "util-deprecate": "^1.0.1" 718 | } 719 | }, 720 | "safe-buffer": { 721 | "version": "5.1.2", 722 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 723 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 724 | }, 725 | "safer-buffer": { 726 | "version": "2.1.2", 727 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 728 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 729 | }, 730 | "send": { 731 | "version": "0.17.1", 732 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 733 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 734 | "requires": { 735 | "debug": "2.6.9", 736 | "depd": "~1.1.2", 737 | "destroy": "~1.0.4", 738 | "encodeurl": "~1.0.2", 739 | "escape-html": "~1.0.3", 740 | "etag": "~1.8.1", 741 | "fresh": "0.5.2", 742 | "http-errors": "~1.7.2", 743 | "mime": "1.6.0", 744 | "ms": "2.1.1", 745 | "on-finished": "~2.3.0", 746 | "range-parser": "~1.2.1", 747 | "statuses": "~1.5.0" 748 | }, 749 | "dependencies": { 750 | "ms": { 751 | "version": "2.1.1", 752 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 753 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 754 | } 755 | } 756 | }, 757 | "serve-static": { 758 | "version": "1.14.1", 759 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 760 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 761 | "requires": { 762 | "encodeurl": "~1.0.2", 763 | "escape-html": "~1.0.3", 764 | "parseurl": "~1.3.3", 765 | "send": "0.17.1" 766 | } 767 | }, 768 | "setprototypeof": { 769 | "version": "1.1.1", 770 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 771 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 772 | }, 773 | "socket.io": { 774 | "version": "2.3.0", 775 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", 776 | "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", 777 | "requires": { 778 | "debug": "~4.1.0", 779 | "engine.io": "~3.4.0", 780 | "has-binary2": "~1.0.2", 781 | "socket.io-adapter": "~1.1.0", 782 | "socket.io-client": "2.3.0", 783 | "socket.io-parser": "~3.4.0" 784 | }, 785 | "dependencies": { 786 | "debug": { 787 | "version": "4.1.1", 788 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 789 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 790 | "requires": { 791 | "ms": "^2.1.1" 792 | } 793 | }, 794 | "ms": { 795 | "version": "2.1.2", 796 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 797 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 798 | } 799 | } 800 | }, 801 | "socket.io-adapter": { 802 | "version": "1.1.2", 803 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", 804 | "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" 805 | }, 806 | "socket.io-client": { 807 | "version": "2.3.0", 808 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", 809 | "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", 810 | "requires": { 811 | "backo2": "1.0.2", 812 | "base64-arraybuffer": "0.1.5", 813 | "component-bind": "1.0.0", 814 | "component-emitter": "1.2.1", 815 | "debug": "~4.1.0", 816 | "engine.io-client": "~3.4.0", 817 | "has-binary2": "~1.0.2", 818 | "has-cors": "1.1.0", 819 | "indexof": "0.0.1", 820 | "object-component": "0.0.3", 821 | "parseqs": "0.0.5", 822 | "parseuri": "0.0.5", 823 | "socket.io-parser": "~3.3.0", 824 | "to-array": "0.1.4" 825 | }, 826 | "dependencies": { 827 | "debug": { 828 | "version": "4.1.1", 829 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 830 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 831 | "requires": { 832 | "ms": "^2.1.1" 833 | } 834 | }, 835 | "ms": { 836 | "version": "2.1.2", 837 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 838 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 839 | }, 840 | "socket.io-parser": { 841 | "version": "3.3.0", 842 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", 843 | "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", 844 | "requires": { 845 | "component-emitter": "1.2.1", 846 | "debug": "~3.1.0", 847 | "isarray": "2.0.1" 848 | }, 849 | "dependencies": { 850 | "debug": { 851 | "version": "3.1.0", 852 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 853 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 854 | "requires": { 855 | "ms": "2.0.0" 856 | } 857 | }, 858 | "ms": { 859 | "version": "2.0.0", 860 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 861 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 862 | } 863 | } 864 | } 865 | } 866 | }, 867 | "socket.io-parser": { 868 | "version": "3.4.0", 869 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.0.tgz", 870 | "integrity": "sha512-/G/VOI+3DBp0+DJKW4KesGnQkQPFmUCbA/oO2QGT6CWxU7hLGWqU3tyuzeSK/dqcyeHsQg1vTe9jiZI8GU9SCQ==", 871 | "requires": { 872 | "component-emitter": "1.2.1", 873 | "debug": "~4.1.0", 874 | "isarray": "2.0.1" 875 | }, 876 | "dependencies": { 877 | "debug": { 878 | "version": "4.1.1", 879 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 880 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 881 | "requires": { 882 | "ms": "^2.1.1" 883 | } 884 | }, 885 | "ms": { 886 | "version": "2.1.2", 887 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 888 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 889 | } 890 | } 891 | }, 892 | "sonic-boom": { 893 | "version": "1.0.1", 894 | "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.0.1.tgz", 895 | "integrity": "sha512-o9tx+bonVEXSaPtptyXQXpP8l6UV9Bi3im2geZskvWw2a/o/hrbWI7EBbbv+rOx6Hubnzun9GgH4WfbgEA3MFQ==", 896 | "requires": { 897 | "atomic-sleep": "^1.0.0", 898 | "flatstr": "^1.0.12" 899 | } 900 | }, 901 | "split2": { 902 | "version": "3.1.1", 903 | "resolved": "https://registry.npmjs.org/split2/-/split2-3.1.1.tgz", 904 | "integrity": "sha512-emNzr1s7ruq4N+1993yht631/JH+jaj0NYBosuKmLcq+JkGQ9MmTw1RB1fGaTCzUuseRIClrlSLHRNYGwWQ58Q==", 905 | "requires": { 906 | "readable-stream": "^3.0.0" 907 | } 908 | }, 909 | "statuses": { 910 | "version": "1.5.0", 911 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 912 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 913 | }, 914 | "string_decoder": { 915 | "version": "1.3.0", 916 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 917 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 918 | "requires": { 919 | "safe-buffer": "~5.2.0" 920 | }, 921 | "dependencies": { 922 | "safe-buffer": { 923 | "version": "5.2.0", 924 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 925 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 926 | } 927 | } 928 | }, 929 | "strip-json-comments": { 930 | "version": "3.1.0", 931 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", 932 | "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==" 933 | }, 934 | "supports-color": { 935 | "version": "5.5.0", 936 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 937 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 938 | "requires": { 939 | "has-flag": "^3.0.0" 940 | } 941 | }, 942 | "to-array": { 943 | "version": "0.1.4", 944 | "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", 945 | "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" 946 | }, 947 | "toidentifier": { 948 | "version": "1.0.0", 949 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 950 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 951 | }, 952 | "type-is": { 953 | "version": "1.6.18", 954 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 955 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 956 | "requires": { 957 | "media-typer": "0.3.0", 958 | "mime-types": "~2.1.24" 959 | } 960 | }, 961 | "unpipe": { 962 | "version": "1.0.0", 963 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 964 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 965 | }, 966 | "util-deprecate": { 967 | "version": "1.0.2", 968 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 969 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 970 | }, 971 | "utils-merge": { 972 | "version": "1.0.1", 973 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 974 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 975 | }, 976 | "vary": { 977 | "version": "1.1.2", 978 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 979 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 980 | }, 981 | "wrappy": { 982 | "version": "1.0.2", 983 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 984 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 985 | }, 986 | "ws": { 987 | "version": "7.2.3", 988 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", 989 | "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" 990 | }, 991 | "xmlhttprequest-ssl": { 992 | "version": "1.5.5", 993 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", 994 | "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" 995 | }, 996 | "yeast": { 997 | "version": "0.1.2", 998 | "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", 999 | "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" 1000 | } 1001 | } 1002 | } 1003 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.17.1", 13 | "pino": "^6.2.0", 14 | "pino-pretty": "^4.0.0", 15 | "rate-limiter-flexible": "^2.1.3", 16 | "socket.io": "^2.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/root/etc/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user www-data; 2 | worker_processes auto; 3 | pid /run/nginx.pid; 4 | include /etc/nginx/modules-enabled/*.conf; 5 | 6 | events { 7 | worker_connections 768; 8 | # multi_accept on; 9 | } 10 | 11 | http { 12 | 13 | ## 14 | # Basic Settings 15 | ## 16 | 17 | upstream socket_nodes { 18 | ip_hash; 19 | server localhost:3000; 20 | } 21 | 22 | sendfile on; 23 | tcp_nopush on; 24 | tcp_nodelay on; 25 | keepalive_timeout 65; 26 | types_hash_max_size 2048; 27 | # server_tokens off; 28 | 29 | # server_names_hash_bucket_size 64; 30 | # server_name_in_redirect off; 31 | 32 | include /etc/nginx/mime.types; 33 | default_type application/octet-stream; 34 | 35 | ## 36 | # SSL Settings 37 | ## 38 | 39 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE 40 | ssl_prefer_server_ciphers on; 41 | 42 | ## 43 | # Logging Settings 44 | ## 45 | 46 | access_log /var/log/nginx/access.log; 47 | error_log /var/log/nginx/error.log; 48 | 49 | ## 50 | # Gzip Settings 51 | ## 52 | 53 | gzip on; 54 | 55 | # gzip_vary on; 56 | # gzip_proxied any; 57 | # gzip_comp_level 6; 58 | # gzip_buffers 16 8k; 59 | # gzip_http_version 1.1; 60 | # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; 61 | 62 | ## 63 | # Virtual Host Configs 64 | ## 65 | 66 | include /etc/nginx/conf.d/*.conf; 67 | include /etc/nginx/sites-enabled/*; 68 | } 69 | 70 | 71 | #mail { 72 | # # See sample authentication script at: 73 | # # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript 74 | # 75 | # # auth_http localhost/auth.php; 76 | # # pop3_capabilities "TOP" "USER"; 77 | # # imap_capabilities "IMAP4rev1" "UIDPLUS"; 78 | # 79 | # server { 80 | # listen localhost:110; 81 | # protocol pop3; 82 | # proxy on; 83 | # } 84 | # 85 | # server { 86 | # listen localhost:143; 87 | # protocol imap; 88 | # proxy on; 89 | # } 90 | #} 91 | -------------------------------------------------------------------------------- /server/root/etc/nginx/sites-available/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server ipv6only=on; 4 | 5 | server_name botparty.dheera.net; # EDIT THIS 6 | 7 | root /usr/share/nginx/html; 8 | index index.html index.htm index.nginx-debian.html; 9 | 10 | rewrite ^ https://$server_name$request_uri? redirect; 11 | 12 | location / { 13 | try_files $uri $uri/ =404; 14 | } 15 | } 16 | 17 | server { 18 | listen 443 ssl spdy; 19 | listen [::]:443 ssl spdy; 20 | 21 | server_name botparty.dheera.net; # EDIT THIS 22 | 23 | root /usr/share/nginx/html; 24 | index index.html index.htm index.nginx-debian.html; 25 | 26 | ssl on; 27 | ssl_certificate /etc/ssl/certs/STAR_dheera_net.pem; # EDIT THIS 28 | ssl_certificate_key /etc/ssl/certs/STAR_dheera_net.key; # EDIT THIS 29 | 30 | ssl_session_timeout 5m; 31 | 32 | ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; 33 | ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; 34 | ssl_prefer_server_ciphers on; 35 | 36 | location / { 37 | proxy_set_header Upgrade $http_upgrade; 38 | proxy_set_header Connection "upgrade"; 39 | proxy_http_version 1.1; 40 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 41 | proxy_set_header Host $host; 42 | proxy_set_header X-Real-IP $remote_addr; 43 | proxy_set_header X-Real-Port $remote_port; 44 | proxy_pass http://socket_nodes; 45 | } 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /server/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd `dirname $0` 4 | 5 | while true 6 | do 7 | node `dirname $0`/server.js 8 | sleep 2 9 | done 10 | 11 | -------------------------------------------------------------------------------- /server/server-io.js: -------------------------------------------------------------------------------- 1 | const { RateLimiterMemory } = require('rate-limiter-flexible'); 2 | const log = require('pino')({prettyPrint: true, level: 'debug'}); 3 | 4 | module.exports = (app, io) => { 5 | 6 | rooms = {}; 7 | 8 | const rateLimiter = new RateLimiterMemory( 9 | { 10 | points: 150, 11 | duration: 1, 12 | }); 13 | 14 | const keywords = ["connection", "connect", "disconnect", "reconnect", "subscribe", "unsubscribe", "publish"]; 15 | 16 | setInterval(() => { 17 | for(roomName in rooms) { 18 | console.log(roomName); 19 | for(i in rooms[roomName]) { 20 | if(!rooms[roomName][i]) { console.log("-"); continue; } 21 | if(!rooms[roomName][i].socket) { console.log("--"); continue; } 22 | console.log(rooms[roomName][i].socket.id); 23 | } 24 | console.log(""); 25 | } 26 | }, 5000); 27 | 28 | io.on('connection', function(socket) { 29 | 30 | // close if client doesn't subscribe to anything in 3 seconds 31 | socket.connectionTimeout = setTimeout(() => { 32 | socket.disconnect(true); 33 | }, 3000); 34 | 35 | socket.subscriptions = []; 36 | 37 | if(socket && socket.handshake && socket.handshake.headers) { 38 | if("x-real-ip" in socket.handshake.headers) { 39 | socket.ip = socket.handshake.headers["x-real-ip"]; 40 | } else { 41 | socket.ip = socket.conn.remoteAddress; 42 | } 43 | } 44 | 45 | log.info({'connection': { 46 | 'socket.id': socket.id, 47 | 'socket.ip': socket.ip, 48 | }}); 49 | 50 | socket.on('subscribe', async (roomName) => { 51 | log.info({'subscribe': { 52 | 'socket.id': socket.id, 53 | 'socket.ip': socket.ip, 54 | 'roomName': roomName, 55 | }}); 56 | try { 57 | await rateLimiter.consume(socket.handshake.address); 58 | if(socket.connectionTimeout) clearTimeout(socket.connectionTimeout); 59 | if(keywords.includes(roomName)) { 60 | log.error({'subscribe.badRoomNameError': {'socket.id': socket.id, 'socket.ip': socket.ip}}); 61 | return; 62 | } 63 | if(roomName.indexOf(".")!==-1) { 64 | log.error({'subscribe.badRoomNameError': {'socket.id': socket.id, 'socket.ip': socket.ip}}); 65 | return; 66 | } 67 | if(socket.subscriptions.length > 10) { 68 | log.error({'subscribe.tooManySubscriptionsError': {'socket.id': socket.id, 'socket.ip': socket.ip}}); 69 | socket.disconnect(true); 70 | return; 71 | } 72 | if(!(roomName in rooms)) { 73 | rooms[roomName] = []; 74 | } 75 | rooms[roomName].push({socket: socket}); 76 | socket.subscriptions.push(roomName); 77 | 78 | log.info({[roomName + '.members']: rooms[roomName].map(s=>s.socket.id)}); 79 | socket.emit(roomName + '.members', rooms[roomName].map(s=>s.socket.id)); 80 | } catch(rejRes) { 81 | log.error({'subscribe.error': rejRes}); 82 | } 83 | }); 84 | 85 | socket.on('unsubscribe', async (roomName) => { 86 | log.info({'unsubscribe': { 87 | 'socket.id': socket.id, 88 | 'socket.ip': socket.ip, 89 | 'roomName': roomName, 90 | }}); 91 | 92 | try { 93 | await rateLimiter.consume(socket.handshake.address); 94 | if(!(roomName in rooms)) { 95 | rooms[roomName] = []; 96 | } 97 | 98 | for(i in rooms[roomName]) { 99 | if(!rooms[roomName][i]) continue; 100 | if(rooms[roomName][i].socket === socket) { 101 | delete(rooms[roomName][i]); 102 | } 103 | } 104 | rooms[roomName] = rooms[roomName].filter(el => el != null); 105 | } catch(rejRes) { 106 | log.error({'subscribe.error': rejRes}); 107 | } 108 | }); 109 | 110 | socket.on('publish', async (data) => { 111 | log.info({'publish': { 112 | 'socket.id': socket.id, 113 | 'socket.ip': socket.ip, 114 | 'data': data, 115 | }}); 116 | try { 117 | await rateLimiter.consume(socket.handshake.address); 118 | if(!data.message) return; 119 | if(!data.roomName) return; 120 | if(!(data.roomName in rooms)) return; 121 | for(i in rooms[data.roomName]) { 122 | if(!rooms[data.roomName][i]) continue; 123 | if(!rooms[data.roomName][i].socket) continue; 124 | rooms[data.roomName][i].socket.emit(data.roomName + '.message', { 125 | sender: socket.id, 126 | message: data.message, 127 | }); 128 | } 129 | } catch(rejRes) { 130 | log.error({'subscribe.error': rejRes}); 131 | } 132 | }); 133 | 134 | socket.on('disconnect', () => { 135 | log.info({'disconnect': { 136 | 'socket.id': socket.id, 137 | 'socket.ip': socket.ip, 138 | }}); 139 | for(roomName in rooms) { 140 | for(i in rooms[roomName]) { 141 | if(!rooms[roomName][i]) continue; 142 | if(rooms[roomName][i].socket === socket) { 143 | delete(rooms[roomName][i]); 144 | } 145 | } 146 | rooms[roomName] = rooms[roomName].filter(el => el != null); 147 | if(rooms[roomName].length === 0) delete(rooms[roomName]); 148 | } 149 | }); 150 | }); 151 | 152 | } 153 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const http = require('http').Server(app); 4 | const io = require('socket.io')(http); 5 | const log = require('pino')({prettyPrint: true, level: 'debug'}); 6 | 7 | require('http').globalAgent.maxSockets = Infinity; 8 | 9 | app.use('/', express.static('static')); 10 | 11 | require("./server-io.js")(app, io); 12 | 13 | http.listen(3000, function(){ 14 | log.info({'listen': 'listening for connections on *:3000'}); 15 | }); 16 | -------------------------------------------------------------------------------- /server/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Foo

7 | 8 | 9 | -------------------------------------------------------------------------------- /server/static/join/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dheera/robot-botparty/8988b9896cb919899c0b8f6d8cc7fb83ee04bcdf/server/static/join/bg.jpg -------------------------------------------------------------------------------- /server/static/join/css/index.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background:#303030; 4 | background-image: url(bg.jpg); 5 | background-repeat: no-repeat; 6 | background-position: center; 7 | -webkit-background-size: cover; 8 | -moz-background-size: cover; 9 | -o-background-size: cover; 10 | background-size: cover; 11 | margin:0; 12 | height: 100vh; 13 | margin: 0; 14 | align-items: center; 15 | justify-content: center; 16 | padding:0; 17 | font-family:sans-serif; 18 | -moz-user-select: none; -webkit-user-select: none; -ms-user-select:none; user-select:none;-o-user-select:none; 19 | } 20 | 21 | #spinnerContainer { 22 | display:none; 23 | z-index:15000; 24 | position:fixed;top:50%;left:50%; 25 | } 26 | 27 | #panelJoin { 28 | position:fixed; 29 | z-index:10000; 30 | color:#ffffff; 31 | position:fixed; 32 | left:0; 33 | top:0; 34 | width:100%; 35 | height:100%; 36 | text-align:center; 37 | } 38 | 39 | #displayRoomName { 40 | text-align:center; 41 | font-size:48pt; 42 | margin-top:30pt; 43 | margin-bottom:30pt; 44 | } 45 | 46 | #panelJoin input { 47 | font-size:18pt; 48 | border:0; 49 | background: #ffffff; 50 | color:#303030; 51 | font-family:sans-serif; 52 | padding:8pt; 53 | height:32pt; 54 | margin:0; 55 | } 56 | 57 | #panelJoin button { 58 | height:48pt; 59 | padding:8pt; 60 | font-size:18pt; 61 | border:0; 62 | background: #0080cc; 63 | color:#ffffff; 64 | font-family:sans-serif; 65 | 66 | margin:0; 67 | } 68 | 69 | #localVideoContainer { 70 | z-index:5001; 71 | background:#404040; 72 | width:100%; 73 | height:100%; 74 | top:0; 75 | left:0; 76 | position:fixed; 77 | } 78 | #loginContainer { 79 | background:rgba(20,20,20,0.8); 80 | z-index:10002; 81 | width:100%; 82 | height:100%; 83 | top:0; 84 | left:0; 85 | position:fixed; 86 | } 87 | 88 | #errorMessage { 89 | font-size:18pt; 90 | color:#f04020; 91 | display:none; 92 | margin-top:24pt; 93 | } 94 | 95 | #remoteVideo { 96 | position:fixed; 97 | z-index:50; 98 | object-fit:cover; 99 | width:100%;height:100%;top:0;left:0; 100 | } 101 | 102 | #panelLocalVideo { 103 | z-index:1000; 104 | position:fixed; 105 | left:5vh; 106 | top:5vh; 107 | width:40vh; 108 | height:30vh; 109 | box-shadow: 5px 5px 5px rgba(0,0,0,0.3); 110 | } 111 | #localVideo { 112 | object-fit:cover; 113 | width:100%;height:100%;top:0;left:0; 114 | opacity:1.0; 115 | } 116 | 117 | .localVideoContainerLoggedIn { 118 | z-index:5001; 119 | opacity:0.5 !important; 120 | position:fixed; 121 | top:10pt !important; 122 | left:10pt !important; 123 | width:24vh !important;; 124 | height:18vh !important; 125 | border:2pt solid black !important; 126 | box-shadow: 5px 5px 5px rgba(0,0,0,0.3) !important; 127 | } 128 | 129 | #botNumDisplay { 130 | position:fixed; 131 | top: 10vh; 132 | right:10vh; 133 | z-index:1000; 134 | font-family:sans-serif; 135 | font-size:20vh; 136 | line-height:1em; 137 | vertical-align:middle; 138 | font-weight:bold; 139 | opacity:0.3; 140 | color:#ffffff; 141 | } 142 | 143 | 144 | .control-container { 145 | z-index:100; 146 | position:fixed; 147 | width:200pt; 148 | height:200pt; 149 | bottom:25pt; 150 | left:50%; 151 | margin-left:-100pt; 152 | } 153 | 154 | .control-area { 155 | width:100%; 156 | height:100%; 157 | border-radius:50%; 158 | background:rgba(64,64,64,0.2); 159 | -webkit-backface-visibility: hidden; 160 | } 161 | 162 | .control-stick { 163 | position:absolute; 164 | left:50%; 165 | top:50%; 166 | margin-left:-20%; 167 | margin-top:-20%; 168 | width:40%; 169 | height:40%; 170 | border-radius:50%; 171 | background:#f04020; 172 | -webkit-transition: -webkit-transform 0.05s ease; 173 | -webkit-backface-visibility: hidden; 174 | } 175 | 176 | .control-stick-game { 177 | background:rgba(0,255,0,0.1); 178 | border-radius:50%; 179 | } 180 | 181 | .control-touch { 182 | position:absolute; 183 | z-index:75; 184 | left:50%; 185 | top:50%; 186 | margin-left:-20%; 187 | margin-top:-20%; 188 | width:40%; 189 | height:40%; 190 | border-radius:50%; 191 | background:white; 192 | opacity:.05; 193 | -webkit-backface-visibility: hidden; 194 | -webkit-transform: translate3d(0, 0, 0); 195 | } 196 | 197 | 198 | @keyframes spinner { 199 | to {transform: rotate(360deg);} 200 | } 201 | 202 | .spinner:before { 203 | content: ''; 204 | box-sizing: border-box; 205 | position: absolute; 206 | top: 50%; 207 | left: 50%; 208 | width: 50pt; 209 | height: 50pt; 210 | margin-top: -25pt; 211 | margin-left: -25pt; 212 | border-radius: 50%; 213 | border-top: 4px solid #ffffff; 214 | border-right: 4px solid transparent; 215 | animation: spinner .6s linear infinite; 216 | } 217 | -------------------------------------------------------------------------------- /server/static/join/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 | 27 | 28 |
29 |
30 |
31 |
32 | 33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /server/static/join/js/algorithms.js: -------------------------------------------------------------------------------- 1 | function randomId(length) { 2 | var result = ''; 3 | var characters = 'abcdefghijklmnpoqrstuvwxyz'; 4 | var charactersLength = characters.length; 5 | for ( var i = 0; i < length; i++ ) { 6 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 7 | } 8 | return result; 9 | } 10 | 11 | let hash = function(b){for(var a=0,c=b.length;c--;)a+=b.charCodeAt(c),a+=a<<10,a^=a>>6;a+=a<<3;a^=a>>11;return((a+(a<<15)&4294967295)>>>0).toString(16)}; 12 | -------------------------------------------------------------------------------- /server/static/join/js/control.js: -------------------------------------------------------------------------------- 1 | var touchCurrentX = 0; 2 | var touchCurrentY = 0; 3 | 4 | var touchStartX = 0; 5 | var touchStartY = 0; 6 | 7 | var touchInterval = null; 8 | var touchLastTime = 0; 9 | 10 | var lastStickTime = 0; 11 | 12 | var currentX = 0; 13 | var currentY = 0; 14 | 15 | function initStick() { 16 | // pass 17 | } 18 | 19 | function moveStick(x, y) { 20 | lastStickTime = Date.now(); 21 | currentX = x; 22 | currentY = y; 23 | $('.control-stick').css('-webkit-transform','translate(' + (100*currentX) + 'pt,' + (100*currentY) + 'pt)'); 24 | } 25 | 26 | $(function() { 27 | $('.control-touch').bind('touchstart',function(e) { 28 | touchStartX = e.originalEvent.touches[0].clientX; 29 | touchStartY = e.originalEvent.touches[0].clientY; 30 | touchCurrentX = touchStartX; 31 | touchCurrentY = touchStartY; 32 | console.log('touch start: ' + touchStartX + ' ' + touchStartY); 33 | touchLastTime = Date.now(); 34 | initStick(); 35 | 36 | touchInterval = setInterval(function() { 37 | if(Date.now() - touchLastTime < 3000 && touchCurrentX !=0 && touchCurrentY !=0) { 38 | translationX = Math.floor(touchCurrentX - touchStartX) / $('.control-area')[0].clientWidth * 2; 39 | translationY = Math.floor(touchCurrentY - touchStartY) / $('.control-area')[0].clientHeight * 2; 40 | if(translationX > 1) translationX = 1; 41 | if(translationX < -1) translationX = -1; 42 | if(translationY > 1) translationY = 1; 43 | if(translationY < -1) translationY = -1; 44 | var marginSize = Math.abs(translationY)*0.1; 45 | if(translationX > 0) { 46 | translationX = Math.max(0, translationX - marginSize); 47 | } 48 | if(translationX < 0) { 49 | translationX = Math.min(0, translationX + marginSize); 50 | } 51 | moveStick(translationX, translationY); 52 | } 53 | }, 40); 54 | 55 | }); 56 | 57 | $('.control-touch').bind('touchmove',function(e) { 58 | e.preventDefault(); 59 | touchLastTime = Date.now(); 60 | touchCurrentX = e.originalEvent.touches[0].clientX; 61 | touchCurrentY = e.originalEvent.touches[0].clientY; 62 | }); 63 | 64 | $('.control-touch').bind('touchend', function(e) { 65 | touchCurrentX = 0; 66 | touchCurrentY = 0; 67 | moveStick(0, 0); 68 | clearInterval(touchInterval); 69 | }); 70 | $('.control-touch').bind('touchleave', function(e) { 71 | touchCurrentX = 0; 72 | touchCurrentY = 0; 73 | moveStick(0, 0); 74 | clearInterval(touchInterval); 75 | }); 76 | $('.control-touch').bind('touchcancel', function(e) { 77 | touchCurrentX = 0; 78 | touchCurrentY = 0; 79 | moveStick(0, 0); 80 | clearInterval(touchInterval); 81 | }); 82 | 83 | $('.control-touch').bind('mousedown',function(e) { 84 | touchStartX = e.originalEvent.clientX; 85 | touchStartY = e.originalEvent.clientY; 86 | touchCurrentX = touchStartX; 87 | touchCurrentY = touchStartY; 88 | console.log('mouse start: ' + touchStartX + ' ' + touchStartY); 89 | touchLastTime = Date.now(); 90 | initStick(); 91 | 92 | touchInterval = setInterval(function() { 93 | if(Date.now() - touchLastTime < 3000 && touchCurrentX !=0 && touchCurrentY !=0) { 94 | translationX = Math.floor(touchCurrentX - touchStartX) / $('.control-area')[0].clientWidth * 2; 95 | translationY = Math.floor(touchCurrentY - touchStartY) / $('.control-area')[0].clientHeight * 2; 96 | if(translationX > 1) translationX = 1; 97 | if(translationX < -1) translationX = -1; 98 | if(translationY > 1) translationY = 1; 99 | if(translationY < -1) translationY = -1; 100 | var marginSize = Math.abs(translationY)*0.1; 101 | if(translationX > 0) { 102 | translationX = Math.max(0, translationX - marginSize); 103 | } 104 | if(translationX < 0) { 105 | translationX = Math.min(0, translationX + marginSize); 106 | } 107 | moveStick(translationX, translationY); 108 | } 109 | }, 40); 110 | isMouseDown = true; 111 | }); 112 | 113 | $(window).bind('mousemove',function(e) { 114 | if(!isMouseDown) return; 115 | e.preventDefault(); 116 | touchLastTime = Date.now(); 117 | touchCurrentX = e.originalEvent.clientX; 118 | touchCurrentY = e.originalEvent.clientY; 119 | }); 120 | 121 | $(window).bind('mouseup', function(e) { 122 | touchCurrentX = 0; 123 | touchCurrentY = 0; 124 | moveStick(0, 0); 125 | clearInterval(touchInterval); 126 | isMouseDown = false; 127 | }); 128 | 129 | $(document).bind('mouseleave', function(e) { 130 | touchCurrentX = 0; 131 | touchCurrentY = 0; 132 | moveStick(0, 0); 133 | clearInterval(touchInterval); 134 | isMouseDown = false; 135 | }); 136 | 137 | }); 138 | 139 | var isMouseDown = false; 140 | 141 | var currentKeyboardX = 0; 142 | var currentKeyboardY = 0; 143 | 144 | $(function() { 145 | $(document).on("keydown", function (e) { 146 | if(![37, 39, 38, 40].includes(e.which)) { return; } 147 | if(currentKeyboardX === 0 && currentKeyboardY === 0) { 148 | initStick(); 149 | } 150 | if(e.which === 37) { 151 | currentKeyboardX -= 0.05; 152 | } else if(e.which === 39) { 153 | currentKeyboardX += 0.05; 154 | } else if(e.which === 38) { 155 | currentKeyboardY -= 0.05; 156 | } else if(e.which === 40) { 157 | currentKeyboardY += 0.05; 158 | } else { 159 | return; 160 | } 161 | if(currentKeyboardX > 1.0) currentKeyboardX = 1.0; 162 | if(currentKeyboardX < -1.0) currentKeyboardX = -1.0; 163 | if(currentKeyboardY > 1.0) currentKeyboardY = 1.0; 164 | if(currentKeyboardY < -1.0) currentKeyboardY = -1.0; 165 | moveStick(currentKeyboardX, currentKeyboardY); 166 | }); 167 | 168 | $(document).on("keyup", function (e) { 169 | if(e.which === 37) { 170 | currentKeyboardX = 0; 171 | } else if(e.which === 39) { 172 | currentKeyboardX = 0; 173 | } else if(e.which === 38) { 174 | currentKeyboardY = 0; 175 | } else if(e.which === 40) { 176 | currentKeyboardY = 0; 177 | } else { 178 | return; 179 | } 180 | moveStick(currentKeyboardX, currentKeyboardY); 181 | }); 182 | }); 183 | 184 | $(()=>{ 185 | let zeros = 0; 186 | setInterval(() => { 187 | if(socket && socket.connected && peerConnection && (peerConnection.connectionState==="connected" || peerConnection.iceConnectionState==="connected")) { 188 | if(Math.abs(currentX) < 1e-3 && Math.abs(currentY) < 1e-3) { 189 | currentX = 0; 190 | currentY = 0; 191 | zeros++; 192 | if(zeros < 5) { 193 | sendMessage({"stick": [currentX, currentY, Date.now()]}); 194 | } 195 | } else { 196 | zeros = 0; 197 | sendMessage({"stick": [currentX, currentY, Date.now()]}); 198 | } 199 | } 200 | }, 100); 201 | }); 202 | -------------------------------------------------------------------------------- /server/static/join/js/index.js: -------------------------------------------------------------------------------- 1 | // Generate random room name if needed 2 | if (!location.hash) { location.hash = randomId(6); } 3 | 4 | window.onhashchange = function() { 5 | window.location.reload(); 6 | } 7 | 8 | // roomName from http://.../robot-party/#roomName 9 | let roomName = location.hash.substring(1); 10 | 11 | // Room name needs to be prefixed with 'observable-' 12 | const configuration = { 13 | iceServers: [{ 14 | urls: 'stun:stun.l.google.com:19302' 15 | }] 16 | }; 17 | 18 | let room; 19 | let peerConnection; 20 | let dataChannel; 21 | 22 | let socket; 23 | let droneRoomName = ""; 24 | let isOfferer; 25 | 26 | function verifyAuth(passCode, onSuccess, onFailure) { 27 | droneRoomName = hash(roomName + "-" + passCode); 28 | 29 | socket = io("https://botparty.dheera.net/"); 30 | 31 | socket.on(droneRoomName + '.members', members => { 32 | console.log('MEMBERS', members); 33 | isOfferer = members.length === 2; 34 | 35 | if(isOfferer) { 36 | onSuccess(); 37 | startWebRTC(isOfferer); 38 | } else { 39 | socket.emit("unsubscribe", droneRoomName); 40 | onFailure(); 41 | } 42 | }); 43 | 44 | socket.emit("subscribe", droneRoomName); 45 | } 46 | 47 | 48 | 49 | function onSuccess() {}; 50 | function onError(error, data) { 51 | console.error(error, data); 52 | }; 53 | 54 | // Send signaling data via Scaledrone 55 | function sendMessage(message) { 56 | if(!socket) { console.error("sendMessage: no socket");return; } 57 | if(!socket.connected) { console.error("sendMessage: socket disconnected");return; } 58 | 59 | socket.emit('publish', { 60 | roomName: droneRoomName, 61 | message: message, 62 | }); 63 | } 64 | 65 | function startWebRTC(isOfferer) { 66 | peerConnection = new RTCPeerConnection(configuration); 67 | 68 | // 'onicecandidate' notifies us whenever an ICE agent needs to deliver a 69 | // message to the other peer through the signaling server 70 | peerConnection.onicecandidate = event => { 71 | console.log("onicecandidate"); 72 | if (event.candidate) { 73 | sendMessage({'candidate': event.candidate}); 74 | } 75 | }; 76 | 77 | // If user is offerer let the 'negotiationneeded' event create the offer 78 | if (isOfferer) { 79 | peerConnection.onnegotiationneeded = () => { 80 | console.log("onnegotiationneeded"); 81 | peerConnection.createOffer().then(localDescCreated).catch(onError); 82 | } 83 | } 84 | 85 | // When a remote stream arrives display it in the #remoteVideo element 86 | peerConnection.ontrack = event => { 87 | console.log("ontrack"); 88 | const stream = event.streams[0]; 89 | if (!remoteVideo.srcObject || remoteVideo.srcObject.id !== stream.id) { 90 | remoteVideo.srcObject = stream; 91 | remoteVideo.onplaying = () => $('#spinnerContainer').hide(); 92 | } 93 | }; 94 | 95 | /* navigator.mediaDevices.getUserMedia({ 96 | audio: true, 97 | video: true, 98 | }).then(stream => { 99 | // Display your local video in #localVideo element 100 | localVideo.srcObject = stream; 101 | // Add your stream to be sent to the conneting peer 102 | stream.getTracks().forEach(track => peerConnection.addTrack(track, stream)); 103 | 104 | }, onError); */ 105 | 106 | stream.getTracks().forEach(track => peerConnection.addTrack(track, stream)); 107 | 108 | // Listen to signaling data from Scaledrone 109 | socket.on(droneRoomName + '.message', (data) => { 110 | // Message was sent by us 111 | if (data.sender === socket.id) { 112 | return; 113 | } 114 | 115 | const message = data.message; 116 | 117 | if (message.sdp) { 118 | console.log("remoteDescription", message.sdp); 119 | // This is called after receiving an offer or answer from another peer 120 | peerConnection.setRemoteDescription(new RTCSessionDescription(message.sdp), () => { 121 | // When receiving an offer lets answer it 122 | if (peerConnection.remoteDescription.type === 'offer') { 123 | peerConnection.createAnswer().then(localDescCreated).catch(onError); 124 | } 125 | }, onError); 126 | } else if (message.candidate) { 127 | // Add the new ICE candidate to our connections remote description 128 | peerConnection.addIceCandidate( 129 | new RTCIceCandidate(message.candidate), onSuccess, onError 130 | ); 131 | } else if(message.ping) { 132 | sendMessage({'pong': true}); 133 | } 134 | }); 135 | } 136 | 137 | function localDescCreated(desc) { 138 | console.log("localDescription", desc.sdp); 139 | peerConnection.setLocalDescription( 140 | desc, 141 | () => sendMessage({'sdp': peerConnection.localDescription}), 142 | onError 143 | ); 144 | } 145 | -------------------------------------------------------------------------------- /server/static/join/js/ui.js: -------------------------------------------------------------------------------- 1 | let stream; 2 | 3 | $(() => { 4 | $("input").hide(); 5 | $("button").hide(); 6 | $("#errorMessage").text("Please enable camera/microphone permissions.").show(); 7 | navigator.mediaDevices.getUserMedia({ 8 | audio: true, 9 | video: true, 10 | }).then(stream_ => { 11 | stream = stream_; 12 | // Display your local video in #localVideo element 13 | localVideo.srcObject = stream; 14 | $("input").show(); 15 | $("button").show(); 16 | $("#errorMessage").text("").hide(); 17 | }).catch(err => { 18 | if(err.name === "NotAllowedError") { 19 | $("#errorMessage").text("Please enable your camera and microphone and reload the page.").slideDown(); 20 | $("input").hide(); 21 | $("button").hide(); 22 | } else if(err.name === "NotFoundError") { 23 | $("#errorMessage").text("Camera or microphone not found. Please ensure that you have one plugged in and reload the page.").slideDown(); 24 | $("input").hide(); 25 | $("button").hide(); 26 | } else { 27 | $("#errorMessage").text(err.name + ": " + err.message).slideDown(); 28 | $("input").hide(); 29 | $("button").hide(); 30 | } 31 | }); 32 | 33 | $("#displayRoomName").text(roomName); 34 | 35 | $("#inputPassCode").on('keyup', function (e) { 36 | if (e.keyCode === 13) { 37 | $('#spinnerContainer').show(); 38 | verifyAuth( 39 | $("#inputPassCode").val(), 40 | () => { 41 | $("#panelJoin").hide(); 42 | $("#localVideoContainer").addClass("localVideoContainerLoggedIn"); 43 | }, ()=> { 44 | console.log("authentication failure"); 45 | $("#errorMessage").text("Bad passcode").slideDown().delay(2000).slideUp(); 46 | $('#spinnerContainer').hide(); 47 | } 48 | ); 49 | } 50 | }); 51 | 52 | $("#buttonJoin").click(()=>{ 53 | $('#spinnerContainer').show(); 54 | verifyAuth( 55 | $("#inputPassCode").val(), 56 | () => { 57 | $("#panelJoin").hide(); 58 | $("#localVideoContainer").addClass("localVideoContainerLoggedIn"); 59 | }, ()=> { 60 | console.log("authentication failure"); 61 | $("#errorMessage").text("Bad passcode").slideDown().delay(2000).slideUp(); 62 | $('#spinnerContainer').hide(); 63 | } 64 | ); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /server/static/robot/css/index.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background:#303030; 4 | margin:0; 5 | height: 100vh; 6 | margin: 0; 7 | align-items: center; 8 | justify-content: center; 9 | padding:0; 10 | font-family:sans-serif; 11 | } 12 | 13 | #buttonConnectContainer { 14 | top:15pt;right:60pt; 15 | opacity:0.2; 16 | z-index:50000; 17 | position:fixed; 18 | } 19 | 20 | #buttonConfigurationContainer { 21 | top:15pt;right:15pt; 22 | opacity:0.2; 23 | z-index:50000; 24 | position:fixed; 25 | } 26 | 27 | #remoteVideo { 28 | position:fixed; 29 | z-index:100; 30 | object-fit:cover; 31 | width:100%;height:100%;top:0;left:0; 32 | } 33 | #localVideo { 34 | object-fit:cover; 35 | width:100%;height:100%;top:0;left:0; 36 | opacity:1.0; 37 | } 38 | 39 | #panelLocalVideo { 40 | z-index:25; 41 | background:#404040; 42 | width:100%; 43 | height:100%; 44 | top:0; 45 | left:0; 46 | position:fixed; 47 | opacity:0.3; 48 | } 49 | 50 | #panelConfiguration { 51 | background:#404040; 52 | z-index:10000; 53 | padding:20pt; 54 | color:#ffffff; 55 | display:none; 56 | background:#303030; 57 | position:fixed;top:0;left:0;width:100%;height:100%; 58 | } 59 | 60 | #botNumDisplay { 61 | position:fixed; 62 | top: 15pt; 63 | left:15pt; 64 | padding:0; 65 | margin:0; 66 | z-index:1000; 67 | font-family:sans-serif; 68 | font-size:40pt; 69 | line-height:1em; 70 | vertical-align:middle; 71 | font-weight:bold; 72 | opacity:0.3; 73 | color:#ffffff; 74 | } 75 | -------------------------------------------------------------------------------- /server/static/robot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 25 |
26 |
27 | 30 |
31 |
32 | 33 |
34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 | 47 |
48 |
49 | 50 |
51 |
52 | 53 |
54 |
-1
55 | 56 | 57 | -------------------------------------------------------------------------------- /server/static/robot/js/BluetoothTerminal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bluetooth Terminal class. 3 | */ 4 | class BluetoothTerminal { 5 | /** 6 | * Create preconfigured Bluetooth Terminal instance. 7 | * @param {!(number|string)} [serviceUuid=0xFFE0] - Service UUID 8 | * @param {!(number|string)} [characteristicUuid=0xFFE1] - Characteristic UUID 9 | * @param {string} [receiveSeparator='\n'] - Receive separator 10 | * @param {string} [sendSeparator='\n'] - Send separator 11 | */ 12 | constructor(serviceUuid = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E", characteristicUuidTx = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E", characteristicUuidRx = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E", 13 | receiveSeparator = '\n', sendSeparator = '\n') { 14 | // Used private variables. 15 | this._receiveBuffer = ''; // Buffer containing not separated data. 16 | this._maxCharacteristicValueLength = 20; // Max characteristic value length. 17 | this._device = null; // Device object cache. 18 | this._service = null; 19 | this._characteristicTx = null; // Characteristic object cache. 20 | this._characteristicRx = null; // Characteristic object cache. 21 | 22 | // Bound functions used to add and remove appropriate event handlers. 23 | this._boundHandleDisconnection = this._handleDisconnection.bind(this); 24 | this._boundHandleCharacteristicValueChanged = 25 | this._handleCharacteristicValueChanged.bind(this); 26 | 27 | // Configure with specified parameters. 28 | this.setServiceUuid(serviceUuid); 29 | this.setCharacteristicUuidTx(characteristicUuidTx); 30 | this.setCharacteristicUuidRx(characteristicUuidRx); 31 | this.setReceiveSeparator(receiveSeparator); 32 | this.setSendSeparator(sendSeparator); 33 | } 34 | 35 | /** 36 | * Set number or string representing service UUID used. 37 | * @param {!(number|string)} uuid - Service UUID 38 | */ 39 | setServiceUuid(uuid) { 40 | if (!Number.isInteger(uuid) && 41 | !(typeof uuid === 'string' || uuid instanceof String)) { 42 | throw new Error('UUID type is neither a number nor a string'); 43 | } 44 | 45 | if (!uuid) { 46 | throw new Error('UUID cannot be a null'); 47 | } 48 | 49 | this._serviceUuid = uuid; 50 | } 51 | 52 | /** 53 | * Set number or string representing characteristic UUID used. 54 | * @param {!(number|string)} uuid - Characteristic UUID 55 | */ 56 | setCharacteristicUuidTx(uuid) { 57 | if (!Number.isInteger(uuid) && 58 | !(typeof uuid === 'string' || uuid instanceof String)) { 59 | throw new Error('UUID type is neither a number nor a string'); 60 | } 61 | 62 | if (!uuid) { 63 | throw new Error('UUID cannot be a null'); 64 | } 65 | 66 | this._characteristicUuidTx = uuid; 67 | } 68 | 69 | setCharacteristicUuidRx(uuid) { 70 | if (!Number.isInteger(uuid) && 71 | !(typeof uuid === 'string' || uuid instanceof String)) { 72 | throw new Error('UUID type is neither a number nor a string'); 73 | } 74 | 75 | if (!uuid) { 76 | throw new Error('UUID cannot be a null'); 77 | } 78 | 79 | this._characteristicUuidRx = uuid; 80 | } 81 | 82 | /** 83 | * Set character representing separator for data coming from the connected 84 | * device, end of line for example. 85 | * @param {string} separator - Receive separator with length equal to one 86 | * character 87 | */ 88 | setReceiveSeparator(separator) { 89 | if (!(typeof separator === 'string' || separator instanceof String)) { 90 | throw new Error('Separator type is not a string'); 91 | } 92 | 93 | if (separator.length !== 1) { 94 | throw new Error('Separator length must be equal to one character'); 95 | } 96 | 97 | this._receiveSeparator = separator; 98 | } 99 | 100 | /** 101 | * Set string representing separator for data coming to the connected 102 | * device, end of line for example. 103 | * @param {string} separator - Send separator 104 | */ 105 | setSendSeparator(separator) { 106 | if (!(typeof separator === 'string' || separator instanceof String)) { 107 | throw new Error('Separator type is not a string'); 108 | } 109 | 110 | if (separator.length !== 1) { 111 | throw new Error('Separator length must be equal to one character'); 112 | } 113 | 114 | this._sendSeparator = separator; 115 | } 116 | 117 | /** 118 | * Launch Bluetooth device chooser and connect to the selected device. 119 | * @return {Promise} Promise which will be fulfilled when notifications will 120 | * be started or rejected if something went wrong 121 | */ 122 | connect() { 123 | return this._connectToDevice(this._device); 124 | } 125 | 126 | /** 127 | * Disconnect from the connected device. 128 | */ 129 | disconnect() { 130 | this._disconnectFromDevice(this._device); 131 | 132 | if (this._characteristicTx) { 133 | this._characteristicTx.removeEventListener('characteristicvaluechanged', 134 | this._boundHandleCharacteristicValueChanged); 135 | this._characteristicTx = null; 136 | } 137 | 138 | this._device = null; 139 | } 140 | 141 | /** 142 | * Data receiving handler which called whenever the new data comes from 143 | * the connected device, override it to handle incoming data. 144 | * @param {string} data - Data 145 | */ 146 | receive(data) { 147 | // Handle incoming data. 148 | } 149 | 150 | /** 151 | * Send data to the connected device. 152 | * @param {string} data - Data 153 | * @return {Promise} Promise which will be fulfilled when data will be sent or 154 | * rejected if something went wrong 155 | */ 156 | send(data) { 157 | // Convert data to the string using global object. 158 | data = String(data || ''); 159 | 160 | // Return rejected promise immediately if data is empty. 161 | if (!data) { 162 | return Promise.reject(new Error('Data must be not empty')); 163 | } 164 | 165 | data += this._sendSeparator; 166 | 167 | // Split data to chunks by max characteristic value length. 168 | const chunks = this.constructor._splitByLength(data, 169 | this._maxCharacteristicValueLength); 170 | 171 | // Return rejected promise immediately if there is no connected device. 172 | if (!this._characteristicRx) { 173 | return Promise.reject(new Error('There is no connected device')); 174 | } 175 | 176 | // Write first chunk to the characteristic immediately. 177 | let promise = this._writeToCharacteristic(this._characteristicRx, chunks[0]); 178 | 179 | // Iterate over chunks if there are more than one of it. 180 | for (let i = 1; i < chunks.length; i++) { 181 | // Chain new promise. 182 | promise = promise.then(() => new Promise((resolve, reject) => { 183 | // Reject promise if the device has been disconnected. 184 | if (!this._characteristicTx) { 185 | reject(new Error('Device has been disconnected')); 186 | } 187 | 188 | // Write chunk to the characteristic and resolve the promise. 189 | this._writeToCharacteristic(this._characteristicRx, chunks[i]). 190 | then(resolve). 191 | catch(reject); 192 | })); 193 | } 194 | 195 | return promise; 196 | } 197 | 198 | /** 199 | * Get the connected device name. 200 | * @return {string} Device name or empty string if not connected 201 | */ 202 | getDeviceName() { 203 | if (!this._device) { 204 | return ''; 205 | } 206 | 207 | return this._device.name; 208 | } 209 | 210 | /** 211 | * Connect to device. 212 | * @param {Object} device 213 | * @return {Promise} 214 | * @private 215 | */ 216 | _connectToDevice(device) { 217 | return (device ? Promise.resolve(device) : this._requestBluetoothDevice()). 218 | then((device) => this._connectDeviceAndCacheCharacteristic(device)). 219 | then((characteristics) => this._startNotifications(characteristics[0])). 220 | catch((error) => { 221 | this._log(error); 222 | return Promise.reject(error); 223 | }); 224 | } 225 | 226 | /** 227 | * Disconnect from device. 228 | * @param {Object} device 229 | * @private 230 | */ 231 | _disconnectFromDevice(device) { 232 | if (!device) { 233 | return; 234 | } 235 | 236 | this._log('Disconnecting from "' + device.name + '" bluetooth device...'); 237 | 238 | device.removeEventListener('gattserverdisconnected', 239 | this._boundHandleDisconnection); 240 | 241 | if (!device.gatt.connected) { 242 | this._log('"' + device.name + 243 | '" bluetooth device is already disconnected'); 244 | return; 245 | } 246 | 247 | device.gatt.disconnect(); 248 | 249 | this._log('"' + device.name + '" bluetooth device disconnected'); 250 | } 251 | 252 | /** 253 | * Request bluetooth device. 254 | * @return {Promise} 255 | * @private 256 | */ 257 | _requestBluetoothDevice() { 258 | this._log('Requesting bluetooth device...'); 259 | 260 | // filters: [{services: [this._serviceUuid]}], 261 | return navigator.bluetooth.requestDevice({ 262 | acceptAllDevices: true, 263 | optionalServices: [this._serviceUuid], 264 | }). 265 | then((device) => { 266 | this._log('"' + device.name + '" bluetooth device selected'); 267 | 268 | this._device = device; // Remember device. 269 | this._device.addEventListener('gattserverdisconnected', 270 | this._boundHandleDisconnection); 271 | 272 | return this._device; 273 | }); 274 | } 275 | 276 | /** 277 | * Connect device and cache characteristic. 278 | * @param {Object} device 279 | * @return {Promise} 280 | * @private 281 | */ 282 | _connectDeviceAndCacheCharacteristic(device) { 283 | // Check remembered characteristic. 284 | if (device.gatt.connected && this._characteristicTx && this._characteristicRx) { 285 | return Promise.resolve([this._characteristicTx, this._characteristicRx]); 286 | } 287 | 288 | this._log('Connecting to GATT server...'); 289 | 290 | return device.gatt.connect(). 291 | then((server) => { 292 | this._log('GATT server connected', 'Getting service...'); 293 | 294 | return server.getPrimaryService(this._serviceUuid); 295 | }). 296 | then((service) => { 297 | this._log('Service found', 'Getting characteristic...'); 298 | this._service = service; 299 | return this._service.getCharacteristic(this._characteristicUuidTx); 300 | }). 301 | then((characteristicTx) => { 302 | this._log('CharacteristicTx found'); 303 | this._characteristicTx = characteristicTx; // Remember characteristic. 304 | return this._service.getCharacteristic(this._characteristicUuidRx); 305 | }). 306 | then((characteristicRx) => { 307 | this._log('CharacteristicRx found'); 308 | this._characteristicRx = characteristicRx; // Remember characteristic. 309 | return [this._characteristicTx, this._characteristicRx]; 310 | }); 311 | } 312 | 313 | /** 314 | * Start notifications. 315 | * @param {Object} characteristic 316 | * @return {Promise} 317 | * @private 318 | */ 319 | _startNotifications(characteristic) { 320 | this._log('Starting notifications...'); 321 | 322 | return characteristic.startNotifications(). 323 | then(() => { 324 | this._log('Notifications started'); 325 | 326 | characteristic.addEventListener('characteristicvaluechanged', 327 | this._boundHandleCharacteristicValueChanged); 328 | }); 329 | } 330 | 331 | /** 332 | * Stop notifications. 333 | * @param {Object} characteristic 334 | * @return {Promise} 335 | * @private 336 | */ 337 | _stopNotifications(characteristic) { 338 | this._log('Stopping notifications...'); 339 | 340 | return characteristic.stopNotifications(). 341 | then(() => { 342 | this._log('Notifications stopped'); 343 | 344 | characteristic.removeEventListener('characteristicvaluechanged', 345 | this._boundHandleCharacteristicValueChanged); 346 | }); 347 | } 348 | 349 | /** 350 | * Handle disconnection. 351 | * @param {Object} event 352 | * @private 353 | */ 354 | _handleDisconnection(event) { 355 | const device = event.target; 356 | 357 | this._log('"' + device.name + 358 | '" bluetooth device disconnected, trying to reconnect...'); 359 | 360 | this._connectDeviceAndCacheCharacteristic(device). 361 | then((characteristic) => this._startNotifications(characteristic)). 362 | catch((error) => this._log(error)); 363 | } 364 | 365 | /** 366 | * Handle characteristic value changed. 367 | * @param {Object} event 368 | * @private 369 | */ 370 | _handleCharacteristicValueChanged(event) { 371 | const value = new TextDecoder().decode(event.target.value); 372 | 373 | for (const c of value) { 374 | if (c === this._receiveSeparator) { 375 | const data = this._receiveBuffer.trim(); 376 | this._receiveBuffer = ''; 377 | 378 | if (data) { 379 | this.receive(data); 380 | } 381 | } else { 382 | this._receiveBuffer += c; 383 | } 384 | } 385 | } 386 | 387 | /** 388 | * Write to characteristic. 389 | * @param {Object} characteristic 390 | * @param {string} data 391 | * @return {Promise} 392 | * @private 393 | */ 394 | _writeToCharacteristic(characteristic, data) { 395 | let ret = characteristic.writeValue(new TextEncoder().encode(data)); 396 | return ret; 397 | } 398 | 399 | /** 400 | * Log. 401 | * @param {Array} messages 402 | * @private 403 | */ 404 | _log(...messages) { 405 | console.log(...messages); // eslint-disable-line no-console 406 | } 407 | 408 | /** 409 | * Split by length. 410 | * @param {string} string 411 | * @param {number} length 412 | * @return {Array} 413 | * @private 414 | */ 415 | static _splitByLength(string, length) { 416 | return string.match(new RegExp('(.|[\r\n]){1,' + length + '}', 'g')); 417 | } 418 | } 419 | 420 | // Export class as a module to support requiring. 421 | /* istanbul ignore next */ 422 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 423 | module.exports = BluetoothTerminal; 424 | } 425 | -------------------------------------------------------------------------------- /server/static/robot/js/algorithms.js: -------------------------------------------------------------------------------- 1 | function randomId(length) { 2 | var result = ''; 3 | var characters = 'abcdefghijklmnpoqrstuvwxyz'; 4 | var charactersLength = characters.length; 5 | for ( var i = 0; i < length; i++ ) { 6 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 7 | } 8 | return result; 9 | } 10 | 11 | let hash = function(b){for(var a=0,c=b.length;c--;)a+=b.charCodeAt(c),a+=a<<10,a^=a>>6;a+=a<<3;a^=a>>11;return((a+(a<<15)&4294967295)>>>0).toString(16)}; 12 | -------------------------------------------------------------------------------- /server/static/robot/js/bluetooth-main.js: -------------------------------------------------------------------------------- 1 | // UI elements. 2 | const deviceNameLabel = document.getElementById('device-name'); 3 | const connectButton = document.getElementById('connect'); 4 | const disconnectButton = document.getElementById('disconnect'); 5 | const terminalContainer = document.getElementById('terminal'); 6 | const sendForm = document.getElementById('send-form'); 7 | const inputField = document.getElementById('input'); 8 | 9 | // Helpers. 10 | const defaultDeviceName = 'Terminal'; 11 | const terminalAutoScrollingLimit = terminalContainer.offsetHeight / 2; 12 | let isTerminalAutoScrolling = true; 13 | 14 | const scrollElement = (element) => { 15 | const scrollTop = element.scrollHeight - element.offsetHeight; 16 | 17 | if (scrollTop > 0) { 18 | element.scrollTop = scrollTop; 19 | } 20 | }; 21 | 22 | const logToTerminal = (message, type = '') => { 23 | terminalContainer.insertAdjacentHTML('beforeend', 24 | `${message}`); 25 | 26 | if (isTerminalAutoScrolling) { 27 | scrollElement(terminalContainer); 28 | } 29 | }; 30 | 31 | /* 32 | UUID: Serial Port (00001101-0000-1000-8000-00805f9b34fb) 33 | UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb) 34 | UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb) 35 | */ 36 | 37 | // Obtain configured instance. 38 | 39 | var serviceuuid = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"; 40 | 41 | var CHARACTERISTIC_UUID_RX = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"; 42 | var CHARACTERISTIC_UUID_TX = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"; 43 | 44 | console.log("Service uuid: " + serviceuuid.toLowerCase()); 45 | console.log("CHARACTERISTIC_UUID_RX: " + CHARACTERISTIC_UUID_RX.toLowerCase()); 46 | 47 | const terminal = new BluetoothTerminal( 48 | serviceuuid.toLowerCase(), 49 | CHARACTERISTIC_UUID_TX.toLowerCase(), 50 | CHARACTERISTIC_UUID_RX.toLowerCase(), 51 | '\n','\n'); 52 | 53 | // Override `receive` method to log incoming data to the terminal. 54 | terminal.receive = function(data) { 55 | logToTerminal(data, 'in'); 56 | }; 57 | 58 | // Override default log method to output messages to the terminal and console. 59 | terminal._log = function(...messages) { 60 | // We can't use `super._log()` here. 61 | messages.forEach((message) => { 62 | logToTerminal(message); 63 | console.log(message); // eslint-disable-line no-console 64 | }); 65 | }; 66 | 67 | // Implement own send function to log outcoming data to the terminal. 68 | const send = (data) => { 69 | terminal.send(data). 70 | then(() => logToTerminal(data, 'out')). 71 | catch((error) => logToTerminal(error)); 72 | }; 73 | 74 | // Bind event listeners to the UI elements. 75 | connectButton.addEventListener('click', () => { 76 | terminal.connect(). 77 | then(() => { 78 | deviceNameLabel.textContent = terminal.getDeviceName() ? 79 | terminal.getDeviceName() : defaultDeviceName; 80 | }); 81 | }); 82 | 83 | disconnectButton.addEventListener('click', () => { 84 | terminal.disconnect(); 85 | deviceNameLabel.textContent = defaultDeviceName; 86 | }); 87 | 88 | sendForm.addEventListener('submit', (event) => { 89 | event.preventDefault(); 90 | 91 | send(inputField.value); 92 | 93 | inputField.value = ''; 94 | inputField.focus(); 95 | }); 96 | 97 | // Switch terminal auto scrolling if it scrolls out of bottom. 98 | terminalContainer.addEventListener('scroll', () => { 99 | const scrollTopOffset = terminalContainer.scrollHeight - 100 | terminalContainer.offsetHeight - terminalAutoScrollingLimit; 101 | 102 | isTerminalAutoScrolling = (scrollTopOffset < terminalContainer.scrollTop); 103 | }); 104 | -------------------------------------------------------------------------------- /server/static/robot/js/gpsNode.js: -------------------------------------------------------------------------------- 1 | // imuNode.js 2 | // by Dheera Venkatraman (`echo qurren | sed -e "s/\(.*\)/\1@\1.arg/" | tr a-z n-za-m`) 3 | 4 | // This is a roslite.js node that runs inside a web browser and makes use of the 5 | // HTML5 Web Sensors API to publish IMU data to /imu/data at 60 Hz. 6 | 7 | (()=>{ 8 | let nh = ros.initNode("gps_node"); 9 | let pub_gps_fix = nh.advertise("/gps/fix", "sensor_msgs/NavSatFix"); 10 | let msg = { "@type": "sensor_msgs/NavSatFix" }; 11 | 12 | msg.position_covariance_type = 2; // COVARIANCE_TYPE_DIAGONAL_KNOWN 13 | 14 | if(!("geolocation" in navigator)) { 15 | console.error("error: geolocation not available"); 16 | return; 17 | } 18 | 19 | let lastDataTime = Date.now(); 20 | navigator.geolocation.watchPosition((position)=>{}); 21 | 22 | setInterval(() => { 23 | navigator.geolocation.getCurrentPosition((position) => { 24 | lastDataTime = Date.now(); 25 | msg.latitude = position.coords.latitude; 26 | msg.longitude = position.coords.longitude; 27 | msg.altitude = position.coords.altitude; 28 | msg.covariance = [ 29 | position.coords.accuracy, 30 | 0, 0, 0, 31 | position.coords.accuracy, 32 | 0, 0, 0, 33 | position.coords.accuracy ** 2 34 | ]; 35 | msg.header = { 36 | timestamp: msg.timestamp / 1000.0, 37 | frame_id: "gps", 38 | }; 39 | pub_gps_fix.publish(msg); 40 | }, (error) => { 41 | msg.latitude = null; 42 | msg.longitude = null; 43 | msg.altitude = null; 44 | msg.covariance = [0, 0, 0, 0, 0, 0, 0, 0, 0]; 45 | msg.header = { 46 | timestamp: msg.timestamp / 1000.0, 47 | frame_id: "gps", 48 | }; 49 | pub_gps_fix.publish(msg); 50 | }, { 51 | timeout: 5000, 52 | enableHighAccuracy: true 53 | }); 54 | }, 5000); 55 | 56 | })(); 57 | -------------------------------------------------------------------------------- /server/static/robot/js/imuNode.js: -------------------------------------------------------------------------------- 1 | (()=>{ 2 | let nh = ros.initNode("imu_node"); 3 | let pub_imu_data = nh.advertise("/imu/data", "sensor_msgs/Imu"); 4 | let msg = { "@type": "sensor_msgs/Imu" }; 5 | 6 | let gyroscopeSensor = new Gyroscope({ 7 | frequency: 60, 8 | referenceFrame: "device" 9 | }); 10 | 11 | let linearAccelerationSensor = new LinearAccelerationSensor({ 12 | frequency: 60, 13 | referenceFrame: "device" 14 | }); 15 | 16 | let orientationSensor = new AbsoluteOrientationSensor({ 17 | frequency: 60, 18 | referenceFrame: "device" 19 | }); 20 | 21 | gyroscopeSensor.addEventListener("reading", e => { 22 | msg.angular_velocity = { 23 | x: gyroscopeSensor.x, 24 | y: gyroscopeSensor.y, 25 | z: gyroscopeSensor.z, 26 | }; 27 | }); 28 | 29 | linearAccelerationSensor.addEventListener("reading", e => { 30 | msg.linear_acceleration = { 31 | x: linearAccelerationSensor.x, 32 | y: linearAccelerationSensor.y, 33 | z: linearAccelerationSensor.z, 34 | }; 35 | }); 36 | 37 | orientationSensor.addEventListener("reading", e => { 38 | msg.orientation = { 39 | x: orientationSensor.quaternion[0], 40 | y: orientationSensor.quaternion[1], 41 | z: orientationSensor.quaternion[2], 42 | w: orientationSensor.quaternion[3], 43 | }; 44 | pub_imu_data.publish(msg); 45 | }); 46 | 47 | gyroscopeSensor.start(); 48 | linearAccelerationSensor.start(); 49 | orientationSensor.start(); 50 | 51 | })(); 52 | -------------------------------------------------------------------------------- /server/static/robot/js/index.js: -------------------------------------------------------------------------------- 1 | // Generate random room name if needed 2 | if (!location.hash) { 3 | location.hash = makeid(6); 4 | } 5 | 6 | window.onhashchange = function() { 7 | window.location.reload(); 8 | } 9 | 10 | let roomName = location.hash.substring(1); 11 | 12 | function makeid(length) { 13 | var result = ''; 14 | var characters = 'abcdefghijklmnpoqrstuvwxyz'; 15 | var charactersLength = characters.length; 16 | for ( var i = 0; i < length; i++ ) { 17 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 18 | } 19 | return result; 20 | } 21 | let hash = function(b){for(var a=0,c=b.length;c--;)a+=b.charCodeAt(c),a+=a<<10,a^=a>>6;a+=a<<3;a^=a>>11;return((a+(a<<15)&4294967295)>>>0).toString(16)}; 22 | 23 | let robotId; 24 | let passCode; 25 | 26 | function readSettings() { 27 | robotId = window.localStorage.getItem("robotId") || -1; 28 | passCode = window.localStorage.getItem("passCode") || ""; 29 | droneRoomName = hash(roomName + "-" + passCode); 30 | } 31 | 32 | let droneRoomName; 33 | const configuration = { 34 | iceServers: [{ 35 | urls: 'stun:stun.l.google.com:19302' 36 | }] 37 | }; 38 | let socket; 39 | let room; 40 | let peerConnection; 41 | let isOfferer = false; 42 | 43 | function onSuccess() {}; 44 | function onError(error) { 45 | console.error(error); 46 | }; 47 | 48 | function connectSocket() { 49 | if(socket) socket.disconnect(); 50 | socket = io("https://botparty.dheera.net/"); 51 | socket.on('connect', ()=>{ 52 | socket.on(droneRoomName + '.members', members => { 53 | console.log('MEMBERS', members); 54 | // If we are the second user to connect to the room we will be creating the offer 55 | isOfferer = members.length === 2; 56 | // const isOfferer = false; 57 | startWebRTC(isOfferer); 58 | }); 59 | 60 | socket.emit("subscribe", droneRoomName); 61 | }); 62 | } 63 | 64 | // Send signaling data via Scaledrone 65 | function sendMessage(message) { 66 | socket.emit('publish', { 67 | roomName: droneRoomName, 68 | message: message, 69 | }); 70 | } 71 | 72 | function startWebRTC(isOfferer) { 73 | peerConnection = new RTCPeerConnection(configuration); 74 | 75 | // 'onicecandidate' notifies us whenever an ICE agent needs to deliver a 76 | // message to the other peer through the signaling server 77 | peerConnection.onicecandidate = event => { 78 | // console.log("onicecandidate", event); 79 | if (event.candidate) { 80 | sendMessage({'candidate': event.candidate}); 81 | } 82 | }; 83 | 84 | // If user is offerer let the 'negotiationneeded' event create the offer 85 | if (isOfferer) { 86 | peerConnection.onnegotiationneeded = () => { 87 | // console.log("onnegotiationneeded"); 88 | peerConnection.createOffer().then(localDescCreated).catch(onError); 89 | } 90 | } 91 | 92 | // When a remote stream arrives display it in the #remoteVideo element 93 | peerConnection.ontrack = event => { 94 | //console.log("ontrack"); 95 | const stream = event.streams[0]; 96 | if (!remoteVideo.srcObject || remoteVideo.srcObject.id !== stream.id) { 97 | remoteVideo.srcObject = stream; 98 | } 99 | $("#panelLocalVideo").hide(); 100 | }; 101 | 102 | peerConnection.onconnectionstatechange = event => { 103 | if(peerConnection.connectionState==="failed") { 104 | peerConnection.close(); 105 | startWebRTC(isOfferer); 106 | } 107 | }; 108 | 109 | navigator.mediaDevices.getUserMedia({ 110 | audio: true, 111 | video: true, 112 | }).then(stream => { 113 | // Display your local video in #localVideo element 114 | localVideo.srcObject = stream; 115 | // Add your stream to be sent to the conneting peer 116 | stream.getTracks().forEach(track => peerConnection.addTrack(track, stream)); 117 | }, onError); 118 | 119 | // Listen to signaling data from Scaledrone 120 | socket.on(droneRoomName + '.message', (data) => { 121 | // Message was sent by us 122 | if (data.sender === socket.id) { 123 | return; 124 | } 125 | 126 | const message = data.message; 127 | 128 | if (message.sdp) { 129 | console.log("remoteDescription", message.sdp); 130 | 131 | // This is called after receiving an offer or answer from another peer 132 | peerConnection.setRemoteDescription(new RTCSessionDescription(message.sdp), () => { 133 | // When receiving an offer lets answer it 134 | if (peerConnection.remoteDescription.type === 'offer') { 135 | peerConnection.createAnswer().then(localDescCreated).catch(onError); 136 | } 137 | }, onError); 138 | } else if (message.candidate) { 139 | // Add the new ICE candidate to our connections remote description 140 | peerConnection.addIceCandidate( 141 | new RTCIceCandidate(message.candidate), onSuccess, onError 142 | ); 143 | } else if (message.stick) { 144 | console.log(message.stick); 145 | driveStick(message.stick); 146 | } 147 | }); 148 | } 149 | 150 | let left_reversed = false; 151 | let right_reversed = true; 152 | let deadband = 0.3; 153 | 154 | function applyDeadband(input) { 155 | if(input > 1.0) return 1.0; 156 | if(input < -1.0) return -1.0; 157 | let sign = Math.sign(input); 158 | let value = Math.abs(input); 159 | 160 | return sign * (deadband + (1.0 - deadband) * value); 161 | } 162 | 163 | function driveStick(stickValues) { 164 | let linear = -stickValues[1]; 165 | let angular = -0.7*stickValues[0]; 166 | console.log("linear", linear, "angular", angular); 167 | 168 | let motor_left = (linear - angular); 169 | let motor_right = (linear + angular); 170 | 171 | motor_left = 255*applyDeadband(motor_left); 172 | motor_right = 255*applyDeadband(motor_right); 173 | 174 | if(left_reversed) motor_left = -motor_left; 175 | if(right_reversed) motor_right = -motor_right; 176 | 177 | if(motor_left > 255) motor_left = 255; 178 | if(motor_left < -255) motor_left = -255; 179 | if(motor_right > 255) motor_right = 255; 180 | if(motor_right < -255) motor_right = -255; 181 | 182 | command = "G "; 183 | 184 | if(motor_right >= 0) command += Math.round(motor_right) + " 0 "; 185 | else command += "0 " + Math.round(-motor_right) + " "; 186 | 187 | if(motor_left >= 0) command += Math.round(motor_left) + " 0 "; 188 | else command += "0 " + Math.round(-motor_left) + " "; 189 | 190 | console.log(command); 191 | send(command); 192 | } 193 | 194 | function localDescCreated(desc) { 195 | console.log("localDescription", desc.sdp); 196 | peerConnection.setLocalDescription( 197 | desc, 198 | () => sendMessage({'sdp': peerConnection.localDescription}), 199 | onError 200 | ); 201 | } 202 | 203 | var serviceuuid = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"; 204 | var CHARACTERISTIC_UUID_RX = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"; 205 | var CHARACTERISTIC_UUID_TX = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"; 206 | 207 | console.log("Service uuid: " + serviceuuid.toLowerCase()); 208 | console.log("CHARACTERISTIC_UUID_RX: " + CHARACTERISTIC_UUID_RX.toLowerCase()); 209 | 210 | const terminal = new BluetoothTerminal( 211 | serviceuuid.toLowerCase(), 212 | CHARACTERISTIC_UUID_TX.toLowerCase(), 213 | CHARACTERISTIC_UUID_RX.toLowerCase(), 214 | '\n','\n'); 215 | 216 | terminal.receive = function(data) { 217 | console.log(data); 218 | }; 219 | 220 | // Implement own send function to log outcoming data to the terminal. 221 | const send = (data) => { 222 | terminal.send(data). 223 | then(() => console.log(data)). 224 | catch((error) => console.log(error)); 225 | }; 226 | 227 | function connectBluetooth() { 228 | terminal.disconnect(); 229 | terminal.connect(). 230 | then(() => { 231 | console.log("connected to " + (terminal.getDeviceName() ? terminal.getDeviceName() : defaultDeviceName)); 232 | }); 233 | } 234 | 235 | $(()=>{ 236 | readSettings(); 237 | connectSocket(); 238 | }) 239 | -------------------------------------------------------------------------------- /server/static/robot/js/roslite.bundle.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ros = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i { 32 | subscriberNode._notify(topicName, msg); 33 | }); 34 | } 35 | 36 | _subscribe(node, topicName, topicType) { 37 | if(!(topicName in this.topics)) { 38 | this.topics[topicName] = { 39 | topicType: topicType, 40 | subscriberNodes: [], 41 | }; 42 | } 43 | this.topics[topicName].subscriberNodes.push(node); 44 | } 45 | }; 46 | 47 | module.exports = ROSLiteCore; 48 | 49 | 50 | },{}],2:[function(require,module,exports){ 51 | const ROSLiteSubscriber = require("./ROSLiteSubscriber.js"); 52 | const ROSLitePublisher = require("./ROSLitePublisher.js"); 53 | 54 | class ROSLiteNode { 55 | constructor(core, nodeName) { 56 | this.core = core; 57 | this.nodeName = nodeName; 58 | this.subscribers = {}; 59 | this.core._registerNode(this); 60 | } 61 | 62 | advertise(topicName, topicType) { 63 | return new ROSLitePublisher(this, topicName, topicType); 64 | } 65 | 66 | subscribe(topicName, topicType, callback) { 67 | this.subscribers[topicName] = new ROSLiteSubscriber(this, topicName, topicType, callback); 68 | this.core._subscribe(this, topicName, topicType); 69 | return this.subscribers[topicName]; 70 | } 71 | 72 | _notify(topicName, msg) { 73 | if(!("@type" in msg)) { 74 | console.error("error: message contains no @type", msg); 75 | return; 76 | } 77 | 78 | this.subscribers[topicName]._notify(msg); 79 | } 80 | 81 | _publish(topicName, msg) { 82 | if(!("@type" in msg)) { 83 | console.error("error: message contains no @type", msg); 84 | } 85 | 86 | this.core._publish(this, topicName, msg); 87 | } 88 | } 89 | 90 | module.exports = ROSLiteNode; 91 | 92 | },{"./ROSLitePublisher.js":3,"./ROSLiteSubscriber.js":4}],3:[function(require,module,exports){ 93 | class ROSLitePublisher { 94 | constructor(node, topicName, topicType) { 95 | this.node = node; 96 | this.topicName = topicName; 97 | this.topicType = topicType; 98 | } 99 | 100 | publish(msg) { 101 | if(!("@type" in msg)) { 102 | console.error("error: message contains no @type", msg); 103 | return; 104 | } 105 | 106 | this.node._publish(this.topicName, msg); 107 | } 108 | } 109 | 110 | module.exports = ROSLitePublisher; 111 | 112 | },{}],4:[function(require,module,exports){ 113 | class ROSLiteSubscriber { 114 | constructor(node, topicName, topicType, callback) { 115 | this.node = node; 116 | this.topicName = topicName; 117 | this.topicType = topicType; 118 | this.callback = callback; 119 | } 120 | 121 | _notify(msg) { 122 | if(!("@type" in msg)) { 123 | console.error("error: message contains no @type", msg); 124 | return; 125 | } 126 | 127 | this.callback(msg); 128 | } 129 | } 130 | 131 | module.exports = ROSLiteSubscriber; 132 | 133 | },{}],5:[function(require,module,exports){ 134 | const ROSLiteCore = require("./ROSLiteCore.js"); 135 | const ROSLiteNode = require("./ROSLiteNode.js"); 136 | 137 | let _core = new ROSLiteCore(); 138 | let initNode = (nodeName) => { 139 | return new ROSLiteNode(_core, nodeName); 140 | } 141 | 142 | module.exports = { 143 | initNode: initNode, 144 | } 145 | 146 | },{"./ROSLiteCore.js":1,"./ROSLiteNode.js":2}]},{},[5])(5) 147 | }); 148 | -------------------------------------------------------------------------------- /server/static/robot/js/roslite.bundle.min.js: -------------------------------------------------------------------------------- 1 | !function s(e,t,i){function r(n,c){if(!t[n]){if(!e[n]){var p="function"==typeof require&&require;if(!c&&p)return p(n,!0);if(o)return o(n,!0);var u=new Error("Cannot find module '"+n+"'");throw u.code="MODULE_NOT_FOUND",u}var h=t[n]={exports:{}};e[n][0].call(h.exports,(function(s){return r(e[n][1][s]||s)}),h,h.exports,s,e,t,i)}return t[n].exports}for(var o="function"==typeof require&&require,n=0;n{s._notify(e,t)}):console.error("error: topic "+e+" is of type "+this.topics[e].topicType+" but a message of type "+t["@type"]+" was published to it")):console.error("error: message contains no @type",t)}_subscribe(s,e,t){e in this.topics||(this.topics[e]={topicType:t,subscriberNodes:[]}),this.topics[e].subscriberNodes.push(s)}}},{}],2:[function(s,e,t){const i=s("./ROSLiteSubscriber.js"),r=s("./ROSLitePublisher.js");e.exports=class{constructor(s,e){this.core=s,this.nodeName=e,this.subscribers={},this.core._registerNode(this)}advertise(s,e){return new r(this,s,e)}subscribe(s,e,t){return this.subscribers[s]=new i(this,s,e,t),this.core._subscribe(this,s,e),this.subscribers[s]}_notify(s,e){"@type"in e?this.subscribers[s]._notify(e):console.error("error: message contains no @type",e)}_publish(s,e){"@type"in e||console.error("error: message contains no @type",e),this.core._publish(this,s,e)}}},{"./ROSLitePublisher.js":3,"./ROSLiteSubscriber.js":4}],3:[function(s,e,t){e.exports=class{constructor(s,e,t){this.node=s,this.topicName=e,this.topicType=t}publish(s){"@type"in s?this.node._publish(this.topicName,s):console.error("error: message contains no @type",s)}}},{}],4:[function(s,e,t){e.exports=class{constructor(s,e,t,i){this.node=s,this.topicName=e,this.topicType=t,this.callback=i}_notify(s){"@type"in s?this.callback(s):console.error("error: message contains no @type",s)}}},{}],5:[function(s,e,t){const i=s("./ROSLiteCore.js"),r=s("./ROSLiteNode.js");let o=new i;e.exports={initNode:s=>new r(o,s)}},{"./ROSLiteCore.js":1,"./ROSLiteNode.js":2}]},{},[5]); 2 | -------------------------------------------------------------------------------- /server/static/robot/js/ui.js: -------------------------------------------------------------------------------- 1 | let saveSettings = () => { 2 | console.log("saving"); 3 | window.localStorage.setItem("robotId", $("#inputRobotId").val()); 4 | window.localStorage.setItem("passCode", $("#inputPassCode").val()); 5 | $("#botNumDisplay").text(robotId); 6 | readSettings(); 7 | connectSocket(); 8 | }; 9 | 10 | $(() => { 11 | if(!window.localStorage.getItem("robotId")) { 12 | $("#panelConfiguration").show(); 13 | $("#inputPassCode").val(makeid(6)); 14 | } else { 15 | robotId = window.localStorage.getItem("robotId"); 16 | passCode = window.localStorage.getItem("passCode"); 17 | $("#inputRobotId").val(robotId); 18 | $("#inputPassCode").val(passCode); 19 | $("#botNumDisplay").text(robotId); 20 | } 21 | 22 | $("#inputRobotId").change(saveSettings); 23 | $("#inputPassCode").change(saveSettings); 24 | 25 | $("#buttonConfigurationContainer").click(()=>{ 26 | $("#panelConfiguration").toggle(); 27 | }); 28 | 29 | $("#buttonConnectContainer").click(()=>{ 30 | connectBluetooth(); 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /server/static/socket.io.slim.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Socket.IO v2.3.0 3 | * (c) 2014-2019 Guillermo Rauch 4 | * Released under the MIT License. 5 | */ 6 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.io=e():t.io=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){"use strict";function n(t,e){"object"===("undefined"==typeof t?"undefined":o(t))&&(e=t,t=void 0),e=e||{};var r,n=i(t),s=n.source,p=n.id,h=n.path,u=c[p]&&h in c[p].nsps,f=e.forceNew||e["force new connection"]||!1===e.multiplex||u;return f?r=a(s,e):(c[p]||(c[p]=a(s,e)),r=c[p]),n.query&&!e.query&&(e.query=n.query),r.socket(n.path,e)}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=r(1),s=r(4),a=r(9);r(3)("socket.io-client");t.exports=e=n;var c=e.managers={};e.protocol=s.protocol,e.connect=n,e.Manager=r(9),e.Socket=r(33)},function(t,e,r){"use strict";function n(t,e){var r=t;e=e||"undefined"!=typeof location&&location,null==t&&(t=e.protocol+"//"+e.host),"string"==typeof t&&("/"===t.charAt(0)&&(t="/"===t.charAt(1)?e.protocol+t:e.host+t),/^(https?|wss?):\/\//.test(t)||(t="undefined"!=typeof e?e.protocol+"//"+t:"https://"+t),r=o(t)),r.port||(/^(http|ws)$/.test(r.protocol)?r.port="80":/^(http|ws)s$/.test(r.protocol)&&(r.port="443")),r.path=r.path||"/";var n=r.host.indexOf(":")!==-1,i=n?"["+r.host+"]":r.host;return r.id=r.protocol+"://"+i+":"+r.port,r.href=r.protocol+"://"+i+(e&&e.port===r.port?"":":"+r.port),r}var o=r(2);r(3)("socket.io-client:url");t.exports=n},function(t,e){var r=/^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,n=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];t.exports=function(t){var e=t,o=t.indexOf("["),i=t.indexOf("]");o!=-1&&i!=-1&&(t=t.substring(0,o)+t.substring(o,i).replace(/:/g,";")+t.substring(i,t.length));for(var s=r.exec(t||""),a={},c=14;c--;)a[n[c]]=s[c]||"";return o!=-1&&i!=-1&&(a.source=e,a.host=a.host.substring(1,a.host.length-1).replace(/;/g,":"),a.authority=a.authority.replace("[","").replace("]","").replace(/;/g,":"),a.ipv6uri=!0),a}},function(t,e){"use strict";t.exports=function(){return function(){}}},function(t,e,r){function n(){}function o(t){var r=""+t.type;if(e.BINARY_EVENT!==t.type&&e.BINARY_ACK!==t.type||(r+=t.attachments+"-"),t.nsp&&"/"!==t.nsp&&(r+=t.nsp+","),null!=t.id&&(r+=t.id),null!=t.data){var n=i(t.data);if(n===!1)return m;r+=n}return r}function i(t){try{return JSON.stringify(t)}catch(t){return!1}}function s(t,e){function r(t){var r=l.deconstructPacket(t),n=o(r.packet),i=r.buffers;i.unshift(n),e(i)}l.removeBlobs(t,r)}function a(){this.reconstructor=null}function c(t){var r=0,n={type:Number(t.charAt(0))};if(null==e.types[n.type])return u("unknown packet type "+n.type);if(e.BINARY_EVENT===n.type||e.BINARY_ACK===n.type){for(var o="";"-"!==t.charAt(++r)&&(o+=t.charAt(r),r!=t.length););if(o!=Number(o)||"-"!==t.charAt(r))throw new Error("Illegal attachments");n.attachments=Number(o)}if("/"===t.charAt(r+1))for(n.nsp="";++r;){var i=t.charAt(r);if(","===i)break;if(n.nsp+=i,r===t.length)break}else n.nsp="/";var s=t.charAt(r+1);if(""!==s&&Number(s)==s){for(n.id="";++r;){var i=t.charAt(r);if(null==i||Number(i)!=i){--r;break}if(n.id+=t.charAt(r),r===t.length)break}n.id=Number(n.id)}if(t.charAt(++r)){var a=p(t.substr(r)),c=a!==!1&&(n.type===e.ERROR||d(a));if(!c)return u("invalid payload");n.data=a}return n}function p(t){try{return JSON.parse(t)}catch(t){return!1}}function h(t){this.reconPack=t,this.buffers=[]}function u(t){return{type:e.ERROR,data:"parser error: "+t}}var f=(r(3)("socket.io-parser"),r(5)),l=r(6),d=r(7),y=r(8);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=n,e.Decoder=a;var m=e.ERROR+'"encode error"';n.prototype.encode=function(t,r){if(e.BINARY_EVENT===t.type||e.BINARY_ACK===t.type)s(t,r);else{var n=o(t);r([n])}},f(a.prototype),a.prototype.add=function(t){var r;if("string"==typeof t)r=c(t),e.BINARY_EVENT===r.type||e.BINARY_ACK===r.type?(this.reconstructor=new h(r),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",r)):this.emit("decoded",r);else{if(!y(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");r=this.reconstructor.takeBinaryData(t),r&&(this.reconstructor=null,this.emit("decoded",r))}},a.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},h.prototype.takeBinaryData=function(t){if(this.buffers.push(t),this.buffers.length===this.reconPack.attachments){var e=l.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null},h.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(t,e,r){function n(t){if(t)return o(t)}function o(t){for(var e in n.prototype)t[e]=n.prototype[e];return t}t.exports=n,n.prototype.on=n.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(e),this},n.prototype.once=function(t,e){function r(){this.off(t,r),e.apply(this,arguments)}return r.fn=e,this.on(t,r),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var r=this._callbacks["$"+t];if(!r)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var n,o=0;o0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},n.prototype.cleanup=function(){for(var t=this.subs.length,e=0;e=this._reconnectionAttempts)this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();this.reconnecting=!0;var r=setTimeout(function(){t.skipReconnect||(t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):t.onreconnect()}))},e);this.subs.push({destroy:function(){clearTimeout(r)}})}},n.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},function(t,e,r){t.exports=r(11),t.exports.parser=r(18)},function(t,e,r){function n(t,e){return this instanceof n?(e=e||{},t&&"object"==typeof t&&(e=t,t=null),t?(t=p(t),e.hostname=t.host,e.secure="https"===t.protocol||"wss"===t.protocol,e.port=t.port,t.query&&(e.query=t.query)):e.host&&(e.hostname=p(e.host).host),this.secure=null!=e.secure?e.secure:"undefined"!=typeof location&&"https:"===location.protocol,e.hostname&&!e.port&&(e.port=this.secure?"443":"80"),this.agent=e.agent||!1,this.hostname=e.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=e.port||("undefined"!=typeof location&&location.port?location.port:this.secure?443:80),this.query=e.query||{},"string"==typeof this.query&&(this.query=h.decode(this.query)),this.upgrade=!1!==e.upgrade,this.path=(e.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!e.forceJSONP,this.jsonp=!1!==e.jsonp,this.forceBase64=!!e.forceBase64,this.enablesXDR=!!e.enablesXDR,this.withCredentials=!1!==e.withCredentials,this.timestampParam=e.timestampParam||"t",this.timestampRequests=e.timestampRequests,this.transports=e.transports||["polling","websocket"],this.transportOptions=e.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=e.policyPort||843,this.rememberUpgrade=e.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=e.onlyBinaryUpgrades,this.perMessageDeflate=!1!==e.perMessageDeflate&&(e.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=e.pfx||null,this.key=e.key||null,this.passphrase=e.passphrase||null,this.cert=e.cert||null,this.ca=e.ca||null,this.ciphers=e.ciphers||null,this.rejectUnauthorized=void 0===e.rejectUnauthorized||e.rejectUnauthorized,this.forceNode=!!e.forceNode,this.isReactNative="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase(),("undefined"==typeof self||this.isReactNative)&&(e.extraHeaders&&Object.keys(e.extraHeaders).length>0&&(this.extraHeaders=e.extraHeaders),e.localAddress&&(this.localAddress=e.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,void this.open()):new n(t,e)}function o(t){var e={};for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e}var i=r(12),s=r(5),a=(r(3)("engine.io-client:socket"),r(32)),c=r(18),p=r(2),h=r(26);t.exports=n,n.priorWebsocketSuccess=!1,s(n.prototype),n.protocol=c.protocol,n.Socket=n,n.Transport=r(17),n.transports=r(12),n.parser=r(18),n.prototype.createTransport=function(t){var e=o(this.query);e.EIO=c.protocol,e.transport=t;var r=this.transportOptions[t]||{};this.id&&(e.sid=this.id);var n=new i[t]({query:e,socket:this,agent:r.agent||this.agent,hostname:r.hostname||this.hostname,port:r.port||this.port,secure:r.secure||this.secure,path:r.path||this.path,forceJSONP:r.forceJSONP||this.forceJSONP,jsonp:r.jsonp||this.jsonp,forceBase64:r.forceBase64||this.forceBase64,enablesXDR:r.enablesXDR||this.enablesXDR,withCredentials:r.withCredentials||this.withCredentials,timestampRequests:r.timestampRequests||this.timestampRequests,timestampParam:r.timestampParam||this.timestampParam,policyPort:r.policyPort||this.policyPort,pfx:r.pfx||this.pfx,key:r.key||this.key,passphrase:r.passphrase||this.passphrase,cert:r.cert||this.cert,ca:r.ca||this.ca,ciphers:r.ciphers||this.ciphers,rejectUnauthorized:r.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:r.perMessageDeflate||this.perMessageDeflate,extraHeaders:r.extraHeaders||this.extraHeaders,forceNode:r.forceNode||this.forceNode,localAddress:r.localAddress||this.localAddress,requestTimeout:r.requestTimeout||this.requestTimeout,protocols:r.protocols||void 0,isReactNative:this.isReactNative});return n},n.prototype.open=function(){var t;if(this.rememberUpgrade&&n.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)t="websocket";else{if(0===this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(t){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},n.prototype.setTransport=function(t){var e=this;this.transport&&this.transport.removeAllListeners(),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},n.prototype.probe=function(t){function e(){if(u.onlyBinaryUpgrades){var t=!this.supportsBinary&&u.transport.supportsBinary;h=h||t}h||(p.send([{type:"ping",data:"probe"}]),p.once("packet",function(t){if(!h)if("pong"===t.type&&"probe"===t.data){if(u.upgrading=!0,u.emit("upgrading",p),!p)return;n.priorWebsocketSuccess="websocket"===p.name,u.transport.pause(function(){h||"closed"!==u.readyState&&(c(),u.setTransport(p),p.send([{type:"upgrade"}]),u.emit("upgrade",p),p=null,u.upgrading=!1,u.flush())})}else{var e=new Error("probe error");e.transport=p.name,u.emit("upgradeError",e)}}))}function r(){h||(h=!0,c(),p.close(),p=null)}function o(t){var e=new Error("probe error: "+t);e.transport=p.name,r(),u.emit("upgradeError",e)}function i(){o("transport closed")}function s(){o("socket closed")}function a(t){p&&t.name!==p.name&&r()}function c(){p.removeListener("open",e),p.removeListener("error",o),p.removeListener("close",i),u.removeListener("close",s),u.removeListener("upgrading",a)}var p=this.createTransport(t,{probe:1}),h=!1,u=this;n.priorWebsocketSuccess=!1,p.once("open",e),p.once("error",o),p.once("close",i),this.once("close",s),this.once("upgrading",a),p.open()},n.prototype.onOpen=function(){if(this.readyState="open",n.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause)for(var t=0,e=this.upgrades.length;t1?{type:b[o],data:t.substring(1)}:{type:b[o]}:k}var i=new Uint8Array(t),o=i[0],s=f(t,1);return w&&"blob"===r&&(s=new w([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var r=b[t.charAt(0)];if(!p)return{type:r,data:{base64:!0,data:t.substr(1)}};var n=p.decode(t.substr(1));return"blob"===e&&w&&(n=new w([n])),{type:r,data:n}},e.encodePayload=function(t,r,n){function o(t){return t.length+":"+t}function i(t,n){e.encodePacket(t,!!s&&r,!1,function(t){n(null,o(t))})}"function"==typeof r&&(n=r,r=null);var s=u(t);return r&&s?w&&!g?e.encodePayloadAsBlob(t,n):e.encodePayloadAsArrayBuffer(t,n):t.length?void c(t,i,function(t,e){return n(e.join(""))}):n("0:")},e.decodePayload=function(t,r,n){if("string"!=typeof t)return e.decodePayloadAsBinary(t,r,n);"function"==typeof r&&(n=r,r=null);var o;if(""===t)return n(k,0,1);for(var i,s,a="",c=0,p=t.length;c0;){for(var s=new Uint8Array(o),a=0===s[0],c="",p=1;255!==s[p];p++){if(c.length>310)return n(k,0,1);c+=s[p]}o=f(o,2+c.length),c=parseInt(c);var h=f(o,0,c);if(a)try{h=String.fromCharCode.apply(null,new Uint8Array(h))}catch(t){var u=new Uint8Array(h);h="";for(var p=0;pn&&(r=n),e>=n||e>=r||0===n)return new ArrayBuffer(0);for(var o=new Uint8Array(t),i=new Uint8Array(r-e),s=e,a=0;s=55296&&e<=56319&&o65535&&(e-=65536,o+=d(e>>>10&1023|55296),e=56320|1023&e),o+=d(e);return o}function o(t,e){if(t>=55296&&t<=57343){if(e)throw Error("Lone surrogate U+"+t.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function i(t,e){return d(t>>e&63|128)}function s(t,e){if(0==(4294967168&t))return d(t);var r="";return 0==(4294965248&t)?r=d(t>>6&31|192):0==(4294901760&t)?(o(t,e)||(t=65533),r=d(t>>12&15|224),r+=i(t,6)):0==(4292870144&t)&&(r=d(t>>18&7|240),r+=i(t,12),r+=i(t,6)),r+=d(63&t|128)}function a(t,e){e=e||{};for(var n,o=!1!==e.strict,i=r(t),a=i.length,c=-1,p="";++c=f)throw Error("Invalid byte index");var t=255&u[l];if(l++,128==(192&t))return 63&t;throw Error("Invalid continuation byte")}function p(t){var e,r,n,i,s;if(l>f)throw Error("Invalid byte index");if(l==f)return!1;if(e=255&u[l],l++,0==(128&e))return e;if(192==(224&e)){if(r=c(),s=(31&e)<<6|r,s>=128)return s;throw Error("Invalid continuation byte")}if(224==(240&e)){if(r=c(),n=c(),s=(15&e)<<12|r<<6|n,s>=2048)return o(s,t)?s:65533;throw Error("Invalid continuation byte")}if(240==(248&e)&&(r=c(),n=c(),i=c(),s=(7&e)<<18|r<<12|n<<6|i,s>=65536&&s<=1114111))return s;throw Error("Invalid UTF-8 detected")}function h(t,e){e=e||{};var o=!1!==e.strict;u=r(t),f=u.length,l=0;for(var i,s=[];(i=p(o))!==!1;)s.push(i);return n(s)}/*! https://mths.be/utf8js v2.1.2 by @mathias */ 8 | var u,f,l,d=String.fromCharCode;t.exports={version:"2.1.2",encode:a,decode:h}},function(t,e){!function(){"use strict";for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r=new Uint8Array(256),n=0;n>2],i+=t[(3&n[r])<<4|n[r+1]>>4],i+=t[(15&n[r+1])<<2|n[r+2]>>6],i+=t[63&n[r+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(t){var e,n,o,i,s,a=.75*t.length,c=t.length,p=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var h=new ArrayBuffer(a),u=new Uint8Array(h);for(e=0;e>4,u[p++]=(15&o)<<4|i>>2,u[p++]=(3&i)<<6|63&s;return h}}()},function(t,e){function r(t){return t.map(function(t){if(t.buffer instanceof ArrayBuffer){var e=t.buffer;if(t.byteLength!==e.byteLength){var r=new Uint8Array(t.byteLength);r.set(new Uint8Array(e,t.byteOffset,t.byteLength)),e=r.buffer}return e}return t})}function n(t,e){e=e||{};var n=new i;return r(t).forEach(function(t){n.append(t)}),e.type?n.getBlob(e.type):n.getBlob()}function o(t,e){return new Blob(r(t),e||{})}var i="undefined"!=typeof i?i:"undefined"!=typeof WebKitBlobBuilder?WebKitBlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder&&MozBlobBuilder,s=function(){try{var t=new Blob(["hi"]);return 2===t.size}catch(t){return!1}}(),a=s&&function(){try{var t=new Blob([new Uint8Array([1,2])]);return 2===t.size}catch(t){return!1}}(),c=i&&i.prototype.append&&i.prototype.getBlob;"undefined"!=typeof Blob&&(n.prototype=Blob.prototype,o.prototype=Blob.prototype),t.exports=function(){return s?a?Blob:o:c?n:void 0}()},function(t,e){e.encode=function(t){var e="";for(var r in t)t.hasOwnProperty(r)&&(e.length&&(e+="&"),e+=encodeURIComponent(r)+"="+encodeURIComponent(t[r]));return e},e.decode=function(t){for(var e={},r=t.split("&"),n=0,o=r.length;n0);return e}function n(t){var e=0;for(h=0;h';i=document.createElement(t)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),c=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=c,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),n(),t=t.replace(h,"\\\n"),this.area.value=t.replace(p,"\\n");try{this.form.submit()}catch(t){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&r()}:this.iframe.onload=r}}).call(e,function(){return this}())},function(t,e,r){function n(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowserWebSocket=o&&!t.forceNode,this.protocols=t.protocols,this.usingBrowserWebSocket||(u=i),s.call(this,t)}var o,i,s=r(17),a=r(18),c=r(26),p=r(27),h=r(28);r(3)("engine.io-client:websocket");if("undefined"!=typeof WebSocket?o=WebSocket:"undefined"!=typeof self&&(o=self.WebSocket||self.MozWebSocket),"undefined"==typeof window)try{i=r(31)}catch(t){}var u=o||i;t.exports=n,p(n,s),n.prototype.name="websocket",n.prototype.supportsBinary=!0,n.prototype.doOpen=function(){if(this.check()){var t=this.uri(),e=this.protocols,r={agent:this.agent,perMessageDeflate:this.perMessageDeflate};r.pfx=this.pfx,r.key=this.key,r.passphrase=this.passphrase,r.cert=this.cert,r.ca=this.ca,r.ciphers=this.ciphers,r.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(r.headers=this.extraHeaders),this.localAddress&&(r.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket&&!this.isReactNative?e?new u(t,e):new u(t):new u(t,e,r)}catch(t){return this.emit("error",t)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},n.prototype.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.onOpen()},this.ws.onclose=function(){t.onClose()},this.ws.onmessage=function(e){t.onData(e.data)},this.ws.onerror=function(e){t.onError("websocket error",e)}},n.prototype.write=function(t){function e(){r.emit("flush"),setTimeout(function(){r.writable=!0,r.emit("drain")},0)}var r=this;this.writable=!1;for(var n=t.length,o=0,i=n;o0&&t.jitter<=1?t.jitter:0,this.attempts=0}t.exports=r,r.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),r=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-r:t+r}return 0|Math.min(t,this.max)},r.prototype.reset=function(){this.attempts=0},r.prototype.setMin=function(t){this.ms=t},r.prototype.setMax=function(t){this.max=t},r.prototype.setJitter=function(t){this.jitter=t}}])}); 9 | //# sourceMappingURL=socket.io.slim.js.map --------------------------------------------------------------------------------