UNPKG

mikesoftp3

Version:

A simple, easy-to-use library for connecting to the Mikesoft P3 network.

583 lines (522 loc) 12.4 kB
const { io } = require("socket.io-client"); class P3 { /** * Your P3 secret set when creating the interface. * @type {string} * * */ key /** * Your P3 address, provided by the network. * @type string */ adr /** * Wether the interface is connected to the P3 network. * @type boolean */ active /** * Constructs a P3 interface object. * @param options{{autoinit:boolean,secret:string,url:string}|string} - The P3 config options. Strings will be treated as your secret. */ constructor(options) { options=options||"" if(typeof options=='string'||options instanceof String) { var url="" var secret=options } else { var {secret,url}=options if(options.autoinit) { setTimeout(()=>this.start(),3) } } url=String(url||"wss://p3.windows96.net/")||"wss://p3.windows96.net/" var $CONNECTED=false; var $totalMsg=0; var letters = "abcdefghijklmnopqrstuvwxyz+1234567890/ABCDEFGH,:#\\?@-_]{}~"; function waitUntil(bool) { return new Promise(function (g) { var X$=setInterval(function () { if(bool()){ clearInterval(X$); g(); } }) }) } var key=secret||generateSecretKey() var usedPorts = []; function $ResponsePort() { var p$=[]; for(var i = 0; i < 70001;i++) { if(!usedPorts.includes(i)) { p$.push(i) } } return p$[ Math.floor( Math.random()*p$.length ) ] }; function generatePeerID() { var $r=''; for(var i = 0; i < 40;i++) { $r+=letters[Math.floor( Math.random()*letters.length )] } return btoa($r) } function generateSecretKey() { var $r=''; for(var i = 0; i < 10;i++) { $r+=letters[Math.floor( Math.random()*letters.length )] } return btoa($r) } var skt=io(url,{ autoConnect: false }); skt.on("connect", function (E) { setTimeout(function () { skt.emit("hello", key) }, 100) }); function setskey(sky){ key=sky; } /** * Constructs client to connect to a P3 Server. * @param {string} adr - The P3 address to connect to. * @param {string|number} prt - The port to connect to. */ function P3Client(adr,prt) { /**@private*/ var $NONCE = this.$NONCE = { count: 0 }; /**@private*/ var $E=this.$EVT={ "connect":[], "message":[], 'disconnect':[] }; /**@private*/ var $H=this.$HBData={ intervalId:0, beat:15000 } var that=this /** * Information about the server you are connecting to. */ var $S=this.server = { /** * The address of the server you are connecting to. */ address:adr, /** * The port of the server you are connecting to. */ port:prt, /** * The address and port of the server, combined by a colon. */ destination:adr+":"+prt, /** * The internal peer ID of your client. * @type {string|null} */ id: null, }; var rpt=$ResponsePort(); /**@private*/ this.$CLIENT = { responsePort:rpt } skt.on('packet', function (a$) { if(a$.source==$S.address&&a$.port==rpt&&a$.data.type=='message') { for(var i = 0; i < $E.message.length;i++) { try { $E.message[i]({ data:a$.data.data, toString:()=>"[object P3ServerResponseEvent]", }) } catch (E){ null } } } else if(a$.source==$S.address&&a$.port==rpt&&a$.data.type=='disconnect') { for(var i = 0; i < $E.message.length;i++) { try { $E.disconnect[i]({ force:false, toString:()=>"[object P3ServerEndEvent]" }) } catch (E){ null } } $CONNECTED=false that.ended=true } }); var $$ACK=false var hb$ = function (a$) { if($$ACK){return} $$ACK=true for(var i = 0; i < $E.connect.length;i++) { try { $E.connect[i]({ heartbeatInterval:$a.data.heartbeat, peerID:$a.data.peerID, toString:()=>"[object P3ServerConnectEvent]" }) } catch (E) { null } } $S.id=a$.data.peerID; $H.beat=a$.data.heartbeat; $H.intervalId=setInterval(function () { skt.emit('packet', { dest: adr+":"+prt, data: { type: 'heartbeat', peerID: $S.id }, nonce:0 }); }, a$.data.heartbeat) } var $$WAITFORACCEPT = function (a$) { if(a$.data.type==='ack'&&a$.port==rpt) { hb$(a$) } } this.ended=false; setTimeout(async function () { await waitUntil(function(){return $CONNECTED}) skt.emit('packet', { data: { type: 'connect', peerID: null, responsePort: rpt, nonce: 0 }, nonce:0, dest: adr+":"+prt, }); $NONCE.count=$NONCE.count+1; },500) skt.on("packet", $$WAITFORACCEPT) } /** * Listens for events. * @param {string} event The name of the event. * @param {(...args:any[])=>any} handler The function to call when the event is triggered. */ P3Client.prototype.on = function(event,handler) { if(!handler instanceof Function) { throw new TypeError("listener must be a function") } if(!this.$EVT[event]){return} this.$EVT[event].push(handler) } /** * Ends the connection with the server. */ P3Client.prototype.end = function () { clearInterval(this.$HBData.intervalId); skt.emit("packet", { dest:this.server.destination, data:{ type:'disconnect', peerID:this.server.id }, nonce:0, }); /** * Wether the server has killed the connection with the client. */ this.ended=true; } /** * Sends data to the server. * @param {*} data The data to send to the server. */ P3Client.prototype.emit = function (data) { if(this.ended) { throw new ReferenceError( "cannot emit on closed connection" ) } skt.emit('packet', { dest:this.server.destination, data: { type:'message', peerID:this.server.id, data:data, nonce:this.$NONCE.count }, nonce:0 }); this.$NONCE.count=this.$NONCE.count+1; } skt.on("packet", function(args) { if(args.data.type=="connect") { if(ports[args.port]) { var peerid=generatePeerID(); skt.emit('packet', { data:{ type:'ack', message: "Connection accepted", peerID:peerid, success:true, heartbeat:15000, code:100, nonce:0 }, dest:args.source+":"+args.data.responsePort, nonce:0 }); var t$EVT = { message:[] } var $NONCE={count:1} ports[args.port].evt({ toString:function(){return "[object P3ClientEvent]"}, peerId:peerid, peer: { adr: args.source, port: args.data.responsePort, emit: function (data) { skt.emit('packet', { data:{ type:'message', data:data, peerID:null, success:true, nonce:0 }, dest:args.source+":"+args.data.responsePort, nonce:$NONCE.count }); $NONCE.count = $NONCE.count+1; } }, nonceCount: {n$:$NONCE}, on: CreateMessageListener( $NONCE, peerid, args.source, args.data.responsePort, args.port, t$EVT ), emit: function (data) { skt.emit('packet', { data:{ type:'message', data:data, peerID:null, success:true, nonce:0 }, dest:args.source+":"+args.data.responsePort, nonce:$NONCE.count }); $NONCE.count = $NONCE.count+1; } }) } } }) function CreateMessageListener(n$,i$,a$,p$,h$) { return function (e$,f$) { if(e$==='message') { skt.on('packet', function(evt){ if(evt.port===h$&&evt.source===a$&&evt.data.type==='message'&&evt.data.peerID==i$) { f$({ data:evt.data.data, toString:()=>"[object P3ClientInputEvent]" }) } }) } else if(e$==="disconnect") { skt.on('packet', function(evt){ if(evt.port===h$&&evt.source===a$&&evt.data.type==='disconnect'&&evt.data.peerID==i$) { f$({ toString:()=>"[object P3ClientEndEvent]" }) } }) } } } var L$ = { 'fail':[], 'connect':[] }; var ports = {}; function addPort(portNumber,listenerFunction) { if(!listenerFunction instanceof Function) { throw new TypeError("listener must be a function"); } if(usedPorts.includes(portNumber)) { throw new ReferenceError("port is already being used") } if(isNaN(portNumber)) { throw new TypeError("port must be an event name or number"); } ports[portNumber]={ evt:listenerFunction } } /** * Listens for instance-wide P3 events. * @param {string} event The name of the event. * @param {(...args:any[])=>any} handler The function to call when the event is triggered. */ this.on=function(event,handler) { if(!handler instanceof Function) { throw new TypeError("listener must be a function"); } if(L$[event]) { L$[event].push(handler); return; } } function removePort(portNumber) { ports[portNumber]=undefined delete ports[portNumber] } var $EVT = { dispatch: function (name,evt) { if(!L$[name]) { return; } var gL=L$[name] for(var i=0;i<gL.length;i++){ gL[i](evt) } } } this.endPort=removePort var $ADR=""; skt.on("hello", function(e){ console.log(e) if(!e.success && e.message==="PPP Server Error: Address already in use") { $EVT.dispatch( 'fail', { reason: "Address is in use", code: "ADDRESS_IN_USE", toStrng:()=>'[object P3FailEvent]' } ) } else if(!e.success) {return } else { $CONNECTED=true $ADR=e.address $EVT.dispatch( 'connect', { address:$ADR, toString:()=>'[object P3ConnectEvent]' } ) } }) /** * Listens for a connection on a specific port. * @param {string|mumber} port - The port to listen on * @param {(client:P3Event)=>any} listener - The callback when the port is connected to */ this.listen=function(port,listener){return addPort(port,listener)} Object.defineProperty(this,'adr',{ get:function() { return $ADR } }) Object.defineProperty(this,'active',{ get:function() { return skt.connected } }) Object.defineProperty(this,'key',{ get:function() { return key }, set:function(secret) { key=secret } }) /** * Creates a client to connect to a P3 server. * @param {string} address * @param {string|number} port */ this.createClient=function(address,port) { return new P3Client(address,port) } class P3Event { /** * The internal peer ID. * @type {string} */ peerID /** * Represents the connected client. * @type {P3ClientPeer} */ peer /** * Sends data to the connected peer. * @param {any} data The data to send. */ emit(data) {} /** * Listens for events. * @param {string} event The name of the event to listen for. * @param {(...args:any[])=>any} handler The function to call when the event is triggered. */ on(event,handler) } class P3ClientPeer { /** * The peer's P3 address. * @type {string} */ adr /** * The response port of the peer. * @type {string} */ port /** * Sends data to the connected peer. * @param {any} data The data to send. * @deprecated Use P3Event.emit instead of P3Event.peer.emit. */ emit(data){} } /** * Force exits the network and terminates all active P3 connections. */ this.kill=function(){ skt.close() } /** * Starts the P3 session if it has been killed. */ this.start=function(){ skt.open() } } } module.exports = {P3}