UNPKG

opay

Version:

opay library for Open Payment Protocol

402 lines (376 loc) 16.2 kB
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).Postmate=t()}(this,function(){"use strict";var h="application/x-postmate-v1+json",r=0,n={handshake:1,"handshake-reply":1,call:1,emit:1,reply:1,request:1},p=function(e,t){return("string"!=typeof t||e.origin===t)&&(!!e.data&&(("object"!=typeof e.data||"postmate"in e.data)&&(e.data.type===h&&!!n[e.data.postmate])))},c=function(){function e(e){var a=this;this.parent=e.parent,this.frame=e.frame,this.child=e.child,this.childOrigin=e.childOrigin,this.events={},this.listener=function(e){if(!p(e,a.childOrigin))return!1;var t=((e||{}).data||{}).value||{},n=t.data,i=t.name;"emit"===e.data.postmate&&i in a.events&&a.events[i].forEach(function(e){e.call(a,n)})},this.parent.addEventListener("message",this.listener,!1)}var t=e.prototype;return t.get=function(e){var a=this;return new l.Promise(function(n){var i=++r;a.parent.addEventListener("message",function e(t){t.data.uid===i&&"reply"===t.data.postmate&&(a.parent.removeEventListener("message",e,!1),n(t.data.value))},!1),a.child.postMessage({postmate:"request",type:h,property:e,uid:i},a.childOrigin)})},t.call=function(e,t){this.child.postMessage({postmate:"call",type:h,property:e,data:t},this.childOrigin)},t.on=function(e,t){this.events[e]||(this.events[e]=[]),this.events[e].push(t)},t.destroy=function(){window.removeEventListener("message",this.listener,!1),this.frame.parentNode.removeChild(this.frame)},e}(),s=function(){function e(e){var d=this;this.model=e.model,this.parent=e.parent,this.parentOrigin=e.parentOrigin,this.child=e.child,this.child.addEventListener("message",function(t){if(p(t,d.parentOrigin)){var e,n,i,a=t.data,r=a.property,s=a.uid,o=a.data;if("call"!==t.data.postmate)(e=d.model,n=r,i="function"==typeof e[n]?e[n]():e[n],l.Promise.resolve(i)).then(function(e){return t.source.postMessage({property:r,postmate:"reply",type:h,uid:s,value:e},t.origin)});else r in d.model&&"function"==typeof d.model[r]&&d.model[r](o)}})}return e.prototype.emit=function(e,t){this.parent.postMessage({postmate:"emit",type:h,value:{name:e,data:t}},this.parentOrigin)},e}(),l=function(){function e(e){var t=e.container,n=void 0===t?void 0!==n?n:document.body:t,i=e.model,a=e.url,r=e.name,s=e.classListArray,o=void 0===s?[]:s;return this.parent=window,this.frame=document.createElement("iframe"),this.frame.name=r||"",this.frame.classList.add.apply(this.frame.classList,o),n.appendChild(this.frame),this.child=this.frame.contentWindow||this.frame.contentDocument.parentWindow,this.model=i||{},this.sendHandshake(a)}return e.prototype.sendHandshake=function(a){var r,s=this,o=function(e){var t=document.createElement("a");t.href=e;var n=4<t.protocol.length?t.protocol:window.location.protocol,i=t.host.length?"80"===t.port||"443"===t.port?t.hostname:t.host:window.location.host;return t.origin||n+"//"+i}(a),d=0;return new e.Promise(function(n,i){s.parent.addEventListener("message",function e(t){return!!p(t,o)&&("handshake-reply"===t.data.postmate?(clearInterval(r),s.parent.removeEventListener("message",e,!1),s.childOrigin=t.origin,n(new c(s))):i("Failed handshake"))},!1);var e=function(){d++,s.child.postMessage({postmate:"handshake",type:h,model:s.model},o),5===d&&clearInterval(r)},t=function(){e(),r=setInterval(e,500)};s.frame.attachEvent?s.frame.attachEvent("onload",t):s.frame.addEventListener("load",t),s.frame.src=a})},e}();return l.debug=!1,l.Promise=function(){try{return window?window.Promise:Promise}catch(e){return null}}(),l.Model=function(){function e(e){return this.child=window,this.model=e,this.parent=this.child.parent,this.sendHandshakeReply()}return e.prototype.sendHandshakeReply=function(){var r=this;return new l.Promise(function(i,a){r.child.addEventListener("message",function e(t){if(t.data.postmate){if("handshake"!==t.data.postmate)return a("Handshake Reply Failed");r.child.removeEventListener("message",e,!1),t.source.postMessage({postmate:"handshake-reply",type:h},t.origin),r.parentOrigin=t.origin;var n=t.data.model;return n&&Object.keys(n).forEach(function(e){r.model[e]=n[e]}),i(new s(r))}},!1)})},e}(),l}); let GG = {}; let _default = { product:"OPay Product", timeout:5*60, app:{name:"Opay App"}, debug:false, handshake:null, child:null, elFrame:null, event_on:false, cb:null, debug:false, expireObj:{}, current_orderID:0, wallet:null, UI:{close:true}, } let gHandshake = null let gEventOn = false const script_url = document.currentScript.src; const toolServer = "https://nb-namecheck.glitch.me"; let log = console.log; function clearChecker(orderID){ log("in clearChecker"); const checkingObj = GG.expireObj[orderID]; if(checkingObj){ log("clearChecker timeoutID:",checkingObj.timeoutID) GG.expireObj[orderID] = null; return true; } return false; } async function checkReturn(ID){ //const domain = document.location.origin; log("checker:",GG.expireObj,"ID:",ID); const checkingObj = GG.expireObj[ID]; if(checkingObj==null) { log("no checkingObj"); return; } const time_now = Date.now(); if(time_now>checkingObj.expire){ clearChecker(ID); return; } const replyURL = toolServer + "/get_pr/"+ ID + "?notify=1"; log(replyURL); const res = await fetch(replyURL); const data = await res.text(); log(data); if(data!="404"){ handle_result(false,data); }else{ log("continue to check") checkingObj.timeoutID = setTimeout(checkReturn,2000,ID); } return data; } function handle_result(bVbox,data){ if(!clearChecker(GG.current_orderID)&&!bVbox) return; //found re-entry,return const json = JSON.parse(data); json.usedWallet = GG.usedWallet; log('got_reply1 data=',json,"isVbox:",bVbox); const ret = GG.cb(json); log("return from app:",ret); if(json.ack_url && ret ){ let retAck = ret; retAck.id = json.id; const url = json.ack_url+"?ack="+JSON.stringify(retAck); fetch(url); } opay.close(); } async function loadScript(url, callback){ return new Promise(resolve=>{ var script = document.createElement("script") script.type = "text/javascript"; if (script.readyState){ //IE script.onreadystatechange = function(){ if (script.readyState == "loaded" || script.readyState == "complete"){ script.onreadystatechange = null; if(callback){ callback(); resolve(); } } }; } else { //Others script.onload = function(){ if(callback){ callback(); resolve(); } }; } script.src = url; document.getElementsByTagName("head")[0].appendChild(script); }) } export default class opay{ static async init(option){ GG = { ..._default, ...option } if(!option.debug){ log=(msg)=>{}; } log("opay init option=",option); if(window.opay_inited) return; await this._initLibs(); window.opay_inited = true; } static async _initLibs(){ if(!window.filepay){ log("loading filepay"); await loadScript("https://unpkg.com/filepay@latest/dist/filepay.min.js",()=>{ if(window.filepay){ opay.filepay = window.filepay; opay.bsv = window.filepay.bsv; log("filepay loaded:",window.filepay); }else{ log("filepay failed to load"); } }); }else{ opay.filepay = window.filepay; opay.bsv = window.filepay.bsv; } var style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = ` .opayClass { position: absolute; display:none; width:100%; height:100%; top: 0; left: 0; /* bring your own prefixes */ border-radius: 5px; border:1px solid #BDBDBD; background:#fff; } `; document.getElementsByTagName('head')[0].appendChild(style); } static _setupIframe(){ let elFrame = document.querySelector("iframe[name='my-iframe-opay']"); if(true){ log("Create opay Frame") if(gHandshake){ log("gHandshake exist. Destroy child"); gHandshake.then(child => child.destroy()); GG.event_on = false; const frame = document.querySelector("iframe[name='my-iframe-opay']") if(frame){ const parent = frame.parentNode log("found frame and destory it:",parent) parent.removeChild(frame) } } const path = script_url.slice(0,script_url.lastIndexOf('/'))+"/iframe.html"; // Kick off the handshake with the iFrame let el_container = document.querySelector('body'); if(GG.containerID!=null){ el_container = document.querySelector("#"+GG.containerID); el_container.style.position = "relative"; } gHandshake = new Postmate({ container: el_container, // Element to inject frame into url: path, // Page to load, must have postmate.js. This will also be the origin used for communication. name: 'my-iframe-opay', // Set Iframe name attribute. Useful to get `window.name` in the child. classListArray: ["opayClass"] //Classes to add to the iframe via classList, useful for styling. }); GG.elFrame = document.querySelector("iframe[name='my-iframe-opay']"); elFrame = GG.elFrame; } if(GG.UI){ if(GG.UI.border) elFrame.style.border = GG.UI.border; gHandshake.then(child => { if(window.VBox) GG.UI.VBox = true; log("Calling child:",GG.UI); child.call("setUI",GG.UI); }); } } static setWallet(wallet){ GG.wallet = wallet; } static close(){ clearChecker(GG.current_orderID); if(GG.elFrame) GG.elFrame.style.display="none"; } static setUI(UI){ GG.UI = UI; } static changeContainer(ID){ const elm = document.querySelector('#'+ID); if(elm){ elm.style.position = "relative"; if(GG.elFrame) elm.appendChild(GG.elFrame); GG.containerID = ID; } } static sha256(dataObj){ let hash1 = opay.bsv.crypto.Hash.sha256(opay.bsv.deps.Buffer(dataObj)).toString('hex'); return hash1; } static verify(data_hash,strSig,strPubKey){ const bsv = opay.bsv; let sig = bsv.crypto.Signature.fromString(strSig); let pubKey = bsv.PublicKey.fromString(strPubKey); let hash2 = bsv.crypto.Hash.sha256(bsv.deps.Buffer.from(data_hash,'hex')); return bsv.crypto.ECDSA.verify(hash2,sig,pubKey); } static getSupportedWallets(){ return ['vbox','dot']; } static genQRImg(options, containerID, cb){ GG.cb = cb; loadScript("https://cdnjs.cloudflare.com/ajax/libs/qrcode-generator/1.4.4/qrcode.min.js",()=>{ opay._preHandleOptions(options); const strQR = opay.genQRString(options); var typeNumber = 0; var errorCorrectionLevel = 'L'; var qr = qrcode(typeNumber, errorCorrectionLevel); qr.addData(strQR); qr.make(); let strImgTag = qr.createImgTag(); document.getElementById(containerID).innerHTML = strImgTag; opay.start_checker(); }) } static genQRString(options){ let req = null; if(options.pay_request){ req = options.pay_request; } if(options.sign_request){ req = options.sign_request; } if(req.url==null){ //const domain = document.location.origin; const saveURL = toolServer+"/save_pr"; const getURL = toolServer+"/get_pr/"; const reqBody = req.data; reqBody.notify_url = toolServer+"/notify"; fetch(saveURL, { method: 'post', headers: { 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json' }, body: JSON.stringify(reqBody) }).then(function (response) { //log(response); return response.json(); }).then(function (data) { log('reply from save_pr:', data); }); //req.url = getURL+reqBody.id; const url = getURL+reqBody.id; const newOption = options.pay_request?{pay_request:{url:url}}:{sign_request:{url:url}}; return JSON.stringify(newOption); } return null; } static _preHandleOptions(options){ GG.options = options; let req = {}; if(options.pay_request) req = options.pay_request; if(options.sign_request) req = options.sign_request; log(JSON.stringify(options)); const data = req.data; if(data.to){ for(let i=0;i<data.to.length;i++){ const item = data.to[i]; if(item.data){ const script_data = window.filepay.data2script(item.data).toHex(); item.script = script_data; item.value=0; delete item.data; } } } if(!data.product) data.product = GG.product; if(!data.expire) data.expire = Date.now() + GG.timeout*1000; if(!data.id) data.id = (Date.now().toString()); if(!data.v) data.v = 2; if(!data.app) data.app = GG.app; } static start_checker(){ //start checker const body = GG.options.pay_request?GG.options.pay_request.data : GG.options.sign_request.data GG.current_orderID = body.id; if(GG.current_orderID){ const timeoutID = setTimeout(checkReturn,2000,GG.current_orderID); log("checker started, timeoutID:",timeoutID); GG.expireObj[GG.current_orderID]={expire:body.expire,timeoutID:timeoutID}; } } static request(options,cb){ GG.cb = cb; opay._preHandleOptions(options); const QRString = opay.genQRString(options); if(GG.wallet){ callWallet(GG.wallet); return; } //get state from cookie var nbpay_state ={}; var s_state = ("; "+document.cookie).split("; nbpay_state=").pop().split(";").shift(); if(s_state!=""){ nbpay_state = JSON.parse(s_state); } if(!GG.wallet){//No wallet specified opay._setupIframe(); } function callWallet(name){ GG.usedWallet = name; GG.wallet = name; if(name==="dot"){ let url = "https://www.ddpurse.com/wallet/open/nbdomain?opp="; url += encodeURIComponent(QRString); window.open(url,GG.current_orderID); opay.start_checker(); } if(name==="vbox"){ let vbox_option = GG.options.pay_request ? {pay_request:{data:JSON.stringify(GG.options.pay_request.data)}} : {sign_request:{data:JSON.stringify(GG.options.sign_request.data)}} log("calling vbox with options:",vbox_option); VBox.Request(vbox_option,(data)=>{ handle_result(true,data); }); } } function call_child(child){ console.log("calling setup:",child) GG.elFrame.style.display="block"; child.call("setup",{strQR:QRString,state:nbpay_state}); opay.start_checker(); } gHandshake.then(child => { GG.child = child; if(!GG.event_on) { child.on('opay_frm_cmd',cmd =>{ log(cmd); if(cmd.cmd=="close"){ GG.elFrame.style.display="none"; const state = JSON.stringify(cmd.state); document.cookie="nbpay_state="+state; opay.close(); if(typeof GG.UI.close === 'function'){ GG.UI.close(); } } if(cmd.cmd=="wallet"){ callWallet(cmd.name); if(cmd.state){ const state = JSON.stringify(cmd.state); document.cookie="nbpay_state="+state; } } }) GG.event_on = true; } call_child(child); }); } }