refactor: rename ployfill to polyfill
Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
1
packages/polyfill/.npmignore
Symbolic link
1
packages/polyfill/.npmignore
Symbolic link
@ -0,0 +1 @@
|
||||
../../.npmignore
|
28
packages/polyfill/package.json
Normal file
28
packages/polyfill/package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@ccms/polyfill",
|
||||
"version": "0.13.0",
|
||||
"description": "MiaoScript polyfill package",
|
||||
"author": "MiaoWoo <admin@yumc.pw>",
|
||||
"homepage": "https://github.com/circlecloud/ms.git",
|
||||
"license": "ISC",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"watch": "tsc --watch",
|
||||
"build": "yarn clean && tsc",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ccms/i18n": "^0.13.0",
|
||||
"@ccms/nodejs": "^0.13.0",
|
||||
"@ccms/nashorn": "^0.13.0",
|
||||
"core-js": "^3.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ccms/nashorn": "^0.13.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^4.0.5"
|
||||
}
|
||||
}
|
26
packages/polyfill/src/es5-ext.ts
Normal file
26
packages/polyfill/src/es5-ext.ts
Normal file
@ -0,0 +1,26 @@
|
||||
// ES2015 String ployfill must force overwrite to js method
|
||||
Object.defineProperty(String.prototype, 'contains', {
|
||||
value: require(`es5-ext/string/#/contains/shim`),
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true
|
||||
})
|
||||
Object.defineProperty(String.prototype, 'repeat', {
|
||||
value: require(`es5-ext/string/#/repeat/shim`),
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true
|
||||
})
|
||||
Object.defineProperty(String.prototype, 'startsWith', {
|
||||
value: require(`es5-ext/string/#/starts-with/shim`),
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true
|
||||
})
|
||||
Object.defineProperty(String.prototype, 'endsWith', {
|
||||
value: require(`es5-ext/string/#/ends-with/shim`),
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true
|
||||
})
|
||||
|
15
packages/polyfill/src/index.ts
Normal file
15
packages/polyfill/src/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/// <reference types="@ccms/nashorn" />
|
||||
import '@ccms/nodejs'
|
||||
import i18n from '@ccms/i18n'
|
||||
let polyfillStartTime = new Date().getTime()
|
||||
i18n.initialize()
|
||||
console.i18n("ms.polyfill.initialize")
|
||||
import './es5-ext'
|
||||
import './node-shim'
|
||||
import 'core-js'
|
||||
//@ts-ignore
|
||||
process.on('exit', () => require.disable())
|
||||
global.setGlobal('Proxy', require('./proxy').Proxy)
|
||||
global.setGlobal('XMLHttpRequest', require('./xml-http-request').XMLHttpRequest)
|
||||
global.setGlobal('Blob', require('blob-polyfill').Blob)
|
||||
console.i18n("ms.polyfill.completed", { time: (new Date().getTime() - polyfillStartTime) / 1000 })
|
207
packages/polyfill/src/node-shim.ts
Normal file
207
packages/polyfill/src/node-shim.ts
Normal file
@ -0,0 +1,207 @@
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
const System = Java.type('java.lang.System')
|
||||
const Thread = Java.type('java.lang.Thread')
|
||||
const InterruptedException = Java.type('java.lang.InterruptedException')
|
||||
const ThreadGroup = Java.type("java.lang.ThreadGroup")
|
||||
const AtomicInteger = Java.type("java.util.concurrent.atomic.AtomicInteger")
|
||||
const Callable = Java.type('java.util.concurrent.Callable')
|
||||
const ThreadFactory = Java.type("java.util.concurrent.ThreadFactory")
|
||||
const TimeoutException = Java.type('java.util.concurrent.TimeoutException')
|
||||
const ThreadPoolExecutor = Java.type('java.util.concurrent.ThreadPoolExecutor')
|
||||
const LinkedBlockingQueue = Java.type("java.util.concurrent.LinkedBlockingQueue")
|
||||
const TimeUnit = Java.type('java.util.concurrent.TimeUnit')
|
||||
const DelayQueue = Java.type('java.util.concurrent.DelayQueue')
|
||||
const JavaScriptTask = Java.type(base.getJavaScriptTaskClass().name)
|
||||
|
||||
const threadCount = new AtomicInteger(0)
|
||||
const threadGroup = new ThreadGroup("@ccms/ployfill-micro-task")
|
||||
const microTaskPool = new ThreadPoolExecutor(
|
||||
100, 200, 60, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue(300),
|
||||
new ThreadFactory((run: any) => new Thread(threadGroup, run, "@ccms/micro-task-" + threadCount.incrementAndGet()))
|
||||
)
|
||||
class Process extends EventEmitter {
|
||||
env = {
|
||||
__noSuchProperty__: (prop) => {
|
||||
return System.getenv(prop)
|
||||
}
|
||||
}
|
||||
platform = System.getProperty("os.name")
|
||||
constructor() {
|
||||
super()
|
||||
this.on('exit', () => {
|
||||
console.log(`await microTaskPool termination! queueTask: ${microTaskPool.shutdownNow().size()} remainTask: ${threadGroup.activeCount()}`)
|
||||
microTaskPool.awaitTermination(3000, TimeUnit.MILLISECONDS)
|
||||
})
|
||||
}
|
||||
on(event: string | symbol, listener: (...args: any[]) => void) {
|
||||
return super.on(event, (...args) => {
|
||||
try {
|
||||
listener(...args)
|
||||
} catch (error) {
|
||||
try {
|
||||
super.emit('error', error)
|
||||
} catch (error) {
|
||||
console.ex(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
nextTick(func: Function) {
|
||||
microTaskPool.execute(func)
|
||||
}
|
||||
exit(code: number) {
|
||||
console.log(`process exit by code ${code}!`)
|
||||
this.emit('exit', code)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "[object process]"
|
||||
}
|
||||
}
|
||||
|
||||
class EventLoop {
|
||||
private eventLoopMainThread = undefined
|
||||
private eventLoopTaskQueue = new DelayQueue()
|
||||
private taskExecTimeout = 3
|
||||
private fixedThreadPool = undefined
|
||||
|
||||
constructor() {
|
||||
this.taskExecTimeout = parseInt(process.env.MS_NODE_EVENT_LOOP_TIMEOUT) || 3
|
||||
this.fixedThreadPool = new ThreadPoolExecutor(
|
||||
1, 1, 0, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue(300),
|
||||
new ThreadFactory((run: any) => {
|
||||
let thread = new Thread(run, "@ccms/node-shim/event-loop-exec")
|
||||
thread.setDaemon(true)
|
||||
return thread
|
||||
}))
|
||||
this.eventLoopMainThread = new Thread(() => {
|
||||
let task = undefined
|
||||
try {
|
||||
while (!this.eventLoopMainThread.isInterrupted()) {
|
||||
task = this.eventLoopTaskQueue.take()
|
||||
try {
|
||||
task.getTask()()
|
||||
} catch (cause) {
|
||||
try {
|
||||
process.emit('error', cause)
|
||||
} catch (error) {
|
||||
console.error(cause)
|
||||
console.ex(cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`EventLoop Thread isInterrupted exit! remainTask: ${this.eventLoopTaskQueue.size()}`)
|
||||
this.eventLoopTaskQueue.clear()
|
||||
this.eventLoopTaskQueue = undefined
|
||||
this.timeoutCount = undefined
|
||||
this.timeoutTasks = undefined
|
||||
this.intervalCount = undefined
|
||||
this.intervalTasks = undefined
|
||||
this.eventLoopMainThread = undefined
|
||||
}
|
||||
}, "@ccms/node-shim/event-loop")
|
||||
this.eventLoopMainThread.setDaemon(true)
|
||||
process.on('exit', () => {
|
||||
this.eventLoopMainThread.interrupt()
|
||||
this.fixedThreadPool.shutdownNow()
|
||||
console.log(`await fixedThreadPool termination!`)
|
||||
this.fixedThreadPool.awaitTermination(3000, TimeUnit.MILLISECONDS)
|
||||
})
|
||||
}
|
||||
|
||||
startEventLoop() {
|
||||
this.eventLoopMainThread.start()
|
||||
}
|
||||
|
||||
private putDelayTask(id: number, callback: Function, ms: number) {
|
||||
this.eventLoopTaskQueue.put(new JavaScriptTask(id, callback, ms))
|
||||
}
|
||||
|
||||
private limitTimeTask(name: string, callback: Function, ...args: any[]) {
|
||||
if (!callback) {
|
||||
throw new Error(`task ${name} callback function can't be null!`)
|
||||
}
|
||||
if (this.fixedThreadPool.isShutdown()) { return console.warn(`FixedThreadPool isTerminated! ignore Task ${name}!`) }
|
||||
try {
|
||||
this.fixedThreadPool.submit(new Callable({
|
||||
call: () => {
|
||||
try {
|
||||
callback.apply(undefined, args)
|
||||
} catch (cause) {
|
||||
cause = cause.getCause && cause.getCause() || cause
|
||||
try {
|
||||
process.emit('error', cause)
|
||||
} catch (error) {
|
||||
console.error(cause)
|
||||
console.ex(cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
})).get(this.taskExecTimeout, TimeUnit.SECONDS)
|
||||
} catch (error) {
|
||||
if (error instanceof InterruptedException) {
|
||||
return console.warn(`FixedThreadPool isInterrupted exit! Task ${name} exec exit!`)
|
||||
}
|
||||
if (error instanceof TimeoutException) {
|
||||
return console.warn(`Task ${name} => ${callback} exec time greater than ${this.taskExecTimeout}s!`)
|
||||
}
|
||||
throw error.getCause && error.getCause() || error
|
||||
}
|
||||
}
|
||||
|
||||
private timeoutCount = new AtomicInteger(0)
|
||||
private timeoutTasks = []
|
||||
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
|
||||
let taskId = this.timeoutCount.incrementAndGet()
|
||||
this.timeoutTasks[taskId] = callback
|
||||
console.trace(`create setTimeout task ${taskId} => ${callback}`)
|
||||
this.putDelayTask(taskId, () => {
|
||||
if (this.timeoutTasks[taskId]) {
|
||||
this.limitTimeTask(`setTimeout-${taskId}`, callback, ...args)
|
||||
} else {
|
||||
console.trace(`ignore setTimeout ${ms}ms task ${taskId} because it's cancelled!`)
|
||||
}
|
||||
}, ms)
|
||||
return taskId
|
||||
}
|
||||
clearTimeout(taskId: number) {
|
||||
delete this.timeoutTasks[taskId]
|
||||
}
|
||||
|
||||
private intervalCount = new AtomicInteger(0)
|
||||
private intervalTasks = []
|
||||
setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]) {
|
||||
let taskId = this.intervalCount.incrementAndGet()
|
||||
this.intervalTasks[taskId] = callback
|
||||
console.trace(`create setInterval ${ms}ms task ${taskId} => ${callback}`)
|
||||
let intervalTask = () => {
|
||||
if (this.intervalTasks[taskId]) {
|
||||
this.limitTimeTask(`setInterval-${taskId}`, callback, ...args)
|
||||
this.putDelayTask(taskId, intervalTask, ms)
|
||||
} else {
|
||||
console.trace(`ignore setInterval task ${taskId} because it's cancelled!`)
|
||||
}
|
||||
}
|
||||
this.putDelayTask(taskId, intervalTask, ms)
|
||||
return taskId
|
||||
}
|
||||
clearInterval(taskId: number) {
|
||||
delete this.intervalTasks[taskId]
|
||||
}
|
||||
}
|
||||
global.setGlobal('process', new Process(), {})
|
||||
Object.defineProperty(process, require('core-js/es/symbol/to-string-tag'), { value: '[object process]' })
|
||||
const eventLoop = new EventLoop()
|
||||
Object.defineProperty(process, 'eventLoop', { value: eventLoop })
|
||||
eventLoop.startEventLoop()
|
||||
global.setGlobal('queueMicrotask', (func: any) => microTaskPool.execute(func), {})
|
||||
global.setGlobal('setTimeout', eventLoop.setTimeout.bind(eventLoop), {})
|
||||
global.setGlobal('clearTimeout', eventLoop.clearTimeout.bind(eventLoop), {})
|
||||
global.setGlobal('setInterval', eventLoop.setInterval.bind(eventLoop), {})
|
||||
global.setGlobal('clearInterval', eventLoop.clearInterval.bind(eventLoop), {})
|
||||
global.setGlobal('setImmediate', (callback: (...args: any[]) => void, ...args: any[]) => eventLoop.setTimeout(callback, 0, ...args), { writable: true })
|
||||
global.setGlobal('clearImmediate ', eventLoop.clearTimeout.bind(eventLoop), { writable: true })
|
23
packages/polyfill/src/proxy.ts
Normal file
23
packages/polyfill/src/proxy.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type { ProxyHandle } from '@ccms/nashorn'
|
||||
|
||||
// Nashorn JSAdapter See https://wiki.openjdk.java.net/display/Nashorn/Nashorn+extensions#Nashornextensions-JSAdapterconstructor
|
||||
let createProxy = eval(`
|
||||
function(handle){ return new JSAdapter(handle) }
|
||||
`)
|
||||
export class Proxy {
|
||||
static newProxy(target: any, handle: Partial<ProxyHandle>): any {
|
||||
return new Proxy(target, handle)
|
||||
}
|
||||
constructor(target: any, handle: Partial<ProxyHandle>) {
|
||||
return createProxy({
|
||||
__get__: (name: string) => handle.get ? handle.get(target, name, undefined) : target[name],
|
||||
__put__: (name: string, value: any) => handle.set ? handle.set(target, name, value, undefined) : target[name] = value,
|
||||
__call__: (name: string, ...args: any) => handle.apply ? handle.apply(target, name, args) : target[name].apply(target, args),
|
||||
__new__: (...args: any) => handle.construct ? handle.construct(target, args, target) : new target(...args),
|
||||
__getIds__: () => handle.ownKeys ? handle.ownKeys(target) : Object.keys(target),
|
||||
__getValues__: () => handle.values ? handle.values(target) : Object.values(target),
|
||||
__has__: (name: string) => handle.has ? handle.has(target, name) : Object.getOwnPropertyDescriptor(target, name) != undefined,
|
||||
__delete__: (name: string) => handle.deleteProperty ? handle.deleteProperty(target, name) : delete target[name]
|
||||
})
|
||||
}
|
||||
}
|
141
packages/polyfill/src/task.ts
Normal file
141
packages/polyfill/src/task.ts
Normal file
@ -0,0 +1,141 @@
|
||||
(function nashornEventLoopMain(context) {
|
||||
'use strict';
|
||||
|
||||
var Thread = Java.type('java.lang.Thread');
|
||||
var Phaser = Java.type('java.util.concurrent.Phaser');
|
||||
var ArrayDeque = Java.type('java.util.ArrayDeque');
|
||||
var HashMap = Java.type('java.util.HashMap');
|
||||
var TimeUnit = Java.type("java.util.concurrent.TimeUnit");
|
||||
var Runnable = Java.type('java.lang.Runnable');
|
||||
|
||||
var globalTimerId;
|
||||
var timerMap;
|
||||
var eventLoop;
|
||||
var phaser = new Phaser();
|
||||
|
||||
// __NASHORN_POLYFILL_TIMER__ type is ScheduledExecutorService
|
||||
var scheduler = context.__NASHORN_POLYFILL_TIMER__;
|
||||
|
||||
resetEventLoop();
|
||||
|
||||
// console.log('main javasript thread ' + Thread.currentThread().getName());
|
||||
|
||||
function resetEventLoop() {
|
||||
globalTimerId = 1;
|
||||
if (timerMap) {
|
||||
timerMap.forEach(function(key, value) {
|
||||
value.cancel(true);
|
||||
})
|
||||
}
|
||||
timerMap = new HashMap();
|
||||
eventLoop = new ArrayDeque();
|
||||
}
|
||||
|
||||
function waitForMessages() {
|
||||
phaser.register();
|
||||
var wait = !(eventLoop.size() === 0);
|
||||
phaser.arriveAndDeregister();
|
||||
return wait;
|
||||
}
|
||||
|
||||
function processNextMessages() {
|
||||
var remaining = 1;
|
||||
while (remaining) {
|
||||
phaser.register();
|
||||
var message = eventLoop.removeFirst();
|
||||
remaining = eventLoop.size();
|
||||
phaser.arriveAndDeregister();
|
||||
|
||||
var fn = message.fn;
|
||||
var args = message.args;
|
||||
|
||||
try {
|
||||
fn.apply(context, args);
|
||||
} catch (e) {
|
||||
console.trace(e);
|
||||
console.trace(fn);
|
||||
console.trace(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.nashornEventLoop = {
|
||||
process: function() {
|
||||
while (waitForMessages()) {
|
||||
processNextMessages()
|
||||
}
|
||||
},
|
||||
reset: resetEventLoop
|
||||
};
|
||||
|
||||
|
||||
function createRunnable(fn, timerId, args, repeated) {
|
||||
return new Runnable({
|
||||
run: function() {
|
||||
try {
|
||||
var phase = phaser.register();
|
||||
eventLoop.addLast({
|
||||
fn: fn,
|
||||
args: args
|
||||
});
|
||||
} catch (e) {
|
||||
console.trace(e);
|
||||
} finally {
|
||||
if (!repeated) timerMap.remove(timerId);
|
||||
phaser.arriveAndDeregister();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var setTimeout = function(fn, millis /* [, args...] */) {
|
||||
var args = [].slice.call(arguments, 2, arguments.length);
|
||||
|
||||
var timerId = globalTimerId++;
|
||||
var runnable = createRunnable(fn, timerId, args, false);
|
||||
|
||||
var task = scheduler.schedule(runnable, millis, TimeUnit.MILLISECONDS);
|
||||
timerMap.put(timerId, task);
|
||||
|
||||
return timerId;
|
||||
};
|
||||
|
||||
var setImmediate = function(fn /* [, args...] */) {
|
||||
var args = [].slice.call(arguments, 1, arguments.length);
|
||||
// @ts-ignore
|
||||
return setTimeout(fn, 0, args);
|
||||
}
|
||||
|
||||
var clearImmediate = function(timerId) {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
|
||||
var clearTimeout = function(timerId) {
|
||||
var task = timerMap.get(timerId);
|
||||
if (task) {
|
||||
task.cancel(true);
|
||||
timerMap.remove(timerId);
|
||||
}
|
||||
};
|
||||
|
||||
var setInterval = function(fn, delay /* [, args...] */) {
|
||||
var args = [].slice.call(arguments, 2, arguments.length);
|
||||
var timerId = globalTimerId++;
|
||||
var runnable = createRunnable(fn, timerId, args, true);
|
||||
var task = scheduler.scheduleWithFixedDelay(runnable, delay, delay, TimeUnit.MILLISECONDS);
|
||||
timerMap.put(timerId, task);
|
||||
return timerId;
|
||||
};
|
||||
|
||||
var clearInterval = function(timerId) {
|
||||
clearTimeout(timerId);
|
||||
};
|
||||
|
||||
context.setTimeout = setTimeout;
|
||||
context.clearTimeout = clearTimeout;
|
||||
context.setImmediate = setImmediate;
|
||||
context.clearImmediate = clearImmediate;
|
||||
context.setInterval = setInterval;
|
||||
context.clearInterval = clearInterval;
|
||||
// @ts-ignore
|
||||
})(typeof global !== "undefined" && global || typeof self !== "undefined" && self || this);
|
255
packages/polyfill/src/xml-http-request.ts
Normal file
255
packages/polyfill/src/xml-http-request.ts
Normal file
@ -0,0 +1,255 @@
|
||||
import '@ccms/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<T> {
|
||||
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' | 'GET'
|
||||
| 'delete' | 'DELETE'
|
||||
| 'head' | 'HEAD'
|
||||
| 'options' | 'OPTIONS'
|
||||
| 'post' | 'POST'
|
||||
| 'put' | 'PUT'
|
||||
| 'patch' | 'PATCH'
|
||||
type ResponseType =
|
||||
| 'arraybuffer'
|
||||
| 'blob'
|
||||
| 'document'
|
||||
| 'json'
|
||||
| 'text'
|
||||
| 'stream'
|
||||
type EventType =
|
||||
| 'load'
|
||||
| 'error'
|
||||
| 'abort'
|
||||
| 'progress'
|
||||
| 'timeout'
|
||||
| 'loadend'
|
||||
| 'loadstart'
|
||||
type HttpHeader = { [key: string]: string }
|
||||
|
||||
|
||||
const executor = Executors.newCachedThreadPool()
|
||||
|
||||
export class XMLHttpRequest {
|
||||
private _timeout: number
|
||||
private _responseType: ResponseType = 'text';
|
||||
private _withCredentials: boolean
|
||||
|
||||
private _readyState: ReadyState = ReadyState.UNSENT;
|
||||
|
||||
private _method: RequestMethod
|
||||
private _url: string
|
||||
private _async: boolean
|
||||
private _mimeType: string
|
||||
private _requestHeaders: HttpHeader = {};
|
||||
|
||||
private _status: number = 0;
|
||||
private _statusText: string = null;
|
||||
private _response: any
|
||||
private _responseText: any
|
||||
private _responseURL: string
|
||||
private _responseHeaders: HttpHeader = {};
|
||||
|
||||
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 this._response || this.get()
|
||||
}
|
||||
get responseText() {
|
||||
return this._responseText
|
||||
}
|
||||
get responseXML() {
|
||||
return this._response
|
||||
}
|
||||
get responseURL() {
|
||||
return this._responseURL
|
||||
}
|
||||
|
||||
public onload: () => void
|
||||
public onerror: (ex: Error) => void
|
||||
public onabort: () => void
|
||||
public onprogress: () => void
|
||||
public ontimeout: (ex: Error) => void
|
||||
public onloadend: () => void
|
||||
public onloadstart: () => void
|
||||
public onreadystatechange: () => void
|
||||
|
||||
setRequestHeader(key: string, val: string) {
|
||||
this._requestHeaders[key] = val
|
||||
}
|
||||
getResponseHeader(key: string): string {
|
||||
return this._responseHeaders[key]
|
||||
}
|
||||
getAllResponseHeaders(): any {
|
||||
return this._responseHeaders
|
||||
}
|
||||
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) {
|
||||
if (this._readyState !== ReadyState.UNSENT) { throw new Error(`Error Status ${this._readyState}!`) }
|
||||
|
||||
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<string> {
|
||||
for (const header in this._requestHeaders) {
|
||||
this._connection.setRequestProperty(header, this._requestHeaders[header])
|
||||
}
|
||||
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
|
||||
}
|
||||
get() {
|
||||
if (this._response === undefined) {
|
||||
switch (this._responseType) {
|
||||
case "json":
|
||||
return this._response = JSON.parse(this._responseText)
|
||||
case "text":
|
||||
return this._response = this._responseText
|
||||
default:
|
||||
throw Error(`Unsupport ResponseType: ${this._responseType} !`)
|
||||
}
|
||||
}
|
||||
return this._response
|
||||
}
|
||||
abort() {
|
||||
this._connection.disconnect()
|
||||
this.onabort && this.onabort()
|
||||
}
|
||||
|
||||
private _send(body?: string | object) {
|
||||
try {
|
||||
this._connection.connect()
|
||||
this.onloadstart && this.onloadstart()
|
||||
if (body) {
|
||||
let bodyType = Object.prototype.toString.call(body)
|
||||
if (typeof body !== "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._responseText = this.readOutput(this._connection.getInputStream())
|
||||
} else if (this._status >= 300 && this._status < 400) {
|
||||
this._responseURL = this.getResponseHeader('Location')
|
||||
} else {
|
||||
this._responseText = this.readOutput(this._connection.getErrorStream())
|
||||
}
|
||||
this.setResponseHeaders(this._connection.getHeaderFields())
|
||||
this.onloadend && this.onloadend()
|
||||
} catch (ex) {
|
||||
if (ex instanceof SocketTimeoutException && this.ontimeout) {
|
||||
return this.ontimeout(ex)
|
||||
} else if (this.onerror) {
|
||||
return this.onerror(ex)
|
||||
}
|
||||
throw ex
|
||||
} finally {
|
||||
this._connection.disconnect()
|
||||
this.setReadyState(ReadyState.DONE)
|
||||
}
|
||||
}
|
||||
|
||||
private setResponseHeaders(header: any) {
|
||||
header.forEach((key: string | number, value: string | any[]) => {
|
||||
this._responseHeaders[key + ''] = value[value.length - 1] + ''
|
||||
})
|
||||
}
|
||||
|
||||
private setReadyState(state: ReadyState) {
|
||||
this._readyState = state
|
||||
this.onreadystatechange && 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')
|
||||
}
|
||||
}
|
7
packages/polyfill/tsconfig.json
Normal file
7
packages/polyfill/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"outDir": "dist"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user