awv3
Version:
⚡ AWV3 embedded CAD
247 lines (211 loc) • 8.46 kB
JavaScript
import Base from './base';
import { createContext, handleResult } from '../core/parser';
import Parser from '../core/parser';
export default class SignalR extends Base {
constructor(options = {}) {
super(options);
this.ping = options.ping || 10000;
this.onPause = undefined;
this.onResume = undefined;
this._connection;
this._heartbeat;
this._timeout;
this._queueBlock;
this._currentResolve;
this._currentReject;
this._queue = [];
this._sequence = Promise.resolve();
this._handler = null;
this._defaultContext = createContext();
this._defaultHandler = async data => {
await handleResult(this._defaultContext, data);
this._defaultContext.promises = [];
this._defaultContext.results = [];
};
this.connected = false;
this.paused = false;
this.transport = '';
this.serverState = undefined;
// Initialize hub
let hub = require('../communication/signalrhub').default;
hub(this);
this._proxy.clientHub.client.result = data => {
// Parse data into JSON
var obj = JSON.parse(data);
if (!this.connected) {
if (obj.command == 'PERMISSION') {
this.connected = true;
this.transport = obj.transport;
this._currentResolve(this);
} else if (obj.command == 'WAIT') {
this.transport = obj.transport;
}
} else {
if (obj.command === 'Service') {
// Connection to a ClassCAD instance has been paused
if (obj.event === 'Pause') {
this.paused = true;
this.serverState = obj.state;
if (!!this.onPause) this.onPause(obj);
// Connection to ClassCAD is reestablished
} else if (obj.event === 'Resume') {
this.paused = false;
if (!!this.onResume) this.onResume(obj);
}
} else
(this._handler || this._defaultHandler)(obj);
}
};
this._proxy.clientHub.client.disconnect = () => {
if (this._heartbeat) clearInterval(this._heartbeat);
this._hub.stop();
this.connected = false;
};
this._proxy.clientHub.client.debug = message => {
console.log(message);
};
this._proxy.clientHub.client.queueNext = () => {
var message = this._queue.shift();
if (message !== undefined) this._proxy.clientHub.server.send(message);
else this._queueBlock = true;
};
this._hub.disconnected(() => {
if (this._heartbeat) clearInterval(this._heartbeat);
if (this._timeout) clearTimeout(this._timeout);
this.connected = false;
this._currentReject(this._hub.url + ' not found');
});
}
connect(url = this._hub.url) {
console.log('connecting to ' + url);
if (this.connected) return Promise.reject('Disconnect first!');
var first;
/*if (!!this.options.loadBallanced) {
first = this.canvas.parser.stream(url + '/ip').then(context => {
if (context.results.length > 0) {
var scaledUri = context.results[0].result.url + '/signalr';
return scaledUri;
}
}).catch(reason => Promise.reject("Supply URL!"));
} else*/
first = Promise.resolve(url + '/signalr');
return first.then(url => {
if (!url) return Promise.reject('Supply URL!');
if (this._timeout) clearTimeout(this._timeout);
this._timeout = setTimeout(
function() {
// Slots occupied
},
4000
);
// Link URL
this._hub.url = url;
// Start hub
this._hub.start(this.options).done(() => {
this._proxy.clientHub.server.init(
false,
!!this.options.pause,
!!this.options.timeOut ? this.options.timeOut : 0,
!!this.options.rebuild ? this.options.rebuild : false
);
if (this._heartbeat) clearInterval(this._heartbeat);
this._heartbeat = setInterval(
() => {
this._proxy.clientHub.server.ping();
},
this.ping
);
});
// Return promise
return new Promise((resolve, reject) => {
this._currentResolve = resolve;
this._currentReject = reject;
});
});
}
disconnect() {
if (!this.connected) return;
if (this._proxy.clientHub !== undefined) {
this._proxy.clientHub.client.disconnect();
this._currentResolve = null;
this._currentReject = null;
this.connected = false;
if (this._timeout) clearTimeout(this._timeout);
}
}
send(message) {
if (this.transport === 'webSockets' || this._queueBlock) {
this._queueBlock = false;
this._proxy.clientHub.server.send(message);
} else {
this._queue.push(message);
}
}
request(command, factory, timeout) {
if (!this.connected) return Promise.reject('Not connected!');
// If we're pause, we need to load our previous state
if (this.paused && !!this.serverState) {
var state = this.serverState;
this.serverState = undefined;
this.setState(state, !!this.options.rebuild, !!this.options.rebuild);
}
command = Array.isArray(command) ? command : [command];
var action = () => {
return new Promise((resolve, reject) => {
var timeout = setTimeout(reject, timeout || 120000),
context = createContext(factory, resolve, reject, command);
context.options.callback({ type: Parser.Factory.Started, context });
// Override result callback for each transaction & handle all incoming packages
this._handler = data => handleResult(context, data);
this.send(
JSON.stringify({
command: 'BeginFrame',
transactionID: context.id
})
);
for (let item of command)
this.send(JSON.stringify(item));
this.send(
JSON.stringify({
command: 'EndFrame',
transactionID: context.id
})
);
})
.then(results => results, failure => failure)
.then(results => {
results.options.callback({ type: Parser.Factory.Finished, context: results });
// Clean up and return context
clearTimeout(this._timeout);
this._handler = null;
return results;
});
};
// Fullfil last transaction, then queue next
this._sequence = this._sequence.then(action, action);
return this._sequence;
}
}
SignalR.eliminate = (urls, options) => {
if (urls.length === 1) return new SignalR(options).connect(urls.shift());
let first = urls.shift();
return urls.reduce(
function(sequence, url) {
return sequence.then(server => server, () => new SignalR(options).connect(url));
},
new SignalR(options).connect(first)
);
};
SignalR.race = (urls, options) => {
return new Promise(function(resolve, reject) {
let count = 0, winner;
let connections = urls.map(function(url) {
return new SignalR(options)
.connect(url)
.then(item => !winner ? resolve(winner = item) : item.disconnect())
.catch(() => {
if (++count === urls.length) reject();
});
});
});
};