ts-client WebSockets + JSON

This commit is contained in:
moozzyk 2016-10-24 17:51:37 -07:00
parent 5e3be6e212
commit 6859d33536
7 changed files with 239 additions and 1 deletions

View File

@ -0,0 +1 @@
js/

View File

@ -3,7 +3,55 @@
<head>
<meta charset="utf-8" />
<title></title>
<script src="js/signalr-client.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
let connectButton = document.getElementById('connect');
let connection = new RpcConnection(`http://${document.location.host}/hubs`);
connection.on('Send',(msg) => { addLine(msg); });
let isConnected = false;
connectButton.addEventListener('click', () => {
connection.start()
.then(() => {
isConnected = true;
})
.catch(err => {
addLine(err, true);
});
});
document.getElementById('sendmessage').addEventListener('submit', event => {
let data = document.getElementById('data').value;
if (isConnected) {
connection.invoke('SocketsSample.Hubs.Chat.Send', data)
.then(result => {
console.log("invocation completed successfully: " + (result === null ? '(null)' : result));
if (result != null) {
addLine(result);
}
})
.catch(err => {
addLine(err, true);
});
}
event.preventDefault();
});
});
function addLine(line, isError) {
var child = document.createElement('li');
if (isError === true) {
child.style.color = 'red';
}
child.innerText = line;
document.getElementById('messages').appendChild(child);
}
/*
function hubConnection(url) {
var ws = new WebSocket(url);
var id = 0;
@ -108,7 +156,7 @@
event.preventDefault();
});
};
*/
</script>
</head>
<body>

View File

@ -0,0 +1,35 @@
class HttpClient {
get(url: string): Promise<string> {
return this.xhr("GET", url);
}
post(url: string): Promise<string> {
return this.xhr("POST", url);
}
private xhr(method: string, url: string): Promise<string> {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.send();
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
}
else {
reject({
status: xhr.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = () => {
reject({
status: xhr.status,
statusText: xhr.statusText
});
};
});
}
}

View File

@ -0,0 +1,5 @@
interface ITransport {
connect(url: string, queryString: string): Promise<void>;
send(data: string): Promise<void>;
stop(): void;
}

View File

@ -0,0 +1,101 @@
interface InvocationDescriptor {
readonly Id: string;
readonly Method: string;
readonly Arguments: Array<any>;
}
interface InvocationResultDescriptor {
readonly Id: string;
readonly Error: string;
readonly Result: any;
}
class RpcConnection {
// TODO: add connection state
private url: string;
private queryString: string;
private callbacks: Map<string, (any) => void>;
private methods: Map<string, (...args:any[]) => void>;
private transport: ITransport;
private id: number;
constructor(url: string, queryString: string) {
this.url = url;
this.queryString = queryString;
this.callbacks = new Map<string, (any) => void>();
this.methods = new Map<string, (...args:any[]) => void>();
this.id = 0;
}
private messageReceived(data: string) {
//TODO: separate JSON parsing
var descriptor = JSON.parse(data);
if (descriptor.Method === undefined) {
let invocationResult: InvocationResultDescriptor = descriptor;
let callback = this.callbacks[invocationResult.Id];
if (callback != null) {
callback(invocationResult);
this.callbacks.delete(invocationResult.Id);
}
}
else {
let invocation: InvocationDescriptor = descriptor;
let method = this.methods[invocation.Method];
if (method != null) {
// TODO: bind? args?
method.apply(this, invocation.Arguments);
}
}
}
start(): Promise<void> {
return new Promise((resolve, reject) => {
new HttpClient().get(this.url + "/getid?" + this.queryString)
.then(id => {
this.transport = new WebSocketTransport(data => this.messageReceived(data));
return this.transport.connect(this.url, `id=${id}`);
})
.then(() => {
resolve();
})
.catch(() => {
reject();
});
});
}
stop(): void {
this.transport.stop();
}
invoke(methodName: string, ...args: any[]): Promise<void> {
let invocationDescriptor: InvocationDescriptor = {
"Id": this.id.toString(),
"Method": methodName,
"Arguments": args
};
let p = new Promise<any>((resolve, reject) => {
this.callbacks[this.id] = (invocationResult: InvocationResultDescriptor) => {
if (invocationResult.Error != null) {
reject(invocationResult.Error);
}
else {
resolve(invocationResult.Result);
}
};
this.transport.send(JSON.stringify(invocationDescriptor))
.catch(e => reject(e));
});
this.id++;
//TODO: separate conversion to enable different data formats
return p;
}
on(methodName: string, method: (...args: any[]) => void) {
this.methods[methodName] = method;
}
}

View File

@ -0,0 +1,40 @@
class WebSocketTransport implements ITransport {
private webSocket: WebSocket;
// TODO: make the callback a named type
// TODO: string won't work for binary formats
private receiveCallback: (data: string) => void;
constructor(receiveCallback: (data: string) => void) {
this.receiveCallback = receiveCallback;
}
connect(url: string, queryString: string): Promise<void> {
return new Promise((resolve, reject) => {
url = url.replace(/^http/, "ws");
let connectUrl = url + "/ws?" + queryString;
this.webSocket = new WebSocket(connectUrl);
this.webSocket.onopen = (event: Event) => {
console.log(`WebSocket connected to ${connectUrl}`);
resolve();
};
this.webSocket.onerror = (event: Event) => {
// TODO: handle when connection was opened successfully
reject();
};
this.webSocket.onmessage = (message: MessageEvent) => {
this.receiveCallback(message.data);
}
});
}
send(data: string): Promise<void> {
this.webSocket.send(data);
return Promise.resolve();
}
stop(): void {
this.webSocket.close();
}
}

View File

@ -0,0 +1,8 @@
{
"compileOnSave": true,
"compilerOptions": {
"target": "es6",
"out": "../../samples/SocketsSample/wwwroot/js/signalr-client.js"
},
"include": [ "*.ts" ]
}