From 6859d33536ba2ee71888668378e2a09b1faf739d Mon Sep 17 00:00:00 2001 From: moozzyk Date: Mon, 24 Oct 2016 17:51:37 -0700 Subject: [PATCH] ts-client WebSockets + JSON --- samples/SocketsSample/wwwroot/.gitignore | 1 + samples/SocketsSample/wwwroot/hubs.html | 50 ++++++++- .../HttpClient.ts | 35 ++++++ .../ITransport.ts | 5 + .../RpcConnection.ts | 101 ++++++++++++++++++ .../WebSocketTransport.ts | 40 +++++++ .../tsconfig.json | 8 ++ 7 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 samples/SocketsSample/wwwroot/.gitignore create mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/HttpClient.ts create mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/ITransport.ts create mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/RpcConnection.ts create mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/WebSocketTransport.ts create mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/tsconfig.json diff --git a/samples/SocketsSample/wwwroot/.gitignore b/samples/SocketsSample/wwwroot/.gitignore new file mode 100644 index 0000000000..0b2fbe69c1 --- /dev/null +++ b/samples/SocketsSample/wwwroot/.gitignore @@ -0,0 +1 @@ +js/ \ No newline at end of file diff --git a/samples/SocketsSample/wwwroot/hubs.html b/samples/SocketsSample/wwwroot/hubs.html index c93ef33a64..486aeb8a41 100644 --- a/samples/SocketsSample/wwwroot/hubs.html +++ b/samples/SocketsSample/wwwroot/hubs.html @@ -3,7 +3,55 @@ + diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/HttpClient.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/HttpClient.ts new file mode 100644 index 0000000000..96f18aeb86 --- /dev/null +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/HttpClient.ts @@ -0,0 +1,35 @@ +class HttpClient { + get(url: string): Promise { + return this.xhr("GET", url); + } + + post(url: string): Promise { + return this.xhr("POST", url); + } + + private xhr(method: string, url: string): Promise { + 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 + }); + }; + }); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/ITransport.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/ITransport.ts new file mode 100644 index 0000000000..92fd8de54f --- /dev/null +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/ITransport.ts @@ -0,0 +1,5 @@ +interface ITransport { + connect(url: string, queryString: string): Promise; + send(data: string): Promise; + stop(): void; +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/RpcConnection.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/RpcConnection.ts new file mode 100644 index 0000000000..45bc03de9b --- /dev/null +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/RpcConnection.ts @@ -0,0 +1,101 @@ +interface InvocationDescriptor { + readonly Id: string; + readonly Method: string; + readonly Arguments: Array; +} + +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 void>; + private methods: Map void>; + private transport: ITransport; + private id: number; + + constructor(url: string, queryString: string) { + this.url = url; + this.queryString = queryString; + this.callbacks = new Map void>(); + this.methods = new Map 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 { + 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 { + let invocationDescriptor: InvocationDescriptor = { + "Id": this.id.toString(), + "Method": methodName, + "Arguments": args + }; + + let p = new Promise((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; + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/WebSocketTransport.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/WebSocketTransport.ts new file mode 100644 index 0000000000..817ea0f070 --- /dev/null +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/WebSocketTransport.ts @@ -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 { + 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 { + this.webSocket.send(data); + return Promise.resolve(); + } + + stop(): void { + this.webSocket.close(); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/tsconfig.json b/src/Microsoft.AspNetCore.SignalR.Client.TS/tsconfig.json new file mode 100644 index 0000000000..0bdca265e8 --- /dev/null +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "es6", + "out": "../../samples/SocketsSample/wwwroot/js/signalr-client.js" + }, + "include": [ "*.ts" ] +} \ No newline at end of file