Skip to content

Instantly share code, notes, and snippets.

@radu-matei
Created November 25, 2017 19:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save radu-matei/84f12b835fbd0fbe3cbe165a70e13e34 to your computer and use it in GitHub Desktop.
Save radu-matei/84f12b835fbd0fbe3cbe165a70e13e34 to your computer and use it in GitHub Desktop.
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.signalR = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
class Base64EncodedHubProtocol {
constructor(protocol) {
this.wrappedProtocol = protocol;
this.name = this.wrappedProtocol.name;
this.type = 1 /* Text */;
}
parseMessages(input) {
// The format of the message is `size:message;`
let pos = input.indexOf(":");
if (pos == -1 || input[input.length - 1] != ';') {
throw new Error("Invalid payload.");
}
let lenStr = input.substring(0, pos);
if (!/^[0-9]+$/.test(lenStr)) {
throw new Error(`Invalid length: '${lenStr}'`);
}
let messageSize = parseInt(lenStr, 10);
// 2 accounts for ':' after message size and trailing ';'
if (messageSize != input.length - pos - 2) {
throw new Error("Invalid message size.");
}
let encodedMessage = input.substring(pos + 1, input.length - 1);
// atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use
// base64-js module
let s = atob(encodedMessage);
let payload = new Uint8Array(s.length);
for (let i = 0; i < payload.length; i++) {
payload[i] = s.charCodeAt(i);
}
return this.wrappedProtocol.parseMessages(payload.buffer);
}
writeMessage(message) {
let payload = new Uint8Array(this.wrappedProtocol.writeMessage(message));
let s = "";
for (let i = 0; i < payload.byteLength; i++) {
s += String.fromCharCode(payload[i]);
}
// atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use
// base64-js module
let encodedMessage = btoa(s);
return `${encodedMessage.length.toString()}:${encodedMessage};`;
}
}
exports.Base64EncodedHubProtocol = Base64EncodedHubProtocol;
},{}],2:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
var TextMessageFormat;
(function (TextMessageFormat) {
const RecordSeparator = String.fromCharCode(0x1e);
function write(output) {
return `${output}${RecordSeparator}`;
}
TextMessageFormat.write = write;
function parse(input) {
if (input[input.length - 1] != RecordSeparator) {
throw new Error("Message is incomplete.");
}
let messages = input.split(RecordSeparator);
messages.pop();
return messages;
}
TextMessageFormat.parse = parse;
})(TextMessageFormat = exports.TextMessageFormat || (exports.TextMessageFormat = {}));
var BinaryMessageFormat;
(function (BinaryMessageFormat) {
// The length prefix of binary messages is encoded as VarInt. Read the comment in
// the BinaryMessageParser.TryParseMessage for details.
function write(output) {
// msgpack5 uses returns Buffer instead of Uint8Array on IE10 and some other browser
// in which case .byteLength does will be undefined
let size = output.byteLength || output.length;
let lenBuffer = [];
do {
let sizePart = size & 0x7f;
size = size >> 7;
if (size > 0) {
sizePart |= 0x80;
}
lenBuffer.push(sizePart);
} while (size > 0);
// msgpack5 uses returns Buffer instead of Uint8Array on IE10 and some other browser
// in which case .byteLength does will be undefined
size = output.byteLength || output.length;
let buffer = new Uint8Array(lenBuffer.length + size);
buffer.set(lenBuffer, 0);
buffer.set(output, lenBuffer.length);
return buffer.buffer;
}
BinaryMessageFormat.write = write;
function parse(input) {
let result = [];
let uint8Array = new Uint8Array(input);
const maxLengthPrefixSize = 5;
const numBitsToShift = [0, 7, 14, 21, 28];
for (let offset = 0; offset < input.byteLength;) {
let numBytes = 0;
let size = 0;
let byteRead;
do {
byteRead = uint8Array[offset + numBytes];
size = size | ((byteRead & 0x7f) << (numBitsToShift[numBytes]));
numBytes++;
} while (numBytes < Math.min(maxLengthPrefixSize, input.byteLength - offset) && (byteRead & 0x80) != 0);
if ((byteRead & 0x80) !== 0 && numBytes < maxLengthPrefixSize) {
throw new Error("Cannot read message size.");
}
if (numBytes === maxLengthPrefixSize && byteRead > 7) {
throw new Error("Messages bigger than 2GB are not supported.");
}
if (uint8Array.byteLength >= (offset + numBytes + size)) {
// IE does not support .slice() so use subarray
result.push(uint8Array.slice
? uint8Array.slice(offset + numBytes, offset + numBytes + size)
: uint8Array.subarray(offset + numBytes, offset + numBytes + size));
}
else {
throw new Error("Incomplete message.");
}
offset = offset + numBytes + size;
}
return result;
}
BinaryMessageFormat.parse = parse;
})(BinaryMessageFormat = exports.BinaryMessageFormat || (exports.BinaryMessageFormat = {}));
},{}],3:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const HttpError_1 = require("./HttpError");
class HttpClient {
get(url, headers) {
return this.xhr("GET", url, headers);
}
options(url, headers) {
return this.xhr("OPTIONS", url, headers);
}
post(url, content, headers) {
return this.xhr("POST", url, headers, content);
}
xhr(method, url, headers, content) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
if (headers) {
headers.forEach((value, header) => xhr.setRequestHeader(header, value));
}
xhr.send(content);
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response || xhr.responseText);
}
else {
reject(new HttpError_1.HttpError(xhr.statusText, xhr.status));
}
};
xhr.onerror = () => {
reject(new HttpError_1.HttpError(xhr.statusText, xhr.status));
};
});
}
}
exports.HttpClient = HttpClient;
},{"./HttpError":5}],4:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const Transports_1 = require("./Transports");
const HttpClient_1 = require("./HttpClient");
const ILogger_1 = require("./ILogger");
const Loggers_1 = require("./Loggers");
class HttpConnection {
constructor(url, options = {}) {
this.features = {};
this.logger = Loggers_1.LoggerFactory.createLogger(options.logging);
this.url = this.resolveUrl(url);
options = options || {};
this.httpClient = options.httpClient || new HttpClient_1.HttpClient();
this.connectionState = 0 /* Initial */;
this.options = options;
}
start() {
return __awaiter(this, void 0, void 0, function* () {
if (this.connectionState != 0 /* Initial */) {
return Promise.reject(new Error("Cannot start a connection that is not in the 'Initial' state."));
}
this.connectionState = 1 /* Connecting */;
this.startPromise = this.startInternal();
return this.startPromise;
});
}
startInternal() {
return __awaiter(this, void 0, void 0, function* () {
try {
let negotiatePayload = yield this.httpClient.options(this.url);
let negotiateResponse = JSON.parse(negotiatePayload);
this.connectionId = negotiateResponse.connectionId;
// the user tries to stop the the connection when it is being started
if (this.connectionState == 3 /* Disconnected */) {
return;
}
this.url += (this.url.indexOf("?") == -1 ? "?" : "&") + `id=${this.connectionId}`;
this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports);
this.transport.onreceive = this.onreceive;
this.transport.onclose = e => this.stopConnection(true, e);
let requestedTransferMode = this.features.transferMode === 2 /* Binary */
? 2 /* Binary */
: 1 /* Text */;
this.features.transferMode = yield this.transport.connect(this.url, requestedTransferMode);
// only change the state if we were connecting to not overwrite
// the state if the connection is already marked as Disconnected
this.changeState(1 /* Connecting */, 2 /* Connected */);
}
catch (e) {
this.logger.log(ILogger_1.LogLevel.Error, "Failed to start the connection. " + e);
this.connectionState = 3 /* Disconnected */;
this.transport = null;
throw e;
}
;
});
}
createTransport(transport, availableTransports) {
if (!transport && availableTransports.length > 0) {
transport = Transports_1.TransportType[availableTransports[0]];
}
if (transport === Transports_1.TransportType.WebSockets && availableTransports.indexOf(Transports_1.TransportType[transport]) >= 0) {
return new Transports_1.WebSocketTransport(this.logger);
}
if (transport === Transports_1.TransportType.ServerSentEvents && availableTransports.indexOf(Transports_1.TransportType[transport]) >= 0) {
return new Transports_1.ServerSentEventsTransport(this.httpClient, this.logger);
}
if (transport === Transports_1.TransportType.LongPolling && availableTransports.indexOf(Transports_1.TransportType[transport]) >= 0) {
return new Transports_1.LongPollingTransport(this.httpClient, this.logger);
}
if (this.isITransport(transport)) {
return transport;
}
throw new Error("No available transports found.");
}
isITransport(transport) {
return typeof (transport) === "object" && "connect" in transport;
}
changeState(from, to) {
if (this.connectionState == from) {
this.connectionState = to;
return true;
}
return false;
}
send(data) {
if (this.connectionState != 2 /* Connected */) {
throw new Error("Cannot send data if the connection is not in the 'Connected' State");
}
return this.transport.send(data);
}
stop() {
return __awaiter(this, void 0, void 0, function* () {
let previousState = this.connectionState;
this.connectionState = 3 /* Disconnected */;
try {
yield this.startPromise;
}
catch (e) {
// this exception is returned to the user as a rejected Promise from the start method
}
this.stopConnection(/*raiseClosed*/ previousState == 2 /* Connected */);
});
}
stopConnection(raiseClosed, error) {
if (this.transport) {
this.transport.stop();
this.transport = null;
}
this.connectionState = 3 /* Disconnected */;
if (raiseClosed && this.onclose) {
this.onclose(error);
}
}
resolveUrl(url) {
// startsWith is not supported in IE
if (url.lastIndexOf("https://", 0) === 0 || url.lastIndexOf("http://", 0) === 0) {
return url;
}
if (typeof window === 'undefined' || !window || !window.document) {
throw new Error(`Cannot resolve '${url}'.`);
}
let parser = window.document.createElement("a");
parser.href = url;
let baseUrl = (!parser.protocol || parser.protocol === ":")
? `${window.document.location.protocol}//${(parser.host || window.document.location.host)}`
: `${parser.protocol}//${parser.host}`;
if (!url || url[0] != '/') {
url = '/' + url;
}
let normalizedUrl = baseUrl + url;
this.logger.log(ILogger_1.LogLevel.Information, `Normalizing '${url}' to '${normalizedUrl}'`);
return normalizedUrl;
}
}
exports.HttpConnection = HttpConnection;
},{"./HttpClient":3,"./ILogger":7,"./Loggers":9,"./Transports":11}],5:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
class HttpError extends Error {
constructor(errorMessage, statusCode) {
super(errorMessage);
this.statusCode = statusCode;
}
}
exports.HttpError = HttpError;
},{}],6:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const HttpConnection_1 = require("./HttpConnection");
const Observable_1 = require("./Observable");
const JsonHubProtocol_1 = require("./JsonHubProtocol");
const Formatters_1 = require("./Formatters");
const Base64EncodedHubProtocol_1 = require("./Base64EncodedHubProtocol");
const ILogger_1 = require("./ILogger");
const Loggers_1 = require("./Loggers");
var Transports_1 = require("./Transports");
exports.TransportType = Transports_1.TransportType;
var HttpConnection_2 = require("./HttpConnection");
exports.HttpConnection = HttpConnection_2.HttpConnection;
var JsonHubProtocol_2 = require("./JsonHubProtocol");
exports.JsonHubProtocol = JsonHubProtocol_2.JsonHubProtocol;
var ILogger_2 = require("./ILogger");
exports.LogLevel = ILogger_2.LogLevel;
var Loggers_2 = require("./Loggers");
exports.ConsoleLogger = Loggers_2.ConsoleLogger;
exports.NullLogger = Loggers_2.NullLogger;
class HubConnection {
constructor(urlOrConnection, options = {}) {
options = options || {};
if (typeof urlOrConnection === "string") {
this.connection = new HttpConnection_1.HttpConnection(urlOrConnection, options);
}
else {
this.connection = urlOrConnection;
}
this.logger = Loggers_1.LoggerFactory.createLogger(options.logging);
this.protocol = options.protocol || new JsonHubProtocol_1.JsonHubProtocol();
this.connection.onreceive = (data) => this.processIncomingData(data);
this.connection.onclose = (error) => this.connectionClosed(error);
this.callbacks = new Map();
this.methods = new Map();
this.closedCallbacks = [];
this.id = 0;
}
processIncomingData(data) {
// Parse the messages
let messages = this.protocol.parseMessages(data);
for (var i = 0; i < messages.length; ++i) {
var message = messages[i];
switch (message.type) {
case 1 /* Invocation */:
this.invokeClientMethod(message);
break;
case 2 /* Result */:
case 3 /* Completion */:
let callback = this.callbacks.get(message.invocationId);
if (callback != null) {
if (message.type == 3 /* Completion */) {
this.callbacks.delete(message.invocationId);
}
callback(message);
}
break;
default:
this.logger.log(ILogger_1.LogLevel.Warning, "Invalid message type: " + data);
break;
}
}
}
invokeClientMethod(invocationMessage) {
let methods = this.methods.get(invocationMessage.target.toLowerCase());
if (methods) {
methods.forEach(m => m.apply(this, invocationMessage.arguments));
if (!invocationMessage.nonblocking) {
// TODO: send result back to the server?
}
}
else {
this.logger.log(ILogger_1.LogLevel.Warning, `No client method with the name '${invocationMessage.target}' found.`);
}
}
connectionClosed(error) {
let errorCompletionMessage = {
type: 3 /* Completion */,
invocationId: "-1",
error: error ? error.message : "Invocation cancelled due to connection being closed.",
};
this.callbacks.forEach(callback => {
callback(errorCompletionMessage);
});
this.callbacks.clear();
this.closedCallbacks.forEach(c => c.apply(this, [error]));
}
start() {
return __awaiter(this, void 0, void 0, function* () {
let requestedTransferMode = (this.protocol.type === 2 /* Binary */)
? 2 /* Binary */
: 1 /* Text */;
this.connection.features.transferMode = requestedTransferMode;
yield this.connection.start();
var actualTransferMode = this.connection.features.transferMode;
yield this.connection.send(Formatters_1.TextMessageFormat.write(JSON.stringify({ protocol: this.protocol.name })));
this.logger.log(ILogger_1.LogLevel.Information, `Using HubProtocol '${this.protocol.name}'.`);
if (requestedTransferMode === 2 /* Binary */ && actualTransferMode === 1 /* Text */) {
this.protocol = new Base64EncodedHubProtocol_1.Base64EncodedHubProtocol(this.protocol);
}
});
}
stop() {
return this.connection.stop();
}
stream(methodName, ...args) {
let invocationDescriptor = this.createInvocation(methodName, args, false);
let subject = new Observable_1.Subject();
this.callbacks.set(invocationDescriptor.invocationId, (invocationEvent) => {
if (invocationEvent.type === 3 /* Completion */) {
let completionMessage = invocationEvent;
if (completionMessage.error) {
subject.error(new Error(completionMessage.error));
}
else if (completionMessage.result) {
subject.error(new Error("Server provided a result in a completion response to a streamed invocation."));
}
else {
// TODO: Log a warning if there's a payload?
subject.complete();
}
}
else {
subject.next(invocationEvent.item);
}
});
let message = this.protocol.writeMessage(invocationDescriptor);
this.connection.send(message)
.catch(e => {
subject.error(e);
this.callbacks.delete(invocationDescriptor.invocationId);
});
return subject;
}
send(methodName, ...args) {
let invocationDescriptor = this.createInvocation(methodName, args, true);
let message = this.protocol.writeMessage(invocationDescriptor);
return this.connection.send(message);
}
invoke(methodName, ...args) {
let invocationDescriptor = this.createInvocation(methodName, args, false);
let p = new Promise((resolve, reject) => {
this.callbacks.set(invocationDescriptor.invocationId, (invocationEvent) => {
if (invocationEvent.type === 3 /* Completion */) {
let completionMessage = invocationEvent;
if (completionMessage.error) {
reject(new Error(completionMessage.error));
}
else {
resolve(completionMessage.result);
}
}
else {
reject(new Error("Streaming methods must be invoked using HubConnection.stream"));
}
});
let message = this.protocol.writeMessage(invocationDescriptor);
this.connection.send(message)
.catch(e => {
reject(e);
this.callbacks.delete(invocationDescriptor.invocationId);
});
});
return p;
}
on(methodName, method) {
if (!methodName || !method) {
return;
}
methodName = methodName.toLowerCase();
if (!this.methods.has(methodName)) {
this.methods.set(methodName, []);
}
this.methods.get(methodName).push(method);
}
off(methodName, method) {
if (!methodName || !method) {
return;
}
methodName = methodName.toLowerCase();
let handlers = this.methods.get(methodName);
if (!handlers) {
return;
}
var removeIdx = handlers.indexOf(method);
if (removeIdx != -1) {
handlers.splice(removeIdx, 1);
}
}
onclose(callback) {
if (callback) {
this.closedCallbacks.push(callback);
}
}
createInvocation(methodName, args, nonblocking) {
let id = this.id;
this.id++;
return {
type: 1 /* Invocation */,
invocationId: id.toString(),
target: methodName,
arguments: args,
nonblocking: nonblocking
};
}
}
exports.HubConnection = HubConnection;
},{"./Base64EncodedHubProtocol":1,"./Formatters":2,"./HttpConnection":4,"./ILogger":7,"./JsonHubProtocol":8,"./Loggers":9,"./Observable":10,"./Transports":11}],7:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["Trace"] = 0] = "Trace";
LogLevel[LogLevel["Information"] = 1] = "Information";
LogLevel[LogLevel["Warning"] = 2] = "Warning";
LogLevel[LogLevel["Error"] = 3] = "Error";
LogLevel[LogLevel["None"] = 4] = "None";
})(LogLevel = exports.LogLevel || (exports.LogLevel = {}));
},{}],8:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const Formatters_1 = require("./Formatters");
class JsonHubProtocol {
constructor() {
this.name = "json";
this.type = 1 /* Text */;
}
parseMessages(input) {
if (!input) {
return [];
}
// Parse the messages
let messages = Formatters_1.TextMessageFormat.parse(input);
let hubMessages = [];
for (var i = 0; i < messages.length; ++i) {
hubMessages.push(JSON.parse(messages[i]));
}
return hubMessages;
}
writeMessage(message) {
return Formatters_1.TextMessageFormat.write(JSON.stringify(message));
}
}
exports.JsonHubProtocol = JsonHubProtocol;
},{"./Formatters":2}],9:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const ILogger_1 = require("./ILogger");
class NullLogger {
log(logLevel, message) {
}
}
exports.NullLogger = NullLogger;
class ConsoleLogger {
constructor(minimumLogLevel) {
this.minimumLogLevel = minimumLogLevel;
}
log(logLevel, message) {
if (logLevel >= this.minimumLogLevel) {
console.log(`${ILogger_1.LogLevel[logLevel]}: ${message}`);
}
}
}
exports.ConsoleLogger = ConsoleLogger;
var LoggerFactory;
(function (LoggerFactory) {
function createLogger(logging) {
if (logging === undefined) {
return new ConsoleLogger(ILogger_1.LogLevel.Information);
}
if (logging === null) {
return new NullLogger();
}
if (logging.log) {
return logging;
}
return new ConsoleLogger(logging);
}
LoggerFactory.createLogger = createLogger;
})(LoggerFactory = exports.LoggerFactory || (exports.LoggerFactory = {}));
},{"./ILogger":7}],10:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
class Subject {
constructor() {
this.observers = [];
}
next(item) {
for (let observer of this.observers) {
observer.next(item);
}
}
error(err) {
for (let observer of this.observers) {
observer.error(err);
}
}
complete() {
for (let observer of this.observers) {
observer.complete();
}
}
subscribe(observer) {
this.observers.push(observer);
}
}
exports.Subject = Subject;
},{}],11:[function(require,module,exports){
"use strict";
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const HttpError_1 = require("./HttpError");
const ILogger_1 = require("./ILogger");
var TransportType;
(function (TransportType) {
TransportType[TransportType["WebSockets"] = 0] = "WebSockets";
TransportType[TransportType["ServerSentEvents"] = 1] = "ServerSentEvents";
TransportType[TransportType["LongPolling"] = 2] = "LongPolling";
})(TransportType = exports.TransportType || (exports.TransportType = {}));
class WebSocketTransport {
constructor(logger) {
this.logger = logger;
}
connect(url, requestedTransferMode) {
return new Promise((resolve, reject) => {
url = url.replace(/^http/, "ws");
let webSocket = new WebSocket(url);
if (requestedTransferMode == 2 /* Binary */) {
webSocket.binaryType = "arraybuffer";
}
webSocket.onopen = (event) => {
this.logger.log(ILogger_1.LogLevel.Information, `WebSocket connected to ${url}`);
this.webSocket = webSocket;
resolve(requestedTransferMode);
};
webSocket.onerror = (event) => {
reject();
};
webSocket.onmessage = (message) => {
this.logger.log(ILogger_1.LogLevel.Trace, `(WebSockets transport) data received: ${message.data}`);
if (this.onreceive) {
this.onreceive(message.data);
}
};
webSocket.onclose = (event) => {
// webSocket will be null if the transport did not start successfully
if (this.onclose && this.webSocket) {
if (event.wasClean === false || event.code !== 1000) {
this.onclose(new Error(`Websocket closed with status code: ${event.code} (${event.reason})`));
}
else {
this.onclose();
}
}
};
});
}
send(data) {
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
this.webSocket.send(data);
return Promise.resolve();
}
return Promise.reject("WebSocket is not in the OPEN state");
}
stop() {
if (this.webSocket) {
this.webSocket.close();
this.webSocket = null;
}
}
}
exports.WebSocketTransport = WebSocketTransport;
class ServerSentEventsTransport {
constructor(httpClient, logger) {
this.httpClient = httpClient;
this.logger = logger;
}
connect(url, requestedTransferMode) {
if (typeof (EventSource) === "undefined") {
Promise.reject("EventSource not supported by the browser.");
}
this.url = url;
return new Promise((resolve, reject) => {
let eventSource = new EventSource(this.url);
try {
eventSource.onmessage = (e) => {
if (this.onreceive) {
try {
this.logger.log(ILogger_1.LogLevel.Trace, `(SSE transport) data received: ${e.data}`);
this.onreceive(e.data);
}
catch (error) {
if (this.onclose) {
this.onclose(error);
}
return;
}
}
};
eventSource.onerror = (e) => {
reject();
// don't report an error if the transport did not start successfully
if (this.eventSource && this.onclose) {
this.onclose(new Error(e.message || "Error occurred"));
}
};
eventSource.onopen = () => {
this.logger.log(ILogger_1.LogLevel.Information, `SSE connected to ${this.url}`);
this.eventSource = eventSource;
// SSE is a text protocol
resolve(1 /* Text */);
};
}
catch (e) {
return Promise.reject(e);
}
});
}
send(data) {
return __awaiter(this, void 0, void 0, function* () {
return send(this.httpClient, this.url, data);
});
}
stop() {
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
}
}
exports.ServerSentEventsTransport = ServerSentEventsTransport;
class LongPollingTransport {
constructor(httpClient, logger) {
this.httpClient = httpClient;
this.logger = logger;
}
connect(url, requestedTransferMode) {
this.url = url;
this.shouldPoll = true;
if (requestedTransferMode === 2 /* Binary */ && (typeof new XMLHttpRequest().responseType !== "string")) {
// This will work if we fix: https://github.com/aspnet/SignalR/issues/742
throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");
}
this.poll(this.url, requestedTransferMode);
return Promise.resolve(requestedTransferMode);
}
poll(url, transferMode) {
if (!this.shouldPoll) {
return;
}
let pollXhr = new XMLHttpRequest();
pollXhr.onload = () => {
if (pollXhr.status == 200) {
if (this.onreceive) {
try {
let response = transferMode === 1 /* Text */
? pollXhr.responseText
: pollXhr.response;
if (response) {
this.logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) data received: ${response}`);
this.onreceive(response);
}
else {
this.logger.log(ILogger_1.LogLevel.Information, "(LongPolling transport) timed out");
}
}
catch (error) {
if (this.onclose) {
this.onclose(error);
}
return;
}
}
this.poll(url, transferMode);
}
else if (this.pollXhr.status == 204) {
if (this.onclose) {
this.onclose();
}
}
else {
if (this.onclose) {
this.onclose(new HttpError_1.HttpError(pollXhr.statusText, pollXhr.status));
}
}
};
pollXhr.onerror = () => {
if (this.onclose) {
// network related error or denied cross domain request
this.onclose(new Error("Sending HTTP request failed."));
}
};
pollXhr.ontimeout = () => {
this.poll(url, transferMode);
};
this.pollXhr = pollXhr;
this.pollXhr.open("GET", `${url}&_=${Date.now()}`, true);
if (transferMode === 2 /* Binary */) {
this.pollXhr.responseType = "arraybuffer";
}
// TODO: consider making timeout configurable
this.pollXhr.timeout = 120000;
this.pollXhr.send();
}
send(data) {
return __awaiter(this, void 0, void 0, function* () {
return send(this.httpClient, this.url, data);
});
}
stop() {
this.shouldPoll = false;
if (this.pollXhr) {
this.pollXhr.abort();
this.pollXhr = null;
}
}
}
exports.LongPollingTransport = LongPollingTransport;
const headers = new Map();
function send(httpClient, url, data) {
return __awaiter(this, void 0, void 0, function* () {
yield httpClient.post(url, data, headers);
});
}
},{"./HttpError":5,"./ILogger":7}]},{},[6])(6)
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment