From 2039a189710ddf7875d4a198348fa02f28926dfb Mon Sep 17 00:00:00 2001 From: moozzyk Date: Wed, 16 Nov 2016 15:13:12 -0800 Subject: [PATCH] Introducing modules for the ts client --- .gitignore | 2 +- package.json | 14 +- samples/ChatSample/Views/Home/Index.cshtml | 55 +++-- samples/ChatSample/project.json | 4 +- samples/ChatSample/wwwroot/_references.js | 1 - samples/SocketsSample/project.json | 4 +- samples/SocketsSample/wwwroot/.gitignore | 1 - samples/SocketsSample/wwwroot/hubs.html | 221 +++++++++--------- .../Connection.ts | 4 +- .../HttpClient.ts | 2 +- .../ITransport.ts | 7 - .../LongPollingTransport.ts | 70 ------ .../RpcConnection.ts | 4 +- .../ServerSentEventsTransport.ts | 61 ----- .../Transports.ts | 199 ++++++++++++++++ .../WebSocketTransport.ts | 58 ----- .../gulpfile.js | 40 ++++ .../tsconfig.json | 5 +- .../project.json | 2 +- 19 files changed, 404 insertions(+), 350 deletions(-) delete mode 100644 samples/SocketsSample/wwwroot/.gitignore delete mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/ITransport.ts delete mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/LongPollingTransport.ts delete mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/ServerSentEventsTransport.ts create mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/Transports.ts delete mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/WebSocketTransport.ts create mode 100644 src/Microsoft.AspNetCore.SignalR.Client.TS/gulpfile.js diff --git a/.gitignore b/.gitignore index 8c619b6002..6e02278d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ node_modules/ *.nuget.props *.nuget.targets autobahnreports/ -signalr-client.js site.min.css .idea/ .vscode/ +signalr-client/ diff --git a/package.json b/package.json index 3c8882edd8..c1d2027be3 100644 --- a/package.json +++ b/package.json @@ -6,19 +6,27 @@ "directories": { "test": "test" }, - "scripts": {}, + "scripts": { + "gulp": "gulp" + }, "repository": { "type": "git", "url": "git+https://github.com/aspnet/SignalR.git" }, "author": "Microsoft", - "license": "MIT", + "license": "Apache-2.0", "bugs": { "url": "https://github.com/aspnet/SignalR/issues" }, "homepage": "https://github.com/aspnet/SignalR#readme", "devDependencies": { + "browserify": "^13.1.1", + "del": "^2.2.2", "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" } } diff --git a/samples/ChatSample/Views/Home/Index.cshtml b/samples/ChatSample/Views/Home/Index.cshtml index bac461c7c3..02716de06a 100644 --- a/samples/ChatSample/Views/Home/Index.cshtml +++ b/samples/ChatSample/Views/Home/Index.cshtml @@ -2,34 +2,6 @@ ViewData["Title"] = "Chat"; } - - -
@@ -39,4 +11,29 @@ -
\ No newline at end of file + + + \ No newline at end of file diff --git a/samples/ChatSample/project.json b/samples/ChatSample/project.json index f7c4e6a098..5c219731cb 100644 --- a/samples/ChatSample/project.json +++ b/samples/ChatSample/project.json @@ -91,7 +91,9 @@ }, "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" ], "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] } diff --git a/samples/ChatSample/wwwroot/_references.js b/samples/ChatSample/wwwroot/_references.js index 843092d673..b6fe9d6264 100644 --- a/samples/ChatSample/wwwroot/_references.js +++ b/samples/ChatSample/wwwroot/_references.js @@ -1,5 +1,4 @@ /// -/// /// /// /// diff --git a/samples/SocketsSample/project.json b/samples/SocketsSample/project.json index fa7c932ad7..285b15fb63 100644 --- a/samples/SocketsSample/project.json +++ b/samples/SocketsSample/project.json @@ -44,7 +44,9 @@ }, "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%" ] } } diff --git a/samples/SocketsSample/wwwroot/.gitignore b/samples/SocketsSample/wwwroot/.gitignore deleted file mode 100644 index 0b2fbe69c1..0000000000 --- a/samples/SocketsSample/wwwroot/.gitignore +++ /dev/null @@ -1 +0,0 @@ -js/ \ No newline at end of file diff --git a/samples/SocketsSample/wwwroot/hubs.html b/samples/SocketsSample/wwwroot/hubs.html index 44d97bc93b..68a0cbb13d 100644 --- a/samples/SocketsSample/wwwroot/hubs.html +++ b/samples/SocketsSample/wwwroot/hubs.html @@ -3,116 +3,6 @@ - -

