mirror of
https://github.com/amark/gun.git
synced 2025-06-06 22:26:48 +00:00
default require Gun
This commit is contained in:
parent
7a8fceba07
commit
f7b422c690
@ -1,12 +1,9 @@
|
|||||||
if(typeof window !== "undefined"){
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
var Gun = window.Gun;
|
|
||||||
} else {
|
|
||||||
var Gun = require('../gun');
|
|
||||||
}
|
|
||||||
|
|
||||||
Gun.on('opt', function(ctx){
|
Gun.on('opt', function(ctx){
|
||||||
this.to.next(ctx);
|
this.to.next(ctx);
|
||||||
if(ctx.once){ return }
|
if(ctx.once){ return }
|
||||||
|
console.log("WARNING: `lib/bye` is out of date!");
|
||||||
ctx.on('in', function(msg){
|
ctx.on('in', function(msg){
|
||||||
if(!msg.peer || !msg.BYE){ return this.to.next(msg) }
|
if(!msg.peer || !msg.BYE){ return this.to.next(msg) }
|
||||||
var peer = msg.peer();
|
var peer = msg.peer();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
var Gun = Gun || require('../gun');
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
|
|
||||||
Gun.on('opt', function(ctx){
|
Gun.on('opt', function(ctx){
|
||||||
this.to.next(ctx);
|
this.to.next(ctx);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
;(function(){
|
;(function(){
|
||||||
var Gun = (typeof window !== 'undefined')? window.Gun : require('../gun');
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
|
|
||||||
var ev = {}, empty = {}, u;
|
var ev = {}, empty = {}, u;
|
||||||
Gun.on('opt', function(root){
|
Gun.on('opt', function(root){
|
||||||
this.to.next(root);
|
this.to.next(root);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
;(function(){
|
;(function(){
|
||||||
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
var Gun = (this||{}).Gun || require('../gun');
|
|
||||||
|
|
||||||
Gun.on('opt', function(ctx){
|
Gun.on('opt', function(ctx){
|
||||||
once(ctx);
|
once(ctx);
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
// This does all old-fashion require stuff before '@std/mjs' steps in...
|
|
||||||
const Gun = require('../gun')
|
|
||||||
require('../nts')
|
|
||||||
require('./s3')
|
|
||||||
try {
|
|
||||||
require('./ws')
|
|
||||||
} catch(e) {
|
|
||||||
require('./wsp/server')
|
|
||||||
}
|
|
||||||
require('./verify')
|
|
||||||
require('./file')
|
|
||||||
require('./bye')
|
|
||||||
|
|
||||||
module.exports = Gun
|
|
@ -1,8 +1,4 @@
|
|||||||
if(typeof window !== "undefined"){
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
var Gun = window.Gun;
|
|
||||||
} else {
|
|
||||||
var Gun = require('../gun');
|
|
||||||
}
|
|
||||||
Gun.chain.open || require('./open');
|
Gun.chain.open || require('./open');
|
||||||
|
|
||||||
Gun.chain.load = function(cb, opt, at){
|
Gun.chain.load = function(cb, opt, at){
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
if(typeof window !== "undefined"){
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
var Gun = window.Gun;
|
|
||||||
} else {
|
|
||||||
var Gun = require('../gun');
|
|
||||||
}
|
|
||||||
|
|
||||||
Gun.chain.open = function(cb, opt, at){
|
Gun.chain.open = function(cb, opt, at){
|
||||||
opt = opt || {};
|
opt = opt || {};
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
if(typeof window !== "undefined"){
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
var Gun = window.Gun;
|
|
||||||
} else {
|
|
||||||
var Gun = require('../gun');
|
|
||||||
}
|
|
||||||
|
|
||||||
Gun.chain.path = function(field, opt){
|
Gun.chain.path = function(field, opt){
|
||||||
var back = this, gun = back, tmp;
|
var back = this, gun = back, tmp;
|
||||||
|
@ -11,11 +11,8 @@
|
|||||||
require('../nts');
|
require('../nts');
|
||||||
require('./store');
|
require('./store');
|
||||||
require('./rs3');
|
require('./rs3');
|
||||||
//try{require('./ws');}catch(e){require('./wsp/server');}
|
|
||||||
require('./wire');
|
require('./wire');
|
||||||
require('./verify');
|
|
||||||
require('./file');
|
require('./file');
|
||||||
require('./bye');
|
|
||||||
require('./evict');
|
require('./evict');
|
||||||
if('debug' === process.env.GUN_ENV){ require('./debug') }
|
if('debug' === process.env.GUN_ENV){ require('./debug') }
|
||||||
module.exports = Gun;
|
module.exports = Gun;
|
||||||
|
35
lib/set.js
35
lib/set.js
@ -1,35 +0,0 @@
|
|||||||
var Gun = Gun || require('../gun');
|
|
||||||
|
|
||||||
/*
|
|
||||||
Gun.chain.set = function(obj, cb, opt){
|
|
||||||
var set = this;
|
|
||||||
opt = opt || {};
|
|
||||||
cb = cb || function(){};
|
|
||||||
set = set.put({}); // insert assumes a graph node. So either create it or merge with the existing one.
|
|
||||||
var error, item = set.chain().put(obj, function(err){ // create the new item in its own context.
|
|
||||||
error = err; // if this happens, it should get called before the .val
|
|
||||||
}).val(function(val){
|
|
||||||
if(error){ return cb.call(set, error) } // which in case it is, allows us to fail fast.
|
|
||||||
var add = {}, soul = Gun.is.soul.on(val);
|
|
||||||
if(!soul){ return cb.call(set, {err: Gun.log("No soul!")}) }
|
|
||||||
add[soul] = val; // other wise, let's then
|
|
||||||
set.put(add, cb); // merge with the graph node.
|
|
||||||
});
|
|
||||||
return item;
|
|
||||||
};*/
|
|
||||||
|
|
||||||
Gun.chain.set = function(val, cb, opt){
|
|
||||||
var gun = this, ctx = {}, drift = Gun.time.now();
|
|
||||||
cb = cb || function(){};
|
|
||||||
opt = opt || {};
|
|
||||||
|
|
||||||
if(!gun._.back){ gun = gun.put({}) }
|
|
||||||
gun = gun.not(function(next, key){
|
|
||||||
return key? this.put({}).key(key) : this.put({});
|
|
||||||
});
|
|
||||||
if(!val && !Gun.is.value(val)){ return gun }
|
|
||||||
|
|
||||||
var obj = {};
|
|
||||||
obj['I' + drift + 'R' + Gun.text.random(5)] = val;
|
|
||||||
return gun.put(obj, cb);
|
|
||||||
}
|
|
@ -1,8 +1,4 @@
|
|||||||
if(typeof window !== "undefined"){
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
var Gun = window.Gun;
|
|
||||||
} else {
|
|
||||||
var Gun = require('../gun');
|
|
||||||
}
|
|
||||||
|
|
||||||
Gun.on('opt', function(ctx){
|
Gun.on('opt', function(ctx){
|
||||||
this.to.next(ctx);
|
this.to.next(ctx);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
var Gun = Gun || require('../gun');
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
|
|
||||||
Gun.chain.promise = function(cb) {
|
Gun.chain.promise = function(cb) {
|
||||||
var gun = this, cb = cb || function(ctx) { return ctx };
|
var gun = this, cb = cb || function(ctx) { return ctx };
|
||||||
return (new Promise(function(res, rej) {
|
return (new Promise(function(res, rej) {
|
||||||
gun.val(function(data, key){
|
gun.once(function(data, key){
|
||||||
res({put: data, get: key, gun: this});
|
res({put: data, get: key, gun: this});
|
||||||
});
|
});
|
||||||
})).then(cb);
|
})).then(cb);
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
if(typeof window !== "undefined"){
|
|
||||||
var Gun = window.Gun;
|
|
||||||
} else {
|
|
||||||
var Gun = require('../gun');
|
|
||||||
}
|
|
||||||
|
|
||||||
;(function(){
|
;(function(){
|
||||||
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
var ify = Gun.node.ify, u;
|
var ify = Gun.node.ify, u;
|
||||||
Gun.chain.time = function(data, a, b){
|
Gun.chain.time = function(data, a, b){
|
||||||
if(data instanceof Function){
|
if(data instanceof Function){
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
if(typeof window !== "undefined"){
|
var Gun = (typeof window !== "undefined")? window.Gun : require('../gun');
|
||||||
var Gun = window.Gun;
|
|
||||||
} else {
|
|
||||||
var Gun = require('gun/gun');
|
|
||||||
}
|
|
||||||
|
|
||||||
const rel_ = Gun.val.link._; // '#'
|
const rel_ = Gun.val.link._; // '#'
|
||||||
const node_ = Gun.node._; // '_'
|
const node_ = Gun.node._; // '_'
|
||||||
|
190
lib/wsp/Peer.js
190
lib/wsp/Peer.js
@ -1,190 +0,0 @@
|
|||||||
/* eslint-disable no-underscore-dangle */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var WebSocket = require('ws');
|
|
||||||
var Emitter = require('events');
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates backoff instances.
|
|
||||||
* @param {Object} [options] - Override the default settings.
|
|
||||||
* @param {Object} options.time=50 - Initial backoff time.
|
|
||||||
* @param {Object} options.factor=2 - How much to multiply the time by.
|
|
||||||
* @param {Object} options.max=1min - Maximum backoff time.
|
|
||||||
* @class
|
|
||||||
*/
|
|
||||||
function Backoff (options) {
|
|
||||||
this.options = options || {};
|
|
||||||
|
|
||||||
// Sets the initial backoff settings.
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increments the time by the factor.
|
|
||||||
* @return {Number} - The next backoff time.
|
|
||||||
*/
|
|
||||||
Backoff.prototype.next = function () {
|
|
||||||
var next = this.time * this.factor;
|
|
||||||
|
|
||||||
if (next > this.max) {
|
|
||||||
this.time = this.max;
|
|
||||||
return this.max;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.time = next;
|
|
||||||
|
|
||||||
return this.time;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the backoff state to it's original condition.
|
|
||||||
* @return {Backoff} - The context.
|
|
||||||
*/
|
|
||||||
Backoff.prototype.reset = function () {
|
|
||||||
var options = this.options;
|
|
||||||
|
|
||||||
this.time = options.time || 50;
|
|
||||||
this.factor = options.factor || 2;
|
|
||||||
this.max = options.max || 1 * 60 * 1000;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules the next connection, according to the backoff.
|
|
||||||
* @param {Peer} peer - A peer instance.
|
|
||||||
* @return {Timeout} - The timeout value from `setTimeout`.
|
|
||||||
*/
|
|
||||||
function scheduleReconnect (peer) {
|
|
||||||
var backoff = peer.backoff;
|
|
||||||
var time = backoff.time;
|
|
||||||
backoff.next();
|
|
||||||
|
|
||||||
var reconnect = peer.connect.bind(peer);
|
|
||||||
|
|
||||||
return setTimeout(reconnect, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles reconnections and defers messages until the socket is ready.
|
|
||||||
* @param {String} url - The address to connect to.
|
|
||||||
* @param {Object} [options] - Override how the socket is managed.
|
|
||||||
* @param {Object} options.backoff - Backoff options (see the constructor).
|
|
||||||
* @class
|
|
||||||
*/
|
|
||||||
function Peer (url, options) {
|
|
||||||
if (!(this instanceof Peer)) {
|
|
||||||
return new Peer(url, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend EventEmitter.
|
|
||||||
Emitter.call(this);
|
|
||||||
this.setMaxListeners(Infinity);
|
|
||||||
|
|
||||||
this.options = options || {};
|
|
||||||
|
|
||||||
// Messages sent before the socket is ready.
|
|
||||||
this.deferredMsgs = [];
|
|
||||||
|
|
||||||
this.url = Peer.formatURL(url);
|
|
||||||
this.backoff = new Backoff(this.options.backoff);
|
|
||||||
|
|
||||||
// Set up the websocket.
|
|
||||||
this.connect();
|
|
||||||
|
|
||||||
var peer = this;
|
|
||||||
var reconnect = scheduleReconnect.bind(null, peer);
|
|
||||||
|
|
||||||
// Handle reconnection.
|
|
||||||
this.on('close', reconnect);
|
|
||||||
this.on('error', function (error) {
|
|
||||||
if (error.code === 'ECONNREFUSED') {
|
|
||||||
reconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send deferred messages.
|
|
||||||
this.on('open', function () {
|
|
||||||
peer.drainQueue();
|
|
||||||
peer.backoff.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns http URLs into WebSocket URLs.
|
|
||||||
* @param {String} url - The url to format.
|
|
||||||
* @return {String} - A correctly formatted WebSocket URL.
|
|
||||||
*/
|
|
||||||
Peer.formatURL = function (url) {
|
|
||||||
|
|
||||||
// Works for `https` and `wss` URLs, too.
|
|
||||||
return url.replace(/^http/, 'ws');
|
|
||||||
};
|
|
||||||
|
|
||||||
util.inherits(Peer, Emitter);
|
|
||||||
var API = Peer.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts a websocket connection.
|
|
||||||
* @return {WebSocket} - The new websocket instance.
|
|
||||||
*/
|
|
||||||
API.connect = function () {
|
|
||||||
var url = this.url;
|
|
||||||
|
|
||||||
// Open a new websocket.
|
|
||||||
var socket = new WebSocket(url, this.options.wsc.protocols, this.options.wsc);
|
|
||||||
|
|
||||||
// Re-use the previous listeners.
|
|
||||||
socket._events = this._events;
|
|
||||||
|
|
||||||
this.socket = socket;
|
|
||||||
|
|
||||||
return socket;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends all the messages in the deferred queue.
|
|
||||||
* @return {Peer} - The context.
|
|
||||||
*/
|
|
||||||
API.drainQueue = function () {
|
|
||||||
var peer = this;
|
|
||||||
|
|
||||||
this.deferredMsgs.forEach(function (msg) {
|
|
||||||
peer.send(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset the queue.
|
|
||||||
this.deferredMsgs = [];
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send data through the socket, or add it to a queue
|
|
||||||
* of deferred messages if it's not ready yet.
|
|
||||||
* @param {Mixed} msg - String, or anything that JSON can handle.
|
|
||||||
* @return {Peer} - The context.
|
|
||||||
*/
|
|
||||||
API.send = function (msg) {
|
|
||||||
var socket = this.socket;
|
|
||||||
var state = socket.readyState;
|
|
||||||
var ready = socket.OPEN;
|
|
||||||
|
|
||||||
// Make sure it's a string.
|
|
||||||
if (typeof msg !== 'string') {
|
|
||||||
msg = JSON.stringify(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the socket is ready.
|
|
||||||
if (state === ready) {
|
|
||||||
socket.send(msg);
|
|
||||||
} else {
|
|
||||||
this.deferredMsgs.push(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Peer;
|
|
101
lib/wsp/Pool.js
101
lib/wsp/Pool.js
@ -1,101 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simpler interface over a collection of sockets. Works with
|
|
||||||
* WebSocket clients, or sockets from a WebSocket server.
|
|
||||||
* @class
|
|
||||||
*/
|
|
||||||
function Pool () {
|
|
||||||
if (!(this instanceof Pool)) {
|
|
||||||
return new Pool();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maps IDs to sockets.
|
|
||||||
this.sockets = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var API = Pool.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the socket by the given ID.
|
|
||||||
* @param {String} id - The unique socket ID.
|
|
||||||
* @return {WebSocket|Null} - The WebSocket, if found.
|
|
||||||
*/
|
|
||||||
API.get = function (id) {
|
|
||||||
return this.sockets[id] || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a socket to the pool.
|
|
||||||
* @param {String} id - The socket ID.
|
|
||||||
* @param {WebSocket} socket - A websocket instance.
|
|
||||||
* @return {Pool} - The context.
|
|
||||||
*/
|
|
||||||
API.add = function (id, socket) {
|
|
||||||
this.sockets[id] = socket;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a socket from the pool.
|
|
||||||
* @param {String} id - The ID of the socket to remove.
|
|
||||||
* @return {Boolean} - Whether the pool contained the socket.
|
|
||||||
*/
|
|
||||||
API.remove = function (id) {
|
|
||||||
var sockets = this.sockets;
|
|
||||||
var hasSocket = sockets.hasOwnProperty(id);
|
|
||||||
|
|
||||||
if (hasSocket) {
|
|
||||||
delete sockets[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasSocket;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a filtered pool of sockets. Works the same as Array#filter.
|
|
||||||
* @param {Function} fn - Called for each socket in the pool.
|
|
||||||
* @param {Mixed} [_this] - The `this` context to use when invoking
|
|
||||||
* the callback.
|
|
||||||
* @return {Pool} - A new, filtered socket pool.
|
|
||||||
*/
|
|
||||||
API.filter = function (fn, _this) {
|
|
||||||
var filtered = Pool();
|
|
||||||
var pool = this;
|
|
||||||
|
|
||||||
_this = _this || pool;
|
|
||||||
|
|
||||||
Object.keys(this.sockets).forEach(function (id) {
|
|
||||||
var socket = pool.sockets[id];
|
|
||||||
|
|
||||||
var shouldAdd = fn.call(_this, socket, id, pool);
|
|
||||||
|
|
||||||
// Add it to the new pool.
|
|
||||||
if (shouldAdd) {
|
|
||||||
filtered.add(id, socket);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message through each socket in the pool.
|
|
||||||
* @param {String} msg - The message to send.
|
|
||||||
* @return {Number} - How many sockets the message was sent to.
|
|
||||||
*/
|
|
||||||
API.send = function (msg) {
|
|
||||||
var pool = this;
|
|
||||||
|
|
||||||
var ids = Object.keys(this.sockets);
|
|
||||||
|
|
||||||
ids.forEach(function (id) {
|
|
||||||
var socket = pool.sockets[id];
|
|
||||||
socket.send(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
return ids.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Pool;
|
|
@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
eslint-disable
|
|
||||||
no-warning-comments,
|
|
||||||
no-underscore-dangle,
|
|
||||||
*/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var Gun = require('../../gun');
|
|
||||||
var Socket = require('./Peer');
|
|
||||||
var Pool = require('./Pool');
|
|
||||||
|
|
||||||
// Maps URLs to sockets.
|
|
||||||
// Shared between all gun instances.
|
|
||||||
var sockets = Pool();
|
|
||||||
var sid = Gun.text.random();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a map of URLs pointing to options and ensure the
|
|
||||||
* urls are using the WS protocol.
|
|
||||||
* @param {Object} peers - Any object with URLs as keys.
|
|
||||||
* @return {Object} - Object with normalized URL keys.
|
|
||||||
*/
|
|
||||||
function normalizeURLs (peers) {
|
|
||||||
var formatted = {};
|
|
||||||
|
|
||||||
Object.keys(peers).forEach(function (url) {
|
|
||||||
var options = peers[url];
|
|
||||||
var id = Socket.formatURL(url);
|
|
||||||
formatted[id] = options;
|
|
||||||
});
|
|
||||||
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns a map of URLs into a socket pool.
|
|
||||||
* @param {Object} peers - Any object with URLs as keys.
|
|
||||||
* @return {Pool} - A pool of sockets corresponding to the URLs.
|
|
||||||
*/
|
|
||||||
function getSocketSubset (peers) {
|
|
||||||
var urls = normalizeURLs(peers);
|
|
||||||
|
|
||||||
return sockets.filter(function (socket) {
|
|
||||||
return urls.hasOwnProperty(socket.url);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Gun.on('out', function (ctx) {
|
|
||||||
this.to.next(ctx);
|
|
||||||
var gun = ctx.gun;
|
|
||||||
var opt = ctx.opt || {};
|
|
||||||
var peers = opt.peers || gun.back('opt.peers');
|
|
||||||
var headers = opt.headers || gun.back('opt.headers') || {};
|
|
||||||
|
|
||||||
if (!peers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var subset = getSocketSubset(peers);
|
|
||||||
|
|
||||||
headers['gun-sid'] = sid;
|
|
||||||
subset.send({
|
|
||||||
headers: headers,
|
|
||||||
body: ctx,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open any new sockets listed,
|
|
||||||
// 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') || {};
|
|
||||||
|
|
||||||
Gun.obj.map(peers, function (options, url) {
|
|
||||||
if (sockets[url]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!options.wsc){ options.wsc = gun.back('opt.wsc') || { protocols:null }; }
|
|
||||||
|
|
||||||
var socket = Socket(url, options);
|
|
||||||
sockets.add(url, socket);
|
|
||||||
|
|
||||||
socket.on('message', function (msg) {
|
|
||||||
var request;
|
|
||||||
|
|
||||||
try {
|
|
||||||
request = JSON.parse(msg);
|
|
||||||
} catch (error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the request.
|
|
||||||
if (!request || !request.body) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.on('in', request.body);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.to.next(context);
|
|
||||||
});
|
|
@ -1,90 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var Gun = require('../../gun');
|
|
||||||
|
|
||||||
var cache = {};
|
|
||||||
var timeout = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all entries in the cache older than 5 minutes.
|
|
||||||
* Reschedules itself to run again when the oldest item
|
|
||||||
* might be too old.
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
function gc () {
|
|
||||||
var now = Date.now();
|
|
||||||
var oldest = now;
|
|
||||||
var maxAge = 5 * 60 * 1000;
|
|
||||||
|
|
||||||
Gun.obj.map(cache, function (time, id) {
|
|
||||||
oldest = Math.min(now, time);
|
|
||||||
|
|
||||||
if ((now - time) < maxAge) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete cache[id];
|
|
||||||
});
|
|
||||||
|
|
||||||
var done = Gun.obj.empty(cache);
|
|
||||||
|
|
||||||
// Disengage GC.
|
|
||||||
if (done) {
|
|
||||||
timeout = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just how old?
|
|
||||||
var elapsed = now - oldest;
|
|
||||||
|
|
||||||
// How long before it's too old?
|
|
||||||
var nextGC = maxAge - elapsed;
|
|
||||||
|
|
||||||
// Schedule the next GC event.
|
|
||||||
timeout = setTimeout(gc, nextGC);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks a memory-efficient cache to see if a string has been seen before.
|
|
||||||
* @param {String} id - A string to keep track of.
|
|
||||||
* @return {Boolean} - Whether it's been seen recently.
|
|
||||||
*/
|
|
||||||
function duplicate (id) {
|
|
||||||
|
|
||||||
// Have we seen this ID recently?
|
|
||||||
var existing = cache.hasOwnProperty(id);
|
|
||||||
|
|
||||||
// Add it to the cache.
|
|
||||||
duplicate.track(id);
|
|
||||||
|
|
||||||
return existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts tracking an ID as a possible future duplicate.
|
|
||||||
* @param {String} id - The ID to track.
|
|
||||||
* @return {String} - The same ID.
|
|
||||||
*/
|
|
||||||
duplicate.track = function (id) {
|
|
||||||
cache[id] = Date.now();
|
|
||||||
|
|
||||||
// Engage GC.
|
|
||||||
if (!timeout) {
|
|
||||||
gc();
|
|
||||||
}
|
|
||||||
|
|
||||||
return id;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a new ID and start tracking it.
|
|
||||||
* @param {Number} [chars] - The number of characters to use.
|
|
||||||
* @return {String} - The newly created ID.
|
|
||||||
*/
|
|
||||||
duplicate.track.newID = function (chars) {
|
|
||||||
var id = Gun.text.random(chars);
|
|
||||||
|
|
||||||
return duplicate.track(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = duplicate;
|
|
@ -1,98 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
var Gun = require('../../gun.js');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the gun instance is attached to a socket server.
|
|
||||||
* @param {Gun} gun - The gun instance in question.
|
|
||||||
* @param {WebSocket.Server} server - A socket server gun might be attached to.
|
|
||||||
* @return {Boolean} - Whether it's attached.
|
|
||||||
*/
|
|
||||||
function isUsingServer (gun, server) {
|
|
||||||
var servers = gun.back(-1)._.servers;
|
|
||||||
|
|
||||||
return servers ? servers.indexOf(server) !== -1 : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls a function when (or if) a socket is ready for messages.
|
|
||||||
* @param {WebSocket} socket - A websocket connection.
|
|
||||||
* @param {Function} cb - Called if or when the socket is ready.
|
|
||||||
* @return {Boolean} - Whether the socket is able to take messages.
|
|
||||||
*/
|
|
||||||
function ready (socket, cb) {
|
|
||||||
var state = socket.readyState;
|
|
||||||
|
|
||||||
// The socket is ready.
|
|
||||||
if (state === socket.OPEN) {
|
|
||||||
cb();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Still opening.
|
|
||||||
if (state === socket.OPENING) {
|
|
||||||
socket.once('open', cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nope, closing or closed.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to a group of clients.
|
|
||||||
* @param {Obejct} msg - An http envelope-like message.
|
|
||||||
* @param {Object} clients - IDs mapped to socket instances.
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
function send (msg, clients) {
|
|
||||||
Gun.obj.map(clients, function (client) {
|
|
||||||
ready(client, function () {
|
|
||||||
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.
|
|
||||||
* @return {server} - The socket server.
|
|
||||||
*/
|
|
||||||
function attach (gun, server) {
|
|
||||||
var root = gun.back(-1);
|
|
||||||
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);
|
|
||||||
pool[socket.id] = socket;
|
|
||||||
/*
|
|
||||||
socket.on('message', function (message) {
|
|
||||||
var data = Gun.obj.ify(message);
|
|
||||||
|
|
||||||
if (!data || !data.body) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
root.on('in', data.body);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
socket.once('close', function () {
|
|
||||||
delete pool[socket.id];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Gun.on('out', function (context) {
|
|
||||||
this.to.next(context);
|
|
||||||
if (!isUsingServer(context.gun, server) || Gun.obj.empty(pool)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg = {
|
|
||||||
headers: { 'gun-sid': sid },
|
|
||||||
body: context,
|
|
||||||
};
|
|
||||||
send(msg, pool);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = attach;
|
|
@ -1,257 +0,0 @@
|
|||||||
/* 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) {
|
|
||||||
this.to.next(at);
|
|
||||||
var gun = at.gun, opt = at.opt;
|
|
||||||
gun.__ = at.root;
|
|
||||||
gun.__.opt.ws = opt.ws = gun.__.opt.ws || opt.ws || {};
|
|
||||||
gun.__.opt.ws.path = gun.__.opt.ws.path || '/gun';
|
|
||||||
|
|
||||||
if(gun.__.opt.web){
|
|
||||||
setTimeout(function(){
|
|
||||||
if(gun.__.opt.web.use){
|
|
||||||
gun.__.opt.web.use(Gun.serve);
|
|
||||||
}
|
|
||||||
start(gun.__.opt.web);
|
|
||||||
},1);
|
|
||||||
}
|
|
||||||
function start (server, port, app) {
|
|
||||||
if (app && app.use) {
|
|
||||||
app.use(gun.wsp.server);
|
|
||||||
}
|
|
||||||
server = gun.__.opt.ws.server = gun.__.opt.ws.server || gun.__.opt.web || opt.ws.server || server;
|
|
||||||
|
|
||||||
if (!gun.wsp.ws) {
|
|
||||||
//console.log("????????", gun.__.opt.ws);
|
|
||||||
gun.wsp.ws = new WSS(gun.__.opt.ws);
|
|
||||||
attach(gun, gun.wsp.ws);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
var ev = this; ev.to.next(msg);
|
|
||||||
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.
|
|
||||||
});
|
|
||||||
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) {
|
|
||||||
console.log("WARNING: gun.wsp(server) should be switched to Gun({web: server}) by v0.7!")
|
|
||||||
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')) {
|
|
||||||
start(server, server.get('port'));
|
|
||||||
return gun;
|
|
||||||
}
|
|
||||||
var listen = server.listen;
|
|
||||||
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; }
|
|
||||||
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') {
|
|
||||||
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) {
|
|
||||||
next();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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'])) {
|
|
||||||
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; }
|
|
||||||
res({headers: {'gun-sid': stream.sid}, body: stream.queue });
|
|
||||||
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) {
|
|
||||||
var ev = this; ev.to.next(req);
|
|
||||||
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); };
|
|
||||||
clearTimeout(stream.off);
|
|
||||||
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) {
|
|
||||||
require('https').globalAgent.maxSockets = require('http').globalAgent.maxSockets = gun.__.opt.maxSockets || Infinity;
|
|
||||||
}
|
|
||||||
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 () {
|
|
||||||
var now = Gun.time.is();
|
|
||||||
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]) {
|
|
||||||
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 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;
|
|
||||||
gun.on('in', 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.
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
cb({
|
|
||||||
headers: reply.headers,
|
|
||||||
body: {
|
|
||||||
'#': gun.wsp.msg(),
|
|
||||||
'@': body['#'],
|
|
||||||
'$': 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;
|
|
||||||
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) {
|
|
||||||
wsp(opt.server);
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,40 +0,0 @@
|
|||||||
var Gun = require('../../gun')
|
|
||||||
, url = require('url');
|
|
||||||
module.exports = function(wss, server, opt){
|
|
||||||
wss.on('connection', function(ws){
|
|
||||||
var req = {};
|
|
||||||
ws.upgradeReq = ws.upgradeReq || {};
|
|
||||||
req.url = url.parse(ws.upgradeReq.url||'');
|
|
||||||
req.method = (ws.upgradeReq.method||'').toLowerCase();
|
|
||||||
req.headers = ws.upgradeReq.headers || {};
|
|
||||||
//Gun.log("wsReq", req);
|
|
||||||
ws.on('message', function(msg){
|
|
||||||
msg = Gun.obj.ify(msg);
|
|
||||||
msg.url = msg.url || {};
|
|
||||||
msg.url.pathname = (req.url.pathname||'') + (msg.url.pathname||'');
|
|
||||||
Gun.obj.map(req.url, function(val, i){
|
|
||||||
msg.url[i] = msg.url[i] || val; // reattach url
|
|
||||||
});
|
|
||||||
msg.method = msg.method || msg.body? 'put' : 'get';
|
|
||||||
msg.headers = msg.headers || {};
|
|
||||||
Gun.obj.map(opt.headers || req.headers, function(val, i){
|
|
||||||
msg.headers[i] = msg.headers[i]; // reattach headers
|
|
||||||
});
|
|
||||||
server.call(ws, msg, function(reply){
|
|
||||||
if(!ws || !ws.send || !ws._socket || !ws._socket.writable){ return }
|
|
||||||
reply = reply || {};
|
|
||||||
if(msg && msg.headers && msg.headers['ws-rid']){
|
|
||||||
(reply.headers = reply.headers || {})['ws-rid'] = msg.headers['ws-rid'];
|
|
||||||
}
|
|
||||||
try{ws.send(Gun.text.ify(reply));
|
|
||||||
}catch(e){} // juuuust in case.
|
|
||||||
});
|
|
||||||
});
|
|
||||||
ws.off = function(m){
|
|
||||||
//Gun.log("ws.off", m);
|
|
||||||
ws.send = null;
|
|
||||||
}
|
|
||||||
ws.on('close', ws.off);
|
|
||||||
ws.on('error', ws.off);
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user