opay
Version:
opay library for Open Payment Protocol
402 lines (376 loc) • 16.2 kB
JavaScript
!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);
});
}
}