@@ -160,4 +50,113 @@
    - \ No newline at end of file + + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/Connection.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/Connection.ts index d10c0e10d7..112bddb045 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.TS/Connection.ts +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/Connection.ts @@ -1,3 +1,5 @@ +import { ITransport, WebSocketTransport, ServerSentEventsTransport, LongPollingTransport } from "./Transports" +import { HttpClient } from "./HttpClient" enum ConnectionState { Disconnected, @@ -5,7 +7,7 @@ enum ConnectionState { Connected } -class Connection { +export class Connection { private connectionState: ConnectionState; private url: string; private queryString: string; diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/HttpClient.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/HttpClient.ts index e8fba57c02..31fef741e0 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.TS/HttpClient.ts +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/HttpClient.ts @@ -1,4 +1,4 @@ -class HttpClient { +export class HttpClient { get(url: string): Promise { return this.xhr("GET", url); } diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/ITransport.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/ITransport.ts deleted file mode 100644 index 2b561c8359..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Client.TS/ITransport.ts +++ /dev/null @@ -1,7 +0,0 @@ -interface ITransport { - connect(url: string, queryString: string): Promise; - send(data: any): Promise; - stop(): void; - onDataReceived: DataReceived; - onError: ErrorHandler; -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/LongPollingTransport.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/LongPollingTransport.ts deleted file mode 100644 index 776e3047ca..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Client.TS/LongPollingTransport.ts +++ /dev/null @@ -1,70 +0,0 @@ -class LongPollingTransport implements ITransport { - private url: string; - private queryString: string; - private pollXhr: XMLHttpRequest; - - connect(url: string, queryString: string): Promise { - 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 { - 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; -} \ 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 index 5e99486b4c..c7a3bb5363 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.TS/RpcConnection.ts +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/RpcConnection.ts @@ -1,3 +1,5 @@ +import { Connection } from "./Connection" + interface InvocationDescriptor { readonly Id: string; readonly Method: string; @@ -10,7 +12,7 @@ interface InvocationResultDescriptor { readonly Result: any; } -class RpcConnection { +export class RpcConnection { private connection: Connection; private callbacks: Map void>; private methods: Map void>; diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/ServerSentEventsTransport.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/ServerSentEventsTransport.ts deleted file mode 100644 index 192aef43f4..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Client.TS/ServerSentEventsTransport.ts +++ /dev/null @@ -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 { - 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 { - 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; -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/Transports.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/Transports.ts new file mode 100644 index 0000000000..5526d87992 --- /dev/null +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/Transports.ts @@ -0,0 +1,199 @@ +import { HttpClient } from "./HttpClient" + +export interface ITransport { + connect(url: string, queryString: string): Promise; + send(data: any): Promise; + stop(): void; + onDataReceived: DataReceived; + onError: ErrorHandler; +} + +export class WebSocketTransport implements ITransport { + private webSocket: WebSocket; + + connect(url: string, queryString: string = ""): Promise { + 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 { + 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 { + 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 { + 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 { + 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 { + 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; +} diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/WebSocketTransport.ts b/src/Microsoft.AspNetCore.SignalR.Client.TS/WebSocketTransport.ts deleted file mode 100644 index 480ba11191..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Client.TS/WebSocketTransport.ts +++ /dev/null @@ -1,58 +0,0 @@ -class WebSocketTransport implements ITransport { - private webSocket: WebSocket; - - connect(url: string, queryString: string = ""): Promise { - 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 { - 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; -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client.TS/gulpfile.js b/src/Microsoft.AspNetCore.SignalR.Client.TS/gulpfile.js new file mode 100644 index 0000000000..5888aeec86 --- /dev/null +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/gulpfile.js @@ -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']); \ 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 index 0bdca265e8..52e54792a9 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.TS/tsconfig.json +++ b/src/Microsoft.AspNetCore.SignalR.Client.TS/tsconfig.json @@ -1,8 +1,9 @@ { "compileOnSave": true, "compilerOptions": { + "module": "umd", "target": "es6", - "out": "../../samples/SocketsSample/wwwroot/js/signalr-client.js" + "outDir": "../../artifacts/lib/signalr-client-modules" }, "include": [ "*.ts" ] -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.SignalR.Test.Server/project.json b/test/Microsoft.AspNetCore.SignalR.Test.Server/project.json index 69ac3c8356..86f0d6b15a 100644 --- a/test/Microsoft.AspNetCore.SignalR.Test.Server/project.json +++ b/test/Microsoft.AspNetCore.SignalR.Test.Server/project.json @@ -39,7 +39,7 @@ }, "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%" ] } }