Add envelope-system server sync

Using gun's new envelope system (where routing and de-duplication happen
inside gun core), server-to-server synchronization has been
implemented.
However, it comes with this warning: the chain isn't yet ready and
you'll have difficulty reading or writing data via the chain.
This commit is contained in:
Jesse Gibson 2016-11-25 14:38:03 -07:00
parent 9d9dea2553
commit 4848493530
4 changed files with 188 additions and 465 deletions

39
gun.js
View File

@ -1,4 +1,5 @@
/* eslint-disable */
/* eslint-enable no-console */
//console.log("!!!!!!!!!!!!!!!! WARNING THIS IS GUN 0.5 !!!!!!!!!!!!!!!!!!!!!!");
;(function(){
@ -929,21 +930,26 @@
gun.on('in', input, at);
gun.on('out', output, at);
}
function output(at){
var cat = this, gun = cat.gun, tmp;
console.log("OUT!", Gun.obj.to(at, {gun: null}));
if(at['#']){
dedup.track(at['#']);
function output(at){
var cat = this, gun = cat.gun, tmp;
if(at['#']){
dedup.track(at['#']);
}
if(at.put){
if(at.put){
cat.on('in', obj_to(at, {gun: cat.gun}));
}
if(!at.gun){
at = Gun.obj.to(at, {gun: gun});
}
if(at.put){ Gun.on('put', at) }
if(at.get){ get(at, cat) }
Gun.on('out', at); return;
if(at.put){ Gun.on('put', at) }
if(at.get){ get(at, cat) }
// Reads and writes both trigger output.
if (at.put !== undefined || at.get !== undefined) {
Gun.on('out', at);
}
// Gun.on('out', at);
return;
if(!cat.back){ return }
cat.back.on('out', at);
}
@ -963,8 +969,7 @@
Gun.on('get', at);
}
function input(at){ var cat = this;
console.log("IN", at);
if(at['@']){
if(at['@']){
if(!at['#']){
at['#'] = Gun.text.random();
dedup.track(at['#']);
@ -979,15 +984,15 @@
return;
}
*/
if(at.put){
if(!at.gun){ at.gun = cat.gun }
if(at.put){
if(cat.graph){
Gun.obj.map(at.put, ham, {at: at, cat: this}); // all unions must happen first, sadly.
}
Gun.obj.map(at.put, map, {at: at, cat: this});
Gun.on('put', at);
}
if(!at.gun){ at.gun = cat.gun }
if(at.get){ Gun.on('get', at) }
if(at.get){ Gun.on('get', at) }
}
function ham(data, key){
var cat = this.cat, graph = cat.graph;
@ -2182,8 +2187,7 @@
}
if(!ws.readyState){ return setTimeout(function(){ r.ws(opt, cb, req) },100), true }
ws.sending = true;
console.log("websocket out", req);
ws.send(JSON.stringify(req));
ws.send(JSON.stringify(req));
return true;
}
if(ws === false){ return }
@ -2382,8 +2386,7 @@
Tab.peers.request.createServer(function(req, res){
if(!req || !res || !req.body || !req.headers){ return }
var msg = req.body;
console.log("SERVER", req);
gun.on('in', req.body);
gun.on('in', req.body);
return;
// AUTH for non-replies.
if(server.msg(msg['#'])){ return }

View File

@ -8,27 +8,11 @@
var Gun = require('../../gun');
var Socket = require('./Peer');
var Pool = require('./Pool');
var duplicate = require('./duplicate');
// Maps URLs to sockets.
// Shared between all gun instances.
var sockets = Pool();
var emitter = { on: Gun.on };
var server = {
// Session id.
sid: Gun.text.random(),
// Request handlers.
handlers: [],
// Call handlers.
handle: function (req, res) {
server.handlers.forEach(function (server) {
server(req, res);
});
},
};
var sid = Gun.text.random();
/**
* Take a map of URLs pointing to options and ensure the
@ -61,108 +45,20 @@ function getSocketSubset (peers) {
});
}
// Handle read requests.
Gun.on('get', function (at) {
var gun = at.gun;
var opt = at.opt || {};
Gun.on('out', function (ctx) {
var gun = ctx.gun;
var opt = ctx.opt || {};
var peers = opt.peers || gun.Back('opt.peers');
if (!peers || Gun.obj.empty(peers)) {
at.gun.Back(Infinity).on('in', {
'@': at['#'],
});
if (!peers) {
return;
}
var id = at['#'] || Gun.text.random(9);
// Create a new message.
var msg = {
// msg ID
'#': id,
// msg BODY
'$': at.get,
};
// Listen for a response.
// TODO: ONE? PERF! Clear out listeners, maybe with setTimeout?
emitter.on(id, function (err, data) {
var obj = {
'@': at['#'],
err: err,
put: data,
};
if (data) {
at.gun.Back(-1).on('out', obj);
} else {
at.gun.Back(-1).on('in', obj);
}
});
var subset = getSocketSubset(peers);
// Broadcast to the connected peers.
subset.send({
headers: { 'gun-sid': server.sid },
body: msg,
});
});
// Handle write requests.
Gun.on('put', function (at) {
if (at['@']) {
return;
}
var peers = at.gun.Back('opt.peers');
var enabled = at.gun.Back('opt.websocket');
var options = at.opt || {};
if (!peers || Gun.obj.empty(peers)) {
// TODO: What about wsp/server clients? Maybe we shouldn't
// immediately assume there's no data to be found.
at.gun.Back(-1).on('in', {
'@': at['#'],
});
return;
}
if (options.websocket === false || enabled === false) {
return;
}
var id = at['#'] || Gun.text.random(9);
var msg = {
// Message ID.
'#': id,
// Message body.
'$': at.put,
};
// TODO: ONE? PERF! Clear out listeners, maybe with setTimeout?
// Listen for acknowledgement(s).
Gun.on(id, function (err, ok) {
at.gun.Back(-1).on('in', {
'@': at['#'],
err: err,
ok: ok,
});
});
var subset = getSocketSubset(peers);
subset.send({
headers: { 'gun-sid': server.sid },
body: msg,
headers: { 'gun-sid': sid },
body: ctx,
});
});
@ -170,6 +66,7 @@ Gun.on('put', function (at) {
// adding them to the global pool.
Gun.on('opt', function (context) {
var gun = context.gun;
var root = gun.Back(Infinity);
var peers = gun.Back('opt.peers') || {};
@ -181,22 +78,6 @@ Gun.on('opt', function (context) {
var socket = Socket(url, options);
sockets.add(url, socket);
/**
* Handle responses to requests, adding default headers.
* @param {Object} reply - A gun reply object.
* @return {undefined}
*/
function respond (reply) {
// Make sure headers are defined.
var headers = reply.headers = reply.headers || {};
// Add 'gun-sid' if it doesn't exist.
headers['gun-sid'] = headers['gun-sid'] || server.sid;
socket.send(reply);
}
socket.on('message', function (msg) {
var request;
@ -211,132 +92,7 @@ Gun.on('opt', function (context) {
return;
}
request.headers = request.headers || {};
// emit extra events.
server.handle(request, respond);
root.on('in', request.body);
});
});
});
Gun.on('opt', function (at) {
var gun = at.gun;
var root = gun.Back(Infinity);
var options = (root._.opt = root._.opt || {});
// Only register once per gun instance.
if (options['@client']) {
return;
}
var driver = options['@client'] = {
/**
* Handles get requests sent from other peers.
* @param {Object} req - The get request.
* @param {Function} cb - Handles replies.
* @return {undefined}
*/
get: function (req, cb) {
var body = req.body;
var lex = body.$;
var graph = gun._.root._.graph;
var node = graph[lex['#']];
// TODO: Reply even if it's not in memory.
if (!node) {
return;
}
cb({
body: {
'#': duplicate.track.newID(),
'@': body['#'],
'$': node,
},
});
},
/**
* Handles put requests sent from other peers.
* @param {Object} req - The put request.
* @param {Function} cb - A response callback.
* @return {undefined}
*/
put: function (req, cb) {
var body = req.body;
var graph = body.$;
// Cached gun paths.
var path = gun._.root._.path || {};
graph = Gun.obj.map(graph, function (node, soul, map) {
if (!path[soul]) {
return;
}
map(soul, node);
});
// filter out what we don't have in memory.
if (!graph) {
return;
}
var id = Gun.on.ask(function (ack, event) {
if (!ack) {
return;
}
event.off();
cb({
body: {
'#': duplicate.track.newID(),
'@': body['#'],
'$': ack,
'!': ack.err,
},
});
});
gun.on('out', {
'#': duplicate.track(id),
gun: gun,
opt: { websocket: false },
put: graph,
});
},
};
server.handlers.push(function (req, res) {
// Validate request.
if (!req || !res || !req.body || !req.headers) {
return;
}
var msg = req.body;
if (duplicate(msg['#'])) {
return;
}
// It's a response, no need to reply.
if (msg['@']) {
var reqID = msg['@'];
emitter.on(reqID, [
msg['!'],
msg.$,
]);
return;
}
var isLex = msg.$ && msg.$['#'];
var method = isLex ? 'get' : 'put';
driver[method](req, res);
});
});

View File

@ -38,68 +38,20 @@ function ready (socket, cb) {
}
/**
* Send a request to a list of clients.
* @param {Obejct} context - A gun request context.
* Send a message to a group of clients.
* @param {Obejct} msg - An http envelope-like message.
* @param {Object} clients - IDs mapped to socket instances.
* @param {Function} cb - Called for each response.
* @return {undefined}
*/
function request (context, clients, cb) {
var id = context['#'] || Gun.text.random(9);
Gun.on(id, function (err, data, event) {
cb(err, data);
event.off();
});
function send (msg, clients) {
Gun.obj.map(clients, function (client) {
ready(client, function () {
var msg = {
headers: {},
body: {
'#': id,
'$': context.get,
},
};
var serialized = JSON.stringify(msg);
client.send(serialized);
});
});
}
/**
* Pushes a graph update to a collection of clients.
* @param {Object} context - The context object passed by gun.
* @param {Object} clients - An object mapping URLs to clients.
* @param {Function} cb - Invoked on each client response.
* @return {undefined}
*/
function update (context, clients, cb) {
var id = context['#'] || Gun.text.random(9);
Gun.on(id, function (err, data, event) {
cb(err, data);
event.off();
});
Gun.obj.map(clients, function (client) {
ready(client, function () {
var msg = {
headers: {},
body: {
'#': id,
'$': context.put,
},
};
var serialized = JSON.stringify(msg);
client.send(serialized);
});
});
}
/** * Attaches server push middleware to gun.
* @param {Gun} gun - The gun instance to attach to.
* @param {WebSocket.Server} server - A websocket server instance.
@ -110,6 +62,7 @@ function attach (gun, server) {
root._.servers = root._.servers || [];
root._.servers.push(server);
var pool = {};
var sid = Gun.text.random();
server.on('connection', function (socket) {
socket.id = socket.id || Gun.text.random(10);
@ -122,12 +75,7 @@ function attach (gun, server) {
return;
}
var msg = data.body;
if (msg['@']) {
Gun.on.ack(msg['@'], [msg['!'], msg.$]);
return;
}
root.on('in', data.body);
});
socket.once('close', function () {
@ -135,40 +83,17 @@ function attach (gun, server) {
});
});
Gun.on('get', function (context) {
if (!isUsingServer(context.gun, server)) {
Gun.on('out', function (context) {
if (!isUsingServer(context.gun, server) || Gun.obj.empty(pool)) {
return;
}
request(context, pool, function (err, data) {
var response = {
'@': context['#'],
put: data,
err: err,
};
var msg = {
headers: { 'gun-sid': sid },
body: context,
};
var root = context.gun.Back(Infinity);
root.on(data ? 'out' : 'in', response);
});
});
Gun.on('put', function (context) {
if (!isUsingServer(context.gun, server)) {
return;
}
if (context.nopush) {
return;
}
update(context, pool, function (err, data) {
var ack = {
'!': err || null,
'$': data.$,
};
Gun.on.ack(context, ack);
});
send(msg, pool);
});
}

View File

@ -1,22 +1,24 @@
var Gun = require('../../gun')
, formidable = require('formidable')
, http = require('../http')
, url = require('url')
, wsp = {}
, WS = require('ws')
, WSS = WS.Server
, attach = require('./server-push');
/* eslint-disable require-jsdoc, no-underscore-dangle */
'use strict';
var Gun = require('../../gun');
var http = require('../http');
var url = require('url');
var WS = require('ws');
var WSS = WS.Server;
var attach = require('./server-push');
// Handles server to server sync.
require('./client.js');
Gun.on('opt', function(at){
Gun.on('opt', function (at) {
var gun = at.gun, opt = at.opt;
gun.__ = at.root._;
gun.__.opt.ws = opt.ws = gun.__.opt.ws || opt.ws || {};
function start(server, port, app){
if(app && app.use){ app.use(gun.wsp.server) }
function start (server, port, app) {
if (app && app.use) {
app.use(gun.wsp.server);
}
server = gun.__.opt.ws.server = gun.__.opt.ws.server || opt.ws.server || server;
if (!gun.wsp.ws) {
@ -25,141 +27,155 @@ Gun.on('opt', function(at){
}
gun.wsp.ws = gun.wsp.ws || new WSS(gun.__.opt.ws);
require('./ws')(gun.wsp.ws, function(req, res){
require('./ws')(gun.wsp.ws, function (req, res) {
var ws = this;
req.headers['gun-sid'] = ws.sid = ws.sid? ws.sid : req.headers['gun-sid'];
ws.sub = ws.sub || gun.wsp.on('network', function(msg, ev){
if(!ws || !ws.send || !ws._socket || !ws._socket.writable){ return ev.off() }
if(!msg || (msg.headers && msg.headers['gun-sid'] === ws.sid)){ return }
if(msg && msg.headers){ delete msg.headers['ws-rid'] }
req.headers['gun-sid'] = ws.sid = ws.sid ? ws.sid : req.headers['gun-sid'];
ws.sub = ws.sub || gun.wsp.on('network', function (msg, ev) {
if (!ws || !ws.send || !ws._socket || !ws._socket.writable) { return ev.off(); }
if (!msg || (msg.headers && msg.headers['gun-sid'] === ws.sid)) { return; }
if (msg && msg.headers) { delete msg.headers['ws-rid']; }
// TODO: BUG? ^ What if other peers want to ack? Do they use the ws-rid or a gun declared id?
try{ws.send(Gun.text.ify(msg));
}catch(e){} // juuuust in case.
try { ws.send(Gun.text.ify(msg));
} catch (e) {} // juuuust in case.
});
gun.wsp.wire(req, res);
}, {headers: {'ws-rid': 1, 'gun-sid': 1}});
gun.__.opt.ws.port = gun.__.opt.ws.port || opt.ws.port || port || 80;
}
var wsp = gun.wsp = gun.wsp || function(server){
if(!server){ return gun }
if(Gun.fns.is(server.address)){
if(server.address()){
var wsp = gun.wsp = gun.wsp || function (server) {
if (!server) { return gun; }
if (Gun.fns.is(server.address)) {
if (server.address()) {
start(server, server.address().port);
return gun;
}
}
if(Gun.fns.is(server.get) && server.get('port')){
if (Gun.fns.is(server.get) && server.get('port')) {
start(server, server.get('port'));
return gun;
}
var listen = server.listen;
server.listen = function(port){
server.listen = function (port) {
var serve = listen.apply(server, arguments);
start(serve, port, server);
return serve;
}
};
return gun;
}
};
gun.wsp.on = gun.wsp.on || Gun.on;
gun.wsp.regex = gun.wsp.regex || opt.route || opt.path || /^\/gun/i;
gun.wsp.poll = gun.wsp.poll || opt.poll || 1;
gun.wsp.pull = gun.wsp.pull || opt.pull || gun.wsp.poll * 1000;
gun.wsp.server = gun.wsp.server || function(req, res, next){ // http
next = next || function(){};
if(!req || !res){ return next(), false }
if(!req.url){ return next(), false }
if(!req.method){ return next(), false }
gun.wsp.server = gun.wsp.server || function (req, res, next) { // http
next = next || function () {};
if (!req || !res) { return next(), false; }
if (!req.url) { return next(), false; }
if (!req.method) { return next(), false; }
var msg = {};
msg.url = url.parse(req.url, true);
if(!gun.wsp.regex.test(msg.url.pathname)){ return next(), false } // TODO: BUG! If the option isn't a regex then this will fail!
if(msg.url.pathname.replace(gun.wsp.regex,'').slice(0,3).toLowerCase() === '.js'){
if (!gun.wsp.regex.test(msg.url.pathname)) { return next(), false; } // TODO: BUG! If the option isn't a regex then this will fail!
if (msg.url.pathname.replace(gun.wsp.regex, '').slice(0, 3).toLowerCase() === '.js') {
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.end(gun.wsp.js = gun.wsp.js || require('fs').readFileSync(__dirname + '/../../gun.js')); // gun server is caching the gun library for the client
return true;
}
if(!req.upgrade){
if (!req.upgrade) {
next();
return false;
}
return http(req, res, function(req, res){
if(!req){ return next() }
return http(req, res, function (req, res) {
if (!req) { return next(); }
var stream, cb = res = require('../jsonp')(req, res);
if(req.headers && (stream = req.headers['gun-sid'])){
if (req.headers && (stream = req.headers['gun-sid'])) {
stream = (gun.wsp.peers = gun.wsp.peers || {})[stream] = gun.wsp.peers[stream] || {sid: stream};
stream.drain = stream.drain || function(res){
if(!res || !stream || !stream.queue || !stream.queue.length){ return }
stream.drain = stream.drain || function (res) {
if (!res || !stream || !stream.queue || !stream.queue.length) { return; }
res({headers: {'gun-sid': stream.sid}, body: stream.queue });
stream.off = setTimeout(function(){ stream = null }, gun.wsp.pull);
stream.off = setTimeout(function () { stream = null; }, gun.wsp.pull);
stream.reply = stream.queue = null;
return true;
}
stream.sub = stream.sub || gun.wsp.on('network', function(req, ev){
if(!stream){ return ev.off() } // self cleans up after itself!
if(!req || (req.headers && req.headers['gun-sid'] === stream.sid)){ return }
};
stream.sub = stream.sub || gun.wsp.on('network', function (req, ev) {
if (!stream) { return ev.off(); } // self cleans up after itself!
if (!req || (req.headers && req.headers['gun-sid'] === stream.sid)) { return; }
(stream.queue = stream.queue || []).push(req);
stream.drain(stream.reply);
});
cb = function(r){ (r.headers||{}).poll = gun.wsp.poll; res(r) }
cb = function (r) { (r.headers || {}).poll = gun.wsp.poll; res(r); };
clearTimeout(stream.off);
if(req.headers.pull){
if(stream.drain(cb)){ return }
if (req.headers.pull) {
if (stream.drain(cb)) { return; }
return stream.reply = cb;
}
}
gun.wsp.wire(req, cb);
}), true;
}
if((gun.__.opt.maxSockets = opt.maxSockets || gun.__.opt.maxSockets) !== false){
};
if ((gun.__.opt.maxSockets = opt.maxSockets || gun.__.opt.maxSockets) !== false) {
require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = gun.__.opt.maxSockets || Infinity;
}
gun.wsp.msg = gun.wsp.msg || function(id){
if(!id){
gun.wsp.msg = gun.wsp.msg || function (id) {
if (!id) {
return gun.wsp.msg.debounce[id = Gun.text.random(9)] = Gun.time.is(), id;
}
clearTimeout(gun.wsp.msg.clear);
gun.wsp.msg.clear = setTimeout(function(){
gun.wsp.msg.clear = setTimeout(function () {
var now = Gun.time.is();
Gun.obj.map(gun.wsp.msg.debounce, function(t,id){
if((now - t) < (1000 * 60 * 5)){ return }
Gun.obj.map(gun.wsp.msg.debounce, function (t, id) {
if ((now - t) < (1000 * 60 * 5)) { return; }
Gun.obj.del(gun.wsp.msg.debounce, id);
});
},500);
if(id = gun.wsp.msg.debounce[id]){
}, 500);
if (id = gun.wsp.msg.debounce[id]) {
return gun.wsp.msg.debounce[id] = Gun.time.is(), id;
}
gun.wsp.msg.debounce[id] = Gun.time.is();
return;
};
gun.wsp.msg.debounce = gun.wsp.msg.debounce || {};
gun.wsp.wire = gun.wsp.wire || (function(){
// all streams, technically PATCH but implemented as PUT or POST, are forwarded to other trusted peers
// except for the ones that are listed in the message as having already been sending to.
// all states, implemented with GET, are replied to the source that asked for it.
function tran(req, res){
if(!req || !res || !req.body || !req.headers){ return }
if(req.url){ req.url = url.format(req.url) }
var msg = req.body;
// AUTH for non-replies.
if(gun.wsp.msg(msg['#'])){ return }
gun.wsp.on('network', Gun.obj.copy(req));
if(msg['@']){ return } // no need to process.
if(msg['$'] && msg['$']['#']){ return tran.get(req, res) }
//if(Gun.is.lex(msg['$'])){ return tran.get(req, res) }
else { return tran.put(req, res) }
cb({body: {hello: 'world'}});
// TODO: BUG! server put should push.
gun.wsp.wire = gun.wsp.wire || (function () {
// all streams, technically PATCH but implemented as
// PUT or POST, are forwarded to other trusted peers
// except for the ones that are listed in the message
// as having already been sent to.
// all states, implemented with GET, are replied to the
// source that asked for it.
function tran (req, res) {
if (!req || !res || !req.body || !req.headers) {
return;
}
if (req.url) {
req.url = url.format(req.url);
}
// var msg = req.body;
// console.log('SERVER', req);
gun.on('in', req.body);
// console.log('-----------------');
// // AUTH for non-replies.
// if(gun.wsp.msg(msg['#'])){ return }
// gun.wsp.on('network', Gun.obj.copy(req));
// if(msg['@']){ return } // no need to process.
// if(msg['$'] && msg['$']['#']){ return tran.get(req, res) }
// //if(Gun.is.lex(msg['$'])){ return tran.get(req, res) }
// else { return tran.put(req, res) }
// cb({body: {hello: 'world'}});
// // TODO: BUG! server put should push.
}
tran.get = function(req, cb){
var body = req.body, lex = body['$'], reply = {headers: {'Content-Type': tran.json}};
tran.get = function (req, cb) {
var body = req.body;
var lex = body.$;
var reply = {
headers: { 'Content-Type': tran.json },
};
var graph = gun.Back(Infinity)._.graph;
var node = graph[lex['#']];
var result = Gun.graph.ify(node);
if (node) {
return cb({
cb({
headers: reply.headers,
body: {
'#': gun.wsp.msg(),
@ -167,41 +183,64 @@ Gun.on('opt', function(at){
'$': result,
},
});
return;
}
gun.on('out', {gun: gun, get: lex, req: 1, '#': body['#'] || Gun.on.ask(function(at, ev){
ev.off();
var graph = at.put;
return cb({headers: reply.headers, body: {
'#': gun.wsp.msg(),
'@': body['#'],
'$': graph,
'!': at.err
}});
})});
}
tran.put = function(req, cb){
// NOTE: It is highly recommended you do your own PUT/POSTs through your own API that then saves to gun manually.
// This will give you much more fine-grain control over security, transactions, and what not.
var body = req.body, graph = body['$'], reply = {headers: {'Content-Type': tran.json}}, opt;
gun.on('out', {gun: gun, put: graph, '#': Gun.on.ask(function(ack, ev){
//Gun.on('put', {gun: gun, put: graph, '#': Gun.on.ask(function(ack, ev){
ev.off();
return cb({headers: reply.headers, body: {
'#': gun.wsp.msg(),
'@': body['#'],
'$': ack,
'!': ack.err
}});
})});
}
gun.wsp.on('network', function(rq){
// TODO: MARK! You should move the networking events to here, not in WSS only.
});
gun.on('out', {
gun: gun,
get: lex,
req: 1,
'#': body['#'] || Gun.on.ask(function (at, ev) {
ev.off();
var graph = at.put;
return cb({
headers: reply.headers,
body: {
'#': gun.wsp.msg(),
'@': body['#'],
'$': graph,
'!': at.err,
},
});
}),
});
};
tran.put = function (req, cb) {
// NOTE: It is highly recommended you do your own PUT/POSTs
// through your own API that then saves to gun manually.
// This will give you much more fine-grain control over
// security, transactions, and what not.
var body = req.body;
var graph = body.$;
var reply = {
headers: { 'Content-Type': tran.json },
};
gun.on('out', {
gun: gun,
put: graph,
'#': Gun.on.ask(function (ack, ev) {
ev.off();
return cb({
headers: reply.headers,
body: {
'#': gun.wsp.msg(),
'@': body['#'],
'$': ack,
'!': ack.err,
},
});
}),
});
};
tran.json = 'application/json';
return tran;
}());
if(opt.server){
if (opt.server) {
wsp(opt.server);
}
});