From 10cb22c510e7c07135daa593edf274ffa92b73af Mon Sep 17 00:00:00 2001 From: MiaoWoo Date: Fri, 27 Sep 2019 18:39:03 +0800 Subject: [PATCH] feat: add XMLHttpRequest Signed-off-by: MiaoWoo --- packages/ployfill/.gitignore | 5 + packages/ployfill/.npmignore | 22 +++ packages/ployfill/package.json | 27 +++ packages/ployfill/src/index.ts | 7 + packages/ployfill/src/xml-http-request.ts | 213 ++++++++++++++++++++++ packages/ployfill/tsconfig.json | 7 + 6 files changed, 281 insertions(+) create mode 100644 packages/ployfill/.gitignore create mode 100644 packages/ployfill/.npmignore create mode 100644 packages/ployfill/package.json create mode 100644 packages/ployfill/src/index.ts create mode 100644 packages/ployfill/src/xml-http-request.ts create mode 100644 packages/ployfill/tsconfig.json diff --git a/packages/ployfill/.gitignore b/packages/ployfill/.gitignore new file mode 100644 index 00000000..cdc3d90d --- /dev/null +++ b/packages/ployfill/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/dist +/package-lock.json +/yarn.lock +/docs diff --git a/packages/ployfill/.npmignore b/packages/ployfill/.npmignore new file mode 100644 index 00000000..b0eede3b --- /dev/null +++ b/packages/ployfill/.npmignore @@ -0,0 +1,22 @@ +src +test +typings +bundled +build +coverage +docs +wiki +gulpfile.js +bower.json +karma.conf.js +tsconfig.json +typings.json +CONTRIBUTING.md +ISSUE_TEMPLATE.md +PULL_REQUEST_TEMPLATE.md +tslint.json +wallaby.js +.travis.yml +.gitignore +.vscode +type_definitions \ No newline at end of file diff --git a/packages/ployfill/package.json b/packages/ployfill/package.json new file mode 100644 index 00000000..1f148dd8 --- /dev/null +++ b/packages/ployfill/package.json @@ -0,0 +1,27 @@ +{ + "name": "@ms/ployfill", + "version": "0.1.0", + "description": "MiaoScript ployfill package", + "author": "MiaoWoo ", + "homepage": "https://github.com/circlecloud/ms.git", + "license": "ISC", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "publishConfig": { + "registry": "https://repo.yumc.pw/repository/npm-hosted/" + }, + "scripts": { + "clean": "rimraf dist", + "watch": "npx tsc --watch", + "build": "yarn clean && npx tsc", + "test": "echo \"Error: run tests from root\" && exit 1" + }, + "dependencies": { + "@ms/nashorn": "^0.1.0" + }, + "devDependencies": { + "reflect-metadata": "^0.1.13", + "rimraf": "^3.0.0", + "typescript": "^3.6.2" + } +} \ No newline at end of file diff --git a/packages/ployfill/src/index.ts b/packages/ployfill/src/index.ts new file mode 100644 index 00000000..b0361cd3 --- /dev/null +++ b/packages/ployfill/src/index.ts @@ -0,0 +1,7 @@ +import { XMLHttpRequest as xhr } from './xml-http-request' + +var XMLHttpRequest = xhr; + +export { + XMLHttpRequest +} \ No newline at end of file diff --git a/packages/ployfill/src/xml-http-request.ts b/packages/ployfill/src/xml-http-request.ts new file mode 100644 index 00000000..7e9d1cb7 --- /dev/null +++ b/packages/ployfill/src/xml-http-request.ts @@ -0,0 +1,213 @@ +import '@ms/nashorn' + +const URL = Java.type("java.net.URL"); +const Files = Java.type("java.nio.file.Files"); +const StandardCopyOption = Java.type("java.nio.file.StandardCopyOption"); +const JavaString = Java.type("java.lang.String"); +const SecureRandom = Java.type("java.security.SecureRandom"); +const SSLContext = Java.type("javax.net.ssl.SSLContext"); +const HttpsURLConnection = Java.type("javax.net.ssl.HttpsURLConnection"); +const HostnameVerifier = Java.type("javax.net.ssl.HostnameVerifier"); +const X509TrustManager = Java.type("javax.net.ssl.X509TrustManager"); + +const SocketTimeoutException = Java.type('java.net.SocketTimeoutException'); + +const Callable = Java.type('java.util.concurrent.Callable'); +const Executors = Java.type('java.util.concurrent.Executors') + +const UTF_8 = "UTF-8" + +const TrustAnyHostnameVerifier = new HostnameVerifier({ verify: () => true }); + +const SSLSocketFactory = function initSSLSocketFactory() { + let sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, [new X509TrustManager({ + getAcceptedIssuers: () => null, + checkClientTrusted: () => { }, + checkServerTrusted: () => { } + })], new SecureRandom()); + return sslContext.getSocketFactory(); +}(); + +interface Future { + cancel(): boolean; + isCancelled(): boolean; + isDone(): boolean; + get(): T; +} + +enum ReadyState { + UNSENT,//Client has been created. open() not called yet. + OPENED,//open() has been called. + HEADERS_RECEIVED,//send() has been called, and headers and status are available. + LOADING,//Downloading; responseText holds partial data. + DONE,//The operation is complete. +} +type RequestMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH'; +type ResponseType = '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text'; +type EventType = 'load' | 'error' | 'abort' | 'progress' | 'timeout' | 'loadend' | 'loadstart'; + +const executor = Executors.newCachedThreadPool(); + +export class XMLHttpRequest { + private _timeout: number; + private _responseType: ResponseType; + private _withCredentials: boolean; + + private _readyState: ReadyState = ReadyState.UNSENT; + + private _method: RequestMethod; + private _url: string; + private _async: boolean; + private _mimeType: string; + + private _status: number = 0; + private _statusText: string = null; + private _response: any; + private _responseURL: string; + + private _connection = null; + + get timeout() { + return this._timeout; + } + set timeout(timeout: number) { + this._timeout = timeout + } + get readyState() { + return this._readyState; + } + set responseType(type: ResponseType) { + this._responseType = type; + } + get responseType() { + return this._responseType; + } + + get status() { + return this._status; + } + get statusText() { + return this._statusText; + } + get response() { + return JSON.parse(this._response); + } + get responseText() { + return this._response; + } + get responseXML() { + return this._response; + } + get responseURL() { + return this._responseURL; + } + + onreadystatechange() { + + } + onprogress() { + + } + onabort() { + + } + onerror(ex: Error) { + + } + ontimeout(ex: Error) { + + } + + setRequestHeader(key: string, val: string) { + this._connection.setRequestProperty(key, val); + } + getResponseHeader(key: string): string { + return this._connection.getHeaderField(key); + } + getAllResponseHeaders(): any { + return this._connection.getHeaderFields(); + } + addEventListener(event: EventType, callback: Function) { + this[`on${event.toLowerCase()}`] = callback; + } + overrideMimeType(mimeType: string) { + this._mimeType = mimeType; + } + open(method: RequestMethod, url: string, async: boolean = true, user?: string, password?: string) { + this._method = method; + this._url = url; + this._async = async; + + this._connection = new URL(this._url).openConnection() + if (this._connection instanceof HttpsURLConnection) { + this._connection.setHostnameVerifier(TrustAnyHostnameVerifier); + this._connection.setSSLSocketFactory(SSLSocketFactory); + } + this._connection.setRequestMethod(this._method); + this._connection.setDoOutput(true); + this._connection.setDoInput(true); + this._connection.setConnectTimeout(this._timeout); + this._connection.setReadTimeout(this._timeout); + + this.setReadyState(ReadyState.OPENED); + } + send(body?: string | object): Future { + if (this._readyState !== ReadyState.OPENED) { throw new Error(`Error Status ${this._readyState}!`) } + let future = executor.submit(new Callable({ call: () => this._send(body) })); + if (!this._async) { future.get() } + return future; + } + abort() { + this._connection.disconnect(); + this.onabort(); + } + + private _send(body?: string | object) { + try { + this._connection.connect(); + + if (body) { + let bodyType = Object.prototype.toString.call(body); + if (bodyType !== '[object String]') { throw new Error(`body(${bodyType}) must be string!`) } + var out = this._connection.getOutputStream(); + out.write(new JavaString(body).getBytes(UTF_8)); + out.flush(); + out.close(); + } + + this.setReadyState(ReadyState.LOADING); + this._status = this._connection.getResponseCode(); + this._statusText = this._connection.getResponseMessage(); + + if (this._status >= 0 && this._status < 300) { + this._response = this.readOutput(this._connection.getInputStream()); + } else if (this._status >= 300 && this._status < 400) { + this._responseURL = this.getResponseHeader('Location'); + } else { + this._response = this.readOutput(this._connection.getErrorStream()); + } + return this._response; + } catch (ex) { + if (ex instanceof SocketTimeoutException) { + this.ontimeout(ex) + } else { + this.onerror(ex); + } + } finally { + this._connection.disconnect(); + this.setReadyState(ReadyState.DONE); + } + } + + private setReadyState(state: ReadyState) { + this._readyState = state; + this.onreadystatechange(); + } + + private readOutput(input: any) { + var tempFile = Files.createTempFile('xhr', '.response'); + Files.copy(input, tempFile, StandardCopyOption['REPLACE_EXISTING']); tempFile.toFile().deleteOnExit(); + return new JavaString(Files.readAllBytes(tempFile), 'UTF-8'); + } +} diff --git a/packages/ployfill/tsconfig.json b/packages/ployfill/tsconfig.json new file mode 100644 index 00000000..7aae5d2b --- /dev/null +++ b/packages/ployfill/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "src", + "outDir": "dist" + } +} \ No newline at end of file