@d3x0r/sack-gui
Version:
SACK abstraction library exposed to JS to provide low level system services.
161 lines (143 loc) • 4.5 kB
JavaScript
import {sack} from "sack.vfs"
import {openServer} from "./server.mjs"
import {Events} from "../events/events.mjs";
const JSOX = sack.JSOX;
const debug_ = false;
function loopBack( that, to ) {
return function f( ws ) {
to.call(that, this, ws);
}
}
export class Protocol extends Events {
protocol = null;
server = null;
#opts = null;
static #WS = null;
/**
* @param {object} opts - options for the protocol
*/
constructor( opts ) {
super();
this.#opts = opts|| { resourcePath:"ui", port:Number(process.env.PORT)||4321 };
// resource path is from current working directory (where it ran from)
if( opts && opts.protocol ) Protocol.protocol = opts.portocol;
if( "WS" in opts ) {
Protocol.#WS = opts.WS;
}
this.server = openServer( this.#opts
, loopBack( this, this.#accept ), loopBack( this, this.#connect ) );
//this.on( "close", )
}
#accept( server, ws ) {
//console.log( "this, server, ws", this, server, ws );
const results = this.on( "accept", ws );
if( results && results.length > 0 ) {
if( results.includes( true ) ) server.accept();
else server.reject();
return;
}
const protocol = ws.headers['Sec-WebSocket-Protocol'] || (ws.headers['Sec-Websocket-Protocol'] /* horray for heroku*/);
if( this.protocol && protocol != this.protocol ) {
console.log( "protocol failed:", protocol. Protocol.protocol );
this.reject();
return;
}
server.accept();
}
#connect(ws) {
const myWS = Protocol.#WS?new Protocol.#WS(ws) : new WS( ws );
const this_ = this;
//console.log( "--------------- NEW CONNECTION ------------------" );
const results = this.on( "connect", [ws, myWS] );
ws.onmessage = handleMessage;
ws.onclose = handleClose;
const parser = sack.JSOX.begin(
(object)=>Protocol.#dispatchMessage(this_, myWS,object) );
if( results && results.length ) {
// assume the on-connect provdies its own open/close handlers
if( results.includes( true ) ) return;
}
function handleClose( code, reason ) {
this_.on( "close", [myWS,code,reason] );
myWS.on("close", [code.reason]);
}
function handleMessage( msg ) {
const result = this_.on( "message", [ws,msg])
//console.log( "handle message:", result, msg );
if( !result || ! result.reduce( (acc,val)=>acc|=!!val, false ) ) {
const res2 = myWS.on("message",[ws,msg]);
//console.log( "socket handler?", res2, msg )
if( !res2 || ! res2.reduce( (acc,val)=>acc|=!!val, false ) )
parser.write( msg );
}
}
}
static #dispatchMessage(protocol, ws, msg ) {
debug_ && console.log( "invoking handler for:", msg.op, msg )
protocol.on( msg.op, [ws, msg] );
}
addFileHandler( ) {
//console.log( "Adding websocket handler for 'get'" );
this.on( "get", (myWS,msg)=>{
let response = {
headers:null,
content:null,
status : 0,
statusText : "Ok",
}
// this gets passed to
const url = new URL( msg.url );
debug_ && console.log( "url parts:", url, url.message );
this.server.handleEvent ( {url:url.pathname,
connection: {
headers:{}, remoteAddress:"myRemote" }
}, {
set statusText(val) {
response.statusText = val;
},
get statusText() {
return response.statusText;
},
writeHead(A,B) {
response.status = A;
response.headers = B;
},
end( content ) {
response.content = content;
//console.log( "Reply with got and content?", response );
myWS.send( { op:"got", id:msg.id, response } );
},
} );
} );
}
}
export class WS extends Events{
ws = null;
constructor(ws){
super();
this.ws = ws;
}
/**
* send a message - with automatic JSOX encoding if the message is an object.
* @param {*} msg - message to send to the server, if an object, it will be sent as a JSOX object, otherwise it will be sent as a literal string.
*/
send( msg ) {
if( "object" === typeof msg )
this.ws.send( JSOX.stringify(msg) );
else
this.ws.send( msg );
}
/**
* emit an event to the server
* @param {*} cmd - string command to genereate
* @param {*} data - data to send with the command, if an object, it will be sent as a JSOX object,
* otherwise it will be sent as a JSOX object with the key being the command.
*/
emit( cmd, data ){
if( "object" === typeof data ) {
this.ws.send( JSOX.stringify(Object.assign( {op:cmd}, data )) );
} else
this.ws.send( JSOX.stringify({ op:cmd, [cmd]:data }) );
}
}
//export const protocol = new Protocol();