├── .gitignore ├── .editorconfig ├── misc.godot.ts ├── webapi.misc.d.ts ├── webapi.d.ts ├── README.md ├── index.unity.ts ├── index.godot.tsx ├── misc.unity.ts ├── animationframe.ts ├── xhr ├── url.ts ├── thirdpart │ ├── url-parser │ │ ├── requires-port.js │ │ ├── querystringify.js │ │ └── url-parser.js │ ├── mimetype │ │ ├── serializer.js │ │ ├── utils.js │ │ ├── parser.js │ │ └── mime-type.js │ └── querystring │ │ └── querystring.js ├── webapi.xhr.d.ts ├── xhr.unity.ts ├── xhr.common.ts └── xhr.godot.ts ├── storage.godot.ts ├── index.common.ts ├── storage.unity.ts ├── console.d.ts ├── webapi.storage.d.ts ├── webapi.timer.d.ts ├── timer.ts ├── storage.ts ├── console.unity.ts ├── webapi.performance.d.ts ├── performance.ts ├── webapi.event.d.ts └── event.ts /.gitignore: -------------------------------------------------------------------------------- 1 | *.jsx 2 | *.meta 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = false 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /misc.godot.ts: -------------------------------------------------------------------------------- 1 | function atob(data: string): string { 2 | return godot.Marshalls.base64_to_utf8(data); 3 | } 4 | 5 | function btoa(data: string): string { 6 | return godot.Marshalls.utf8_to_base64(data); 7 | } 8 | 9 | export default { 10 | exports: { 11 | atob, 12 | btoa 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /webapi.misc.d.ts: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | declare module globalThis { 3 | /** 4 | * Creates a base-64 encoded ASCII string from a string of binary data. 5 | */ 6 | function btoa(text: string): string; 7 | 8 | /** 9 | * Decodes a string of data which has been encoded using base-64 encoding. 10 | */ 11 | function atob(base64: string): string; 12 | } 13 | -------------------------------------------------------------------------------- /webapi.d.ts: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | declare module globalThis { 3 | const window: typeof globalThis; 4 | function cancelAnimationFrame(handle: number): void; 5 | function requestAnimationFrame(callback: (now: number)=>void): number; 6 | } 7 | 8 | declare module WebAPI { 9 | function tick(); 10 | function finalize(); 11 | function getHighResTimeStamp(): number; 12 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebAPI 2 | Minimal Web API support for godot and Unity 3 | 4 | ### Implemented Web APIs 5 | - WindowOrWorkerGlobalScope 6 | - setTimeout 7 | - clearTimeout 8 | - setInterval 9 | - clearInterval 10 | - btoa 11 | - atob 12 | - performance 13 | - localStorage 14 | - sessionStorage 15 | - Performance API 16 | - Storage API 17 | - Event 18 | - EventTarget 19 | - XMLHttpRequest 20 | -------------------------------------------------------------------------------- /index.unity.ts: -------------------------------------------------------------------------------- 1 | import './console.unity'; 2 | import animation_frame from './animationframe'; 3 | import event from './event'; 4 | import timer from './timer'; 5 | import performance from './performance'; 6 | import misc from './misc.unity'; 7 | import storage from './storage.unity'; 8 | import xhr from './xhr/xhr.unity'; 9 | 10 | import { initialize } from "./index.common"; 11 | initialize([ 12 | animation_frame, 13 | event, 14 | timer, 15 | performance, 16 | misc, 17 | storage, 18 | xhr, 19 | ]); 20 | 21 | export default window; -------------------------------------------------------------------------------- /index.godot.tsx: -------------------------------------------------------------------------------- 1 | import event from './event'; 2 | import timer from './timer'; 3 | import performance from './performance'; 4 | import xhr from './xhr/xhr.godot'; 5 | import misc from './misc.godot'; 6 | import storage from './storage.godot'; 7 | 8 | import { initialize, finalize } from "./index.common"; 9 | initialize([ 10 | event, 11 | timer, 12 | performance, 13 | storage, 14 | misc, 15 | xhr 16 | ]); 17 | 18 | //@ts-ignore 19 | export default class GodotWebAPISingleton extends godot.Node { 20 | _exit_tree() { 21 | finalize(); 22 | } 23 | } -------------------------------------------------------------------------------- /misc.unity.ts: -------------------------------------------------------------------------------- 1 | import { System } from "csharp"; 2 | Object.setPrototypeOf(System.Text.Encoding.UTF8, System.Text.Encoding.prototype); 3 | Object.setPrototypeOf(System.Text.Encoding.ASCII, System.Text.Encoding.prototype); 4 | 5 | function btoa(text: string): string { 6 | return System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(text)); 7 | } 8 | 9 | function atob(base64: string): string { 10 | let data = System.Convert.FromBase64String(base64); 11 | let base64Decoded = System.Text.Encoding.ASCII.GetString(data); 12 | return base64Decoded; 13 | } 14 | 15 | export default { 16 | exports: { 17 | atob, 18 | btoa 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /animationframe.ts: -------------------------------------------------------------------------------- 1 | interface FrameAction { 2 | id: number; 3 | callback: (now: number)=>void; 4 | } 5 | 6 | let actions: FrameAction[] = []; 7 | let global_action_id = 0; 8 | 9 | function cancelAnimationFrame(handle: number): void { 10 | actions = actions.filter( a => a.id !== handle ); 11 | } 12 | 13 | function requestAnimationFrame(callback: (now: number)=>void): number { 14 | let action = { 15 | callback, 16 | id: ++global_action_id, 17 | } 18 | actions.push(action); 19 | return action.id; 20 | } 21 | 22 | function tick(now: number) { 23 | for (const action of actions) { 24 | action.callback(now); 25 | } 26 | } 27 | 28 | export default { 29 | tick, 30 | exports: { 31 | requestAnimationFrame, 32 | cancelAnimationFrame 33 | } 34 | }; -------------------------------------------------------------------------------- /xhr/url.ts: -------------------------------------------------------------------------------- 1 | export interface IURL { 2 | url: string; 3 | hostname?: string; 4 | path?: string; 5 | port?: number; 6 | protocal?: string; 7 | } 8 | import Url from './thirdpart/url-parser/url-parser' 9 | 10 | export function parse_url(url: string): IURL { 11 | // const regex = /^([a-z]+?)\:\/\/([^\/?#:]+)(:(\d+))?(?:[\/?#]|$)(.*)/i; 12 | // const matches = url.match(regex); 13 | // if (matches) { 14 | // return { 15 | // url, 16 | // protocal: matches[1], 17 | // hostname: matches[2], 18 | // port: matches[4] ? parseInt(matches[4]) : undefined, 19 | // path: matches[5], 20 | // }; 21 | // } else { 22 | // return { 23 | // url 24 | // }; 25 | // } 26 | const u = new Url(url); 27 | u.url = url; 28 | return u; 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /xhr/thirdpart/url-parser/requires-port.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if we're required to add a port number. 3 | * 4 | * @see https://url.spec.whatwg.org/#default-port 5 | * @param {Number|String} port Port number we need to check 6 | * @param {String} protocol Protocol we need to check against. 7 | * @returns {Boolean} Is it a default port for the given protocol 8 | * @api private 9 | */ 10 | export default function required(port, protocol) { 11 | protocol = protocol.split(':')[0]; 12 | port = +port; 13 | 14 | if (!port) return false; 15 | 16 | switch (protocol) { 17 | case 'http': 18 | case 'ws': 19 | return port !== 80; 20 | 21 | case 'https': 22 | case 'wss': 23 | return port !== 443; 24 | 25 | case 'ftp': 26 | return port !== 21; 27 | 28 | case 'gopher': 29 | return port !== 70; 30 | 31 | case 'file': 32 | return false; 33 | } 34 | 35 | return port !== 0; 36 | }; -------------------------------------------------------------------------------- /storage.godot.ts: -------------------------------------------------------------------------------- 1 | import { Storage } from "./storage"; 2 | 3 | class LocalStorage extends Storage { 4 | protected file: string; 5 | constructor(file: string = "user://localStorage.json") { 6 | super(); 7 | this.file = file; 8 | const fs = new godot.File(); 9 | if (fs.file_exists(file)) { 10 | if (godot.Error.OK == fs.open(file, godot.File.READ)) { 11 | let text = fs.get_as_text() || "[]"; 12 | fs.close(); 13 | this._items = JSON.parse(text); 14 | } else { 15 | throw new Error("Cannot open storage file " + file); 16 | } 17 | } 18 | } 19 | 20 | protected flush() { 21 | const file = new godot.File(); 22 | if (godot.Error.OK == file.open(this.file, godot.File.WRITE_READ)) { 23 | let text = JSON.stringify(this._items, undefined, '\t'); 24 | file.store_string(text); 25 | file.close(); 26 | } else { 27 | throw new Error("Cannot open storage file " + this.file); 28 | } 29 | } 30 | } 31 | 32 | export default { 33 | exports: { 34 | Storage, 35 | sessionStorage: new Storage(), 36 | localStorage: new LocalStorage(), 37 | } 38 | }; -------------------------------------------------------------------------------- /index.common.ts: -------------------------------------------------------------------------------- 1 | interface WebAPIModule { 2 | tick?: (now: number) => void; 3 | initialize?: () => void; 4 | uninitialize?: () => void; 5 | exports?: Record 6 | } 7 | 8 | let registered_modules: WebAPIModule[] = []; 9 | 10 | export function initialize(modules: WebAPIModule[]) { 11 | Object.defineProperty(globalThis, 'window', { value: globalThis }); 12 | for (const m of modules) { 13 | if (m.initialize) m.initialize(); 14 | if (!m.exports) continue; 15 | for (const key in m.exports) { 16 | Object.defineProperty(window, key, { value: m.exports[key] }); 17 | } 18 | } 19 | registered_modules = modules; 20 | } 21 | 22 | export function finalize() { 23 | for (const m of registered_modules) { 24 | if (m.uninitialize) m.uninitialize(); 25 | } 26 | } 27 | 28 | export function tick() { 29 | for (const m of registered_modules) { 30 | if (m.tick && WebAPI.getHighResTimeStamp) { 31 | m.tick(WebAPI.getHighResTimeStamp()); 32 | } 33 | } 34 | } 35 | 36 | Object.defineProperty(globalThis, "WebAPI", { value: { 37 | tick, 38 | finalize, 39 | getHighResTimeStamp: Date.now, 40 | }}); 41 | -------------------------------------------------------------------------------- /storage.unity.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { System, UnityEngine } from "csharp"; 3 | import { Storage } from './storage' 4 | 5 | class LocalStorage extends Storage { 6 | protected $file: string; 7 | protected $directory: string; 8 | 9 | constructor(file: string = `${UnityEngine.Application.persistentDataPath}/webapi/localStorage.json`) { 10 | super(); 11 | this.$file = file; 12 | this.$directory = System.IO.Path.GetDirectoryName(this.$file); 13 | if (System.IO.File.Exists(file)) { 14 | try { 15 | const stream = new System.IO.StreamReader(file); 16 | const text = stream.ReadToEnd(); 17 | this._items = JSON.parse(text); 18 | } catch (error) { 19 | throw new Error("Cannot open storage file " + file); 20 | } 21 | } 22 | } 23 | 24 | protected flush() { 25 | if (!System.IO.File.Exists(this.$directory)) { 26 | System.IO.Directory.CreateDirectory(this.$directory); 27 | } 28 | const stream = new System.IO.StreamWriter(this.$file); 29 | if (stream) { 30 | let text = JSON.stringify(this._items, undefined, '\t'); 31 | stream.Write(text); 32 | stream.Flush(); 33 | } else { 34 | throw new Error("Cannot open storage file " + this.$file); 35 | } 36 | } 37 | } 38 | 39 | export default { 40 | exports: { 41 | Storage, 42 | sessionStorage: new Storage(), 43 | localStorage: new LocalStorage(), 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /console.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The Console API provides functionality to allow developers to perform debugging tasks, such as logging messages or the values of variables at set points in your code, or timing how long an operation takes to complete. 3 | */ 4 | //@ts-ignore 5 | declare const console: { 6 | /** 7 | * Outputs a message to the console. The message may be a single string (with optional substitution values), or it may be any one or more JavaScript objects. 8 | * @param message A list of JavaScript objects to output. The string representations of each of these objects are appended together in the order listed and output. 9 | */ 10 | log(...message): void; 11 | 12 | /** 13 | * Outputs a warning message to the console. 14 | * @param message list of JavaScript objects to output. The string representations of each of these objects are appended together in the order listed and output. 15 | */ 16 | warn(...message): void; 17 | 18 | /** 19 | * Outputs an error message to the console. 20 | * @param message A list of JavaScript objects to output. The string representations of each of these objects are appended together in the order listed and output. 21 | */ 22 | error(...message): void; 23 | 24 | /** Outputs a stack trace to the console. 25 | * @param message A list of JavaScript objects to output. The string representations of each of these objects are appended together in the order listed and output. 26 | */ 27 | trace(...message): void; 28 | 29 | /** Log JavaScript Objects as JSON format */ 30 | LOG_OBJECT_TO_JSON: boolean; 31 | } -------------------------------------------------------------------------------- /webapi.storage.d.ts: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | declare module globalThis { 3 | /** This Web Storage API interface provides access to a particular domain's session or local storage. It allows, for example, the addition, modification, or deletion of stored data items. */ 4 | class Storage { 5 | /** 6 | * Returns the number of key/value pairs currently present in the list associated with the object. 7 | */ 8 | readonly length: number; 9 | /** 10 | * Empties the list associated with the object of all key/value pairs, if there are any. 11 | */ 12 | clear(): void; 13 | /** 14 | * Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object. 15 | */ 16 | getItem(key: string): string | null; 17 | /** 18 | * Returns the name of the nth key in the list, or null if n is greater than or equal to the number of key/value pairs in the object. 19 | */ 20 | key(index: number): string | null; 21 | /** 22 | * Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists. 23 | */ 24 | removeItem(key: string): void; 25 | /** 26 | * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously. 27 | * 28 | * Throws a "QuotaExceededError" DOMException exception if the new value couldn't be set. (Setting could fail if, e.g., the user has disabled storage for the site, or if the quota has been exceeded.) 29 | */ 30 | setItem(key: string, value: string): void; 31 | [name: string]: any; 32 | } 33 | 34 | const localStorage: Storage; 35 | const sessionStorage: Storage; 36 | } 37 | -------------------------------------------------------------------------------- /xhr/thirdpart/mimetype/serializer.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2017–2018 Domenic Denicola 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 3 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5 | 6 | import { solelyContainsHTTPTokenCodePoints } from "./utils.js"; 7 | 8 | export default mimeType => { 9 | let serialization = `${mimeType.type}/${mimeType.subtype}`; 10 | 11 | if (mimeType.parameters.size === 0) { 12 | return serialization; 13 | } 14 | 15 | for (let [name, value] of mimeType.parameters) { 16 | serialization += ";"; 17 | serialization += name; 18 | serialization += "="; 19 | 20 | if (!solelyContainsHTTPTokenCodePoints(value) || value.length === 0) { 21 | value = value.replace(/(["\\])/g, "\\$1"); 22 | value = `"${value}"`; 23 | } 24 | 25 | serialization += value; 26 | } 27 | 28 | return serialization; 29 | }; 30 | -------------------------------------------------------------------------------- /webapi.timer.d.ts: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | declare module globalThis { 3 | type TimerHandler = Function; 4 | type TimerID = number; 5 | 6 | /** 7 | * Sets a timer which executes a function once the timer expires. 8 | * @param handler A function to be executed after the timer expires. 9 | * @param timeout The time, in milliseconds (thousandths of a second), the timer should wait before the specified function or code is executed. If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, the next event cycle. 10 | * @param arguments Additional arguments which are passed through to the function specified by function 11 | * @returns The returned `timeoutID` is a positive integer value; this value can be passed to `clearTimeout()` to cancel the timeout. 12 | */ 13 | function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): TimerID; 14 | 15 | /** 16 | * Cancels a timeout previously established by calling `setTimeout()` 17 | * @param timeoutID The identifier of the timeout you want to cancel. This ID was returned by the corresponding call to `setTimeout()`. 18 | */ 19 | function clearTimeout(timeoutID: TimerID): void; 20 | 21 | /** 22 | * Repeatedly calls a function with a fixed time delay between each call. 23 | * @param handler A function to be executed every delay milliseconds. 24 | * @param timeout The time, in milliseconds (thousandths of a second), the timer should delay in between executions of the specified function or code. 25 | * @param arguments Additional arguments which are passed through to the function specified by func once the timer expires. 26 | * @returns The returned `intervalID` is a numeric, non-zero value which identifies the timer created by the call to `setInterval()`; this value can be passed to `clearInterval()` to cancel the timeout. 27 | */ 28 | function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): TimerID; 29 | 30 | /** 31 | * Cancels a timed, repeating action which was previously established by a call to setInterval() 32 | * @param intervalID The identifier of the repeated action you want to cancel. This ID was returned by the corresponding call to `setInterval()`. 33 | */ 34 | function clearInterval(intervalID: TimerID): void; 35 | } 36 | -------------------------------------------------------------------------------- /timer.ts: -------------------------------------------------------------------------------- 1 | type TimerHandler = Function; 2 | 3 | interface Timer { 4 | handler: TimerHandler; 5 | timeout: number; 6 | next_time?: number; 7 | args?: any[]; 8 | oneshot?: boolean; 9 | } 10 | let global_timer_id = 0; 11 | 12 | const pending_timers = new Map(); 13 | const processing_timers = new Map(); 14 | const removing_timers = new Set(); 15 | 16 | function timer_loop(){ 17 | const now = WebAPI.getHighResTimeStamp(); 18 | 19 | for (const [id, timer] of pending_timers) { 20 | processing_timers.set(id, timer); 21 | } 22 | pending_timers.clear(); 23 | 24 | for (const id of removing_timers) { 25 | processing_timers.delete(id); 26 | } 27 | removing_timers.clear(); 28 | 29 | for (const [id, timer] of processing_timers) { 30 | if (timer.next_time <= now) { 31 | if (timer.handler) timer.handler.apply(null, timer.args); 32 | if (timer.oneshot) { 33 | removing_timers.add(id); 34 | } else { 35 | timer.next_time = now + timer.timeout; 36 | } 37 | } 38 | } 39 | } 40 | 41 | function make_timer(handler: TimerHandler, timeout?: number, ...args: any[]): Timer { 42 | return { 43 | handler, 44 | timeout, 45 | next_time: WebAPI.getHighResTimeStamp() + (timeout || 0), 46 | args 47 | }; 48 | } 49 | 50 | function pend_timer(timer: Timer): number { 51 | pending_timers.set(++global_timer_id, timer); 52 | return global_timer_id; 53 | } 54 | 55 | function setTimeout(handler: TimerHandler, timeout?: number, ...args: any[]): number { 56 | const timer = make_timer(handler, timeout, ...args); 57 | timer.oneshot = true; 58 | return pend_timer(timer); 59 | } 60 | 61 | function clearTimeout(handle?: number): void { 62 | removing_timers.add(handle); 63 | } 64 | 65 | function setInterval(handler: TimerHandler, timeout?: number, ...args: any[]): number { 66 | return pend_timer(make_timer(handler, timeout, ...args)); 67 | } 68 | 69 | function clearInterval(handle?: number): void { 70 | removing_timers.add(handle); 71 | } 72 | 73 | let timer_loop_id = 0; 74 | 75 | export default { 76 | initialize() { 77 | timer_loop_id = requestAnimationFrame(timer_loop); 78 | }, 79 | uninitialize() { 80 | cancelAnimationFrame(timer_loop_id); 81 | }, 82 | exports: { 83 | setTimeout, 84 | clearTimeout, 85 | setInterval, 86 | clearInterval, 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /storage.ts: -------------------------------------------------------------------------------- 1 | interface StorageItem { 2 | key: string, 3 | value: string, 4 | } 5 | 6 | export class Storage { 7 | 8 | protected _items: StorageItem[] = []; 9 | 10 | /** 11 | * Returns the number of key/value pairs currently present in the list associated with the object. 12 | */ 13 | get length(): number { return this._items.length; } 14 | 15 | /** 16 | * Empties the list associated with the object of all key/value pairs, if there are any. 17 | */ 18 | clear(): void { 19 | this._items = []; 20 | this.flush(); 21 | } 22 | 23 | /** 24 | * Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object. 25 | */ 26 | getItem(key: string): string | null { 27 | for (const item of this._items) { 28 | if (item.key === key) 29 | return item.value; 30 | } 31 | return null; 32 | } 33 | 34 | /** 35 | * Returns the name of the nth key in the list, or null if n is greater than or equal to the number of key/value pairs in the object. 36 | */ 37 | key(index: number): string | null { 38 | for (let i = 0; i < this._items.length; i++) { 39 | if (i === index) 40 | return this._items[i].key; 41 | } 42 | return null; 43 | } 44 | 45 | /** 46 | * Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists. 47 | */ 48 | removeItem(key: string): void { 49 | let idx = -1; 50 | for (let i = 0; i < this._items.length; i++) { 51 | if (this._items[i].key === key) { 52 | idx = i; 53 | break; 54 | } 55 | } 56 | if (idx != -1) { 57 | this._items.splice(idx, 1); 58 | this.flush(); 59 | } 60 | } 61 | 62 | /** 63 | * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously. 64 | * 65 | * Throws a "QuotaExceededError" DOMException exception if the new value couldn't be set. (Setting could fail if, e.g., the user has disabled storage for the site, or if the quota has been exceeded.) 66 | */ 67 | setItem(key: string, value: string): void { 68 | let idx = -1; 69 | for (let i = 0; i < this._items.length; i++) { 70 | if (this._items[i].key === key) { 71 | idx = i; 72 | break; 73 | } 74 | } 75 | if (idx != -1) { 76 | if (this._items[idx].value != value) { 77 | this._items[idx].value = value; 78 | this.flush(); 79 | } 80 | } else { 81 | this._items.push({key, value}); 82 | this.flush(); 83 | } 84 | } 85 | protected flush() {} 86 | } 87 | 88 | export default { 89 | exports: { 90 | Storage, 91 | sessionStorage: new Storage(), 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /xhr/thirdpart/mimetype/utils.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2017–2018 Domenic Denicola 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 3 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5 | 6 | export const removeLeadingAndTrailingHTTPWhitespace = string => { 7 | return string.replace(/^[ \t\n\r]+/, "").replace(/[ \t\n\r]+$/, ""); 8 | }; 9 | 10 | export const removeTrailingHTTPWhitespace = string => { 11 | return string.replace(/[ \t\n\r]+$/, ""); 12 | }; 13 | 14 | export const isHTTPWhitespaceChar = char => { 15 | return char === " " || char === "\t" || char === "\n" || char === "\r"; 16 | }; 17 | 18 | export const solelyContainsHTTPTokenCodePoints = string => { 19 | return /^[-!#$%&'*+.^_`|~A-Za-z0-9]*$/.test(string); 20 | }; 21 | 22 | export const soleyContainsHTTPQuotedStringTokenCodePoints = string => { 23 | return /^[\t\u0020-\u007E\u0080-\u00FF]*$/.test(string); 24 | }; 25 | 26 | export const asciiLowercase = string => { 27 | return string.replace(/[A-Z]/g, l => l.toLowerCase()); 28 | }; 29 | 30 | // This variant only implements it with the extract-value flag set. 31 | export const collectAnHTTPQuotedString = (input, position) => { 32 | let value = ""; 33 | 34 | position++; 35 | 36 | while (true) { 37 | while (position < input.length && input[position] !== "\"" && input[position] !== "\\") { 38 | value += input[position]; 39 | ++position; 40 | } 41 | 42 | if (position >= input.length) { 43 | break; 44 | } 45 | 46 | const quoteOrBackslash = input[position]; 47 | ++position; 48 | 49 | if (quoteOrBackslash === "\\") { 50 | if (position >= input.length) { 51 | value += "\\"; 52 | break; 53 | } 54 | 55 | value += input[position]; 56 | ++position; 57 | } else { 58 | break; 59 | } 60 | } 61 | 62 | return [value, position]; 63 | }; 64 | -------------------------------------------------------------------------------- /console.unity.ts: -------------------------------------------------------------------------------- 1 | import { UnityEngine, UnityEditor } from 'csharp'; 2 | import { $typeof } from 'puerts'; 3 | enum LogType { 4 | Error = 0, 5 | Assert = 1, 6 | Warning = 2, 7 | Log = 3, 8 | Exception = 4 9 | } 10 | 11 | const scriptResources = new Map(); 12 | const emptyResources = new UnityEngine.Object(); 13 | const isUnityEditor = UnityEngine.Application.isEditor; 14 | 15 | function print(type: LogType, showStack : boolean, ...args) { 16 | let message = ''; 17 | for (let i = 0; i < args.length; i++) { 18 | const element = args[i]; 19 | if (typeof element === 'object' && console.LOG_OBJECT_TO_JSON) { 20 | message += JSON.stringify(element); 21 | } else { 22 | message += element; 23 | } 24 | if (i < args.length - 1) { 25 | message += ' '; 26 | } 27 | } 28 | let unityLogTarget: UnityEngine.Object = null; 29 | if (showStack || UnityEngine.Application.isEditor) { 30 | var stacks = new Error().stack.split('\n'); 31 | for (let i = 3; i < stacks.length; i++) { 32 | let line = stacks[i]; 33 | message += '\n'; 34 | if (isUnityEditor) { 35 | const matches = line.match(/at\s.*?\s\((.*?)\:(\d+)/); 36 | if (matches && matches.length >= 3) { 37 | let file = matches[1].replace(/\\/g, '/'); 38 | file = file.replace(/.*\/Assets\//, 'Assets/'); 39 | const lineNumber = matches[2]; 40 | line = line.replace(/\s\(/, ` (`); 41 | line = line.replace(/\)$/, ' )'); 42 | if (!unityLogTarget) { 43 | if (!scriptResources.has(file)) { 44 | scriptResources.set(file, UnityEditor.AssetDatabase.LoadAssetAtPath(file, $typeof(UnityEngine.Object))); 45 | } 46 | unityLogTarget = scriptResources.get(file); 47 | } 48 | } 49 | } 50 | message += line; 51 | } 52 | } 53 | message = message.replace(/{/gm, '{{'); 54 | message = message.replace(/}/gm, '}}'); 55 | UnityEngine.Debug.LogFormat(type, UnityEngine.LogOption.NoStacktrace, unityLogTarget || emptyResources, message); 56 | } 57 | 58 | const ConsoleObject = { 59 | log: (...args) => print(LogType.Log, false, ...args), 60 | info: (...args) => print(LogType.Log, true, ...args), 61 | trace: (...args) => print(LogType.Log, true, ...args), 62 | warn: (...args) => print(LogType.Warning, true, ...args), 63 | error: (...args) => print(LogType.Error, true, ...args), 64 | LOG_OBJECT_TO_JSON: false, 65 | }; 66 | 67 | if (typeof(console) === 'undefined') { 68 | Object.defineProperty(globalThis, 'console', { 69 | value: ConsoleObject, 70 | enumerable: true, 71 | configurable: true, 72 | writable: false 73 | }); 74 | } else { 75 | let globalConsole = (globalThis as unknown)['console']; 76 | for (const key in ConsoleObject) { 77 | Object.defineProperty(globalConsole, key, { value: ConsoleObject[key], enumerable: true, configurable: true, writable: typeof(ConsoleObject[key]) !== 'function' }); 78 | } 79 | } -------------------------------------------------------------------------------- /webapi.performance.d.ts: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | declare module globalThis { 3 | 4 | type PerformanceEntryList = PerformanceEntry[]; 5 | 6 | /** Provides access to performance-related information for the current page. It's part of the High Resolution Time API, but is enhanced by the Performance Timeline API, the Navigation Timing API, the User Timing API, and the Resource Timing API. */ 7 | class Performance { 8 | readonly timeOrigin: number; 9 | /** Removes the given mark from the performance entry buffer. */ 10 | clearMarks(markName: string): void; 11 | /** Removes the given measure from the performance entry buffer. */ 12 | clearMeasures(measureName: string): void; 13 | /** Returns a list of `PerformanceEntry` objects based on the given filter. */ 14 | getEntries(): PerformanceEntryList; 15 | /** Returns a list of `PerformanceEntry` objects based on the given name and entry type. */ 16 | getEntriesByName(name: string, type?: string): PerformanceEntryList; 17 | /** Returns a list of `PerformanceEntry` objects of the given entry type. */ 18 | getEntriesByType(type: string): PerformanceEntryList; 19 | /** Creates a timestamp in the performance entry buffer with the given name. */ 20 | mark(markName: string): PerformanceMark; 21 | /** Creates a named timestamp in the performance entry buffer between two specified marks (known as the start mark and end mark, respectively). */ 22 | measure(measureName: string, startMark: string, endMark: string): PerformanceMeasure; 23 | /** Returns a DOMHighResTimeStamp representing the number of milliseconds elapsed since a reference instant. */ 24 | now(): number; 25 | /** Is a jsonizer returning a json object representing the Performance object. */ 26 | toJSON(): { timeOrigin : number }; 27 | } 28 | 29 | /** Encapsulates a single performance metric that is part of the performance timeline. A performance entry can be directly created by making a performance mark or measure (for example by calling the mark() method) at an explicit point in an application. Performance entries are also created in indirect ways such as loading a resource (such as an image). */ 30 | class PerformanceEntry { 31 | readonly duration: number; 32 | readonly entryType: string; 33 | readonly name: string; 34 | readonly startTime: number; 35 | toJSON(): { duration: number; entryType: string; name: string; startTime: number; }; 36 | } 37 | 38 | /** PerformanceMark is an abstract interface for PerformanceEntry objects with an entryType of "mark". Entries of this type are created by calling performance.mark() to add a named DOMHighResTimeStamp (the mark) to the browser's performance timeline. */ 39 | class PerformanceMark extends PerformanceEntry {} 40 | 41 | /** PerformanceMeasure is an abstract interface for PerformanceEntry objects with an entryType of "measure". Entries of this type are created by calling performance.measure() to add a named DOMHighResTimeStamp (the measure) between two marks to the browser's performance timeline. */ 42 | class PerformanceMeasure extends PerformanceEntry {} 43 | 44 | const performance: Performance; 45 | } 46 | -------------------------------------------------------------------------------- /xhr/thirdpart/url-parser/querystringify.js: -------------------------------------------------------------------------------- 1 | const has = Object.prototype.hasOwnProperty; 2 | const undef = undefined; 3 | /** 4 | * Decode a URI encoded string. 5 | * 6 | * @param {String} input The URI encoded string. 7 | * @returns {String|Null} The decoded string. 8 | * @api private 9 | */ 10 | function decode(input) { 11 | try { 12 | return decodeURIComponent(input.replace(/\+/g, ' ')); 13 | } catch (e) { 14 | return null; 15 | } 16 | } 17 | 18 | /** 19 | * Attempts to encode a given input. 20 | * 21 | * @param {String} input The string that needs to be encoded. 22 | * @returns {String|Null} The encoded string. 23 | * @api private 24 | */ 25 | function encode(input) { 26 | try { 27 | return encodeURIComponent(input); 28 | } catch (e) { 29 | return null; 30 | } 31 | } 32 | 33 | /** 34 | * Simple query string parser. 35 | * 36 | * @param {String} query The query string that needs to be parsed. 37 | * @returns {Object} 38 | * @api public 39 | */ 40 | function querystring(query) { 41 | let parser = /([^=?#&]+)=?([^&]*)/g 42 | , result = {} 43 | , part; 44 | 45 | while (part = parser.exec(query)) { 46 | let key = decode(part[1]) 47 | , value = decode(part[2]); 48 | 49 | // 50 | // Prevent overriding of existing properties. This ensures that build-in 51 | // methods like `toString` or __proto__ are not overriden by malicious 52 | // querystrings. 53 | // 54 | // In the case if failed decoding, we want to omit the key/value pairs 55 | // from the result. 56 | // 57 | if (key === null || value === null || key in result) continue; 58 | result[key] = value; 59 | } 60 | 61 | return result; 62 | } 63 | 64 | /** 65 | * Transform a query string to an object. 66 | * 67 | * @param {Object} obj Object that should be transformed. 68 | * @param {String} prefix Optional prefix. 69 | * @returns {String} 70 | * @api public 71 | */ 72 | function querystringify(obj, prefix) { 73 | prefix = prefix || ''; 74 | 75 | let pairs = [] 76 | , value 77 | , key; 78 | 79 | // 80 | // Optionally prefix with a '?' if needed 81 | // 82 | if ('string' !== typeof prefix) prefix = '?'; 83 | 84 | for (key in obj) { 85 | if (has.call(obj, key)) { 86 | value = obj[key]; 87 | 88 | // 89 | // Edge cases where we actually want to encode the value to an empty 90 | // string instead of the stringified value. 91 | // 92 | if (!value && (value === null || value === undef || isNaN(value))) { 93 | value = ''; 94 | } 95 | 96 | key = encode(key); 97 | value = encode(value); 98 | 99 | // 100 | // If we failed to encode the strings, we should bail out as we don't 101 | // want to add invalid strings to the query. 102 | // 103 | if (key === null || value === null) continue; 104 | pairs.push(key +'='+ value); 105 | } 106 | } 107 | 108 | return pairs.length ? prefix + pairs.join('&') : ''; 109 | } 110 | 111 | // 112 | // Expose the module. 113 | // 114 | export const stringify = querystringify; 115 | export const parse = querystring; -------------------------------------------------------------------------------- /xhr/thirdpart/querystring/querystring.js: -------------------------------------------------------------------------------- 1 | // Copyright Joyent, Inc. and other Node contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 'use strict'; 23 | 24 | // If obj.hasOwnProperty has been overridden, then calling 25 | // obj.hasOwnProperty(prop) will break. 26 | // See: https://github.com/joyent/node/issues/1707 27 | function hasOwnProperty(obj, prop) { 28 | return Object.prototype.hasOwnProperty.call(obj, prop); 29 | } 30 | 31 | export function decode(qs, sep, eq, options) { 32 | sep = sep || '&'; 33 | eq = eq || '='; 34 | var obj = {}; 35 | 36 | if (typeof qs !== 'string' || qs.length === 0) { 37 | return obj; 38 | } 39 | 40 | var regexp = /\+/g; 41 | qs = qs.split(sep); 42 | 43 | var maxKeys = 1000; 44 | if (options && typeof options.maxKeys === 'number') { 45 | maxKeys = options.maxKeys; 46 | } 47 | 48 | var len = qs.length; 49 | // maxKeys <= 0 means that we should not limit keys count 50 | if (maxKeys > 0 && len > maxKeys) { 51 | len = maxKeys; 52 | } 53 | 54 | for (var i = 0; i < len; ++i) { 55 | var x = qs[i].replace(regexp, '%20'), 56 | idx = x.indexOf(eq), 57 | kstr, vstr, k, v; 58 | 59 | if (idx >= 0) { 60 | kstr = x.substr(0, idx); 61 | vstr = x.substr(idx + 1); 62 | } else { 63 | kstr = x; 64 | vstr = ''; 65 | } 66 | 67 | k = decodeURIComponent(kstr); 68 | v = decodeURIComponent(vstr); 69 | 70 | if (!hasOwnProperty(obj, k)) { 71 | obj[k] = v; 72 | } else if (Array.isArray(obj[k])) { 73 | obj[k].push(v); 74 | } else { 75 | obj[k] = [obj[k], v]; 76 | } 77 | } 78 | 79 | return obj; 80 | }; 81 | 82 | 83 | var stringifyPrimitive = function (v) { 84 | switch (typeof v) { 85 | case 'string': 86 | return v; 87 | 88 | case 'boolean': 89 | return v ? 'true' : 'false'; 90 | 91 | case 'number': 92 | return isFinite(v) ? v : ''; 93 | 94 | default: 95 | return ''; 96 | } 97 | }; 98 | 99 | export function encode(obj, sep, eq, name) { 100 | sep = sep || '&'; 101 | eq = eq || '='; 102 | if (obj === null) { 103 | obj = undefined; 104 | } 105 | 106 | if (typeof obj === 'object') { 107 | return Object.keys(obj).map(function (k) { 108 | var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; 109 | if (Array.isArray(obj[k])) { 110 | return obj[k].map(function (v) { 111 | return ks + encodeURIComponent(stringifyPrimitive(v)); 112 | }).join(sep); 113 | } else { 114 | return ks + encodeURIComponent(stringifyPrimitive(obj[k])); 115 | } 116 | }).filter(Boolean).join(sep); 117 | 118 | } 119 | 120 | if (!name) return ''; 121 | return encodeURIComponent(stringifyPrimitive(name)) + eq + 122 | encodeURIComponent(stringifyPrimitive(obj)); 123 | }; 124 | 125 | export const parse = decode; 126 | export const stringify = encode; 127 | -------------------------------------------------------------------------------- /xhr/thirdpart/mimetype/parser.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2017–2018 Domenic Denicola 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 3 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5 | 6 | import { 7 | removeLeadingAndTrailingHTTPWhitespace, 8 | removeTrailingHTTPWhitespace, 9 | isHTTPWhitespaceChar, 10 | solelyContainsHTTPTokenCodePoints, 11 | soleyContainsHTTPQuotedStringTokenCodePoints, 12 | asciiLowercase, 13 | collectAnHTTPQuotedString 14 | } from "./utils.js"; 15 | 16 | export default input => { 17 | input = removeLeadingAndTrailingHTTPWhitespace(input); 18 | 19 | let position = 0; 20 | let type = ""; 21 | while (position < input.length && input[position] !== "/") { 22 | type += input[position]; 23 | ++position; 24 | } 25 | 26 | if (type.length === 0 || !solelyContainsHTTPTokenCodePoints(type)) { 27 | return null; 28 | } 29 | 30 | if (position >= input.length) { 31 | return null; 32 | } 33 | 34 | // Skips past "/" 35 | ++position; 36 | 37 | let subtype = ""; 38 | while (position < input.length && input[position] !== ";") { 39 | subtype += input[position]; 40 | ++position; 41 | } 42 | 43 | subtype = removeTrailingHTTPWhitespace(subtype); 44 | 45 | if (subtype.length === 0 || !solelyContainsHTTPTokenCodePoints(subtype)) { 46 | return null; 47 | } 48 | 49 | const mimeType = { 50 | type: asciiLowercase(type), 51 | subtype: asciiLowercase(subtype), 52 | parameters: new Map() 53 | }; 54 | 55 | while (position < input.length) { 56 | // Skip past ";" 57 | ++position; 58 | 59 | while (isHTTPWhitespaceChar(input[position])) { 60 | ++position; 61 | } 62 | 63 | let parameterName = ""; 64 | while (position < input.length && input[position] !== ";" && input[position] !== "=") { 65 | parameterName += input[position]; 66 | ++position; 67 | } 68 | parameterName = asciiLowercase(parameterName); 69 | 70 | if (position < input.length) { 71 | if (input[position] === ";") { 72 | continue; 73 | } 74 | 75 | // Skip past "=" 76 | ++position; 77 | } 78 | 79 | let parameterValue = null; 80 | if (input[position] === "\"") { 81 | [parameterValue, position] = collectAnHTTPQuotedString(input, position); 82 | 83 | while (position < input.length && input[position] !== ";") { 84 | ++position; 85 | } 86 | } else { 87 | parameterValue = ""; 88 | while (position < input.length && input[position] !== ";") { 89 | parameterValue += input[position]; 90 | ++position; 91 | } 92 | 93 | parameterValue = removeTrailingHTTPWhitespace(parameterValue); 94 | 95 | if (parameterValue === "") { 96 | continue; 97 | } 98 | } 99 | 100 | if (parameterName.length > 0 && 101 | solelyContainsHTTPTokenCodePoints(parameterName) && 102 | soleyContainsHTTPQuotedStringTokenCodePoints(parameterValue) && 103 | !mimeType.parameters.has(parameterName)) { 104 | mimeType.parameters.set(parameterName, parameterValue); 105 | } 106 | } 107 | 108 | return mimeType; 109 | }; 110 | -------------------------------------------------------------------------------- /performance.ts: -------------------------------------------------------------------------------- 1 | import { EventTarget } from "./event"; 2 | 3 | /** Encapsulates a single performance metric that is part of the performance timeline. A performance entry can be directly created by making a performance mark or measure (for example by calling the mark() method) at an explicit point in an application. Performance entries are also created in indirect ways such as loading a resource (such as an image). */ 4 | class PerformanceEntry { 5 | readonly duration: number; 6 | readonly entryType: string; 7 | readonly name: string; 8 | readonly startTime: number; 9 | 10 | constructor(name: string, startTime: number, entryType: string, duration = 0) { 11 | this.startTime = startTime; 12 | this.name = name; 13 | this.entryType = entryType; 14 | this.duration = duration; 15 | } 16 | 17 | toJSON() { 18 | return { 19 | duration: this.duration, 20 | entryType: this.entryType, 21 | name: this.name, 22 | startTime: this.startTime, 23 | }; 24 | }; 25 | } 26 | 27 | class PerformanceMark extends PerformanceEntry {} 28 | class PerformanceMeasure extends PerformanceEntry {} 29 | type PerformanceEntryList = PerformanceEntry[]; 30 | 31 | const MARK_TYPE = "mark"; 32 | const MEASURE_TYPE = "measure"; 33 | 34 | class Performance extends EventTarget { 35 | readonly timeOrigin: number; 36 | private _entries = new Map(); 37 | 38 | now() { 39 | return Date.now() - this.timeOrigin; 40 | } 41 | 42 | constructor() { 43 | super(); 44 | this.timeOrigin = Date.now(); 45 | } 46 | 47 | getEntries(): PerformanceEntryList { 48 | let ret: PerformanceEntryList = []; 49 | for (const [type, list] of this._entries) { 50 | ret = ret.concat(list); 51 | } 52 | return ret; 53 | } 54 | 55 | getEntriesByName(name: string, type?: string): PerformanceEntryList { 56 | let ret: PerformanceEntryList = []; 57 | for (const [entryType, list] of this._entries) { 58 | if (type && type != entryType) continue; 59 | list.map(e => { 60 | if (e.name == name) { 61 | ret.push(e); 62 | } 63 | }); 64 | } 65 | return ret; 66 | } 67 | 68 | getEntriesByType(type: string): PerformanceEntryList { 69 | return this._entries.get(type); 70 | } 71 | 72 | mark(markName: string) { 73 | const mark = new PerformanceMark(markName, this.now(), MARK_TYPE); 74 | let marks: PerformanceEntryList = this._entries.get(MARK_TYPE); 75 | if (!marks) { 76 | marks = [ mark ]; 77 | this._entries.set(MARK_TYPE, marks); 78 | } else { 79 | marks.push(mark); 80 | } 81 | return mark; 82 | } 83 | 84 | measure(measureName: string, startMark: string, endMark: string) { 85 | let starts = this.getEntriesByName(startMark, MARK_TYPE); 86 | if (starts.length == 0) throw new Error(`The mark '${startMark}' does not exist.`); 87 | let ends = this.getEntriesByName(endMark, MARK_TYPE); 88 | if (ends.length == 0) throw new Error(`The mark '${endMark}' does not exist.`); 89 | const start = starts[starts.length - 1]; 90 | const end = ends[ends.length - 1]; 91 | const measure = new PerformanceMeasure(measureName, start.startTime, MEASURE_TYPE, end.startTime - start.startTime); 92 | let measures: PerformanceEntryList = this._entries.get(MEASURE_TYPE); 93 | if (!measures) { 94 | measures = [ measure ]; 95 | this._entries.set(MEASURE_TYPE, measures); 96 | } else { 97 | measures.push(measure); 98 | } 99 | return measure; 100 | } 101 | 102 | clearMarks(markName: string): void { 103 | let marks = this._entries.get(MARK_TYPE); 104 | if (marks) { 105 | marks = marks.filter( m => m.name === markName ); 106 | this._entries.set(MARK_TYPE, marks); 107 | } 108 | } 109 | 110 | clearMeasures(measureName: string): void { 111 | let measures = this._entries.get(MARK_TYPE); 112 | if (measures) { 113 | measures = measures.filter( m => m.name === measureName ); 114 | this._entries.set(MEASURE_TYPE, measures); 115 | } 116 | } 117 | 118 | toJSON() { 119 | return { 120 | timeOrigin: this.timeOrigin, 121 | } 122 | } 123 | 124 | }; 125 | 126 | export default { 127 | exports: { 128 | Performance, 129 | PerformanceEntry, 130 | PerformanceMark, 131 | PerformanceMeasure, 132 | performance: new Performance() 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /xhr/thirdpart/mimetype/mime-type.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2017–2018 Domenic Denicola 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 3 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 5 | 6 | 7 | import parse from "./parser.js"; 8 | import serialize from "./serializer.js"; 9 | import { 10 | asciiLowercase, 11 | solelyContainsHTTPTokenCodePoints, 12 | soleyContainsHTTPQuotedStringTokenCodePoints 13 | } from "./utils.js"; 14 | 15 | export default class MIMEType { 16 | constructor(string) { 17 | string = String(string); 18 | const result = parse(string); 19 | if (result === null) { 20 | throw new Error(`Could not parse MIME type string "${string}"`); 21 | } 22 | 23 | this._type = result.type; 24 | this._subtype = result.subtype; 25 | this._parameters = new MIMETypeParameters(result.parameters); 26 | } 27 | 28 | static parse(string) { 29 | try { 30 | return new this(string); 31 | } catch (e) { 32 | return null; 33 | } 34 | } 35 | 36 | get essence() { 37 | return `${this.type}/${this.subtype}`; 38 | } 39 | 40 | get type() { 41 | return this._type; 42 | } 43 | 44 | set type(value) { 45 | value = asciiLowercase(String(value)); 46 | 47 | if (value.length === 0) { 48 | throw new Error("Invalid type: must be a non-empty string"); 49 | } 50 | if (!solelyContainsHTTPTokenCodePoints(value)) { 51 | throw new Error(`Invalid type ${value}: must contain only HTTP token code points`); 52 | } 53 | 54 | this._type = value; 55 | } 56 | 57 | get subtype() { 58 | return this._subtype; 59 | } 60 | 61 | set subtype(value) { 62 | value = asciiLowercase(String(value)); 63 | 64 | if (value.length === 0) { 65 | throw new Error("Invalid subtype: must be a non-empty string"); 66 | } 67 | if (!solelyContainsHTTPTokenCodePoints(value)) { 68 | throw new Error(`Invalid subtype ${value}: must contain only HTTP token code points`); 69 | } 70 | 71 | this._subtype = value; 72 | } 73 | 74 | get parameters() { 75 | return this._parameters; 76 | } 77 | 78 | toString() { 79 | // The serialize function works on both "MIME type records" (i.e. the results of parse) and on this class, since 80 | // this class's interface is identical. 81 | return serialize(this); 82 | } 83 | 84 | isJavaScript({ 85 | allowParameters = false 86 | } = {}) { 87 | switch (this._type) { 88 | case "text": { 89 | switch (this._subtype) { 90 | case "ecmascript": 91 | case "javascript": 92 | case "javascript1.0": 93 | case "javascript1.1": 94 | case "javascript1.2": 95 | case "javascript1.3": 96 | case "javascript1.4": 97 | case "javascript1.5": 98 | case "jscript": 99 | case "livescript": 100 | case "x-ecmascript": 101 | case "x-javascript": { 102 | return allowParameters || this._parameters.size === 0; 103 | } 104 | default: { 105 | return false; 106 | } 107 | } 108 | } 109 | case "application": { 110 | switch (this._subtype) { 111 | case "ecmascript": 112 | case "javascript": 113 | case "x-ecmascript": 114 | case "x-javascript": { 115 | return allowParameters || this._parameters.size === 0; 116 | } 117 | default: { 118 | return false; 119 | } 120 | } 121 | } 122 | default: { 123 | return false; 124 | } 125 | } 126 | } 127 | isXML() { 128 | return (this._subtype === "xml" && (this._type === "text" || this._type === "application")) || 129 | this._subtype.endsWith("+xml"); 130 | } 131 | isHTML() { 132 | return this._subtype === "html" && this._type === "text"; 133 | } 134 | }; 135 | 136 | class MIMETypeParameters { 137 | constructor(map) { 138 | this._map = map; 139 | } 140 | 141 | get size() { 142 | return this._map.size; 143 | } 144 | 145 | get(name) { 146 | name = asciiLowercase(String(name)); 147 | return this._map.get(name); 148 | } 149 | 150 | has(name) { 151 | name = asciiLowercase(String(name)); 152 | return this._map.has(name); 153 | } 154 | 155 | set(name, value) { 156 | name = asciiLowercase(String(name)); 157 | value = String(value); 158 | 159 | if (!solelyContainsHTTPTokenCodePoints(name)) { 160 | throw new Error(`Invalid MIME type parameter name "${name}": only HTTP token code points are valid.`); 161 | } 162 | if (!soleyContainsHTTPQuotedStringTokenCodePoints(value)) { 163 | throw new Error(`Invalid MIME type parameter value "${value}": only HTTP quoted-string token code points are ` + 164 | `valid.`); 165 | } 166 | 167 | return this._map.set(name, value); 168 | } 169 | 170 | clear() { 171 | this._map.clear(); 172 | } 173 | 174 | delete(name) { 175 | name = asciiLowercase(String(name)); 176 | return this._map.delete(name); 177 | } 178 | 179 | forEach(callbackFn, thisArg) { 180 | this._map.forEach(callbackFn, thisArg); 181 | } 182 | 183 | keys() { 184 | return this._map.keys(); 185 | } 186 | 187 | values() { 188 | return this._map.values(); 189 | } 190 | 191 | entries() { 192 | return this._map.entries(); 193 | } 194 | 195 | [Symbol.iterator]() { 196 | return this._map[Symbol.iterator](); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /xhr/webapi.xhr.d.ts: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | declare module globalThis { 3 | type XMLHttpRequestResponseType = "" | "arraybuffer" | "blob" | "document" | "json" | "text"; 4 | type XMLHttpRequestMethod = "GET" | "POST" | "DELETE" | "PUT"; 5 | type BodyInit = string | Record; 6 | 7 | interface XMLHttpRequestEventTargetEventMap { 8 | "abort": ProgressEvent; 9 | "error": ProgressEvent; 10 | "load": ProgressEvent; 11 | "loadend": ProgressEvent; 12 | "loadstart": ProgressEvent; 13 | "progress": ProgressEvent; 14 | "timeout": ProgressEvent; 15 | } 16 | 17 | interface XMLHttpRequestEventMap extends XMLHttpRequestEventTargetEventMap { 18 | "readystatechange": Event; 19 | } 20 | 21 | class XMLHttpRequestEventTarget extends EventTarget { 22 | onabort: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null; 23 | onerror: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null; 24 | onload: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null; 25 | onloadend: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null; 26 | onloadstart: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null; 27 | onprogress: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null; 28 | ontimeout: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null; 29 | addEventListener(type: K, listener: (this: XMLHttpRequestEventTarget, ev: XMLHttpRequestEventTargetEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; 30 | removeEventListener(type: K, listener: (this: XMLHttpRequestEventTarget, ev: XMLHttpRequestEventTargetEventMap[K]) => any, options?: boolean | EventListenerOptions): void; 31 | } 32 | 33 | class XMLHttpRequestUpload extends XMLHttpRequestEventTarget {} 34 | 35 | enum XMLHttpRequestReadyState { 36 | UNSENT = 0, 37 | OPENED = 1, 38 | HEADERS_RECEIVED = 2, 39 | LOADING = 3, 40 | DONE = 4 41 | } 42 | 43 | /** Use XMLHttpRequest (XHR) objects to interact with servers. You can retrieve data from a URL without having to do a full page refresh. This enables a Web page to update just part of a page without disrupting what the user is doing. */ 44 | class XMLHttpRequest extends XMLHttpRequestEventTarget { 45 | onreadystatechange: ((this: XMLHttpRequest, ev: Event) => any) | null; 46 | /** 47 | * Returns client's state. 48 | */ 49 | get readyState(): XMLHttpRequestReadyState; 50 | set readyState(value: XMLHttpRequestReadyState); 51 | protected _readyState: XMLHttpRequestReadyState; 52 | /** 53 | * Returns the response's body. 54 | */ 55 | get response(): any; 56 | protected _response: any; 57 | /** 58 | * Returns the text response. 59 | * 60 | * Throws an "InvalidStateError" DOMException if responseType is not the empty string or "text". 61 | */ 62 | readonly responseText: string; 63 | /** 64 | * Returns the response type. 65 | * 66 | * Can be set to change the response type. Values are: the empty string (default), "arraybuffer", "blob", "document", "json", and "text". 67 | * 68 | * When set: setting to "document" is ignored if current global object is not a Window object. 69 | * 70 | * When set: throws an "InvalidStateError" DOMException if state is loading or done. 71 | * 72 | * When set: throws an "InvalidAccessError" DOMException if the synchronous flag is set and current global object is a Window object. 73 | */ 74 | responseType: XMLHttpRequestResponseType; 75 | readonly responseURL: string; 76 | /** 77 | * Returns the document response. 78 | * 79 | * Throws an "InvalidStateError" DOMException if responseType is not the empty string or "document". 80 | */ 81 | readonly responseXML: string | null; 82 | readonly status: number; 83 | readonly statusText: string; 84 | /** 85 | * Can be set to a time in milliseconds. When set to a non-zero value will cause fetching to terminate after the given time has passed. When the time has passed, the request has not yet completed, and the synchronous flag is unset, a timeout event will then be dispatched, or a "TimeoutError" DOMException will be thrown otherwise (for the send() method). 86 | * 87 | * When set: throws an "InvalidAccessError" DOMException if the synchronous flag is set and current global object is a Window object. 88 | */ 89 | timeout: number; 90 | /** 91 | * Returns the associated XMLHttpRequestUpload object. It can be used to gather transmission information when data is transferred to a server. 92 | */ 93 | get upload(): XMLHttpRequestUpload; 94 | /** 95 | * True when credentials are to be included in a cross-origin request. False when they are to be excluded in a cross-origin request and when cookies are to be ignored in its response. Initially false. 96 | * 97 | * When set: throws an "InvalidStateError" DOMException if state is not unsent or opened, or if the send() flag is set. 98 | */ 99 | withCredentials: boolean; 100 | /** 101 | * Cancels any network activity. 102 | */ 103 | abort(): void; 104 | getAllResponseHeaders(): string; 105 | getResponseHeader(name: string): string | null; 106 | /** 107 | * Sets the request method, request URL, and synchronous flag. 108 | * 109 | * Throws a "SyntaxError" DOMException if either method is not a valid HTTP method or url cannot be parsed. 110 | * 111 | * Throws a "SecurityError" DOMException if method is a case-insensitive match for `CONNECT`, `TRACE`, or `TRACK`. 112 | * 113 | * Throws an "InvalidAccessError" DOMException if async is false, current global object is a Window object, and the timeout attribute is not zero or the responseType attribute is not the empty string. 114 | */ 115 | open(method: XMLHttpRequestMethod, url: string, async?: boolean, username?: string | null, password?: string | null): void; 116 | /** 117 | * Acts as if the `Content-Type` header value for response is mime. (It does not actually change the header though.) 118 | * 119 | * Throws an "InvalidStateError" DOMException if state is loading or done. 120 | */ 121 | overrideMimeType(mime: string): void; 122 | /** 123 | * Initiates the request. The body argument provides the request body, if any, and is ignored if the request method is GET or HEAD. 124 | * 125 | * Throws an "InvalidStateError" DOMException if either state is not opened or the send() flag is set. 126 | */ 127 | send(body?: BodyInit | null): void; 128 | /** 129 | * Combines a header in author request headers. 130 | * 131 | * Throws an "InvalidStateError" DOMException if either state is not opened or the send() flag is set. 132 | * 133 | * Throws a "SyntaxError" DOMException if name is not a header name or if value is not a header value. 134 | */ 135 | setRequestHeader(name: string, value: string): void; 136 | addEventListener(type: K, listener: (this: XMLHttpRequest, ev: XMLHttpRequestEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; 137 | removeEventListener(type: K, listener: (this: XMLHttpRequest, ev: XMLHttpRequestEventMap[K]) => any, options?: boolean | EventListenerOptions): void; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /xhr/xhr.unity.ts: -------------------------------------------------------------------------------- 1 | import { IURL, parse_url } from "./url"; 2 | import MIMEType from "./thirdpart/mimetype/mime-type"; 3 | import { XMLHttpRequestReadyState, BodyInit, XMLHttpRequestBase, XMLHttpRequestMethod, XMLHttpRequestEventTargetEventMap, XMLHttpRequestEventTarget, XMLHttpRequestUpload, HttpStatusCode } from "./xhr.common"; 4 | import { UnityEngine, System } from "csharp"; 5 | 6 | class UnityXMLHttpRequest extends XMLHttpRequestBase { 7 | 8 | public get url() : Readonly { return this._url; } 9 | protected _url : IURL; 10 | protected _request: UnityEngine.Networking.UnityWebRequest; 11 | // protected _head_request: UnityEngine.Networking.UnityWebRequest; 12 | protected _method: XMLHttpRequestMethod; 13 | protected _connect_start_time: number; 14 | protected _internal_request: UnityEngine.Networking.UnityWebRequestAsyncOperation; 15 | private _progress: number; 16 | private _timeout_until: number; 17 | private _status: HttpStatusCode; 18 | private _request_body?: string; 19 | private _internal_respons_headers: System.Collections.Generic.Dictionary$2; 20 | get status(): HttpStatusCode { 21 | return this._status; 22 | } 23 | 24 | get responseURL(): string { 25 | if (this.url) 26 | return this.url.url; 27 | return null; 28 | } 29 | 30 | get responseXML(): string { 31 | return this.responseText; 32 | } 33 | 34 | get responseText(): string { 35 | if (this._request && this._request.downloadHandler) { 36 | return this._request.downloadHandler.text; 37 | } 38 | return undefined; 39 | } 40 | 41 | getAllResponseHeaders(): string { 42 | let text = ''; 43 | if (this._internal_respons_headers) { 44 | let enumerator = this._internal_respons_headers.GetEnumerator(); 45 | while (enumerator.MoveNext()) { 46 | text += `${enumerator.Current.Key}: ${enumerator.Current.Value}\r\n`; 47 | } 48 | } 49 | return text; 50 | } 51 | 52 | getResponseHeader(name: string): string { 53 | if (this._internal_respons_headers) { 54 | if (this._internal_respons_headers.ContainsKey(name)) { 55 | return this._internal_respons_headers.get_Item(name); 56 | } 57 | } 58 | return ''; 59 | } 60 | 61 | constructor() { 62 | super(); 63 | } 64 | 65 | open(method: XMLHttpRequestMethod, url: string, async?: boolean, username?: string | null, password?: string | null): void { 66 | this._url = parse_url(url); 67 | if (!this.url.port) { 68 | this._url.port = this.url.protocal === 'https' ? 443 : 80; 69 | } 70 | this._method = method; 71 | this._readyState = XMLHttpRequestReadyState.UNSENT; 72 | this._connect_start_time = Date.now(); 73 | } 74 | 75 | send(body?: BodyInit | null): void { 76 | if (typeof body === 'object') { 77 | this.setRequestHeader('Content-Type', 'application/json; charset=utf-8'); 78 | body = JSON.stringify(body); 79 | } else if (body === 'string') { 80 | if (!('Content-Type' in this._request_headers)) { 81 | this.setRequestHeader('Content-Type', 'text/plain'); 82 | } 83 | } 84 | this._request_body = body; 85 | // this._head_request = UnityEngine.Networking.UnityWebRequest.Head(this._url.url); 86 | // for (let key of Object.getOwnPropertyNames(this._request_headers)) { 87 | // const value = this._request_headers[key]; 88 | // this._head_request.SetRequestHeader(key, value); 89 | // } 90 | // this._head_request.SendWebRequest(); 91 | 92 | switch (this._method) { 93 | case 'PUT': 94 | this._request = UnityEngine.Networking.UnityWebRequest.Put(this._url.url, this._request_body); 95 | break; 96 | case 'POST': 97 | // Note: 这里故意用 put 创建,因为用 POST的话 Unity 会自作主张地帮你编码 body 内容 98 | this._request = UnityEngine.Networking.UnityWebRequest.Put(this._url.url, this._request_body); 99 | this._request.method = this._method; 100 | break; 101 | case 'GET': 102 | this._request = UnityEngine.Networking.UnityWebRequest.Get(this._url.url); 103 | break; 104 | case 'DELETE': 105 | this._request = UnityEngine.Networking.UnityWebRequest.Delete(this._url.url); 106 | break; 107 | default: 108 | this._request = new UnityEngine.Networking.UnityWebRequest(this._url.url, this._method); 109 | break; 110 | } 111 | for (let key of Object.getOwnPropertyNames(this._request_headers)) { 112 | const value = this._request_headers[key]; 113 | this._request.SetRequestHeader(key, value); 114 | } 115 | this._internal_request = this._request.SendWebRequest(); 116 | 117 | if (typeof this.timeout === 'number') { 118 | this._timeout_until = Date.now() + this.timeout; 119 | } 120 | this.$dispatch_event('loadstart'); 121 | this.$start_poll(); 122 | } 123 | 124 | abort(): void { 125 | if (this._request) { 126 | this._request.Abort(); 127 | this.$dispatch_event('abort'); 128 | this.$stop_poll(); 129 | } 130 | } 131 | 132 | protected $get_progress(): ProgressEventInit { 133 | return { 134 | lengthComputable: this._progress !== undefined, 135 | loaded: this._progress, 136 | total: 1 137 | }; 138 | } 139 | 140 | public $tick() { 141 | if (this._request) { 142 | 143 | this._status = Number(this._request.responseCode) as HttpStatusCode; 144 | if (this._status) { 145 | this.readyState = XMLHttpRequestReadyState.OPENED; 146 | } 147 | 148 | const now = Date.now(); 149 | if (this._timeout_until && now > this._timeout_until) { 150 | this._request.Abort(); 151 | this.$dispatch_event('timeout'); 152 | this.$finished_load(); 153 | this._status = HttpStatusCode.RequestTimeout; 154 | return; 155 | } 156 | 157 | this._status = Number(this._request.responseCode) || HttpStatusCode.Continue; 158 | if (this.readyState === XMLHttpRequestReadyState.OPENED) { 159 | this._internal_respons_headers = this._request.GetResponseHeaders(); 160 | if (this._internal_respons_headers && this._internal_respons_headers.Count) { 161 | this.readyState = XMLHttpRequestReadyState.HEADERS_RECEIVED; 162 | } 163 | } 164 | 165 | if (this.readyState === XMLHttpRequestReadyState.HEADERS_RECEIVED && this._status == HttpStatusCode.OK) { 166 | this.readyState = XMLHttpRequestReadyState.LOADING; 167 | } 168 | 169 | if (this._request.isNetworkError || this._request.isHttpError || this._request.isDone) { 170 | this.$finished_load(); 171 | } else if (this._internal_request) { 172 | if (this._progress !== this._internal_request.progress) { 173 | this._progress = this._internal_request.progress; 174 | this.$dispatch_event('progress'); 175 | } 176 | } 177 | } 178 | } 179 | 180 | private $finished_load() { 181 | this.readyState = XMLHttpRequestReadyState.DONE; 182 | if (this._request.isDone || this._request.isHttpError) { 183 | this._internal_respons_headers = this._request.GetResponseHeaders(); 184 | this.$process_response(); 185 | } 186 | if (this._request.isNetworkError || this._request.isHttpError) { 187 | this.$dispatch_event('error'); 188 | } else { 189 | this._progress = 1; 190 | this.$dispatch_event('progress'); 191 | this.$dispatch_event('load') 192 | } 193 | this.$dispatch_event('loadend'); 194 | this.$stop_poll(); 195 | } 196 | 197 | protected $process_response() { 198 | if (this.responseType === undefined) { 199 | const mime = new MIMEType(this._overrided_mime || this.getResponseHeader("Content-Type") || 'text/plain'); 200 | if (mime.type === 'application' && mime.subtype === 'json') { 201 | this.responseType = 'json'; 202 | } else if (mime.type === 'text') { 203 | this.responseType = 'text'; 204 | } else { 205 | this.responseType = 'arraybuffer'; 206 | } 207 | } 208 | switch (this.responseType) { 209 | case '': 210 | case 'document': 211 | case 'text': 212 | this._response = this.responseText; 213 | break; 214 | case 'json': 215 | const text = this.responseText; 216 | if (text) { 217 | this._response = JSON.parse(text); 218 | } else { 219 | this._response = null; 220 | } 221 | break; 222 | default: 223 | this._response = this._request.downloadHandler ? this._request.downloadHandler.data : null; 224 | break; 225 | } 226 | } 227 | } 228 | 229 | export default { 230 | exports: { 231 | XMLHttpRequestEventTarget, 232 | XMLHttpRequestReadyState, 233 | XMLHttpRequestUpload, 234 | XMLHttpRequest: UnityXMLHttpRequest, 235 | } 236 | }; -------------------------------------------------------------------------------- /webapi.event.d.ts: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | declare module globalThis { 3 | 4 | /** Evaluation phase of the event flow */ 5 | enum Phase { 6 | /** No event is being processed at this time. */ 7 | NONE = 0, 8 | /** The event is being propagated through the target's ancestor objects. */ 9 | CAPTURING_PHASE = 1, 10 | /** The event has arrived at the event's target. Event listeners registered for this phase are called at this time. If Event.bubbles is false, processing the event is finished after this phase is complete. */ 11 | AT_TARGET = 2, 12 | /** The event is propagating back up through the target's ancestors in reverse order, starting with the parent, and eventually reaching the containing Window. This is known as bubbling, and occurs only if Event.bubbles is true. Event listeners registered for this phase are triggered during this process. */ 13 | BUBBLING_PHASE = 3 14 | } 15 | 16 | interface EventInit { 17 | bubbles?: boolean; 18 | cancelable?: boolean; 19 | composed?: boolean; 20 | } 21 | 22 | /** 23 | * The Event interface represents an event which takes place in the DOM. 24 | * 25 | * An event can be triggered by the user action e.g. clicking the mouse button or tapping keyboard, or generated by APIs to represent the progress of an asynchronous task. It can also be triggered programmatically, such as by calling the HTMLElement.click() method of an element, or by defining the event, then sending it to a specified target using EventTarget.dispatchEvent(). 26 | * 27 | * There are many types of events, some of which use other interfaces based on the main Event interface. Event itself contains the properties and methods which are common to all events. 28 | * 29 | * Many DOM elements can be set up to accept (or "listen" for) these events, and execute code in response to process (or "handle") them. Event-handlers are usually connected (or "attached") to various HTML elements (such as