ln.vue/js/ln.vue.js

634 lines
19 KiB
JavaScript
Raw Normal View History

2019-11-29 09:29:30 +01:00
(function(globals){
2019-11-15 13:44:44 +01:00
let _unique = new Date().getTime();
2019-11-29 09:29:30 +01:00
let __idles__ = [];
class LN
{
constructor(opts){
}
static $each(oa,cb){
if (oa instanceof Array){
let result = [];
oa.forEach((value,index)=>{
result.push(cb.call(value,index,value));
});
return result;
} else if (oa instanceof Object){
let result = {};
Object.keys(oa).forEach((key)=>{
if (oa.hasOwnProperty(key)){
result[key] = cb.call(oa[key],key,oa[key]);
}
});
return result;
}
}
static $idle(cb,thisval = null){
let scheduled = __idles__.length > 0;
let n=0;
for (;n<__idles__.length;n++){
let idle = __idles__[n];
if ((idle[0] == cb) && (idle[1] == thisval))
break;
}
if (n == __idles__.length)
__idles__.push([cb,thisval]);
if (!scheduled)
setTimeout(()=>{
while (__idles__.length > 0){
let idle = __idles__.pop();
idle[0].call(idle[1]);
}
},0);
}
static $unique(){
return _unique++;
}
static $fetch(url,cb){
return new LN.Promise((resolve,reject)=>{
fetch(url)
.then((response => {
if (response.status.toString().startsWith("2"))
{
let t = response.text();
cb && cb(t,null);
resolve(t);
} else {
cb && cb(null, response.statusText);
reject(response.statusText);
}
}));
}, `fetch(${url})`);
}
static $add(classpath,c){
let p = classpath.split(".");
if (p.length < 1)
throw "invalid classpath";
let container = globals;
while (p.length > 1){
let next = p.shift();
if (!container[next])
container[next] = {}
container = container[next];
}
let prev = container[p[0]];
container[p[0]] = c;
if (prev)
{
LN.$each(prev,(key,value)=>{
c[key] = value;
});
}
};
2019-11-15 13:44:44 +01:00
}
2019-11-29 09:29:30 +01:00
LN.prototypes = {};
2019-11-15 13:44:44 +01:00
2019-11-29 09:29:30 +01:00
LN.$add("LN", LN);
})(window);
2019-11-15 13:44:44 +01:00
2019-11-29 09:29:30 +01:00
(function(){
2019-11-15 13:44:44 +01:00
/*\
|*|
|*| Base64 / binary data / UTF-8 strings utilities (#1)
|*|
|*| https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|*|
|*| Author: madmurphy
|*|
2019-11-29 09:29:30 +01:00
|*| Harald Wolff-Thobaben: Small adaptions to create a static class
|*|
2019-11-15 13:44:44 +01:00
\*/
2019-11-29 09:29:30 +01:00
class Base64
{
constructor(){
}
2019-11-15 13:44:44 +01:00
2019-11-29 09:29:30 +01:00
static b64ToUint6(nChr){
return nChr > 64 && nChr < 91 ?
nChr - 65
: nChr > 96 && nChr < 123 ?
nChr - 71
: nChr > 47 && nChr < 58 ?
nChr + 4
: nChr === 43 ?
62
: nChr === 47 ?
63
:
0;
}
static uint6ToB64(nUint6){
return nUint6 < 26 ?
nUint6 + 65
: nUint6 < 52 ?
nUint6 + 71
: nUint6 < 62 ?
nUint6 - 4
: nUint6 === 62 ?
43
: nUint6 === 63 ?
47
:
65;
}
2019-11-15 13:44:44 +01:00
2019-11-29 09:29:30 +01:00
static encode(aBytes){
var eqLen = (3 - (aBytes.length % 3)) % 3, sB64Enc = "";
2019-11-15 13:44:44 +01:00
2019-11-29 09:29:30 +01:00
for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
nMod3 = nIdx % 3;
nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
sB64Enc += String.fromCharCode(Base64.uint6ToB64(nUint24 >>> 18 & 63), Base64.uint6ToB64(nUint24 >>> 12 & 63), Base64.uint6ToB64(nUint24 >>> 6 & 63), Base64.uint6ToB64(nUint24 & 63));
nUint24 = 0;
}
}
2019-11-15 13:44:44 +01:00
2019-11-29 09:29:30 +01:00
return eqLen === 0 ?
sB64Enc
:
sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "==");
}
2019-11-15 13:44:44 +01:00
2019-11-29 09:29:30 +01:00
static decode(sBase64, nBlockSize) {
var
sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
nOutLen = nBlockSize ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2, aBytes = new Uint8Array(nOutLen);
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= Base64.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
}
nUint24 = 0;
}
}
return aBytes;
}
}
LN.$add("LN.Base64",Base64);
})();
2019-11-15 13:44:44 +01:00
2019-11-29 09:29:30 +01:00
(function(){
class LNPromise
2019-11-15 13:44:44 +01:00
{
2019-11-29 09:29:30 +01:00
constructor(executor, label){
2019-11-15 13:44:44 +01:00
this.promise = new Promise(
(resolve,reject) => {
2019-11-29 09:29:30 +01:00
this.resolve = (v) => {
this._s.state = "ready";
resolve(v);
this.release();
},
this.reject = (e) => {
this._s.state = "failed";
reject(e);
this.release();
}
executor && executor(
this.resolve,
this.reject
2019-11-15 13:44:44 +01:00
);
}
);
if (!label)
label = "N.D.";
this._s = {
label,
state: "waiting"
};
2019-11-29 09:29:30 +01:00
this.idx = LN.$unique();
LNPromise.$current[this.idx] = this._s;
2019-11-15 13:44:44 +01:00
}
label(){
return this._s.label;
}
state(){
return this._s.state;
}
release(){
setTimeout(()=>{
2019-11-29 09:29:30 +01:00
Vue.delete(LNPromise.$current, this.idx);
2019-11-15 13:44:44 +01:00
},1000);
}
then(){
return this.promise.then.apply(this.promise, arguments);
}
2019-11-29 09:29:30 +01:00
static getCurrentPromises(){ return LNPromise.$current; }
2019-11-15 13:44:44 +01:00
}
2019-11-29 09:29:30 +01:00
LNPromise.$current = {};
2019-11-15 13:44:44 +01:00
2019-11-29 09:29:30 +01:00
LN.$add("LN.Promise",LNPromise);
})();
(function(){
2019-11-15 13:44:44 +01:00
class LNVueWebSocket
{
constructor(lnvue,o){
this.LNVue = lnvue;
this.options = Object.assign({},o);
if (!this.options.url)
this.options.url = this.constructURL();
this._id = 1;
this.defaultTimeout = 30000;
this.websocket = null;
this.callbacks = {};
this._state = "initialized";
this._retry = null;
this.closing = false;
}
constructURL(){
var pageURI = window.location;
var scheme = pageURI.scheme == "https" ? "wss:" : "ws:";
var host = pageURI.host;
return scheme + "//" + host + "/socket";
}
open(){
if (this._retry){
clearTimeout(this._retry);
}
this.closing = false;
this.websocket = new WebSocket(this.options.url);
2019-11-26 12:22:27 +01:00
this.websocket.onopen = (e) =>{
console.log("WebSocket connected");
this._state = "ONLINE";
let WSHello = {
ApplicationSessionID: this.LNVue.sessionID(),
};
console.log("WSHello request",WSHello);
this.request("WSHello",WSHello)
.then((wsh)=>{
console.log("WSHello response",wsh);
this.LNVue.sessionID(wsh.message.ApplicationSessionID);
2019-11-29 09:29:30 +01:00
this.LNVue.identity = new LN.Identity(wsh.message.SessionIdentity);
2019-11-26 12:22:27 +01:00
});
};
2019-11-15 13:44:44 +01:00
this.websocket.onclose = (e)=>{ this._onclose(e); this._state = "OFFLINE"; };
this.websocket.onerror = (e)=>{ this._onerror(e); this._state = "ERROR"; };
this.websocket.onmessage = (e)=>{ this._onmessage(e); };
return this;
}
close(){
if (this.websocket){
this.closing = true;
this.websocket.close(200,"close() called");
}
}
request(msgtype,msg,timeout){
let message = {
id: this._id++,
type: msgtype,
message: msg,
}
2019-10-15 23:46:02 +02:00
2019-11-15 13:44:44 +01:00
if (!timeout)
timeout = this.defaultTimeout;
if (timeout != -1){
return new Promise((resolve,reject)=>{
let to = setTimeout(()=>{
delete this.callbacks[message.id];
reject("timed out");
},timeout);
this.callbacks[message.id] = (msgtype,msg)=>{
clearTimeout(to);
delete this.callbacks[message.id];
if (msgtype == "error")
reject(msg);
else
resolve({type: msgtype,message: msg});
};
this.websocket.send(
JSON.stringify(message)
);
});
} else {
new Promise((resolve,reject)=>{
this.websocket.send(
JSON.stringify(message)
);
resolve();
});
}
}
_onclose(evt){
this.websocket = null;
this.options.onclose && this.options.onclose(evt);
if (!this.closing)
{
this._retry = setTimeout(() => {
this._retry = null;
console.log("reconnect...")
this.open();
}, 5000);
}
}
_onerror(evt){
this.options.onerror && this.options.onerror(evt);
}
_onmessage(evt){
try
{
let j = JSON.parse(evt.data);
let cb = this.callbacks[ j.id ];
cb && cb(j.type,j.message);
} catch(exc){
console.log(exc,evt.data);
}
}
}
2019-11-29 09:29:30 +01:00
LN.$add("LN.Vue.WebSocket",LNVueWebSocket);
})();
2019-10-15 23:46:02 +02:00
2019-11-29 09:29:30 +01:00
(function (){
2019-10-15 23:46:02 +02:00
class LNVue
{
constructor(el,options = {}){
this.options = Object.assign({
routes: [],
data: {},
}, options );
this._el = el;
2019-10-15 23:46:02 +02:00
this.data = Object.assign({}, options.data, { LNVue: this, msg: "Hello World" });
this.promises = [];
2019-10-15 23:46:02 +02:00
2019-11-15 13:44:44 +01:00
this.statusText = "LNVue preparing";
2019-10-15 23:46:02 +02:00
Vue.prototype.$LNVue = this;
LNVue.$_ = this;
2019-10-15 23:46:02 +02:00
this.navigation = {};
2019-11-29 09:29:30 +01:00
this.identity = new LN.Identity();
2019-10-15 23:46:02 +02:00
Promise
.all(LNVue.promises)
.then(()=>{
2019-11-15 13:44:44 +01:00
this.status("LNVue: starting");
2019-10-15 23:46:02 +02:00
LNVue.vueRouter.addRoutes([{
2019-10-15 23:46:02 +02:00
path: "*",
component: {
template: `<h2>404 Not Found</h2>The URL you tried to reach is not existing.`,
},
}]);
2019-11-15 13:44:44 +01:00
},
(cause)=>{
this.status("LNVue: start failed: " + cause);
});
this.vue = null;
2019-11-24 15:10:36 +01:00
2019-11-29 09:29:30 +01:00
LNVue.$instance.resolve(this);
}
2019-10-15 23:46:02 +02:00
Start(){
Promise
.all(this.promises)
.then(()=>{
2019-11-29 09:29:30 +01:00
LN.$idle(()=>{
this.vue = new Vue({
el: this._el,
data: this.data,
router: LNVue.vueRouter,
});
2019-11-15 13:44:44 +01:00
});
2019-10-15 23:46:02 +02:00
});
2019-11-29 09:29:30 +01:00
LN.$idle(()=>{
this.socket = new LN.Vue.WebSocket(this);
2019-11-15 13:44:44 +01:00
this.socket.open();
});
2019-11-29 09:29:30 +01:00
LN.$idle(()=>{
LNVue.$start.resolve(this);
2019-11-24 15:10:36 +01:00
});
2019-10-15 23:46:02 +02:00
}
2019-11-26 12:22:27 +01:00
storage(){
return window.localStorage;
}
2019-11-15 13:44:44 +01:00
2019-11-26 12:22:27 +01:00
sessionID(){
if (arguments.length == 1){
this.storage().setItem("LNVueSessionID",arguments[0]);
console.log("LNVue.SID <= " + arguments[0]);
return this;
} else
{
let sid = this.storage().getItem("LNVueSessionID");
console.log("LNVue.SID == " + sid);
if (!sid)
{
sid = "00000000-0000-0000-0000-000000000000";
}
return sid;
}
}
2019-11-15 13:44:44 +01:00
2019-10-16 22:00:38 +02:00
Version(){ return "0.2alpha"; };
2019-11-15 13:44:44 +01:00
getCurrentPromises() {
2019-11-29 09:29:30 +01:00
return LN.Promise.getCurrentPromises();
2019-11-15 13:44:44 +01:00
}
status(){
if (arguments.length == 1){
this.statusText = arguments[0];
return this;
} else if (arguments.length == 0){
return this.statusText;
} else
throw "LNVue.status(): too many arguments";
}
2019-10-15 23:46:02 +02:00
addModule(modSpec){
if (modSpec.navigation instanceof Object){
LNVue.deepAssign(modSpec.navigation,this.navigation);
}
2019-11-29 09:29:30 +01:00
LN.$each(modSpec.routes,(key,route)=>{
if ((route instanceof Object) && route.url)
{
2019-11-29 09:29:30 +01:00
let p = new LN.Promise((resolve,reject)=>{
LN.$fetch(route.url)
.then((src)=>{
this.addRoute(key,{ template: src, data: ()=>{ return this.data; }, });
resolve();
2019-11-15 13:44:44 +01:00
},
(cause)=>{
console.log("loading route.url failed: ",cause);
});
2019-11-29 09:29:30 +01:00
},`addModule(${route.url})`);
this.promises.push(p);
} else if (route instanceof Object){
this.addRoute(key,{ template: route.template, data: ()=>{ return this.data; }, } );
} else {
this.addRoute(key,{ template: route, data: ()=>{ return this.data; }, } );
}
});
}
addRoute(path,component){
LNVue.vueRouter.addRoutes([
{ path, component, },
]);
2019-11-29 09:29:30 +01:00
2019-11-24 15:10:36 +01:00
if (this.vue){
let route = this.vue.$route;
LNVue.vueRouter.replace("/");
LNVue.vueRouter.replace(route);
}
}
2019-11-15 13:44:44 +01:00
/* Authentication API */
requestChallenges(identityName,secureAttributeTypeName){
return new Promise((resolve,reject)=>{
this.socket.request("AuthenticationRequest",{
IdentityName: identityName,
SecureAttributeTypeName: secureAttributeTypeName,
})
.then((challenges)=>{
resolve(challenges);
},
(error)=>{
console.log("Login challenges could not be retrieved", error);
}
);
});
}
authenticate(identityName,secureAttributeID,challenge,prove){
let authenticationProve = {
IdentityName: identityName,
SecureAttributeUniqueID: secureAttributeID,
Challenge: challenge,
Prove: prove,
};
this.socket.request("AuthenticationProve", authenticationProve)
.then((identity)=>{
2019-11-29 09:29:30 +01:00
this.identity = new LN.Identity(identity.message);
2019-11-15 13:44:44 +01:00
},
(error)=>{
2019-11-29 09:29:30 +01:00
this.identity = new LN.Identity();
2019-11-15 13:44:44 +01:00
});
}
2019-11-24 15:10:36 +01:00
rpc(moduleName,methodName,parameters){
return new Promise((resolve,reject)=>{
2019-11-29 09:29:30 +01:00
let rpcCall = {
2019-11-24 15:10:36 +01:00
module: moduleName,
method: methodName,
parameters: parameters
2019-11-29 09:29:30 +01:00
};
this.socket.request("RPCCall",rpcCall)
2019-11-24 15:10:36 +01:00
.then(
(result)=>{
2019-11-29 09:29:30 +01:00
if (result.message.error)
{
console.log("rpc call failed", result.message.error);
reject(result.message.error);
}
else
resolve(result.message.Result);
2019-11-24 15:10:36 +01:00
},
(error)=>{
console.log("rpc failed", error);
reject(error);
}
);
});
}
2019-11-29 09:29:30 +01:00
static $LNVue(){
return LNVue.$_;
}
2019-10-15 23:46:02 +02:00
}
2019-11-29 09:29:30 +01:00
LNVue.$instance = new LN.Promise(()=>{},'LN.Vue Instance Promise');
LNVue.$start = new LN.Promise(()=>{},'LN Vue Startup Promise');
2019-10-15 23:46:02 +02:00
2019-11-24 15:10:36 +01:00
LNVue.vueRouter = new VueRouter({
mode: 'history',
routes: [],
});
LNVue.deepAssign = function(source,target){
2019-11-29 09:29:30 +01:00
LN.$each(source,function(key){
if (target[key] instanceof Object){
LNVue.deepAssign(src[key],target[key]);
} else {
target[key] = source[key];
}
});
}
2019-10-15 23:46:02 +02:00
LNVue.$ = function(src){
let el = document.createElement("parse");
el.innerHTML = src;
return el.firstChild;
}
LNVue.routes = [];
LNVue.promises = [];
2019-11-15 13:44:44 +01:00
LNVue.encodeHex = (bytes) => bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
LNVue.decodeHex = (hexString) => new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
ArrayBuffer.combine = function(...args){
let byteLength = 0;
args.forEach((arg,index)=>{
byteLength = byteLength + arg.byteLength;
});
let result = new Uint8Array(byteLength);
let p = 0;
args.forEach((arg,index)=>{
for (let n=0;n<arg.byteLength;n++)
{
result[p++] = arg[n];
}
});
console.log("combine",new Uint8Array(result));
return result.buffer
};
2019-11-29 09:29:30 +01:00
LN.$add("LN.Vue",LNVue);
LN.$add("LNVue",LNVue);
2019-10-15 23:46:02 +02:00
})();