Introducing modules for the ts client
This commit is contained in:
parent
1b59fc6f80
commit
2039a18971
|
|
@ -35,7 +35,7 @@ node_modules/
|
||||||
*.nuget.props
|
*.nuget.props
|
||||||
*.nuget.targets
|
*.nuget.targets
|
||||||
autobahnreports/
|
autobahnreports/
|
||||||
signalr-client.js
|
|
||||||
site.min.css
|
site.min.css
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
signalr-client/
|
||||||
|
|
|
||||||
14
package.json
14
package.json
|
|
@ -6,19 +6,27 @@
|
||||||
"directories": {
|
"directories": {
|
||||||
"test": "test"
|
"test": "test"
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {
|
||||||
|
"gulp": "gulp"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/aspnet/SignalR.git"
|
"url": "git+https://github.com/aspnet/SignalR.git"
|
||||||
},
|
},
|
||||||
"author": "Microsoft",
|
"author": "Microsoft",
|
||||||
"license": "MIT",
|
"license": "Apache-2.0",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/aspnet/SignalR/issues"
|
"url": "https://github.com/aspnet/SignalR/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/aspnet/SignalR#readme",
|
"homepage": "https://github.com/aspnet/SignalR#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"browserify": "^13.1.1",
|
||||||
|
"del": "^2.2.2",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"jasmine": "^2.5.2"
|
"gulp-typescript": "^3.1.3",
|
||||||
|
"jasmine": "^2.5.2",
|
||||||
|
"typescript": "^2.0.10",
|
||||||
|
"vinyl-source-stream": "^1.1.0",
|
||||||
|
"yargs": "^6.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,19 @@
|
||||||
ViewData["Title"] = "Chat";
|
ViewData["Title"] = "Chat";
|
||||||
}
|
}
|
||||||
|
|
||||||
<script src="~/js/signalr-client.js"></script>
|
<div id="chat-area">
|
||||||
|
<ul id="messages"></ul>
|
||||||
|
<ul id="users"></ul>
|
||||||
|
<div class="clear">
|
||||||
|
</div>
|
||||||
|
<form id="sendmessage" action="#">
|
||||||
|
<input type="text" id="new-message" />
|
||||||
|
<input type="submit" id="send" value="Send" class="send" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script src="lib/signalr-client/signalr-client.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
let connection = new signalR.RpcConnection(`http://${document.location.host}/chat`, 'formatType=json&format=text');
|
||||||
let connection = new RpcConnection(`http://${document.location.host}/chat`, 'formatType=json&format=text');
|
|
||||||
|
|
||||||
connection.on('Send', function (message) {
|
connection.on('Send', function (message) {
|
||||||
var child = document.createElement('li');
|
var child = document.createElement('li');
|
||||||
|
|
@ -27,16 +36,4 @@
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="chat-area">
|
|
||||||
<ul id="messages"></ul>
|
|
||||||
<ul id="users"></ul>
|
|
||||||
<div class="clear">
|
|
||||||
</div>
|
|
||||||
<form id="sendmessage" action="#">
|
|
||||||
<input type="text" id="new-message" />
|
|
||||||
<input type="submit" id="send" value="Send" class="send" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
@ -91,7 +91,9 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"precompile": [ "dotnet bundle", "tsc --project ../../src/Microsoft.AspNetCore.SignalR.Client.TS/ --out wwwroot/js/signalr-client.js" ],
|
"precompile": [ "dotnet bundle",
|
||||||
|
"npm install",
|
||||||
|
"npm run gulp -- --gulpfile %project:Directory%/../../src/Microsoft.AspNetCore.SignalR.Client.TS/gulpfile.js bundle-client --bundleOutDir %project:Directory%/wwwroot/lib/signalr-client/" ],
|
||||||
"prepublish": [ "bower install" ],
|
"prepublish": [ "bower install" ],
|
||||||
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
/// <autosync enabled="true" />
|
/// <autosync enabled="true" />
|
||||||
/// <reference path="js/signalr-client.js" />
|
|
||||||
/// <reference path="js/site.js" />
|
/// <reference path="js/site.js" />
|
||||||
/// <reference path="lib/bootstrap/dist/js/bootstrap.js" />
|
/// <reference path="lib/bootstrap/dist/js/bootstrap.js" />
|
||||||
/// <reference path="lib/jquery/dist/jquery.js" />
|
/// <reference path="lib/jquery/dist/jquery.js" />
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"precompile": [ "tsc --project ../../src/Microsoft.AspNetCore.SignalR.Client.TS/ --out wwwroot/js/signalr-client.js" ],
|
"precompile": [
|
||||||
|
"npm install",
|
||||||
|
"npm run gulp -- --gulpfile %project:Directory%/../../src/Microsoft.AspNetCore.SignalR.Client.TS/gulpfile.js bundle-client --bundleOutDir %project:Directory%/wwwroot/lib/signalr-client/" ],
|
||||||
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
js/
|
|
||||||
|
|
@ -3,7 +3,55 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title></title>
|
<title></title>
|
||||||
<script src="js/signalr-client.js"></script>
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id="head1"></h1>
|
||||||
|
<div>
|
||||||
|
<select id="formatType">
|
||||||
|
<option value="json">json</option>
|
||||||
|
<option value="line">line</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type="button" id="connect" value="Connect" />
|
||||||
|
<input type="button" id="disconnect" value="Disconnect" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h4>To Everybody</h4>
|
||||||
|
<form class="form-inline">
|
||||||
|
<div class="input-append">
|
||||||
|
<input type="text" id="msg" placeholder="Type a message, name or group" />
|
||||||
|
<input type="button" id="broadcast" class="btn" value="Broadcast" />
|
||||||
|
<input type="button" id="broadcast-exceptme" class="btn" value="Broadcast (All Except Me)" />
|
||||||
|
<input type="button" id="join" class="btn" value="Enter Name" />
|
||||||
|
<input type="button" id="join-group" class="btn" value="Join Group" />
|
||||||
|
<input type="button" id="leave-group" class="btn" value="Leave Group" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h4>To Me</h4>
|
||||||
|
<form class="form-inline">
|
||||||
|
<div class="input-append">
|
||||||
|
<input type="text" id="me" placeholder="Type a message" />
|
||||||
|
<input type="button" id="send" class="btn" value="Send to me" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h4>Private Message</h4>
|
||||||
|
<form class="form-inline">
|
||||||
|
<div class="input-prepend input-append">
|
||||||
|
<input type="text" name="message" id="message" placeholder="Type a message" />
|
||||||
|
<input type="text" name="user" id="target" placeholder="Type a user or group name" />
|
||||||
|
|
||||||
|
<input type="button" id="privatemsg" class="btn" value="Send to user" />
|
||||||
|
<input type="button" id="groupmsg" class="btn" value="Send to group" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ul id="messages"></ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<script src="lib/signalr-client/signalr-client.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var isConnected = false;
|
var isConnected = false;
|
||||||
function getParameterByName(name, url) {
|
function getParameterByName(name, url) {
|
||||||
|
|
@ -47,12 +95,20 @@
|
||||||
return document.getElementById(id).value;
|
return document.getElementById(id).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
function addLine(line, color) {
|
||||||
|
var child = document.createElement('li');
|
||||||
|
if (color) {
|
||||||
|
child.style.color = color;
|
||||||
|
}
|
||||||
|
child.innerText = line;
|
||||||
|
document.getElementById('messages').appendChild(child);
|
||||||
|
}
|
||||||
|
|
||||||
let transport = getParameterByName('transport') || 'webSockets';
|
let transport = getParameterByName('transport') || 'webSockets';
|
||||||
|
|
||||||
document.getElementById('head1').innerHTML = transport;
|
document.getElementById('head1').innerHTML = transport;
|
||||||
|
|
||||||
let connection = new RpcConnection(`http://${document.location.host}/hubs`, 'formatType=json&format=text');
|
let connection = new signalR.RpcConnection(`http://${document.location.host}/hubs`, 'formatType=json&format=text');
|
||||||
connection.on('Send', msg => {
|
connection.on('Send', msg => {
|
||||||
addLine(msg);
|
addLine(msg);
|
||||||
});
|
});
|
||||||
|
|
@ -102,62 +158,5 @@
|
||||||
let message = getText('message');
|
let message = getText('message');
|
||||||
invoke(connection, 'SocketsSample.Hubs.Chat.SendToGroup', groupName, message);
|
invoke(connection, 'SocketsSample.Hubs.Chat.SendToGroup', groupName, message);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
function addLine(line, color) {
|
|
||||||
var child = document.createElement('li');
|
|
||||||
if (color) {
|
|
||||||
child.style.color = color;
|
|
||||||
}
|
|
||||||
child.innerText = line;
|
|
||||||
document.getElementById('messages').appendChild(child);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1 id="head1"></h1>
|
|
||||||
<div>
|
|
||||||
<select id="formatType">
|
|
||||||
<option value="json">json</option>
|
|
||||||
<option value="line">line</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<input type="button" id="connect" value="Connect" />
|
|
||||||
<input type="button" id="disconnect" value="Disconnect" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<h4>To Everybody</h4>
|
|
||||||
<form class="form-inline">
|
|
||||||
<div class="input-append">
|
|
||||||
<input type="text" id="msg" placeholder="Type a message, name or group" />
|
|
||||||
<input type="button" id="broadcast" class="btn" value="Broadcast" />
|
|
||||||
<input type="button" id="broadcast-exceptme" class="btn" value="Broadcast (All Except Me)" />
|
|
||||||
<input type="button" id="join" class="btn" value="Enter Name" />
|
|
||||||
<input type="button" id="join-group" class="btn" value="Join Group" />
|
|
||||||
<input type="button" id="leave-group" class="btn" value="Leave Group" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h4>To Me</h4>
|
|
||||||
<form class="form-inline">
|
|
||||||
<div class="input-append">
|
|
||||||
<input type="text" id="me" placeholder="Type a message" />
|
|
||||||
<input type="button" id="send" class="btn" value="Send to me" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h4>Private Message</h4>
|
|
||||||
<form class="form-inline">
|
|
||||||
<div class="input-prepend input-append">
|
|
||||||
<input type="text" name="message" id="message" placeholder="Type a message" />
|
|
||||||
<input type="text" name="user" id="target" placeholder="Type a user or group name" />
|
|
||||||
|
|
||||||
<input type="button" id="privatemsg" class="btn" value="Send to user" />
|
|
||||||
<input type="button" id="groupmsg" class="btn" value="Send to group" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<ul id="messages"></ul>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { ITransport, WebSocketTransport, ServerSentEventsTransport, LongPollingTransport } from "./Transports"
|
||||||
|
import { HttpClient } from "./HttpClient"
|
||||||
|
|
||||||
enum ConnectionState {
|
enum ConnectionState {
|
||||||
Disconnected,
|
Disconnected,
|
||||||
|
|
@ -5,7 +7,7 @@ enum ConnectionState {
|
||||||
Connected
|
Connected
|
||||||
}
|
}
|
||||||
|
|
||||||
class Connection {
|
export class Connection {
|
||||||
private connectionState: ConnectionState;
|
private connectionState: ConnectionState;
|
||||||
private url: string;
|
private url: string;
|
||||||
private queryString: string;
|
private queryString: string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
class HttpClient {
|
export class HttpClient {
|
||||||
get(url: string): Promise<string> {
|
get(url: string): Promise<string> {
|
||||||
return this.xhr("GET", url);
|
return this.xhr("GET", url);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
interface ITransport {
|
|
||||||
connect(url: string, queryString: string): Promise<void>;
|
|
||||||
send(data: any): Promise<void>;
|
|
||||||
stop(): void;
|
|
||||||
onDataReceived: DataReceived;
|
|
||||||
onError: ErrorHandler;
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
class LongPollingTransport implements ITransport {
|
|
||||||
private url: string;
|
|
||||||
private queryString: string;
|
|
||||||
private pollXhr: XMLHttpRequest;
|
|
||||||
|
|
||||||
connect(url: string, queryString: string): Promise<void> {
|
|
||||||
this.url = url;
|
|
||||||
this.queryString = queryString;
|
|
||||||
this.poll(url + "/poll?" + this.queryString)
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
private poll(url: string): void {
|
|
||||||
let thisLongPollingTransport = this;
|
|
||||||
let pollXhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
pollXhr.onload = () => {
|
|
||||||
if (pollXhr.status == 200) {
|
|
||||||
if (thisLongPollingTransport.onDataReceived) {
|
|
||||||
thisLongPollingTransport.onDataReceived(pollXhr.response);
|
|
||||||
}
|
|
||||||
thisLongPollingTransport.poll(url);
|
|
||||||
}
|
|
||||||
else if (this.pollXhr.status == 204) {
|
|
||||||
// TODO: closed event?
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (thisLongPollingTransport.onError) {
|
|
||||||
thisLongPollingTransport.onError({
|
|
||||||
status: pollXhr.status,
|
|
||||||
statusText: pollXhr.statusText
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pollXhr.onerror = () => {
|
|
||||||
if (thisLongPollingTransport.onError) {
|
|
||||||
thisLongPollingTransport.onError({
|
|
||||||
status: pollXhr.status,
|
|
||||||
statusText: pollXhr.statusText
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pollXhr.ontimeout = () => {
|
|
||||||
thisLongPollingTransport.poll(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pollXhr = pollXhr;
|
|
||||||
this.pollXhr.open("GET", url, true);
|
|
||||||
// TODO: consider making timeout configurable
|
|
||||||
this.pollXhr.timeout = 110000;
|
|
||||||
this.pollXhr.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
send(data: any): Promise<void> {
|
|
||||||
return new HttpClient().post(this.url + "/send?" + this.queryString, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
stop(): void {
|
|
||||||
if (this.pollXhr) {
|
|
||||||
this.pollXhr.abort();
|
|
||||||
this.pollXhr = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDataReceived: DataReceived;
|
|
||||||
onError: ErrorHandler;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Connection } from "./Connection"
|
||||||
|
|
||||||
interface InvocationDescriptor {
|
interface InvocationDescriptor {
|
||||||
readonly Id: string;
|
readonly Id: string;
|
||||||
readonly Method: string;
|
readonly Method: string;
|
||||||
|
|
@ -10,7 +12,7 @@ interface InvocationResultDescriptor {
|
||||||
readonly Result: any;
|
readonly Result: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RpcConnection {
|
export class RpcConnection {
|
||||||
private connection: Connection;
|
private connection: Connection;
|
||||||
private callbacks: Map<string, (any) => void>;
|
private callbacks: Map<string, (any) => void>;
|
||||||
private methods: Map<string, (...args:any[]) => void>;
|
private methods: Map<string, (...args:any[]) => void>;
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// TODO: need EvenSource typings
|
|
||||||
|
|
||||||
class ServerSentEventsTransport implements ITransport {
|
|
||||||
private eventSource: EventSource;
|
|
||||||
private url: string;
|
|
||||||
private queryString: string;
|
|
||||||
|
|
||||||
connect(url: string, queryString: string): Promise<void> {
|
|
||||||
if (typeof (EventSource) === "undefined") {
|
|
||||||
Promise.reject("EventSource not supported by the browser.")
|
|
||||||
}
|
|
||||||
|
|
||||||
this.queryString = queryString;
|
|
||||||
this.url = url;
|
|
||||||
let tmp = `${this.url}/sse?${this.queryString}`;
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let eventSource = new EventSource(`${this.url}/sse?${this.queryString}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let thisEventSourceTransport = this;
|
|
||||||
eventSource.onmessage = (e: MessageEvent) => {
|
|
||||||
if (thisEventSourceTransport.onDataReceived) {
|
|
||||||
thisEventSourceTransport.onDataReceived(e.data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
eventSource.onerror = (e: Event) => {
|
|
||||||
reject();
|
|
||||||
|
|
||||||
// don't report an error if the transport did not start successfully
|
|
||||||
if (thisEventSourceTransport.eventSource && thisEventSourceTransport.onError) {
|
|
||||||
thisEventSourceTransport.onError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventSource.onopen = () => {
|
|
||||||
thisEventSourceTransport.eventSource = eventSource;
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
send(data: any): Promise<void> {
|
|
||||||
return new HttpClient().post(this.url + "/send?" + this.queryString, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
stop(): void {
|
|
||||||
if (this.eventSource) {
|
|
||||||
this.eventSource.close();
|
|
||||||
this.eventSource = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDataReceived: DataReceived;
|
|
||||||
onError: ErrorHandler;
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
import { HttpClient } from "./HttpClient"
|
||||||
|
|
||||||
|
export interface ITransport {
|
||||||
|
connect(url: string, queryString: string): Promise<void>;
|
||||||
|
send(data: any): Promise<void>;
|
||||||
|
stop(): void;
|
||||||
|
onDataReceived: DataReceived;
|
||||||
|
onError: ErrorHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WebSocketTransport implements ITransport {
|
||||||
|
private webSocket: WebSocket;
|
||||||
|
|
||||||
|
connect(url: string, queryString: string = ""): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
url = url.replace(/^http/, "ws");
|
||||||
|
let connectUrl = url + "/ws?" + queryString;
|
||||||
|
|
||||||
|
let webSocket = new WebSocket(connectUrl);
|
||||||
|
let thisWebSocketTransport = this;
|
||||||
|
|
||||||
|
webSocket.onopen = (event: Event) => {
|
||||||
|
console.log(`WebSocket connected to ${connectUrl}`);
|
||||||
|
thisWebSocketTransport.webSocket = webSocket;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
webSocket.onerror = (event: Event) => {
|
||||||
|
reject();
|
||||||
|
};
|
||||||
|
|
||||||
|
webSocket.onmessage = (message: MessageEvent) => {
|
||||||
|
console.log(`(WebSockets transport) data received: ${message.data}`);
|
||||||
|
if (thisWebSocketTransport.onDataReceived) {
|
||||||
|
thisWebSocketTransport.onDataReceived(message.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webSocket.onclose = (event: CloseEvent) => {
|
||||||
|
// webSocket will be null if the transport did not start successfully
|
||||||
|
if (thisWebSocketTransport.webSocket && event.wasClean === false) {
|
||||||
|
if (thisWebSocketTransport.onError) {
|
||||||
|
thisWebSocketTransport.onError(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data: any): Promise<void> {
|
||||||
|
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
|
||||||
|
this.webSocket.send(data);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject("WebSocket is not in OPEN state");
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
if (this.webSocket) {
|
||||||
|
this.webSocket.close();
|
||||||
|
this.webSocket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDataReceived: DataReceived;
|
||||||
|
onError: ErrorHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServerSentEventsTransport implements ITransport {
|
||||||
|
private eventSource: EventSource;
|
||||||
|
private url: string;
|
||||||
|
private queryString: string;
|
||||||
|
|
||||||
|
connect(url: string, queryString: string): Promise<void> {
|
||||||
|
if (typeof (EventSource) === "undefined") {
|
||||||
|
Promise.reject("EventSource not supported by the browser.")
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queryString = queryString;
|
||||||
|
this.url = url;
|
||||||
|
let tmp = `${this.url}/sse?${this.queryString}`;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let eventSource = new EventSource(`${this.url}/sse?${this.queryString}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let thisEventSourceTransport = this;
|
||||||
|
eventSource.onmessage = (e: MessageEvent) => {
|
||||||
|
if (thisEventSourceTransport.onDataReceived) {
|
||||||
|
thisEventSourceTransport.onDataReceived(e.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eventSource.onerror = (e: Event) => {
|
||||||
|
reject();
|
||||||
|
|
||||||
|
// don't report an error if the transport did not start successfully
|
||||||
|
if (thisEventSourceTransport.eventSource && thisEventSourceTransport.onError) {
|
||||||
|
thisEventSourceTransport.onError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventSource.onopen = () => {
|
||||||
|
thisEventSourceTransport.eventSource = eventSource;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data: any): Promise<void> {
|
||||||
|
return new HttpClient().post(this.url + "/send?" + this.queryString, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
if (this.eventSource) {
|
||||||
|
this.eventSource.close();
|
||||||
|
this.eventSource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDataReceived: DataReceived;
|
||||||
|
onError: ErrorHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LongPollingTransport implements ITransport {
|
||||||
|
private url: string;
|
||||||
|
private queryString: string;
|
||||||
|
private pollXhr: XMLHttpRequest;
|
||||||
|
|
||||||
|
connect(url: string, queryString: string): Promise<void> {
|
||||||
|
this.url = url;
|
||||||
|
this.queryString = queryString;
|
||||||
|
this.poll(url + "/poll?" + this.queryString)
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
private poll(url: string): void {
|
||||||
|
let thisLongPollingTransport = this;
|
||||||
|
let pollXhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
pollXhr.onload = () => {
|
||||||
|
if (pollXhr.status == 200) {
|
||||||
|
if (thisLongPollingTransport.onDataReceived) {
|
||||||
|
thisLongPollingTransport.onDataReceived(pollXhr.response);
|
||||||
|
}
|
||||||
|
thisLongPollingTransport.poll(url);
|
||||||
|
}
|
||||||
|
else if (this.pollXhr.status == 204) {
|
||||||
|
// TODO: closed event?
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (thisLongPollingTransport.onError) {
|
||||||
|
thisLongPollingTransport.onError({
|
||||||
|
status: pollXhr.status,
|
||||||
|
statusText: pollXhr.statusText
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pollXhr.onerror = () => {
|
||||||
|
if (thisLongPollingTransport.onError) {
|
||||||
|
thisLongPollingTransport.onError({
|
||||||
|
status: pollXhr.status,
|
||||||
|
statusText: pollXhr.statusText
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pollXhr.ontimeout = () => {
|
||||||
|
thisLongPollingTransport.poll(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pollXhr = pollXhr;
|
||||||
|
this.pollXhr.open("GET", url, true);
|
||||||
|
// TODO: consider making timeout configurable
|
||||||
|
this.pollXhr.timeout = 110000;
|
||||||
|
this.pollXhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data: any): Promise<void> {
|
||||||
|
return new HttpClient().post(this.url + "/send?" + this.queryString, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
if (this.pollXhr) {
|
||||||
|
this.pollXhr.abort();
|
||||||
|
this.pollXhr = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDataReceived: DataReceived;
|
||||||
|
onError: ErrorHandler;
|
||||||
|
}
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
class WebSocketTransport implements ITransport {
|
|
||||||
private webSocket: WebSocket;
|
|
||||||
|
|
||||||
connect(url: string, queryString: string = ""): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
url = url.replace(/^http/, "ws");
|
|
||||||
let connectUrl = url + "/ws?" + queryString;
|
|
||||||
|
|
||||||
let webSocket = new WebSocket(connectUrl);
|
|
||||||
let thisWebSocketTransport = this;
|
|
||||||
|
|
||||||
webSocket.onopen = (event: Event) => {
|
|
||||||
console.log(`WebSocket connected to ${connectUrl}`);
|
|
||||||
thisWebSocketTransport.webSocket = webSocket;
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
webSocket.onerror = (event: Event) => {
|
|
||||||
reject();
|
|
||||||
};
|
|
||||||
|
|
||||||
webSocket.onmessage = (message: MessageEvent) => {
|
|
||||||
console.log(`(WebSockets transport) data received: ${message.data}`);
|
|
||||||
if (thisWebSocketTransport.onDataReceived) {
|
|
||||||
thisWebSocketTransport.onDataReceived(message.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
webSocket.onclose = (event: CloseEvent) => {
|
|
||||||
// webSocket will be null if the transport did not start successfully
|
|
||||||
if (thisWebSocketTransport.webSocket && event.wasClean === false) {
|
|
||||||
if (thisWebSocketTransport.onError) {
|
|
||||||
thisWebSocketTransport.onError(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
send(data: any): Promise<void> {
|
|
||||||
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
|
|
||||||
this.webSocket.send(data);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject("WebSocket is not in OPEN state");
|
|
||||||
}
|
|
||||||
|
|
||||||
stop(): void {
|
|
||||||
if (this.webSocket) {
|
|
||||||
this.webSocket.close();
|
|
||||||
this.webSocket = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDataReceived: DataReceived;
|
|
||||||
onError: ErrorHandler;
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
const gulp = require('gulp');
|
||||||
|
const browserify = require('browserify');
|
||||||
|
const ts = require('gulp-typescript');
|
||||||
|
const source = require('vinyl-source-stream');
|
||||||
|
const del = require('del');
|
||||||
|
const argv = require('yargs').argv;
|
||||||
|
|
||||||
|
const tsProject = ts.createProject('./tsconfig.json');
|
||||||
|
const clientOutDir = tsProject.options.outDir;
|
||||||
|
|
||||||
|
gulp.task('clean', () => {
|
||||||
|
return del([clientOutDir + '/..'], { force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('compile-ts-client', () => {
|
||||||
|
return tsProject.src()
|
||||||
|
.pipe(tsProject())
|
||||||
|
.pipe(gulp.dest(clientOutDir));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('browserify-client', ['compile-ts-client'], () => {
|
||||||
|
return browserify(clientOutDir + '/RpcConnection.js', {standalone: 'signalR'})
|
||||||
|
.bundle()
|
||||||
|
.pipe(source('signalr-client.js'))
|
||||||
|
.pipe(gulp.dest(clientOutDir + '/../signalr-client-bundle'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('build-ts-client', ['clean', 'compile-ts-client', 'browserify-client']);
|
||||||
|
|
||||||
|
gulp.task('bundle-client', ['build-ts-client'], () => {
|
||||||
|
if (!argv.bundleOutDir) {
|
||||||
|
console.log('Use \'--bundleOutDir\' option to specify the target file for the bundled client.');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return gulp.src(clientOutDir + '/../signalr-client-bundle/signalr-client.js')
|
||||||
|
.pipe(gulp.dest(argv.bundleOutDir));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('default', ['build-ts-client']);
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
{
|
{
|
||||||
"compileOnSave": true,
|
"compileOnSave": true,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"module": "umd",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"out": "../../samples/SocketsSample/wwwroot/js/signalr-client.js"
|
"outDir": "../../artifacts/lib/signalr-client-modules"
|
||||||
},
|
},
|
||||||
"include": [ "*.ts" ]
|
"include": [ "*.ts" ]
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"precompile": [ "npm install", "gulp copy-jasmine"],
|
"precompile": [ "npm install", "npm run gulp -- --gulpfile %project:Directory%/gulpfile.js copy-jasmine"],
|
||||||
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue