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