urbit/web/lib/js/urb.js
Matt Steedman 769a62f3a9 Set timeout for urb.req
This sets a 1 minute timout on the urb.req xhr object. If no response
is received after one minute, it passes an error to the callback with a
408 status code.

I've also made sure that a 'beat' response resets the dely property back
to 250, instead of maintaining the previous value.

Fixes urbit/talk#21
2016-06-15 17:04:02 -04:00

326 lines
9.5 KiB
JavaScript

window.urb = window.urb || {}
window.urb.appl = window.urb.appl || null
window.urb.init = function(onload){ // XX proper class?
onload = onload || function(){}
var $init = this.init
if($init.loaded) return onload()
if($init.loading) return $init.loading.push(onload)
$init.loading = [onload]
var s = document.createElement('script')
s.src = "/~/at/~/auth.js" // XX nop.js? auth.json?
s.onload = function(){
$init.loading.map(function(f){f()})
delete $init.loading
$init.loaded = true
}
document.body.appendChild(s)
}
window.urb.init.loaded = window.urb.oryx
window.urb.req = function(method,url,params,json,cb) {
var xhr = new XMLHttpRequest()
method = method.toUpperCase()
if(method == "PUT" || method == "DELETE")
xhr.open("POST", url+"?"+method)
else xhr.open(method, url)
if(json)
xhr.setRequestHeader("content-type", "text/json")
xhr.timeout = 60000
if(!window.urb.oryx) throw "No CSRF token" // XX fetch auth.json
_data = {oryx: window.urb.oryx}
if(params.xyro) { _data.xyro = params.xyro; }
if(params.ship) { _data.ship = params.ship; }
if(params.path) { _data.path = params.path; }
if(params.appl) { _data.appl = params.appl; }
if(params.mark) { _data.mark = params.mark; }
if(params.wire) { _data.wire = params.wire; }
if(cb) {
xhr.onload = function() {
var err,res
try {
err = null
res = {
status:this.status,
data: JSON.parse(this.responseText)
}
if(res.data.reload)
res.reload = res.data.reload
} catch(e) {
// if(urb.wall !== false) document.write(this.responseText) // XX
err = {
message:"Failed to parse JSON",
raw:this.responseText
}
res = null
}
finally {
cb(err,res)
}
}
xhr.ontimeout = function() {
cb({
status:408,
data:null
})
}
xhr.onerror = function() {
cb({
status:this.status,
data:this.responseText
})
}
}
xhr.send(JSON.stringify(_data))
}
// window.urb.getJSON = function(url,cb){ window.urb.reqJSON("GET",url, null, cb)}
// window.urb.reqJSON = function(method, url, data, cb){
// var xhr = new XMLHttpRequest()
// xhr.open(method, url)
// xhr.onload = function(){
// urb.fetchTag.call(xhr)
// if(cb) cb(JSON.parse(xhr.responseText))
// }
// xhr.send(data === null ? null : JSON.stringify(data))
// }
window.urb.reqq = []
window.urb.qreq = function(method,url,params,json,cb) {
walk = function() {
qobj = {}
qobj.oargs = window.urb.reqq[0]
qobj.nargs = [].slice.call(qobj.oargs,0,4)
qobj.nargs.push(function(){
if(this.oargs[4])
this.oargs[4].apply(window.urb,arguments)
window.urb.reqq.shift()
if(window.urb.reqq.length > 0)
walk()
}.bind(qobj))
window.urb.req.apply(this,qobj.nargs)
}
l = window.urb.reqq.length
window.urb.reqq.push(arguments);
if(l == 0) { walk() }
}
window.urb.send = function(data,params,cb) { // or send(data, cb)
if(!params || typeof params === "function")
{cb = params; params = {}}
var url, $send
$send = this.send
params.data = data
params.ship = params.ship || this.ship
params.appl = params.appl || this.appl
params.mark = params.mark || $send.mark
// params.seqn = params.seqn || $send.seqn
params.wire = params.wire || "/"
params.xyro = (typeof(params.data) === 'undefined') ? null : params.data
if(!params.mark) throw new Error("You must specify a mark for urb.send.")
if(!params.appl) throw new Error("You must specify an appl for urb.send.")
url = ["to",params.appl,params.mark]
url = "/~/"+url.join("/")
// $send.seqn++
this.qreq('post',url,params,true,function(err,data) {
/* if(err) { $send.seqn--; }
else */ if(data && data.data.fail && urb.wall !== false) {
document.location = "#ERROR"
document.write("<pre>"+JSON.stringify(params.xyro)+"\n"
+data.data.mess+"</pre>") // XX
}
if(cb) { cb.apply(this,arguments); }
})
}
// window.urb.send.seqn = 0
window.urb.send.mark = "json"
window.urb.gsig = function(params) {
var path = params.path
if(!path) path = ""
if(path[0] !== "/") path = "/"+path
return "~"+params.ship+"/"+
params.appl+
path.replace(/[^\x00-\x7F]/g, "")
}
window.urb.puls = false
window.urb.cabs = {}
window.urb.poll = function(params) {
if(!params) throw new Error("You must supply params to urb.poll.")
var url, $this
seqn = this.poll.seqn
if(params.seqn) seqn = params.seqn()
url = "/~/of/"+this.ixor+"?poll="+seqn
this.puls = true
$this = this
this.req("get",url,params,true,function(err,res) {
$this.poll.dely = params.dely || $this.poll.dely
if(res){
if(res.data.beat) {
$this.poll.dely = params.dely || 250
return $this.poll(params)
}
switch(res.data.type){
case "news":
return document.location.reload() // XX check autoreload
case "rush":
case "mean":
var err2 = err
if(res.data.type == "mean")
err2 = res.data.data
var fn = $this.gsig(res.data.from)
if($this.cabs[fn])
$this.cabs[fn].call(this,err2,
{status: res.status, data: res.data.data.json}) // XX non-json
break;
default:
throw new Error("Lost event %"+res.data.type)
}
if(params.incs)
params.incs()
else
$this.poll.seqn++
$this.poll.dely = 250
return $this.poll(params)
}
else if(err){
setTimeout(function() {
$this.poll(params)
}, $this.poll.dely)
$this.poll.dely += Math.ceil($this.poll.dely*.2)
}
else throw "Neither error nor result on poll"
})
}
window.urb.poll.seqn = 1
window.urb.poll.dely = 250
window.urb.bind = function(path, params, cb, nicecb){ // or bind(path, cb)
if(!params || typeof params === "function")
{cb = params; params = {}}
params.path = path
if(params.path[0] !== "/") params.path = "/"+params.path
params.ship = params.ship || this.ship
params.appl = params.appl || this.appl
params.mark = params.mark || this.bind.mark
params.wire = params.wire || params.path
if(typeof path != "string")
throw new Error("You must specify a string path for urb.bind.")
if(!params.appl) throw new Error("You must specify an appl for urb.bind.")
if(!cb) throw new Error("You must supply a callback to urb.bind.")
var method, perm, url, $this
if(params.mark !== "json")
throw new Error("Non-json subscriptions unimplemented.") // XX
url = "/~/is/"+this.gsig(params)+"."+params.mark
params.path = params.wire
this.cabs[this.gsig(params)] = cb
$this = this
this.qreq("put",url,params,true,function(err,res) {
if(nicecb) { nicecb.apply(this,[err,{status: res.status, data: res.data}])}
// XX give raw data
//
if(!err && !$this.puls) $this.poll(params)
})
}
urb.bind.mark = "json"
window.urb.drop = function(path, params, cb){ // or drop(path,cb)
if(typeof params === "function")
{cb = params; params = {}}
params.path = path
if(params.path[0] !== "/") params.path = "/"+params.path
params.ship = params.ship || this.ship
params.appl = params.appl || this.appl
params.wire = params.wire || params.path
if(typeof path != "string")
throw new Error("You must specify a string path for urb.drop.")
if(!params.appl) throw new Error("You must specify an appl for urb.drop.")
url = "/~/is/"+this.gsig(params)+".json"
method = "delete"
this.req("delete",url,params,true,function(err,res) {
if(cb) cb(err,res)
})
}
window.urb.subscribe = function(params,cb) { // legacy interface
if(!params) throw new Error("You must supply params to urb.subscribe")
return window.urb.bind(params.path, params, cb, cb)
}
window.urb.unsubscribe = function(params,cb) { // legacy intreface
if(!params) throw new Error("You must supply params to urb.unsubscribe.")
return window.urb.drop(params.path, params, cb)
}
window.urb.util = {
isURL: function(s) {
r = new RegExp('^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 'i');
return s.length < 2083 && r.test(s);
},
numDot: function(n) {
_n = String(n)
fun = function(s){
if(s.length <= 3)
return s
return fun(s.slice(0,-3))+"."+s.slice(-3)
}
return fun((_n))
},
toDate: function (dat){
var mils = Math.floor((0x10000 * dat.getUTCMilliseconds()) / 1000).toString(16)
function pad(num, str){
return ((new Array(num + 1)).join('0') + str).substr(-num,num)
}
return '~' + dat.getUTCFullYear() +
'.' + (dat.getUTCMonth() + 1) +
'.' + dat.getUTCDate() +
'..' + pad(2, dat.getUTCHours()) +
'.' + pad(2, dat.getUTCMinutes()) +
'.' + pad(2, dat.getUTCSeconds()) +
'..' + pad(4, mils)
},
basepath: function(spur, pathname){
spur = spur || ''
if(spur === '/') spur = ''
pathname = pathname || window.location.pathname
base = ""
if(pathname.indexOf("/~~") == 0)
base = "/~~"
if(pathname.indexOf("/~/as/") == 0)
base = "/~/as/"+pathname.split("/")[3]
if(pathname.indexOf("/~/away") == 0)
base = "/~/away"
return base+spur
}
}