ngx-signalr-hubservice
Version:
Makes using signalr in angular 2 easy
559 lines • 26.1 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
Object.defineProperty(exports, "__esModule", { value: true });
var core_1 = require("@angular/core");
var rxjs_1 = require("rxjs");
var operators_1 = require("rxjs/operators");
/**
* All the hubs/events we have to subscribe to when setting up the connection.
* signalr requires you to connect to all the hubs before making the connection
* so we store them in a global var.
*/
var allHubProperties = [];
/**
* The decorators for methods/hubs are called out of order
* so this will either return the existing hub properties or create new ones.
* @param target The target class
* @param hubProperties The properties to define for the class.
*/
function getOrCreateHubProperties(target, hubProperties) {
var properties = Reflect.getMetadata("Hub", target);
if (!properties) {
properties = hubProperties;
Reflect.defineMetadata("Hub", hubProperties, target);
}
properties.subscriptions = properties.subscriptions || [];
properties.hubName = properties.hubName || hubProperties.hubName;
properties.hubGroups = properties.hubGroups || hubProperties.hubGroups;
return properties;
}
/**
* Adds the hubName property to the list of hubs to subscribe to
* We have to define hubs and subscriptions before connecting so we only need one connection for all our hubs.
* If your service is referenced in an external module, you have to reference it somewhere in your main/core code.
* @param hubProperties the properties for this class's subscription to the hub
*/
function Hub(hubProperties) {
return function (target) {
var _a;
hubProperties = getOrCreateHubProperties(target.prototype, hubProperties);
var existing = allHubProperties.find(function (props) { return props.hubName === hubProperties.hubName; });
if (existing) {
(_a = existing.subscriptions).push.apply(_a, hubProperties.subscriptions);
return;
}
allHubProperties.push(hubProperties);
};
}
exports.Hub = Hub;
/**
* Subscribes to a hub event
* @param the event to subscribe to. if null, it uses the method name
*/
function HubSubscription(eventName) {
return function (target, // the prototype of the class
propertyKey, // the name of the method
descriptor) {
eventName = eventName || propertyKey;
var hubProperties = getOrCreateHubProperties(target, { hubName: null });
hubProperties.subscriptions.push({
eventName: eventName,
functionName: propertyKey
});
};
}
exports.HubSubscription = HubSubscription;
/**
* Manages a connection to a signalr service, and provides easy access to its hubs and their events
* To start, call hubService.connect();
* Classes that want to subscribe to a hub event must have the @Hub decorator and the name of the hub to subscribe to.
* Example class:
* ```
* @Hub({ hubName: 'searchHub' })
* export class SearchService {
* private hubWrapper: HubWrapper;
* constructor(private hubService: HubService) {
* this.hubWrapper = hubService.register(this);
* }
* public startSearch(data: any): Observable<boolean> {
* return this.hubWrapper.invoke<boolean>('startSearch', data);
* }
* @HubSubscription()
* private searchUpdated(data: any) {
* }
* }
* ```
*/
var HubService = /** @class */ (function () {
function HubService() {
var _this = this;
/** jQuery connection. */
this._connection = null;
/** emitter for connected event */
this.connectedEmitter = new rxjs_1.Subject();
/** emitter for disconnected event */
this.disconnectedEmitter = new rxjs_1.Subject();
/** emitter for reconnecting event */
this.reconnectingEmitter = new rxjs_1.Subject();
/** emitter for reconnected event */
this.reconnectedEmitter = new rxjs_1.Subject();
/** if there was an error connecting */
this._errorConnecting = false;
/** currently trying to reconnect? */
this.tryingReconnect = false;
/** list of services to register after connect is called */
this.deferredRegistrations = [];
// this gets called from within the signalr instance so we have to make it a scoped method on the hubservice
this.connectedCallback = function () {
_this._errorConnecting = !_this.connected;
_this.connectedEmitter.next(_this.connected);
};
// this gets called from within the signalr instance so we have to make it a scoped method on the hubservice
this.connectionErrorCallback = function (err, caught) {
_this._errorConnecting = true;
_this.disconnectedEmitter.next(_this.connected);
return rxjs_1.of(false);
};
// this gets called from within the signalr instance so we have to make it a scoped method on the hubservice
this.reconnectedCallback = function () {
_this.reconnectedEmitter.next(_this.connected);
_this.connectedEmitter.next(_this.connected);
_this.reconnectingObservable.next(_this.connected);
_this.reconnectingObservable = null;
};
// this gets called from within the signalr instance so we have to make it a scoped method on the hubservice
this.reconnectingCallback = function () {
_this.reconnectingEmitter.next();
_this.reconnectingObservable = new rxjs_1.Subject();
};
// this gets called from within the signalr instance so we have to make it a scoped method on the hubservice
this.disconnectedCallback = function () {
if (_this.tryingReconnect) {
return;
}
_this.disconnectedEmitter.next(_this.connected);
if (_this.options.attemptReconnects) {
_this.tryReconnect();
}
};
}
HubService_1 = HubService;
Object.defineProperty(HubService.prototype, "connection", {
get: function () {
return this._connection;
},
enumerable: true,
configurable: true
});
Object.defineProperty(HubService.prototype, "connected", {
/**
* Is the client connected?
*/
get: function () {
return this._connection !== null && this._connection.state === $.signalR.connectionState.connected;
},
enumerable: true,
configurable: true
});
Object.defineProperty(HubService.prototype, "errorConnecting", {
/**
* Was there an error connecting to the server?
*/
get: function () {
return this._errorConnecting;
},
enumerable: true,
configurable: true
});
/**
* Connects to the signalr server. Hubs are registered with the connection through
* the @Hub decorator
* @param options Options to use for the connection
*/
HubService.prototype.connect = function (options) {
if (options === void 0) { options = {}; }
this.options = __assign({ url: "/signalr", attemptReconnects: false }, options);
return this._connect(false);
};
HubService.prototype._connect = function (ignoreReconnecting) {
var _this = this;
// if user calls connect while we're trying to reconnect, just give them that observable
if (!ignoreReconnecting && this.reconnectingObservable != null) {
return this.reconnectingObservable.asObservable();
}
if (this._connection === null) {
this.initConnection();
}
// this._connection.start just returns the connection object, so map it to this.connected when it completes
return rxjs_1.from(this._connection.start()).pipe(operators_1.map(function (value) { return _this.connected; }), operators_1.tap(this.connectedCallback), operators_1.catchError(this.connectionErrorCallback));
};
HubService.prototype.initConnection = function () {
// initialize signalr data structures
this.hubProxies = {};
this._connection = $.hubConnection(this.options.url, { useDefaultPath: false });
this._connection.qs = this.options.qs;
this._connection.logging = false;
// we have to create the hub proxies and subscribe to events before connecting
for (var _i = 0, allHubProperties_1 = allHubProperties; _i < allHubProperties_1.length; _i++) {
var properties = allHubProperties_1[_i];
// make sure we match this group
if (!this.matchesGroup(properties.hubGroups)) {
continue;
}
// we do, so create the proxy.
this.hubProxies[properties.hubName] = this.createHubProxy(properties);
}
for (var _a = 0, _b = this.deferredRegistrations; _a < _b.length; _a++) {
var deferredRegistration = _b[_a];
this.register(deferredRegistration);
}
this._connection.disconnected(this.disconnectedCallback);
this._connection.reconnected(this.reconnectedCallback);
this._connection.reconnecting(this.reconnectingCallback);
this._connection.stateChanged(function (change) {
this.signalRState = change.newState;
});
};
HubService.prototype.matchesGroup = function (hubGroups) {
// if one is null and the other isn't assume we don't match.
if (this.options.hubGroups == null && hubGroups != null) {
return false;
}
if (hubGroups == null && this.options.hubGroups != null) {
return false;
}
// if both null then assume match.
if (hubGroups == null && this.options.hubGroups == null) {
return true;
}
// just force arrays here to simplify the logic.
if (!Array.isArray(hubGroups)) {
hubGroups = [hubGroups];
}
if (!Array.isArray(this.options.hubGroups)) {
this.options.hubGroups = [this.options.hubGroups];
}
// check for at least one match.
var ourGroups = this.options.hubGroups;
return hubGroups.some(function (group) { return ourGroups.some(function (ourGroup) { return group === ourGroup; }); });
};
/**
* Disconnects from the signalr server, and pushes out the disconnected event
*/
HubService.prototype.disconnect = function () {
var _this = this;
// connection.stop just returns the connection object, so map it to this.connected when it completes
return rxjs_1.from(this._connection.stop()).pipe(operators_1.map(function (value) { return _this.connected; }));
};
/**
* Subscribe to the reconnected event
* @param generatorOrNext callback for when we get reconnected to signalr
*/
HubService.prototype.onConnected = function (generatorOrNext) {
return this.connectedEmitter.subscribe(generatorOrNext);
};
/**
* Subscribe to the reconnected event
* @param generatorOrNext callback for when we get reconnected to signalr
*/
HubService.prototype.onReconnected = function (generatorOrNext) {
return this.reconnectedEmitter.subscribe(generatorOrNext);
};
/**
* Subscribe to the reconnecting event
* @param generatorOrNext callback for when we get the reconnecting event from signalr
*/
HubService.prototype.onReconnecting = function (generatorOrNext) {
return this.reconnectingEmitter.subscribe(generatorOrNext);
};
/**
* Subscribe to the disconnected event
* @param generatorOrNext callback for when we get disconnected from signalr
*/
HubService.prototype.onDisconnected = function (generatorOrNext) {
return this.disconnectedEmitter.subscribe(generatorOrNext);
};
/**
* Attemps to reconnect
*/
HubService.prototype.tryReconnect = function () {
var _this = this;
this.tryingReconnect = true;
this.reconnectingObservable = new rxjs_1.Subject();
// try to reconnect forever.
this._connect(this.options.attemptReconnects).subscribe(function (connected) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!!connected) return [3 /*break*/, 2];
return [4 /*yield*/, HubService_1.delay(1000)];
case 1:
_a.sent();
this.tryReconnect();
return [3 /*break*/, 3];
case 2:
this.reconnectedCallback();
this.tryingReconnect = false;
_a.label = 3;
case 3: return [2 /*return*/];
}
});
}); });
};
HubService.delay = function (ms) {
return new Promise(function (resolve) {
setTimeout(function () { return resolve(); }, ms);
});
};
/**
* Calls the method on the server with the provided arguments.
* If the hub connection is disconnected, the message will queue up and send when it reconnects.
* @param hubName The hub name
* @param method The method name
* @param args The arguments to send to the hub
*/
HubService.prototype.invoke = function (hubName, method) {
var _a;
var _this = this;
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
var hubContainer = this.hubProxies[hubName];
if (hubContainer == null) {
throw new Error("Invalid hub name " + hubName);
}
if (this.reconnectingObservable != null) {
// we're reconnecting, so wait on that, then invoke our method
return this.reconnectingObservable.pipe(operators_1.flatMap(function (connected) {
var _a;
if (!connected) {
return rxjs_1.throwError("SignalR disconnected");
}
else {
var invokePromise = (_a = hubContainer.hubProxy).invoke.apply(_a, __spreadArrays([method], args));
return rxjs_1.from(invokePromise);
}
}));
}
else {
// we're not reconnecting, so try to invoke our method
var invokePromise = (_a = hubContainer.hubProxy).invoke.apply(_a, __spreadArrays([method], args));
return rxjs_1.from(invokePromise).pipe(operators_1.catchError(function (err) {
// we lost connection in the middle of the call? wait for reconnecting and send again then.
if (_this.reconnectingObservable != null) {
return _this.invoke(hubName, method, args);
}
else {
// let the caller handle it.
return rxjs_1.throwError(err);
}
}));
}
};
/**
* Register this class instance to the hubs. The class instance must have the @Hub decorator.
* Any subscriptions defined in the @Hub decorator must have a matching method on the class to be called,
* or have the @HubDecorator decorator on their event methods
* @param instance The class to register with the hub service
*/
HubService.prototype.register = function (instance) {
var _this = this;
var hubProperties = Reflect.getMetadata("Hub", instance);
if (!hubProperties) {
throw new Error("You must call register with an instance of a class with the @Hub decorator on it. Instance: " + instance);
}
var hubWrapper = {
invoke: function (method) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
return _this.invoke.apply(_this, __spreadArrays([hubProperties.hubName, method], args));
},
hub: null,
unregister: function () { return _this.unregister(instance); }
};
if (hubProperties.subscriptions.length === 0) {
// signalr ignores hubs with no events(I assume you'd just want to use a POST then). Worth erroring out here.
throw new Error("Hub " + hubProperties.hubName + " must have at least one event subscription.");
}
// allows the caller to register to hubs before actually connecting.
if (this.hubProxies === void 0) {
this.deferredRegistrations.push(instance);
// doesn't matter if were not registered yet we can still return this object
// because the user shouldn't use it until they call connect()
return hubWrapper;
}
// get the hub proxy and set its hub instance if it's not set.
var hubProxy = this.hubProxies[hubProperties.hubName];
if (hubProxy == null) {
// only throw the invalid hub error if it matches this connections group.
if (!this.matchesGroup(hubProperties.hubGroups)) {
return;
}
throw new Error("Invalid hub name " + hubProperties.hubName);
}
if (hubWrapper.hub == null) {
hubWrapper.hub = hubProxy.hubProxy;
}
// subscribe to all the events
for (var _i = 0, _a = hubProperties.subscriptions; _i < _a.length; _i++) {
var subscription = _a[_i];
// if the method for this subscription isn't defined skip it
if (!(subscription.functionName in instance)) {
console.warn(instance + " is subscribing to event " + subscription + " but has no matching method. Skipping subscription.");
continue;
}
// adds a ref to the method on the instance to the list of events for this hub+event pairing
hubProxy.events[subscription.eventName].push({
thisObj: instance,
callback: instance[subscription.functionName]
});
}
return hubWrapper;
};
/**
* Unregisters the instance from events.
* @param instance the class instance to unregister
*/
HubService.prototype.unregister = function (instance) {
var hubProperties = Reflect.getMetadata("Hub", instance);
if (!hubProperties) {
throw new Error("You must call unregister with an instance of a class with the @Hub decorator on it. Instance: " + instance);
}
var proxy = this.hubProxies[hubProperties.hubName];
for (var _i = 0, _a = hubProperties.subscriptions; _i < _a.length; _i++) {
var subscription = _a[_i];
var events = proxy.events[subscription.eventName];
if (events == null) {
continue;
}
for (var i = events.length - 1; i >= 0; i--) {
var event_1 = events[i];
if (event_1.thisObj !== instance) {
continue;
}
events.splice(i, 1);
}
}
};
/**
* Pushes out a message received by the hub to the subscribers registered through register
* @param hub The hub name
* @param subscription The subscription name(event name)
* @param args The arguments from the hub
*/
HubService.prototype.hubMessageReceived = function (hub, subscription, args) {
if (!(hub in this.hubProxies)) {
return;
}
var events = this.hubProxies[hub].events[subscription.eventName];
for (var _i = 0, events_1 = events; _i < events_1.length; _i++) {
var func = events_1[_i];
// wrap all the callbacks in a try/catch so they don't break other callbacks if one fails.
try {
func.callback.apply(func.thisObj, args);
}
catch (err) {
console.error("Hub callback error on hub " + hub + " subscription " + subscription.eventName + ". Error: " + err);
}
}
};
/**
* Creates a hub proxy and registers the subscriptions on it
* @param properties the properties for the hub
*/
HubService.prototype.createHubProxy = function (properties) {
var hubProxy = this._connection.createHubProxy(properties.hubName);
var events = {};
var _this_ = this;
var _loop_1 = function (subscription) {
// don't resubscribe to events.
if (subscription.eventName in events) {
return "continue";
}
events[subscription.eventName] = [];
// this method actually subscribes to the hub function.
// we only subscribe once then push out the message to all subscribers
hubProxy.on(subscription.eventName, function () {
// we lose the "this" context with the jquery promise, so we have to store it as _this_.
_this_.hubMessageReceived(properties.hubName, subscription, arguments);
});
};
for (var _i = 0, _a = properties.subscriptions; _i < _a.length; _i++) {
var subscription = _a[_i];
_loop_1(subscription);
}
return {
hubProxy: hubProxy,
events: events
};
};
var HubService_1;
HubService = HubService_1 = __decorate([
core_1.Injectable()
], HubService);
return HubService;
}());
exports.HubService = HubService;
//# sourceMappingURL=ngx-signalr-hubservice.js.map