Using the https://vanjs.org/ to render html and javascript client build for gun.js
Features:
- server minimalist
- node js
- user ui
- login
- register
- forgot
- change passphrase
- forgot hint
Using the https://vanjs.org/ to render html and javascript client build for gun.js
Features:
| { | |
| "name": "node_van_js", | |
| "version": "1.0.0", | |
| "description": "", | |
| "main": "server.js", | |
| "type": "module", | |
| "scripts": { | |
| "start": "node server.js", | |
| "dev": "node server.js", | |
| "dev0": "nodemon server.js" | |
| }, | |
| "keywords": [], | |
| "author": "", | |
| "license": "ISC", | |
| "dependencies": { | |
| "gun": "^0.2020.1239", | |
| "mini-van-plate": "^0.4.0" | |
| } | |
| } |
| // https://stackoverflow.com/questions/16333790/node-js-quick-file-server-static-files-over-http | |
| import * as url from 'url'; | |
| //import fs from 'fs'; | |
| import fs, { existsSync } from 'node:fs'; | |
| //import fs from 'node:fs'; | |
| import path from 'node:path'; | |
| import http from 'http'; | |
| import Gun from 'gun'; | |
| import van from "mini-van-plate/van-plate"; | |
| const {a, div, body, script, button, input, li, p, ul} = van.tags | |
| const __filename = url.fileURLToPath(import.meta.url); | |
| const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); | |
| //console.log(Gun.serve(__dirname)); | |
| function web_hander(req, res){ | |
| //console.log(`${req.method} ${req.url}`); | |
| // parse URL | |
| const parsedUrl = url.parse(req.url); | |
| // extract URL path | |
| let pathname = `.${parsedUrl.pathname}`; | |
| // based on the URL path, extract the file extension. e.g. .js, .doc, ... | |
| const ext = path.parse(pathname).ext; | |
| // maps file extension to MIME typere | |
| const map = { | |
| '.ico': 'image/x-icon', | |
| '.html': 'text/html', | |
| '.js': 'text/javascript', | |
| '.json': 'application/json', | |
| '.css': 'text/css', | |
| '.png': 'image/png', | |
| '.jpg': 'image/jpeg', | |
| '.wav': 'audio/wav', | |
| '.mp3': 'audio/mpeg', | |
| '.svg': 'image/svg+xml', | |
| '.pdf': 'application/pdf', | |
| '.doc': 'application/msword' | |
| }; | |
| //existsSync(pathname, function (exist) { | |
| let fileExist = existsSync(pathname); | |
| if (fileExist){ | |
| if(!fileExist) { | |
| // if the file is not found, return 404 | |
| res.statusCode = 404; | |
| res.end(`File ${pathname} not found!`); | |
| return; | |
| } | |
| // if is a directory search for index file matching the extension | |
| if (fs.statSync(pathname).isDirectory()) pathname += '/index' + ext; | |
| // read file from file system | |
| fs.readFile(pathname, function(err, data){ | |
| if(err){ | |
| res.statusCode = 500; | |
| res.end(`Error getting the file: ${err}.`); | |
| } else { | |
| // if the file is found, set Content-type and send data | |
| res.setHeader('Content-type', map[ext] || 'text/plain' ); | |
| res.end(data); | |
| } | |
| }); | |
| } | |
| if(req.url == "/"){ | |
| res.writeHead(200, {'Content-Type': 'text/html'}); | |
| //res.write('Hello World!'); | |
| //res.end(); | |
| res.end(van.html( | |
| body( | |
| //p("Your user-agent is: ", req.headers["user-agent"] ?? "Unknown"), | |
| //p("Hello"), | |
| //ul( | |
| //li("World"), | |
| //li(a({href: "https://vanjs.org/"}, "VanJS")), | |
| //), | |
| script({type:"module",src:"/user.js"}), | |
| ), | |
| )); | |
| } | |
| } | |
| function main(){ | |
| let PORT = 3000; | |
| //const server = http.createServer(Gun.serve(__dirname)); | |
| const server = http.createServer(web_hander); | |
| server.listen(PORT); | |
| var gun = Gun({web: server}); | |
| gun.on("hi", peer => { | |
| //peer connect | |
| //console.log('connect peer to',peer); | |
| console.log("peer connect!"); | |
| }); | |
| gun.on("bye", peer => { | |
| // peer disconnect | |
| //console.log('disconnected from', peer); | |
| console.log("disconnected from peer!"); | |
| }); | |
| console.log(`node server... http://localhost:${PORT}`); | |
| console.log('Relay peer started on port ' + PORT + ' with /gun'); | |
| } | |
| main(); |
| import van from "https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.2.0.min.js"; | |
| //import Gun from "https://cdn.jsdelivr.net/npm/gun/gun.js"; | |
| import GUN from "https://cdn.skypack.dev/gun"; | |
| import SEA from "https://cdn.skypack.dev/gun/sea"; | |
| const {span, br, div,label, body, script, button, input, li, p, ul, a} = van.tags; | |
| const gun = GUN(location.origin+"/gun"); | |
| //make sure it connected | |
| gun.on("hi", peer => { | |
| //peer connect | |
| //console.log('connect peer to',peer); | |
| console.log("peer connect!"); | |
| }); | |
| gun.on("bye", peer => { | |
| // peer disconnect | |
| //console.log('disconnected from', peer); | |
| console.log("disconnected from peer!"); | |
| }); | |
| //=============================================== | |
| // ACCESS PAGE | |
| //=============================================== | |
| const LoginEL = () =>{ | |
| const alias = van.state('test'); | |
| const passphrase = van.state('12345678'); | |
| function click_login(){ | |
| const user = gun.user(); | |
| user.auth(alias.val, passphrase.val, (ack)=>{ | |
| if(ack.err){ | |
| console.log("ERROR: ", ack.err); | |
| return; | |
| } | |
| //console.log(ack) | |
| document.getElementById("login").remove(); | |
| van.add(document.body, HomeEL()) | |
| }); | |
| } | |
| function click_register(){ | |
| console.log("Register") | |
| const user = gun.user(); | |
| //user.create(alias, pass, cb, opt) | |
| user.create(alias.val, passphrase.val,(ack)=>{ | |
| if(ack.err){ | |
| console.log("ERROR: ", ack.err); | |
| return; | |
| } | |
| console.log(ack) | |
| }) | |
| } | |
| function click_forgot(){ | |
| document.getElementById("login").remove(); | |
| van.add(document.body, ForgotEL()) | |
| } | |
| return div( | |
| {id:"login"}, | |
| label('Alias:'), | |
| input({placeholder:"User Name",value:alias, oninput: e=>alias.val = e.target.value}), | |
| label('Passphrase:'), | |
| input({placeholder:"Password / Passphrase",value:passphrase, oninput: e=>passphrase.val = e.target.value}), | |
| button({onclick:()=>click_login()},'Login'), | |
| button({onclick:()=>click_register()},'Register'), | |
| button({onclick:()=>click_forgot()},'Forgot'), | |
| ) | |
| }; | |
| const ForgotEL = ()=>{ | |
| const alias = van.state('test'); | |
| const question1 = van.state(''); | |
| const question2 = van.state(''); | |
| const hint = van.state(''); | |
| async function click_get_hint(){ | |
| console.log(gun); | |
| console.log(alias.val); | |
| console.log('alias/'+alias.val) | |
| let graph = await gun.get('~@'+alias.val) | |
| console.log(graph) | |
| let publickey; | |
| for(let obj in graph){//object | |
| //console.log(obj); | |
| //property name for public key | |
| if(SEA.opt.pub(obj)){//check if alias pub key | |
| publickey = SEA.opt.pub(obj); | |
| break; | |
| } | |
| } | |
| if(!publickey){ | |
| console.log("PUB KEY NULL!"); | |
| return; | |
| } | |
| console.log("Alias Pub: ", publickey); | |
| let to = gun.user(publickey);//get user alias graph | |
| let _hint = await to.get('hint').then();//get encrypt hint key graph | |
| let dec = await Gun.SEA.work(question1.val,question2.val);//get fquestion1 and fquestion2 string to mix key | |
| _hint = await Gun.SEA.decrypt(_hint,dec);//get hint and key decrypt message | |
| if(_hint){ | |
| hint.val = _hint; | |
| }else{ | |
| console.log("FAIL HINT") | |
| } | |
| } | |
| function click_back(){ | |
| document.getElementById("forgot").remove(); | |
| van.add(document.body, LoginEL()) | |
| } | |
| return div( | |
| {id:'forgot'}, | |
| label('Alias:'), | |
| input({placeholder:"User Name",value:alias, oninput: e=>alias.val = e.target.value}), | |
| br(), | |
| label('Question #1:'), | |
| input({placeholder:"Question No.1",value:question1, oninput: e=>question1.val = e.target.value}), | |
| br(), | |
| label('Question #2:'), | |
| input({placeholder:"Question No.2",value:question2, oninput: e=>question2.val = e.target.value}), | |
| br(), | |
| label('Hint:'), | |
| input({placeholder:"Hint",value:hint, oninput: e=>hint.val = e.target.value}), | |
| br(), | |
| button({onclick:()=>click_get_hint()},'Get Hint'), | |
| button({onclick:()=>click_back()},'Back'), | |
| ) | |
| } | |
| //document.body.appendChild(LoginEL()); | |
| van.add(document.body, LoginEL()) | |
| //=============================================== | |
| // HOME PAGE | |
| //=============================================== | |
| const HomeEL = () =>{ | |
| const view = van.state('account'); | |
| function click_logout(){ | |
| const user = gun.user(); | |
| user.leave(); | |
| document.getElementById("home").remove(); | |
| van.add(document.body, LoginEL()) | |
| } | |
| return div( | |
| {id:"home"}, | |
| div( | |
| button({onclick:()=>view.val='account'},'Account'), | |
| button({onclick:()=>view.val='message'},'Message'), | |
| button({onclick:()=>view.val='chat'},'Chat'), | |
| button({onclick:()=>click_logout()},'Logout'), | |
| PubDisplayEL() | |
| ), | |
| van.derive(()=>{ | |
| if(view.val == 'account'){ | |
| return AccountEL(); | |
| } | |
| if(view.val == 'message'){ | |
| return div( | |
| label('Message') | |
| ) | |
| } | |
| if(view.val == 'chat'){ | |
| return div( | |
| label('Chat') | |
| ) | |
| } | |
| }) | |
| ); | |
| }; | |
| const AccountEL = () =>{ | |
| const view = van.state('profile'); | |
| return div( | |
| {id:'account'}, | |
| div( | |
| button({onclick:()=>view.val='profile'},'Profile'), | |
| button({onclick:()=>view.val='changepassphrase'},'Change Passphrase'), | |
| button({onclick:()=>view.val='changequestions'},'Change Questions'), | |
| button({onclick:()=>view.val='certs'},'Certs'), | |
| ), | |
| van.derive(()=>{ | |
| if(view.val == 'profile'){ | |
| return ProfileEL() | |
| } | |
| if(view.val == 'changepassphrase'){ | |
| return ChnagePassphraseEL() | |
| } | |
| if(view.val == 'changequestions'){ | |
| return ChnageQuestionsEL() | |
| } | |
| if(view.val == 'certs'){ | |
| return CertsEL() | |
| } | |
| }), | |
| ) | |
| } | |
| const ProfileEL = () =>{ | |
| const alias = van.state('Guest'); | |
| const pub = van.state('Public Key'); | |
| const user = gun.user(); | |
| //console.log(user); | |
| if(user.is.alias){ | |
| alias.val = user.is.alias; | |
| pub.val = user.is.pub; | |
| } | |
| return div({id:'profile'}, | |
| label('Profile'), | |
| br(), | |
| label('Alias:'+alias.val), | |
| br(), | |
| label('Pub: '+pub.val), | |
| ) | |
| }; | |
| const PubDisplayEL = ()=>{ | |
| const alias = van.state('Guest'); | |
| const pub = van.state('Public Key'); | |
| const user = gun.user(); | |
| if(user?.is?.alias){ | |
| alias.val = user.is.alias; | |
| pub.val = user.is.pub; | |
| } | |
| function click_copy(){ | |
| navigator.clipboard.writeText(pub.val); | |
| //alert("Copied the text: " + pub.val); | |
| } | |
| return span( | |
| label(' Alias:'), | |
| label('[ '+alias.val+' ]'), | |
| label({onclick:()=>click_copy()},' [Pub Key Copy]: '), | |
| input({value:pub.val}) | |
| ) | |
| } | |
| const ChnagePassphraseEL = () =>{ | |
| const oldPassphrase = van.state(''); | |
| const newPassphrase = van.state(''); | |
| function click_apply(){ | |
| //console.log(oldPassphrase.val + " > " + newPassphrase.val); | |
| const user = gun.user(); | |
| user.auth(user.is.alias,oldPassphrase.val,(ack)=>{ | |
| if(ack.err){ | |
| console.log("ERROR: ",ack.err) | |
| return; | |
| } | |
| console.log(ack); | |
| //console.log("PASSWORD CHANGE...") | |
| },{change:newPassphrase.val}) | |
| } | |
| return div({id:'changepassphrase'}, | |
| label('Change Passphrase'), | |
| br(), | |
| label('Old passphrase:'), | |
| input({value:oldPassphrase, oninput:e=>oldPassphrase.val=e.target.value}), | |
| br(), | |
| label('New passphrase:'), | |
| input({value:newPassphrase, oninput:e=>newPassphrase.val=e.target.value}), | |
| button({onclick:()=>click_apply()},'Apply'), | |
| ) | |
| }; | |
| const ChnageQuestionsEL = () =>{ | |
| const question1 = van.state(''); | |
| const question2 = van.state(''); | |
| const hint = van.state(''); | |
| async function click_apply_hint(){ | |
| //console.log(oldPassphrase.val + " > " + newPassphrase.val); | |
| const user = gun.user(); | |
| //console.log(user); | |
| const pair = user._.sea; | |
| let enc = await SEA.encrypt(question1.val, pair); | |
| //console.log("question1:",enc); | |
| user.get('question1').put(enc); | |
| let enc1 = await SEA.encrypt(question2.val, pair); | |
| //console.log("question2:",enc1); | |
| user.get('question2').put(enc1); | |
| let enc2 = await SEA.work(question1.val, question2.val); | |
| let data = await SEA.encrypt(hint.val, enc2); | |
| //console.log("HINT:",data) | |
| user.get('hint').put(data); | |
| } | |
| async function click_get_hint(){ | |
| //console.log(oldPassphrase.val + " > " + newPassphrase.val); | |
| const user = gun.user(); | |
| const pair = user._.sea; | |
| let q1 = await user.get('question1').then(); | |
| //console.log("q1: ", q1); | |
| q1 = await SEA.decrypt(q1, pair); | |
| //console.log('q1: ', q1); | |
| question1.val = q1; | |
| let q2 = await user.get('question2').then(); | |
| //console.log("q2: ", q2); | |
| q2 = await SEA.decrypt(q2, pair); | |
| question2.val = q2; | |
| //console.log('q2: ', q2); | |
| let sec = await SEA.work(q1, q2); | |
| let hint0 = await user.get('hint').then(); | |
| //console.log("hint: ", hint); | |
| hint0 = await SEA.decrypt(hint0, sec); | |
| //console.log(hint0) | |
| hint.val = hint0; | |
| } | |
| return div({id:'changequestions'}, | |
| label('Change Questions'), | |
| br(), | |
| label('Question #1:'), | |
| input({value:question1, oninput:e=>question1.val=e.target.value}), | |
| br(), | |
| label('Question #2:'), | |
| input({value:question2, oninput:e=>question2.val=e.target.value}), | |
| br(), | |
| label('Hint:'), | |
| input({value:hint, oninput:e=>hint.val=e.target.value}), | |
| button({onclick:()=>click_get_hint()},'Get'), | |
| button({onclick:()=>click_apply_hint()},'Apply'), | |
| ) | |
| }; | |
| const CertsEL = () =>{ | |
| return div({id:'crets'}, | |
| label('Crets'), | |
| ) | |
| }; |