diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..cdecab1 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +lib/* diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..142e4e0 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,27 @@ +{ + "parser": "@babel/eslint-parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true + } + }, + "plugins": [], + "extends": ["eslint:recommended"], + "rules": { + "comma-dangle": 0, + "no-unused-vars": "warn", + "no-unexpected-multiline": "warn", + "prefer-const": "warn" + }, + "settings": {}, + "env": { + "browser": true, + "node": true, + "jasmine": true, + "jest": true, + "es6": true + } +} diff --git a/.github/workflows/build_and_publish.yaml b/.github/workflows/build_and_publish.yaml new file mode 100644 index 0000000..b5f3b1e --- /dev/null +++ b/.github/workflows/build_and_publish.yaml @@ -0,0 +1,26 @@ +name: Build and Publish +on: + push: + branches: + - 'main' +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + cache: 'npm' + node-version: 'lts/*' + registry-url: 'https://registry.npmjs.org' + - name: Install Dependencies + run: npm ci + - name: Test + run: npm run test + - name: Build + run: npm run build + - name: Publish + if: github.ref == 'refs/heads/main' + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d7354a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +.idea +dist/ +lib/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..067befa --- /dev/null +++ b/.npmignore @@ -0,0 +1,34 @@ +# dependencies +/node_modules + +# testing +/tests +/coverage + +# docs +/docs + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local +/.github +/demo +.esdoc.json + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Development folders and files +public +src +scripts +config +.travis.yml +CHANGELOG.md +README.md +webpack.config.js +babel.config.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..042f3ce --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ diff --git a/README.md b/README.md new file mode 100644 index 0000000..05828e6 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Guacamole Common JS +with Typescript type definitions\ +Guacamole API Version 1.5.3 + +## Usage +Import required modules\ +`import { Client, Tunnel } from '@glokon/guacamole-common-js';` + +Feel free to contribute! + +https://github.com/apache/guacamole-client diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..d679ba5 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [["@babel/env"]], + plugins: [["@babel/plugin-proposal-class-properties"]], +}; diff --git a/guacamole.d.ts b/guacamole.d.ts new file mode 100644 index 0000000..652bde7 --- /dev/null +++ b/guacamole.d.ts @@ -0,0 +1,2819 @@ +declare module '@ashishpatel/guacamole-common-js' { + + + /** + * Dynamic on-screen keyboard. Given the layout object for an on-screen + * keyboard, this object will construct a clickable on-screen keyboard with its + * own key events. + */ + class OnScreenKeyboard { + constructor(layout: OnScreenKeyboard.Layout); + + /** + * The number of mousemove events to require before re-enabling mouse + * event handling after receiving a touch event. + * + * @type {Number} + */ + touchMouseThreshold: number; + + /** + * Fired whenever the user presses a key on this Guacamole.OnScreenKeyboard. + * + * @event + * @param {Number} keysym The keysym of the key being pressed. + */ + onkeydown(keysym: number): void; + /** + * Fired whenever the user releases a key on this Guacamole.OnScreenKeyboard. + * + * @event + * @param {Number} keysym The keysym of the key being released. + */ + onkeyup(keysym: number): void; + + /** + * The keyboard layout provided at time of construction. + * + * @type {Guacamole.OnScreenKeyboard.Layout} + */ + layout: OnScreenKeyboard.Layout; + + /** + * Returns the element containing the entire on-screen keyboard. + * @returns {Element} The element containing the entire on-screen keyboard. + */ + getElement(): any; + + /** + * Resizes all elements within this Guacamole.OnScreenKeyboard such that + * the width is close to but does not exceed the specified width. The + * height of the keyboard is determined based on the width. + * + * @param {Number} width The width to resize this Guacamole.OnScreenKeyboard + * to, in pixels. + */ + resize(width: number): void; + + /** + * Resets the state of this keyboard, releasing all keys, and firing keyup + * events for each released key. + */ + reset(): void; + } + + + namespace OnScreenKeyboard { + + + class Layout { + + /** + * @constructor + * @param {Guacamole.OnScreenKeyboard.Layout|Object} template + * The object whose identically-named properties will be used to initialize + * the properties of this layout. + */ + constructor(template: any); + /** + * The language of keyboard layout, such as "en_US". This property is for + * informational purposes only, but it is recommend to conform to the + * [language code]_[country code] format. + * + * @type {String} + */ + language: string; + + /** + * The type of keyboard layout, such as "qwerty". This property is for + * informational purposes only, and does not conform to any standard. + * + * @type {String} + */ + type: string; + + /** + * Map of key name to corresponding keysym, title, or key object. If only + * the keysym or title is provided, the key object will be created + * implicitly. In all cases, the name property of the key object will be + * taken from the name given in the mapping. + * + * @type {Object.} + */ + keys: any; + + /** + * Arbitrarily nested, arbitrarily grouped key names. The contents of the + * layout will be traversed to produce an identically-nested grouping of + * keys in the DOM tree. All strings will be transformed into their + * corresponding sets of keys, while all objects and arrays will be + * transformed into named groups and anonymous groups respectively. Any + * numbers present will be transformed into gaps of that size, scaled + * according to the same units as each key. + * + * @type {Object} + */ + layout: any; + + /** + * The width of the entire keyboard, in arbitrary units. The width of each + * key is relative to this width, as both width values are assumed to be in + * the same units. The conversion factor between these units and pixels is + * derived later via a call to resize() on the Guacamole.OnScreenKeyboard. + * + * @type {Number} + */ + width: number; + + /** + * The width of each key, in arbitrary units, relative to other keys in + * this layout. The true pixel size of each key will be determined by the + * overall size of the keyboard. If not defined here, the width of each + * key will default to 1. + * + * @type {Object.} + */ + keyWidths: any; + } + + /** + * Represents a single key, or a single possible behavior of a key. Each key + * on the on-screen keyboard must have at least one associated + * Guacamole.OnScreenKeyboard.Key, whether that key is explicitly defined or + * implied, and may have multiple Guacamole.OnScreenKeyboard.Key if behavior + * depends on modifier states. + */ + class Key { + constructor(template: any, name: string); + + /** + * The unique name identifying this key within the keyboard layout. + * + * @type {String} + */ + name: string; + + /** + * The human-readable title that will be displayed to the user within the + * key. If not provided, this will be derived from the key name. + * + * @type {String} + */ + title: string; + + /** + * The name of the modifier set when the key is pressed and cleared when + * this key is released, if any. The names of modifiers are distinct from + * the names of keys; both the "RightShift" and "LeftShift" keys may set + * the "shift" modifier, for example. By default, the key will affect no + * modifiers. + * + * @type {String} + */ + modifier: string; + + /** + * An array containing the names of each modifier required for this key to + * have an effect. For example, a lowercase letter may require nothing, + * while an uppercase letter would require "shift", assuming the Shift key + * is named "shift" within the layout. By default, the key will require + * no modifiers. + * + * @type {String[]} + */ + requires: string[]; + } + + + } + + + /** + * Guacamole protocol client. Given a {@link Guacamole.Tunnel}, + * automatically handles incoming and outgoing Guacamole instructions via the + * provided tunnel, updating its display using one or more canvas elements. + */ + class Client { + /** + * @param {Guacamole.Tunnel} tunnel The tunnel to use to send and receive + * Guacamole instructions. + */ + constructor(tunnel: Tunnel); + + /** + * Produces an opaque representation of Guacamole.Client state which can be + * later imported through a call to importState(). This object is + * effectively an independent, compressed snapshot of protocol and display + * state. Invoking this function implicitly flushes the display. + * + * @param {function} callback + * Callback which should be invoked once the state object is ready. The + * state object will be passed to the callback as the sole parameter. + * This callback may be invoked immediately, or later as the display + * finishes rendering and becomes ready. + */ + exportState(callback: any): void; + + /** + * Restores Guacamole.Client protocol and display state based on an opaque + * object from a prior call to exportState(). The Guacamole.Client instance + * used to that state need not be the same as this instance. + * + * @param {Object} state + * An opaque representation of Guacamole.Client state from a prior call + * to exportState(). + * + * @param {function} [callback] + * The function to invoke when state has finished being imported. This + * may happen immediately, or later as images within the provided state + * object are loaded. + */ + importState(state: any, callback: any): void; + + /** + * Returns the underlying display of this Guacamole.Client. The display + * contains an Element which can be added to the DOM, causing the + * display to become visible. + * + * @return {Guacamole.Display} The underlying display of this + * Guacamole.Client. + */ + getDisplay(): Display; + + /** + * Sends a disconnect instruction to the server and closes the tunnel. + */ + disconnect(): void; + + /** + * Connects the underlying tunnel of this Guacamole.Client, passing the + * given arbitrary data to the tunnel during the connection process. + * + * @param data Arbitrary connection data to be sent to the underlying + * tunnel during the connection process. + * @throws {Guacamole.Status} If an error occurs during connection. + */ + connect(data: any): void; + + /** + * Sends a mouse event having the properties provided by the given mouse + * state. + * + * @param {Guacamole.Mouse.State} mouseState The state of the mouse to send + * in the mouse event. + */ + sendMouseState(mouseState: Mouse.State): void; + + /** + * Sends a key event having the given properties as if the user + * pressed or released a key. + * + * @param {Boolean} pressed Whether the key is pressed (true) or released + * (false). + * @param {Number} keysym The keysym of the key being pressed or released. + */ + sendKeyEvent(pressed: number, keysym: number): void; + + /** + * Sends the current size of the screen. + * + * @param {Number} width The width of the screen. + * @param {Number} height The height of the screen. + */ + sendSize(width: number, height: number): void; + + /** + * Sets the clipboard of the remote client to the given text data. + * + * @deprecated Use createClipboardStream() instead. + * @param {String} data The data to send as the clipboard contents. + */ + setClipboard(data: string): void; + + /** + * Allocates an available stream index and creates a new + * Guacamole.OutputStream using that index, associating the resulting + * stream with this Guacamole.Client. Note that this stream will not yet + * exist as far as the other end of the Guacamole connection is concerned. + * Streams exist within the Guacamole protocol only when referenced by an + * instruction which creates the stream, such as a "clipboard", "file", or + * "pipe" instruction. + * + * @returns {Guacamole.OutputStream} + * A new Guacamole.OutputStream with a newly-allocated index and + * associated with this Guacamole.Client. + */ + createOutputStream(): OutputStream; + + /** + * Opens a new audio stream for writing, where audio data having the give + * mimetype will be sent along the returned stream. The instruction + * necessary to create this stream will automatically be sent. + * + * @param {String} mimetype + * The mimetype of the audio data that will be sent along the returned + * stream. + * + * @return {Guacamole.OutputStream} + * The created audio stream. + */ + createAudioStream(mimetype: string): OutputStream; + + /** + * Opens a new file for writing, having the given index, mimetype and + * filename. The instruction necessary to create this stream will + * automatically be sent. + * + * @param {String} mimetype The mimetype of the file being sent. + * @param {String} filename The filename of the file being sent. + * @return {Guacamole.OutputStream} The created file stream. + */ + createFileStream(mimetype: string, filename: string): OutputStream; + + /** + * Opens a new pipe for writing, having the given name and mimetype. The + * instruction necessary to create this stream will automatically be sent. + * + * @param {String} mimetype The mimetype of the data being sent. + * @param {String} name The name of the pipe. + * @return {Guacamole.OutputStream} The created file stream. + */ + createPipeStream(mimetype: string, name: string): OutputStream; + + /** + * Opens a new clipboard object for writing, having the given mimetype. The + * instruction necessary to create this stream will automatically be sent. + * + * @param {String} mimetype The mimetype of the data being sent. + * @param {String} name The name of the pipe. + * @return {Guacamole.OutputStream} The created file stream. + */ + createClipboardStream(mimetype: string): OutputStream; + + /** + * Creates a new output stream associated with the given object and having + * the given mimetype and name. The legality of a mimetype and name is + * dictated by the object itself. The instruction necessary to create this + * stream will automatically be sent. + * + * @param {Number} index + * The index of the object for which the output stream is being + * created. + * + * @param {String} mimetype + * The mimetype of the data which will be sent to the output stream. + * + * @param {String} name + * The defined name of an output stream within the given object. + * + * @returns {Guacamole.OutputStream} + * An output stream which will write blobs to the named output stream + * of the given object. + */ + createObjectOutputStream(index: number, mimetype: string, name: string): OutputStream; + + /** + * Requests read access to the input stream having the given name. If + * successful, a new input stream will be created. + * + * @param {Number} index + * The index of the object from which the input stream is being + * requested. + * + * @param {String} name + * The name of the input stream to request. + */ + requestObjectInputStream(index: number, name: string): void; + + /** + * Acknowledge receipt of a blob on the stream with the given index. + * + * @param {Number} index The index of the stream associated with the + * received blob. + * @param {String} message A human-readable message describing the error + * or status. + * @param {Number} code The error code, if any, or 0 for success. + */ + sendAck(index: number, message: string, code: number): void; + + /** + * Given the index of a file, writes a blob of data to that file. + * + * @param {Number} index The index of the file to write to. + * @param {String} data Base64-encoded data to write to the file. + */ + sendBlob(index: number, data: string): void; + + /** + * Marks a currently-open stream as complete. The other end of the + * Guacamole connection will be notified via an "end" instruction that the + * stream is closed, and the index will be made available for reuse in + * future streams. + * + * @param {Number} index + * The index of the stream to end. + */ + endStream(index: number): void; + + /** + * Fired whenever the state of this Guacamole.Client changes. + * + * @event + * @param {Number} state The new state of the client. + */ + onstatechange(state: number): void; + + /** + * Fired when the remote client sends a name update. + * + * @event + * @param {String} name The new name of this client. + */ + onname(name: string): void; + + /** + * Fired when an error is reported by the remote client, and the connection + * is being closed. + * + * @event + * @param {Guacamole.Status} status A status object which describes the + * error. + */ + onerror(status: Status): void; + + /** + * Fired when a audio stream is created. The stream provided to this event + * handler will contain its own event handlers for received data. + * + * @event + * @param {Guacamole.InputStream} stream + * The stream that will receive audio data from the server. + * + * @param {String} mimetype + * The mimetype of the audio data which will be received. + * + * @return {Guacamole.AudioPlayer} + * An object which implements the Guacamole.AudioPlayer interface and + * has been initialized to play the data in the provided stream, or null + * if the built-in audio players of the Guacamole client should be + * used. + */ + onaudio(stream: InputStream, mimetype: string): AudioPlayer; + + /** + * Fired when a video stream is created. The stream provided to this event + * handler will contain its own event handlers for received data. + * + * @event + * @param {Guacamole.InputStream} stream + * The stream that will receive video data from the server. + * + * @param {Guacamole.Display.VisibleLayer} layer + * The destination layer on which the received video data should be + * played. It is the responsibility of the Guacamole.VideoPlayer + * implementation to play the received data within this layer. + * + * @param {String} mimetype + * The mimetype of the video data which will be received. + * + * @return {Guacamole.VideoPlayer} + * An object which implements the Guacamole.VideoPlayer interface and + * has been initialied to play the data in the provided stream, or null + * if the built-in video players of the Guacamole client should be + * used. + */ + onvideo(stream: InputStream, layer: Display.VisibleLayer, mimetype: string): VideoPlayer; + + /** + * Fired when the clipboard of the remote client is changing. + * + * @event + * @param {Guacamole.InputStream} stream The stream that will receive + * clipboard data from the server. + * @param {String} mimetype The mimetype of the data which will be received. + */ + onclipboard(stream: InputStream, mimetype: string): void; + + /** + * Fired when a file stream is created. The stream provided to this event + * handler will contain its own event handlers for received data. + * + * @event + * @param {Guacamole.InputStream} stream The stream that will receive data + * from the server. + * @param {String} mimetype The mimetype of the file received. + * @param {String} filename The name of the file received. + */ + onfile(stream: InputStream, mimetype: string, filename: string): void; + + /** + * Fired when a filesystem object is created. The object provided to this + * event handler will contain its own event handlers and functions for + * requesting and handling data. + * + * @event + * @param {Guacamole.Object} object + * The created filesystem object. + * + * @param {String} name + * The name of the filesystem. + */ + onfilesystem(object: Object, name: string): void; + + /** + * Fired when a pipe stream is created. The stream provided to this event + * handler will contain its own event handlers for received data; + * + * @event + * @param {Guacamole.InputStream} stream The stream that will receive data + * from the server. + * @param {String} mimetype The mimetype of the data which will be received. + * @param {String} name The name of the pipe. + */ + onpipe(stream: InputStream, mimetype: string, name: string): void; + + /** + * Fired whenever a sync instruction is received from the server, indicating + * that the server is finished processing any input from the client and + * has sent any results. + * + * @event + * @param {Number} timestamp The timestamp associated with the sync + * instruction. + */ + onsync(timestamp: number): void; + + } + + /** + * Core object providing abstract communication for Guacamole. This object + * is a null implementation whose functions do nothing. Guacamole applications + * should use {@link Guacamole.HTTPTunnel} instead, or implement their own tunnel based + * on this one. + * + * @constructor + * @see Guacamole.HTTPTunnel + */ + class Tunnel { + static INTERNAL_DATA_OPCODE: string; + static State: any; + + + uuid: string; + + + /** + * The current state of this tunnel. + * + * @type {Number} + */ + state: number; + + /** + * The maximum amount of time to wait for data to be received, in + * milliseconds. If data is not received within this amount of time, + * the tunnel is closed with an error. The default value is 15000. + * + * @type {Number} + */ + receiveTimeout: number; + + /** + * Connect to the tunnel with the given optional data. This data is + * typically used for authentication. The format of data accepted is + * up to the tunnel implementation. + * + * @param {String} data The data to send to the tunnel when connecting. + */ + connect(data: string): void; + + /** + * Disconnect from the tunnel. + */ + disconnect(): void; + + /** + * Fired whenever an error is encountered by the tunnel. + * + * @event + * @param {Guacamole.Status} status A status object which describes the + * error. + */ + onerror(error: Status): void; + + /** + * Fired whenever the state of the tunnel changes. + * + * @event + * @param {Number} state The new state of the client. + */ + onstatechange(state: number): void; + + /** + * Fired once for every complete Guacamole instruction received, in order. + * + * @event + * @param {String} opcode The Guacamole instruction opcode. + * @param {Array} parameters The parameters provided for the instruction, + * if any. + */ + oninstruction(opcode: string, parameters: any): void; + + + /** + * Send the given message through the tunnel to the service on the other + * side. All messages are guaranteed to be received in the order sent. + * + * @param {...*} elements + * The elements of the message to send to the service on the other side + * of the tunnel. + */ + sendMessage(elements: any): void; + + /** + * Changes the stored numeric state of this tunnel, firing the onstatechange + * event if the new state is different and a handler has been defined. + * + * @private + * @param {Number} state + * The new state of this tunnel. + */ + setState(state: number): void; + } + + /** + * Guacamole Tunnel implemented over WebSocket via XMLHttpRequest. + */ + class WebSocketTunnel extends Tunnel { + /** + * @param {String} tunnelURL The URL of the WebSocket tunneling service. + */ + constructor(tunnelURL: string); + } + + + class SocketIOTunnel extends Tunnel { + constructor(url: string, connectionOptions: any, eventChannel: string); + /** + * Return the socketio socket + */ + getSocket(): any; + } + + /** + * Guacamole Tunnel which cycles between all specified tunnels until + * no tunnels are left. Another tunnel is used if an error occurs but + * no instructions have been received. If an instruction has been + * received, or no tunnels remain, the error is passed directly out + * through the onerror handler (if defined). + */ + class ChainedTunnel extends Tunnel { + /** + * @param {...*} tunnelChain + * The tunnels to use, in order of priority. + */ + constructor(tunnelChain: any); + } + + /** + * Guacamole Tunnel implemented over HTTP via XMLHttpRequest. + */ + class HTTPTunnel extends Tunnel { + /** + * @param {String} tunnelURL + * The URL of the HTTP tunneling service. + * + * @param {Boolean} [crossDomain=false] + * Whether tunnel requests will be cross-domain, and thus must use CORS + * mechanisms and headers. By default, it is assumed that tunnel requests + * will be made to the same domain. + * + * @param {Object} [extraTunnelHeaders={}] + * Key value pairs containing the header names and values of any additional + * headers to be sent in tunnel requests. By default, no extra headers will + * be added. + */ + constructor(tunnelURL: string, crossDomain: boolean, extraTunnelHeaders: any); + } + + /** + * Guacamole Tunnel which replays a Guacamole protocol dump from a static file + * received via HTTP. Instructions within the file are parsed and handled as + * quickly as possible, while the file is being downloaded. + */ + class StaticHTTPTunnel extends Tunnel { + /** + * @param {String} url + * The URL of a Guacamole protocol dump. + * + * @param {Boolean} [crossDomain=false] + * Whether tunnel requests will be cross-domain, and thus must use CORS + * mechanisms and headers. By default, it is assumed that tunnel requests + * will be made to the same domain. + * + * @param {Object} [extraTunnelHeaders={}] + * Key value pairs containing the header names and values of any additional + * headers to be sent in tunnel requests. By default, no extra headers will + * be added. + */ + constructor(url: string, crossDomain: boolean, extraTunnelHeaders: any); + } + + /** + * Abstract audio player which accepts, queues and plays back arbitrary audio + * data. It is up to implementations of this class to provide some means of + * handling a provided Guacamole.InputStream. Data received along the provided + * stream is to be played back immediately. + */ + class AudioPlayer { + sync(): void; + /** + * Determines whether the given mimetype is supported by any built-in + * implementation of Guacamole.AudioPlayer, and thus will be properly handled + * by Guacamole.AudioPlayer.getInstance(). + * + * @param {String} mimetype + * The mimetype to check. + * + * @returns {Boolean} + * true if the given mimetype is supported by any built-in + * Guacamole.AudioPlayer, false otherwise. + */ + static isSupportedType(mimetype: string): boolean; + + /** + * Returns a list of all mimetypes supported by any built-in + * Guacamole.AudioPlayer, in rough order of priority. Beware that only the core + * mimetypes themselves will be listed. Any mimetype parameters, even required + * ones, will not be included in the list. For example, "audio/L8" is a + * supported raw audio mimetype that is supported, but it is invalid without + * additional parameters. Something like "audio/L8;rate=44100" would be valid, + * however (see https://tools.ietf.org/html/rfc4856). + * + * @returns {String[]} + * A list of all mimetypes supported by any built-in Guacamole.AudioPlayer, + * excluding any parameters. + */ + static getSupportedTypes(): string[]; + + /** + * Returns an instance of Guacamole.AudioPlayer providing support for the given + * audio format. If support for the given audio format is not available, null + * is returned. + * + * @param {Guacamole.InputStream} stream + * The Guacamole.InputStream to read audio data from. + * + * @param {String} mimetype + * The mimetype of the audio data in the provided stream. + * + * @return {Guacamole.AudioPlayer} + * A Guacamole.AudioPlayer instance supporting the given mimetype and + * reading from the given stream, or null if support for the given mimetype + * is absent. + */ + static getInstance(stream: InputStream, mimetype: string): AudioPlayer; + + } + + /** + * Implementation of Guacamole.AudioPlayer providing support for raw PCM format + * audio. This player relies only on the Web Audio API and does not require any + * browser-level support for its audio formats. + */ + class RawAudioPlayer extends AudioPlayer { + /** + * @augments Guacamole.AudioPlayer + * @param {Guacamole.InputStream} stream + * The Guacamole.InputStream to read audio data from. + * + * @param {String} mimetype + * The mimetype of the audio data in the provided stream, which must be a + * "audio/L8" or "audio/L16" mimetype with necessary parameters, such as: + * "audio/L16;rate=44100,channels=2". + */ + constructor(stream: InputStream, mimetype: string); + + + /** @override */ + sync(): void; + + /** + * Determines whether the given mimetype is supported by + * Guacamole.RawAudioPlayer. + * + * @param {String} mimetype + * The mimetype to check. + * + * @returns {Boolean} + * true if the given mimetype is supported by Guacamole.RawAudioPlayer, + * false otherwise. + */ + static isSupportedType(mimetype: string): boolean; + + /** + * Returns a list of all mimetypes supported by Guacamole.RawAudioPlayer. Only + * the core mimetypes themselves will be listed. Any mimetype parameters, even + * required ones, will not be included in the list. For example, "audio/L8" is + * a raw audio mimetype that may be supported, but it is invalid without + * additional parameters. Something like "audio/L8;rate=44100" would be valid, + * however (see https://tools.ietf.org/html/rfc4856). + * + * @returns {String[]} + * A list of all mimetypes supported by Guacamole.RawAudioPlayer, excluding + * any parameters. If the necessary JavaScript APIs for playing raw audio + * are absent, this list will be empty. + */ + static getSupportedTypes(): string[]; + } + + /** + * Abstract audio recorder which streams arbitrary audio data to an underlying + * Guacamole.OutputStream. It is up to implementations of this class to provide + * some means of handling this Guacamole.OutputStream. Data produced by the + * recorder is to be sent along the provided stream immediately. + */ + class AudioRecorder { + /** + * Callback which is invoked when the audio recording process has stopped + * and the underlying Guacamole stream has been closed normally. Audio will + * only resume recording if a new Guacamole.AudioRecorder is started. This + * Guacamole.AudioRecorder instance MAY NOT be reused. + * + * @event + */ + onclose(): void; + + /** + * Callback which is invoked when the audio recording process cannot + * continue due to an error, if it has started at all. The underlying + * Guacamole stream is automatically closed. Future attempts to record + * audio should not be made, and this Guacamole.AudioRecorder instance + * MAY NOT be reused. + * + * @event + */ + onerror(): void; + + /** + * Determines whether the given mimetype is supported by any built-in + * implementation of Guacamole.AudioRecorder, and thus will be properly handled + * by Guacamole.AudioRecorder.getInstance(). + * + * @param {String} mimetype + * The mimetype to check. + * + * @returns {Boolean} + * true if the given mimetype is supported by any built-in + * Guacamole.AudioRecorder, false otherwise. + */ + static isSupportedType(mimetype: string): boolean; + + /** + * Returns a list of all mimetypes supported by any built-in + * Guacamole.AudioRecorder, in rough order of priority. Beware that only the + * core mimetypes themselves will be listed. Any mimetype parameters, even + * required ones, will not be included in the list. For example, "audio/L8" is + * a supported raw audio mimetype that is supported, but it is invalid without + * additional parameters. Something like "audio/L8;rate=44100" would be valid, + * however (see https://tools.ietf.org/html/rfc4856). + * + * @returns {String[]} + * A list of all mimetypes supported by any built-in + * Guacamole.AudioRecorder, excluding any parameters. + */ + static getSupportedTypes(): string[]; + + /** + * Returns an instance of Guacamole.AudioRecorder providing support for the + * given audio format. If support for the given audio format is not available, + * null is returned. + * + * @param {Guacamole.OutputStream} stream + * The Guacamole.OutputStream to send audio data through. + * + * @param {String} mimetype + * The mimetype of the audio data to be sent along the provided stream. + * + * @return {Guacamole.AudioRecorder} + * A Guacamole.AudioRecorder instance supporting the given mimetype and + * writing to the given stream, or null if support for the given mimetype + * is absent. + */ + static getInstance(stream: OutputStream, mimetype: string); + } + + /** + * Implementation of Guacamole.AudioRecorder providing support for raw PCM + * format audio. This recorder relies only on the Web Audio API and does not + * require any browser-level support for its audio formats. + * + * @constructor + * @augments Guacamole.AudioRecorder + * @param {Guacamole.OutputStream} stream + * The Guacamole.OutputStream to write audio data to. + * + * @param {String} mimetype + * The mimetype of the audio data to send along the provided stream, which + * must be a "audio/L8" or "audio/L16" mimetype with necessary parameters, + * such as: "audio/L16;rate=44100,channels=2". + */ + class RawAudioRecorder extends AudioRecorder { + /** + * @augments Guacamole.AudioRecorder + * @param {Guacamole.OutputStream} stream + * The Guacamole.OutputStream to write audio data to. + * + * @param {String} mimetype + * The mimetype of the audio data to send along the provided stream, which + * must be a "audio/L8" or "audio/L16" mimetype with necessary parameters, + * such as: "audio/L16;rate=44100,channels=2". + */ + constructor(stream: AudioRecorder, mimetype: string); + + /** + * Determines whether the given mimetype is supported by + * Guacamole.RawAudioRecorder. + * + * @param {String} mimetype + * The mimetype to check. + * + * @returns {Boolean} + * true if the given mimetype is supported by Guacamole.RawAudioRecorder, + * false otherwise. + */ + static isSupportedType(mimetype: string): boolean; + + /** + * Returns a list of all mimetypes supported by Guacamole.RawAudioRecorder. Only + * the core mimetypes themselves will be listed. Any mimetype parameters, even + * required ones, will not be included in the list. For example, "audio/L8" is + * a raw audio mimetype that may be supported, but it is invalid without + * additional parameters. Something like "audio/L8;rate=44100" would be valid, + * however (see https://tools.ietf.org/html/rfc4856). + * + * @returns {String[]} + * A list of all mimetypes supported by Guacamole.RawAudioRecorder, + * excluding any parameters. If the necessary JavaScript APIs for recording + * raw audio are absent, this list will be empty. + */ + static getSupportedTypes(): string[]; + } + + /** + * Provides cross-browser and cross-keyboard keyboard for a specific element. + * Browser and keyboard layout variation is abstracted away, providing events + * which represent keys as their corresponding X11 keysym. + */ + class Keyboard { + /** + * @param {Element} element The Element to use to provide keyboard events. + */ + constructor(element: any); + /** + * Fired whenever the user presses a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {Number} keysym The keysym of the key being pressed. + * @return {Boolean} true if the key event should be allowed through to the + * browser, false otherwise. + */ + onkeydown(keysym: number): boolean; + + /** + * Fired whenever the user releases a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {Number} keysym The keysym of the key being released. + */ + onkeyup(keysym: number): void; + + /** + * Marks a key as pressed, firing the keydown event if registered. Key + * repeat for the pressed key will start after a delay if that key is + * not a modifier. The return value of this function depends on the + * return value of the keydown event handler, if any. + * + * @param {Number} keysym The keysym of the key to press. + * @return {Boolean} true if event should NOT be canceled, false otherwise. + */ + press(keysym: number): boolean; + + /** + * Marks a key as released, firing the keyup event if registered. + * + * @param {Number} keysym The keysym of the key to release. + */ + release(keysym: number): void; + + /** + * Resets the state of this keyboard, releasing all keys, and firing keyup + * events for each released key. + */ + reset(): void; + } + + /** + * The Guacamole display. The display does not deal with the Guacamole + * protocol, and instead implements a set of graphical operations which + * embody the set of operations present in the protocol. The order operations + * are executed is guaranteed to be in the same order as their corresponding + * functions are called. + */ + class Display { + + /** + * The X coordinate of the hotspot of the mouse cursor. The hotspot is + * the relative location within the image of the mouse cursor at which + * each click occurs. + * + * @type {Number} + */ + cursorHotspotX: number; + + /** + * The Y coordinate of the hotspot of the mouse cursor. The hotspot is + * the relative location within the image of the mouse cursor at which + * each click occurs. + * + * @type {Number} + */ + cursorHotspotY: number; + + /** + * The current X coordinate of the local mouse cursor. This is not + * necessarily the location of the actual mouse - it refers only to + * the location of the cursor image within the Guacamole display, as + * last set by moveCursor(). + * + * @type {Number} + */ + cursorX: number; + + /** + * The current X coordinate of the local mouse cursor. This is not + * necessarily the location of the actual mouse - it refers only to + * the location of the cursor image within the Guacamole display, as + * last set by moveCursor(). + * + * @type {Number} + */ + cursorY: number; + + /** + * Fired when the default layer (and thus the entire Guacamole display) + * is resized. + * + * @event + * @param {Number} width The new width of the Guacamole display. + * @param {Number} height The new height of the Guacamole display. + */ + onresize(width: number, height: number): void; + + /** + * Fired whenever the local cursor image is changed. This can be used to + * implement special handling of the client-side cursor, or to override + * the default use of a software cursor layer. + * + * @event + * @param {HTMLCanvasElement} canvas The cursor image. + * @param {Number} x The X-coordinate of the cursor hotspot. + * @param {Number} y The Y-coordinate of the cursor hotspot. + */ + oncursor(canvas: any, x: number, y: number): void; + + /** + * Returns the element which contains the Guacamole display. + * + * @return {Element} The element containing the Guacamole display. + */ + getElement(): any; + + /** + * Returns the width of this display. + * + * @return {Number} The width of this display; + */ + getWidth(): number; + + /** + * Returns the height of this display. + * + * @return {Number} The height of this display; + */ + getHeight(): number; + + /** + * Returns the default layer of this display. Each Guacamole display always + * has at least one layer. Other layers can optionally be created within + * this layer, but the default layer cannot be removed and is the absolute + * ancestor of all other layers. + * + * @return {Guacamole.Display.VisibleLayer} The default layer. + */ + getDefaultLayer(): Display.VisibleLayer; + + /** + * Returns the cursor layer of this display. Each Guacamole display contains + * a layer for the image of the mouse cursor. This layer is a special case + * and exists above all other layers, similar to the hardware mouse cursor. + * + * @return {Guacamole.Display.VisibleLayer} The cursor layer. + */ + getCursorLayer(): Display.VisibleLayer; + + /** + * Creates a new layer. The new layer will be a direct child of the default + * layer, but can be moved to be a child of any other layer. Layers returned + * by this function are visible. + * + * @return {Guacamole.Display.VisibleLayer} The newly-created layer. + */ + createLayer(): Display.VisibleLayer; + + /** + * Creates a new buffer. Buffers are invisible, off-screen surfaces. They + * are implemented in the same manner as layers, but do not provide the + * same nesting semantics. + * + * @return {Guacamole.Layer} The newly-created buffer. + */ + createBuffer(): Layer; + + /** + * Flush all pending draw tasks, if possible, as a new frame. If the entire + * frame is not ready, the flush will wait until all required tasks are + * unblocked. + * + * @param {function} callback The function to call when this frame is + * flushed. This may happen immediately, or + * later when blocked tasks become unblocked. + */ + flush(callback: any): void; + + /** + * Sets the hotspot and image of the mouse cursor displayed within the + * Guacamole display. + * + * @param {Number} hotspotX The X coordinate of the cursor hotspot. + * @param {Number} hotspotY The Y coordinate of the cursor hotspot. + * @param {Guacamole.Layer} layer The source layer containing the data which + * should be used as the mouse cursor image. + * @param {Number} srcx The X coordinate of the upper-left corner of the + * rectangle within the source layer's coordinate + * space to copy data from. + * @param {Number} srcy The Y coordinate of the upper-left corner of the + * rectangle within the source layer's coordinate + * space to copy data from. + * @param {Number} srcw The width of the rectangle within the source layer's + * coordinate space to copy data from. + * @param {Number} srch The height of the rectangle within the source + * layer's coordinate space to copy data from. + */ + setCursor(hotspotX: number, hotspotY: number, layer: Layer, srcx: number, srcy: number, srcw: number, srch: number): void; + + /** + * Sets whether the software-rendered cursor is shown. This cursor differs + * from the hardware cursor in that it is built into the Guacamole.Display, + * and relies on its own Guacamole layer to render. + * + * @param {Boolean} [shown=true] Whether to show the software cursor. + */ + showCursor(shown: boolean): void; + + /** + * Sets the location of the local cursor to the given coordinates. For the + * sake of responsiveness, this function performs its action immediately. + * Cursor motion is not maintained within atomic frames. + * + * @param {Number} x The X coordinate to move the cursor to. + * @param {Number} y The Y coordinate to move the cursor to. + */ + moveCursor(x: number, y: number): void; + + /** + * Changes the size of the given Layer to the given width and height. + * Resizing is only attempted if the new size provided is actually different + * from the current size. + * + * @param {Guacamole.Layer} layer The layer to resize. + * @param {Number} width The new width. + * @param {Number} height The new height. + */ + resize(layer: Layer, width: number, height: number): void; + + /** + * Draws the specified image at the given coordinates. The image specified + * must already be loaded. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + * @param {Image} image The image to draw. Note that this is an Image + * object - not a URL. + */ + drawImage(layer: Layer, x: number, y: number, image: any): void; + + /** + * Draws the image contained within the specified Blob at the given + * coordinates. The Blob specified must already be populated with image + * data. + * + * @param {Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {Number} x + * The destination X coordinate. + * + * @param {Number} y + * The destination Y coordinate. + * + * @param {Blob} blob + * The Blob containing the image data to draw. + */ + drawBlob(layer: Layer, x: number, y: number, blob: any): void; + + /** + * Draws the image at the specified URL at the given coordinates. The image + * will be loaded automatically, and this and any future operations will + * wait for the image to finish loading. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + * @param {String} url The URL of the image to draw. + */ + draw(layer: Layer, x: number, y: number, url: string): void; + + /** + * Plays the video at the specified URL within this layer. The video + * will be loaded automatically, and this and any future operations will + * wait for the video to finish loading. Future operations will not be + * executed until the video finishes playing. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {String} mimetype The mimetype of the video to play. + * @param {Number} duration The duration of the video in milliseconds. + * @param {String} url The URL of the video to play. + */ + play(layer: Layer, mimetype: string, duration: number, url: string): void; + + /** + * Transfer a rectangle of image data from one Layer to this Layer using the + * specified transfer function. + * + * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. + * @param {Number} srcx The X coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcy The Y coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcw The width of the rectangle within the source Layer's + * coordinate space to copy data from. + * @param {Number} srch The height of the rectangle within the source + * Layer's coordinate space to copy data from. + * @param {Guacamole.Layer} dstLayer The layer to draw upon. + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + * @param {Function} transferFunction The transfer function to use to + * transfer data from source to + * destination. + */ + transfer(srcLayer: Layer, srcx: number, srcy: number, srcw: number, srch: number, dstLayer: Layer, x: number, y: number, transferFunction: any): void; + + /** + * Put a rectangle of image data from one Layer to this Layer directly + * without performing any alpha blending. Simply copy the data. + * + * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. + * @param {Number} srcx The X coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcy The Y coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcw The width of the rectangle within the source Layer's + * coordinate space to copy data from. + * @param {Number} srch The height of the rectangle within the source + * Layer's coordinate space to copy data from. + * @param {Guacamole.Layer} dstLayer The layer to draw upon. + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + */ + put(srcLayer: Layer, srcx: number, srcy: number, srcw: number, srch: number, dstLayer: Layer, x: number, y: number): void; + + /** + * Copy a rectangle of image data from one Layer to this Layer. This + * operation will copy exactly the image data that will be drawn once all + * operations of the source Layer that were pending at the time this + * function was called are complete. This operation will not alter the + * size of the source Layer even if its autosize property is set to true. + * + * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. + * @param {Number} srcx The X coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcy The Y coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcw The width of the rectangle within the source Layer's + * coordinate space to copy data from. + * @param {Number} srch The height of the rectangle within the source + * Layer's coordinate space to copy data from. + * @param {Guacamole.Layer} dstLayer The layer to draw upon. + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + */ + copy(srcLayer: Layer, srcx: number, srcy: number, srcw: number, srch: number, dstLayer: Layer, x: number, y: number): void; + + /** + * Starts a new path at the specified point. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {Number} x The X coordinate of the point to draw. + * @param {Number} y The Y coordinate of the point to draw. + */ + moveTo(layer: Layer, x: number, y: number): void; + + /** + * Add the specified line to the current path. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {Number} x The X coordinate of the endpoint of the line to draw. + * @param {Number} y The Y coordinate of the endpoint of the line to draw. + */ + lineTo(layer: Layer, x: number, y: number): void; + + /** + * Add the specified arc to the current path. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {Number} x The X coordinate of the center of the circle which + * will contain the arc. + * @param {Number} y The Y coordinate of the center of the circle which + * will contain the arc. + * @param {Number} radius The radius of the circle. + * @param {Number} startAngle The starting angle of the arc, in radians. + * @param {Number} endAngle The ending angle of the arc, in radians. + * @param {Boolean} negative Whether the arc should be drawn in order of + * decreasing angle. + */ + arc(layer: Layer, x: number, y: number, radius: number, startAngle: number, endAngle: number, negative: boolean): void; + + /** + * Starts a new path at the specified point. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {Number} cp1x The X coordinate of the first control point. + * @param {Number} cp1y The Y coordinate of the first control point. + * @param {Number} cp2x The X coordinate of the second control point. + * @param {Number} cp2y The Y coordinate of the second control point. + * @param {Number} x The X coordinate of the endpoint of the curve. + * @param {Number} y The Y coordinate of the endpoint of the curve. + */ + curveTo(layer: Layer, cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void; + + /** + * Closes the current path by connecting the end point with the start + * point (if any) with a straight line. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + */ + close(layer: Layer): void; + + /** + * Add the specified rectangle to the current path. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {Number} x The X coordinate of the upper-left corner of the + * rectangle to draw. + * @param {Number} y The Y coordinate of the upper-left corner of the + * rectangle to draw. + * @param {Number} w The width of the rectangle to draw. + * @param {Number} h The height of the rectangle to draw. + */ + rect(layer: Layer, x: number, y: number, w: number, h: number): void; + + /** + * Clip all future drawing operations by the current path. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as fillColor()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {Guacamole.Layer} layer The layer to affect. + */ + clip(layer: Layer): void; + + /** + * Stroke the current path with the specified color. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {String} cap The line cap style. Can be "round", "square", + * or "butt". + * @param {String} join The line join style. Can be "round", "bevel", + * or "miter". + * @param {Number} thickness The line thickness in pixels. + * @param {Number} r The red component of the color to fill. + * @param {Number} g The green component of the color to fill. + * @param {Number} b The blue component of the color to fill. + * @param {Number} a The alpha component of the color to fill. + */ + strokeColor(layer: Layer, cap: string, join: string, thickness: number, r: number, g: number, b: number, a: number): void; + + /** + * Fills the current path with the specified color. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {Number} r The red component of the color to fill. + * @param {Number} g The green component of the color to fill. + * @param {Number} b The blue component of the color to fill. + * @param {Number} a The alpha component of the color to fill. + */ + fillColor(layer: Layer, r: number, g: number, b: number, a: number): void; + + /** + * Stroke the current path with the image within the specified layer. The + * image data will be tiled infinitely within the stroke. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {String} cap The line cap style. Can be "round", "square", + * or "butt". + * @param {String} join The line join style. Can be "round", "bevel", + * or "miter". + * @param {Number} thickness The line thickness in pixels. + * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern + * within the stroke. + */ + strokeLayer(layer: Layer, cap: string, join: string, thickness: number, srcLayer: Layer): void; + + /** + * Fills the current path with the image within the specified layer. The + * image data will be tiled infinitely within the stroke. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern + * within the fill. + */ + fillLayer(layer: Layer, srcLayer: Layer): void; + + /** + * Push current layer state onto stack. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + */ + push(layer: Layer): void; + + /** + * Pop layer state off stack. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + */ + pop(layer: Layer): void; + + /** + * Reset the layer, clearing the stack, the current path, and any transform + * matrix. + * + * @param {Guacamole.Layer} layer The layer to draw upon. + */ + reset(layer: Layer): void; + + /** + * Sets the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {Guacamole.Layer} layer The layer to modify. + * @param {Number} a The first value in the affine transform's matrix. + * @param {Number} b The second value in the affine transform's matrix. + * @param {Number} c The third value in the affine transform's matrix. + * @param {Number} d The fourth value in the affine transform's matrix. + * @param {Number} e The fifth value in the affine transform's matrix. + * @param {Number} f The sixth value in the affine transform's matrix. + */ + setTransform(layer: Layer, a: number, b: number, c: number, d: number, e: number, f: number): void; + + /** + * Applies the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {Guacamole.Layer} layer The layer to modify. + * @param {Number} a The first value in the affine transform's matrix. + * @param {Number} b The second value in the affine transform's matrix. + * @param {Number} c The third value in the affine transform's matrix. + * @param {Number} d The fourth value in the affine transform's matrix. + * @param {Number} e The fifth value in the affine transform's matrix. + * @param {Number} f The sixth value in the affine transform's matrix. + */ + transform(layer: Layer, a: number, b: number, c: number, d: number, e: number, f: number): void; + + /** + * Sets the channel mask for future operations on this Layer. + * + * The channel mask is a Guacamole-specific compositing operation identifier + * with a single bit representing each of four channels (in order): source + * image where destination transparent, source where destination opaque, + * destination where source transparent, and destination where source + * opaque. + * + * @param {Guacamole.Layer} layer The layer to modify. + * @param {Number} mask The channel mask for future operations on this + * Layer. + */ + setChannelMask(layer: Layer, mask: number): void; + + /** + * Sets the miter limit for stroke operations using the miter join. This + * limit is the maximum ratio of the size of the miter join to the stroke + * width. If this ratio is exceeded, the miter will not be drawn for that + * joint of the path. + * + * @param {Guacamole.Layer} layer The layer to modify. + * @param {Number} limit The miter limit for stroke operations using the + * miter join. + */ + setMiterLimit(layer: Layer, limit: number): void; + + /** + * Removes the given layer container entirely, such that it is no longer + * contained within its parent layer, if any. + * + * @param {Guacamole.Display.VisibleLayer} layer + * The layer being removed from its parent. + */ + dispose(layer: Display.VisibleLayer): void; + + /** + * Applies the given affine transform (defined with six values from the + * transform's matrix) to the given layer. + * + * @param {Guacamole.Display.VisibleLayer} layer + * The layer being distorted. + * + * @param {Number} a + * The first value in the affine transform's matrix. + * + * @param {Number} b + * The second value in the affine transform's matrix. + * + * @param {Number} c + * The third value in the affine transform's matrix. + * + * @param {Number} d + * The fourth value in the affine transform's matrix. + * + * @param {Number} e + * The fifth value in the affine transform's matrix. + * + * @param {Number} f + * The sixth value in the affine transform's matrix. + */ + distort(layer: Display.VisibleLayer, a: number, b: number, c: number, d: number, e: number, f: number): void; + + /** + * Moves the upper-left corner of the given layer to the given X and Y + * coordinate, sets the Z stacking order, and reparents the layer + * to the given parent layer. + * + * @param {Guacamole.Display.VisibleLayer} layer + * The layer being moved. + * + * @param {Guacamole.Display.VisibleLayer} parent + * The parent to set. + * + * @param {Number} x + * The X coordinate to move to. + * + * @param {Number} y + * The Y coordinate to move to. + * + * @param {Number} z + * The Z coordinate to move to. + */ + move(layer: Display.VisibleLayer, parent: Display.VisibleLayer, x: number, y: number, z: number): void; + + /** + * Sets the opacity of the given layer to the given value, where 255 is + * fully opaque and 0 is fully transparent. + * + * @param {Guacamole.Display.VisibleLayer} layer + * The layer whose opacity should be set. + * + * @param {Number} alpha + * The opacity to set. + */ + shade(layer: Display.VisibleLayer, alpha: number): void; + + /** + * Sets the scale of the client display element such that it renders at + * a relatively smaller or larger size, without affecting the true + * resolution of the display. + * + * @param {Number} scale The scale to resize to, where 1.0 is normal + * size (1:1 scale). + */ + scale(scale: number): void; + + /** + * Returns the scale of the display. + * + * @return {Number} The scale of the display. + */ + getScale(): number; + + /** + * Returns a canvas element containing the entire display, with all child + * layers composited within. + * + * @return {HTMLCanvasElement} A new canvas element containing a copy of + * the display. + */ + flatten(): any; + + } + + namespace Display { + /** + * Simple container for Guacamole.Layer, allowing layers to be easily + * repositioned and nested. This allows certain operations to be accelerated + * through DOM manipulation, rather than raster operations. + */ + class VisibleLayer { + + /** + * The opacity of the layer container, where 255 is fully opaque and 0 is + * fully transparent. + */ + alpha: number; + + /** + * X coordinate of the upper-left corner of this layer container within + * its parent, in pixels. + * @type {Number} + */ + x: number; + + /** + * Y coordinate of the upper-left corner of this layer container within + * its parent, in pixels. + * @type {Number} + */ + y: number; + + /** + * Z stacking order of this layer relative to other sibling layers. + * @type {Number} + */ + z: number; + + /** + * The affine transformation applied to this layer container. Each element + * corresponds to a value from the transformation matrix, with the first + * three values being the first row, and the last three values being the + * second row. There are six values total. + * + * @type {Number[]} + */ + matrix: number[]; + + /** + * The parent layer container of this layer, if any. + * @type {Guacamole.Display.VisibleLayer} + */ + parent: VisibleLayer; + + /** + * Set of all children of this layer, indexed by layer index. This object + * will have one property per child. + */ + children: any; + + /** + * @augments Guacamole.Layer + * @param {Number} width The width of the Layer, in pixels. The canvas element + * backing this Layer will be given this width. + * @param {Number} height The height of the Layer, in pixels. The canvas element + * backing this Layer will be given this height. + */ + constructor(width: number, height: number); + + resize(width: number, height: number): void; + + /** + * Returns the element containing the canvas and any other elements + * associated with this layer. + * @returns {Element} The element containing this layer's canvas. + */ + getElement(): any; + + /** + * Moves the upper-left corner of this layer to the given X and Y + * coordinate. + * + * @param {Number} x The X coordinate to move to. + * @param {Number} y The Y coordinate to move to. + */ + translate(x: number, y: number): void; + + /** + * Moves the upper-left corner of this VisibleLayer to the given X and Y + * coordinate, sets the Z stacking order, and reparents this VisibleLayer + * to the given VisibleLayer. + * + * @param {Guacamole.Display.VisibleLayer} parent The parent to set. + * @param {Number} x The X coordinate to move to. + * @param {Number} y The Y coordinate to move to. + * @param {Number} z The Z coordinate to move to. + */ + move(parent: VisibleLayer, x: number, y: number, z: number): void; + + /** + * Sets the opacity of this layer to the given value, where 255 is fully + * opaque and 0 is fully transparent. + * + * @param {Number} a The opacity to set. + */ + shade(a: number): void; + + /** + * Removes this layer container entirely, such that it is no longer + * contained within its parent layer, if any. + */ + dispose(): void; + + /** + * Applies the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {Number} a The first value in the affine transform's matrix. + * @param {Number} b The second value in the affine transform's matrix. + * @param {Number} c The third value in the affine transform's matrix. + * @param {Number} d The fourth value in the affine transform's matrix. + * @param {Number} e The fifth value in the affine transform's matrix. + * @param {Number} f The sixth value in the affine transform's matrix. + */ + distort(a: number, b: number, c: number, d: number, e: number, f: number): void; + + } + } + + /** + * Provides cross-browser absolute touch event translation for a given element. + * + * Touch events are translated into mouse events as if the touches occurred + * on a touchscreen (tapping anywhere on the screen clicks at that point, + * long-press to right-click). + */ + class Mouse { + /** + * @param {Element} element The Element to use to provide touch events. + */ + constructor(element: any); + + /** + * Fired whenever a mouse button is effectively pressed. This can happen + * as part of a "mousedown" gesture initiated by the user by pressing one + * finger over the touchscreen element, as part of a "scroll" gesture + * initiated by dragging two fingers up or down, etc. + * + * @event + * @param {Guacamole.Mouse.State} state The current mouse state. + */ + onmousedown(state: Mouse.State): void; + + /** + * Fired whenever a mouse button is effectively released. This can happen + * as part of a "mouseup" gesture initiated by the user by removing the + * finger pressed against the touchscreen element, or as part of a "scroll" + * gesture initiated by dragging two fingers up or down, etc. + * + * @event + * @param {Guacamole.Mouse.State} state The current mouse state. + */ + onmouseup(state: Mouse.State): void; + + /** + * Fired whenever the user moves the mouse by dragging their finger over + * the touchscreen element. Note that unlike Guacamole.Mouse.Touchpad, + * dragging a finger over the touchscreen element will always cause + * the mouse button to be effectively down, as if clicking-and-dragging. + * + * @event + * @param {Guacamole.Mouse.State} state The current mouse state. + */ + onmousemove(state: Mouse.State): void; + + } + + namespace Mouse { + /** + * Simple container for properties describing the state of a mouse. + */ + class State { + /** + * @param {Number} x The X position of the mouse pointer in pixels. + * @param {Number} y The Y position of the mouse pointer in pixels. + * @param {Boolean} left Whether the left mouse button is pressed. + * @param {Boolean} middle Whether the middle mouse button is pressed. + * @param {Boolean} right Whether the right mouse button is pressed. + * @param {Boolean} up Whether the up mouse button is pressed (the fourth + * button, usually part of a scroll wheel). + * @param {Boolean} down Whether the down mouse button is pressed (the fifth + * button, usually part of a scroll wheel). + */ + constructor(x: number, y: number, left: boolean, middle: boolean, right: boolean, up: boolean, down: boolean); + + /** + * The current X position of the mouse pointer. + * @type {Number} + */ + x: number; + + /** + * The current Y position of the mouse pointer. + * @type {Number} + */ + y: number; + + /** + * Whether the left mouse button is currently pressed. + * @type {Boolean} + */ + left: boolean; + + /** + * Whether the middle mouse button is currently pressed. + * @type {Boolean} + */ + middle: boolean; + + /** + * Whether the right mouse button is currently pressed. + * @type {Boolean} + */ + right: boolean; + + /** + * Whether the up mouse button is currently pressed. This is the fourth + * mouse button, associated with upward scrolling of the mouse scroll + * wheel. + * @type {Boolean} + */ + up: boolean; + + /** + * Whether the down mouse button is currently pressed. This is the fifth + * mouse button, associated with downward scrolling of the mouse scroll + * wheel. + * @type {Boolean} + */ + down: boolean; + } + } + + /** + * Abstract ordered drawing surface. Each Layer contains a canvas element and + * provides simple drawing instructions for drawing to that canvas element, + * however unlike the canvas element itself, drawing operations on a Layer are + * guaranteed to run in order, even if such an operation must wait for an image + * to load before completing. + */ + class Layer { + /** + * @param {Number} width The width of the Layer, in pixels. The canvas element + * backing this Layer will be given this width. + * + * @param {Number} height The height of the Layer, in pixels. The canvas element + * backing this Layer will be given this height. + */ + constructor(width: number, height: number); + /** + * Returns the canvas element backing this Layer. Note that the dimensions + * of the canvas may not exactly match those of the Layer, as resizing a + * canvas while maintaining its state is an expensive operation. + * + * @returns {HTMLCanvasElement} + * The canvas element backing this Layer. + */ + getCanvas(): any; + + /** + * Returns a new canvas element containing the same image as this Layer. + * Unlike getCanvas(), the canvas element returned is guaranteed to have + * the exact same dimensions as the Layer. + * + * @returns {HTMLCanvasElement} + * A new canvas element containing a copy of the image content this + * Layer. + */ + toCanvas(): any; + + /** + * Changes the size of this Layer to the given width and height. Resizing + * is only attempted if the new size provided is actually different from + * the current size. + * + * @param {Number} newWidth The new width to assign to this Layer. + * @param {Number} newHeight The new height to assign to this Layer. + */ + resize(newWidth: number, newHeight: number): void; + + /** + * Draws the specified image at the given coordinates. The image specified + * must already be loaded. + * + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + * @param {Image} image The image to draw. Note that this is an Image + * object - not a URL. + */ + drawImage(x: number, y: number, image: any): void; + + /** + * Transfer a rectangle of image data from one Layer to this Layer using the + * specified transfer function. + * + * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. + * @param {Number} srcx The X coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcy The Y coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcw The width of the rectangle within the source Layer's + * coordinate space to copy data from. + * @param {Number} srch The height of the rectangle within the source + * Layer's coordinate space to copy data from. + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + * @param {Function} transferFunction The transfer function to use to + * transfer data from source to + * destination. + */ + transfer(srcLayer: Layer, srcx: number, srcy: number, srcw: number, srch: number, x: number, y: number, transferFunction: any): void; + + /** + * Put a rectangle of image data from one Layer to this Layer directly + * without performing any alpha blending. Simply copy the data. + * + * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. + * @param {Number} srcx The X coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcy The Y coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcw The width of the rectangle within the source Layer's + * coordinate space to copy data from. + * @param {Number} srch The height of the rectangle within the source + * Layer's coordinate space to copy data from. + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + */ + put(srcLayer: Layer, srcx: number, srcy: number, srcw: number, srch: number, x: number, y: number): void; + + /** + * Copy a rectangle of image data from one Layer to this Layer. This + * operation will copy exactly the image data that will be drawn once all + * operations of the source Layer that were pending at the time this + * function was called are complete. This operation will not alter the + * size of the source Layer even if its autosize property is set to true. + * + * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. + * @param {Number} srcx The X coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcy The Y coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcw The width of the rectangle within the source Layer's + * coordinate space to copy data from. + * @param {Number} srch The height of the rectangle within the source + * Layer's coordinate space to copy data from. + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + */ + copy(srcLayer: Layer, srcx: number, srcy: number, srcw: number, srch: number, x: number, y: number): void; + + /** + * Starts a new path at the specified point. + * + * @param {Number} x The X coordinate of the point to draw. + * @param {Number} y The Y coordinate of the point to draw. + */ + moveTo(x: number, y: number): void; + + /** + * Add the specified line to the current path. + * + * @param {Number} x The X coordinate of the endpoint of the line to draw. + * @param {Number} y The Y coordinate of the endpoint of the line to draw. + */ + lineTo(x: number, y: number): void; + + /** + * Add the specified arc to the current path. + * + * @param {Number} x The X coordinate of the center of the circle which + * will contain the arc. + * @param {Number} y The Y coordinate of the center of the circle which + * will contain the arc. + * @param {Number} radius The radius of the circle. + * @param {Number} startAngle The starting angle of the arc, in radians. + * @param {Number} endAngle The ending angle of the arc, in radians. + * @param {Boolean} negative Whether the arc should be drawn in order of + * decreasing angle. + */ + arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, negative: boolean): void; + + /** + * Starts a new path at the specified point. + * + * @param {Number} cp1x The X coordinate of the first control point. + * @param {Number} cp1y The Y coordinate of the first control point. + * @param {Number} cp2x The X coordinate of the second control point. + * @param {Number} cp2y The Y coordinate of the second control point. + * @param {Number} x The X coordinate of the endpoint of the curve. + * @param {Number} y The Y coordinate of the endpoint of the curve. + */ + curveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void; + + /** + * Closes the current path by connecting the end point with the start + * point (if any) with a straight line. + */ + close(): void; + + /** + * Add the specified rectangle to the current path. + * + * @param {Number} x The X coordinate of the upper-left corner of the + * rectangle to draw. + * @param {Number} y The Y coordinate of the upper-left corner of the + * rectangle to draw. + * @param {Number} w The width of the rectangle to draw. + * @param {Number} h The height of the rectangle to draw. + */ + rect(x: number, y: number, w: number, h: number): void; + + /** + * Clip all future drawing operations by the current path. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as fillColor()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + */ + clip(): void; + + /** + * Stroke the current path with the specified color. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {String} cap The line cap style. Can be "round", "square", + * or "butt". + * @param {String} join The line join style. Can be "round", "bevel", + * or "miter". + * @param {Number} thickness The line thickness in pixels. + * @param {Number} r The red component of the color to fill. + * @param {Number} g The green component of the color to fill. + * @param {Number} b The blue component of the color to fill. + * @param {Number} a The alpha component of the color to fill. + */ + strokeColor(cap: string, join: string, thickness: number, r: number, g: number, b: number, a: number): void; + + /** + * Fills the current path with the specified color. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {Number} r The red component of the color to fill. + * @param {Number} g The green component of the color to fill. + * @param {Number} b The blue component of the color to fill. + * @param {Number} a The alpha component of the color to fill. + */ + fillColor(r: number, g: number, b: number, a: number): void; + + /** + * Stroke the current path with the image within the specified layer. The + * image data will be tiled infinitely within the stroke. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {String} cap The line cap style. Can be "round", "square", + * or "butt". + * @param {String} join The line join style. Can be "round", "bevel", + * or "miter". + * @param {Number} thickness The line thickness in pixels. + * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern + * within the stroke. + */ + strokeLayer(cap: string, join: string, thickness: number, srcLayer: Layer): void; + + /** + * Fills the current path with the image within the specified layer. The + * image data will be tiled infinitely within the stroke. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern + * within the fill. + */ + fillLayer(srcLayer: Layer): void; + + /** + * Push current layer state onto stack. + */ + push(): void; + + /** + * Pop layer state off stack. + */ + pop(): void; + + /** + * Reset the layer, clearing the stack, the current path, and any transform + * matrix. + */ + reset(): void; + + /** + * Sets the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {Number} a The first value in the affine transform's matrix. + * @param {Number} b The second value in the affine transform's matrix. + * @param {Number} c The third value in the affine transform's matrix. + * @param {Number} d The fourth value in the affine transform's matrix. + * @param {Number} e The fifth value in the affine transform's matrix. + * @param {Number} f The sixth value in the affine transform's matrix. + */ + setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void; + + /** + * Applies the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {Number} a The first value in the affine transform's matrix. + * @param {Number} b The second value in the affine transform's matrix. + * @param {Number} c The third value in the affine transform's matrix. + * @param {Number} d The fourth value in the affine transform's matrix. + * @param {Number} e The fifth value in the affine transform's matrix. + * @param {Number} f The sixth value in the affine transform's matrix. + */ + transform(a: number, b: number, c: number, d: number, e: number, f: number): void; + + /** + * Sets the channel mask for future operations on this Layer. + * + * The channel mask is a Guacamole-specific compositing operation identifier + * with a single bit representing each of four channels (in order): source + * image where destination transparent, source where destination opaque, + * destination where source transparent, and destination where source + * opaque. + * + * @param {Number} mask The channel mask for future operations on this + * Layer. + */ + setChannelMask(mask: number): void; + + /** + * Sets the miter limit for stroke operations using the miter join. This + * limit is the maximum ratio of the size of the miter join to the stroke + * width. If this ratio is exceeded, the miter will not be drawn for that + * joint of the path. + * + * @param {Number} limit The miter limit for stroke operations using the + * miter join. + */ + setMiterLimit(limit: number): void; + + } + + /** + * A Guacamole status. Each Guacamole status consists of a status code, defined + * by the protocol, and an optional human-readable message, usually only + * included for debugging convenience. + * + * @constructor + * @param {Number} code + * The Guacamole status code, as defined by Guacamole.Status.Code. + * + * @param {String} [message] + * An optional human-readable message. + */ + class Status { + /** + * @param {Number} code + * The Guacamole status code, as defined by Guacamole.Status.Code. + * + * @param {String} [message] + * An optional human-readable message. + */ + constructor(code: number, message: string); + + /** + * Returns whether this status represents an error. + * @returns {Boolean} true if this status represents an error, false + * otherwise. + */ + isError(): boolean; + + /** + * Enumeration of all Guacamole status codes. + */ + static Code: any; + } + + /** + * An input stream abstraction used by the Guacamole client to facilitate + * transfer of files or other binary data. + */ + class InputStream { + /** + * @param {Guacamole.Client} client The client owning this stream. + * @param {Number} index The index of this stream. + */ + constructor(client: Client, index: number); + + /** + * Acknowledges the receipt of a blob. + * + * @param {String} message A human-readable message describing the error + * or status. + * @param {Number} code The error code, if any, or 0 for success. + */ + sendAck(message: string, code: number): void; + + } + + /** + * Abstract stream which can receive data. + */ + class OutputStream { + /** + * @param {Guacamole.Client} client The client owning this stream. + * @param {Number} index The index of this stream. + */ + constructor(client: Client, index: number); + + /** + * Writes the given base64-encoded data to this stream as a blob. + * + * @param {String} data The base64-encoded data to send. + */ + sendBlob(data: string): void; + + /** + * Closes this stream. + */ + sendEnd(): void; + } + + /** + * Integer pool which returns consistently increasing integers while integers + * are in use, and previously-used integers when possible. + */ + class IntegerPool { + /** + * Returns the next available integer in the pool. If possible, a previously + * used integer will be returned. + * + * @return {Number} The next available integer. + */ + next(): number; + + /** + * Frees the given integer, allowing it to be reused. + * + * @param {Number} integer The integer to free. + */ + free(integer: number): void; + + } + + /** + * A reader which automatically handles the given input stream, returning + * strictly text data. Note that this object will overwrite any installed event + * handlers on the given Guacamole.InputStream. + */ + class StringReader { + /** + * @param {Guacamole.InputStream} stream The stream that data will be read + * from. + */ + constructor(stream: InputStream); + + /** + * Fired once for every blob of text data received. + * + * @event + * @param {String} text The data packet received. + */ + ontext(text: string): void; + + /** + * Fired once this stream is finished and no further data will be written. + * @event + */ + onend(): void; + + } + + /** + * A writer which automatically writes to the given output stream with the + * contents of provided Blob objects. + * + * @constructor + * @param {Guacamole.OutputStream} stream + * The stream that data will be written to. + */ + class BlobWriter { + /** + * @param {Guacamole.OutputStream} stream + * The stream that data will be written to. + */ + constructor(stream: OutputStream); + + /** + * Sends the contents of the given blob over the underlying stream. + * + * @param {Blob} blob + * The blob to send. + */ + sendBlob(blob: any): void; + + /** + * Signals that no further text will be sent, effectively closing the + * stream. + */ + sendEnd(): void; + + /** + * Fired for received data, if acknowledged by the server. + * + * @event + * @param {Guacamole.Status} status + * The status of the operation. + */ + onack(status: Status): void; + + /** + * Fired when an error occurs reading a blob passed to + * [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. The transfer for the + * the given blob will cease, but the stream will remain open. + * + * @event + * @param {Blob} blob + * The blob that was being read when the error occurred. + * + * @param {Number} offset + * The offset of the failed read attempt within the blob, in bytes. + * + * @param {DOMError} error + * The error that occurred. + */ + onerror(blob: any, offset: number, error: any): void; + + /** + * Fired for each successfully-read chunk of data as a blob is being sent + * via [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. + * + * @event + * @param {Blob} blob + * The blob that is being read. + * + * @param {Number} offset + * The offset of the read that just succeeded. + */ + onprogress(blob: any, offset: number): void; + + /** + * Fired when a blob passed to + * [sendBlob()]{@link Guacamole.BlobWriter#sendBlob} has finished being + * sent. + * + * @event + * @param {Blob} blob + * The blob that was sent. + */ + oncomplete(blob: any): void; + + } + + /** + * A reader which automatically handles the given input stream, assembling all + * received blobs into a single blob by appending them to each other in order. + * Note that this object will overwrite any installed event handlers on the + * given Guacamole.InputStream. + */ + class BlobReader { + /** + * @param {Guacamole.InputStream} stream The stream that data will be read + * from. + * @param {String} mimetype The mimetype of the blob being built. + */ + constructor(stream: InputStream, mimetype: string); + + /** + * Returns the current length of this Guacamole.InputStream, in bytes. + * @return {Number} The current length of this Guacamole.InputStream. + */ + getLength(): number; + + /** + * Returns the contents of this Guacamole.BlobReader as a Blob. + * @return {Blob} The contents of this Guacamole.BlobReader. + */ + getBlob(): any; + + /** + * Fired once for every blob of data received. + * + * @event + * @param {Number} length The number of bytes received. + */ + onprogress(length: number): void; + + /** + * Fired once this stream is finished and no further data will be written. + * @event + */ + onend(): void; + + } + + /** + * A reader which automatically handles the given input stream, returning + * received blobs as a single data URI built over the course of the stream. + * Note that this object will overwrite any installed event handlers on the + * given Guacamole.InputStream. + */ + class DataURIReader { + /** + * @param {Guacamole.InputStream} stream + * @param mimetype + */ + constructor(stream: InputStream, mimetype: InputStream); + + /** + * Returns the data URI of all data received through the underlying stream + * thus far. + * + * @returns {String} + * The data URI of all data received through the underlying stream thus + * far. + */ + getURI(): string; + + /** + * Fired once this stream is finished and no further data will be written. + * + * @event + */ + onend(): void; + } + + /** + * An object used by the Guacamole client to house arbitrarily-many named + * input and output streams. + */ + class Object { + /** + * @param {Guacamole.Client} client + * The client owning this object. + * + * @param {Number} index + * The index of this object. + */ + constructor(client: Client, index: number); + + /** + * Called when this object receives the body of a requested input stream. + * By default, all objects will invoke the callbacks provided to their + * requestInputStream() functions based on the name of the stream + * requested. This behavior can be overridden by specifying a different + * handler here. + * + * @event + * @param {Guacamole.InputStream} inputStream + * The input stream of the received body. + * + * @param {String} mimetype + * The mimetype of the data being received. + * + * @param {String} name + * The name of the stream whose body has been received. + */ + onbody(inputStream: InputStream, mimetype: string, name: string): void; + + /** + * Called when this object is being undefined. Once undefined, no further + * communication involving this object may occur. + * + * @event + */ + onundefine(): void; + + /** + * Requests read access to the input stream having the given name. If + * successful, a new input stream will be created. + * + * @param {String} name + * The name of the input stream to request. + * + * @param {Function} [bodyCallback] + * The callback to invoke when the body of the requested input stream + * is received. This callback will be provided a Guacamole.InputStream + * and its mimetype as its two only arguments. If the onbody handler of + * this object is overridden, this callback will not be invoked. + */ + requestInputStream(name: string, bodyCallback: any): void; + + /** + * Creates a new output stream associated with this object and having the + * given mimetype and name. The legality of a mimetype and name is dictated + * by the object itself. + * + * @param {String} mimetype + * The mimetype of the data which will be sent to the output stream. + * + * @param {String} name + * The defined name of an output stream within this object. + * + * @returns {Guacamole.OutputStream} + * An output stream which will write blobs to the named output stream + * of this object. + */ + createOutputStream(mimetype: string, name: string): OutputStream; + } + + /** + * A reader which automatically handles the given input stream, assembling all + * received blobs into a JavaScript object by appending them to each other, in + * order, and decoding the result as JSON. Note that this object will overwrite + * any installed event handlers on the given Guacamole.InputStream. + */ + class JSONReader { + /** + * @param {Guacamole.InputStream} stream + */ + constructor(stream: InputStream); + + /** + * Returns the current length of this Guacamole.JSONReader, in characters. + * + * @return {Number} + * The current length of this Guacamole.JSONReader. + */ + getLength(): number; + + /** + * Returns the contents of this Guacamole.JSONReader as a JavaScript + * object. + * + * @return {Object} + * The contents of this Guacamole.JSONReader, as parsed from the JSON + * contents of the input stream. + */ + getJSON(): any; + + /** + * Fired once for every blob of data received. + * + * @event + * @param {Number} length + * The number of characters received. + */ + onprogress(length: number): void; + + /** + * Fired once this stream is finished and no further data will be written. + * + * @event + */ + onend(): void; + } + + /** + * A writer which automatically writes to the given output stream with text + * data. + */ + class StringWriter { + /** + * @param stream @param {Guacamole.OutputStream} stream The stream that data will be written to. + */ + constructor(stream: InputStream); + + /** + * Sends the given text. + * + * @param {String} text The text to send. + */ + sendText(text: string): void; + + /** + * Signals that no further text will be sent, effectively closing the + * stream. + */ + sendEnd(): void; + + /** + * Fired for received data, if acknowledged by the server. + * @event + * @param {Guacamole.Status} status The status of the operation. + */ + onack(status: Status): void; + } + + /** + * Simple Guacamole protocol parser that invokes an oninstruction event when + * full instructions are available from data received via receive(). + */ + class Parser { + /** + * Appends the given instruction data packet to the internal buffer of + * this Guacamole.Parser, executing all completed instructions at + * the beginning of this buffer, if any. + * + * @param {String} packet The instruction data to receive. + */ + receive(packet: string): void; + + /** + * Fired once for every complete Guacamole instruction received, in order. + * + * @event + * @param {String} opcode The Guacamole instruction opcode. + * @param {Array} parameters The parameters provided for the instruction, + * if any. + */ + oninstruction(opcode: string, parameters: any): void; + + } + + /** + * A reader which automatically handles the given input stream, returning + * strictly received packets as array buffers. Note that this object will + * overwrite any installed event handlers on the given Guacamole.InputStream. + */ + class ArrayBufferReader { + /** + * @param {Guacamole.InputStream} stream The stream that data will be read + */ + constructor(stream: InputStream); + + /** + * Fired once for every blob of data received. + * + * @event + * @param {ArrayBuffer} buffer The data packet received. + */ + ondata(buffer: ArrayBuffer): void; + + /** + * Fired once this stream is finished and no further data will be written. + * @event + */ + onend(): void; + } + + /** + * A writer which automatically writes to the given output stream with arbitrary + * binary data, supplied as ArrayBuffers. + */ + class ArrayBufferWriter { + /** + * @param {Guacamole.OutputStream} stream The stream that data will be written to. + */ + constructor(stream: OutputStream); + + /** + * Sends the given data. + * + * @param {ArrayBuffer|TypedArray} data The data to send. + */ + sendData(data: any): void; + + /** + * Signals that no further text will be sent, effectively closing the + * stream. + */ + sendEnd(): void; + /** + * Fired for received data, if acknowledged by the server. + * @event + * @param {Guacamole.Status} status The status of the operation. + */ + onack(status: Status): void; + } + + /** + * A description of the format of raw PCM audio, such as that used by + * Guacamole.RawAudioPlayer and Guacamole.RawAudioRecorder. This object + * describes the number of bytes per sample, the number of channels, and the + * overall sample rate. + */ + class RawAudioFormat { + /** + * @param {Guacamole.RawAudioFormat|Object} template + * The object whose properties should be copied into the corresponding + * properties of the new Guacamole.RawAudioFormat. + */ + constructor(template: any); + + /** + * Parses the given mimetype, returning a new Guacamole.RawAudioFormat + * which describes the type of raw audio data represented by that mimetype. If + * the mimetype is not a supported raw audio data mimetype, null is returned. + * + * @param {String} mimetype + * The audio mimetype to parse. + * + * @returns {Guacamole.RawAudioFormat} + * A new Guacamole.RawAudioFormat which describes the type of raw + * audio data represented by the given mimetype, or null if the given + * mimetype is not supported. + */ + static parse(mimetype: string): RawAudioFormat; + } + + /** + * Abstract video player which accepts, queues and plays back arbitrary video + * data. It is up to implementations of this class to provide some means of + * handling a provided Guacamole.InputStream and rendering the received data to + * the provided Guacamole.Display.VisibleLayer. Data received along the + * provided stream is to be played back immediately. + */ + class VideoPlayer { + /** + * Notifies this Guacamole.VideoPlayer that all video up to the current + * point in time has been given via the underlying stream, and that any + * difference in time between queued video data and the current time can be + * considered latency. + */ + sync(): void; + /** + * Determines whether the given mimetype is supported by any built-in + * implementation of Guacamole.VideoPlayer, and thus will be properly handled + * by Guacamole.VideoPlayer.getInstance(). + * + * @param {String} mimetype + * The mimetype to check. + * + * @returns {Boolean} + * true if the given mimetype is supported by any built-in + * Guacamole.VideoPlayer, false otherwise. + */ + static isSupportedType(mimetype: string): boolean; + /** + * Returns a list of all mimetypes supported by any built-in + * Guacamole.VideoPlayer, in rough order of priority. Beware that only the core + * mimetypes themselves will be listed. Any mimetype parameters, even required + * ones, will not be included in the list. + * + * @returns {String[]} + * A list of all mimetypes supported by any built-in Guacamole.VideoPlayer, + * excluding any parameters. + */ + static getSupportedTypes(): string[]; + /** + * Returns an instance of Guacamole.VideoPlayer providing support for the given + * video format. If support for the given video format is not available, null + * is returned. + * + * @param {Guacamole.InputStream} stream + * The Guacamole.InputStream to read video data from. + * + * @param {Guacamole.Display.VisibleLayer} layer + * The destination layer in which this Guacamole.VideoPlayer should play + * the received video data. + * + * @param {String} mimetype + * The mimetype of the video data in the provided stream. + * + * @return {Guacamole.VideoPlayer} + * A Guacamole.VideoPlayer instance supporting the given mimetype and + * reading from the given stream, or null if support for the given mimetype + * is absent. + */ + static getInstance(stream: InputStream, layer: Display.VisibleLayer, mimetype: string): VideoPlayer; + } +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..cd1de60 --- /dev/null +++ b/index.js @@ -0,0 +1,41 @@ +require('./src/ArrayBufferReader'); +require('./src/ArrayBufferWriter'); +require('./src/AudioContextFactory'); +require('./src/AudioPlayer'); +require('./src/AudioRecorder'); +require('./src/BlobReader'); +require('./src/BlobWriter'); +require('./src/Client'); +require('./src/DataURIReader'); +require('./src/Display'); +require('./src/Event'); +require('./src/InputSink'); +require('./src/InputStream'); +require('./src/IntegerPool'); +require('./src/JSONReader'); +require('./src/Keyboard'); +require('./src/KeyEventInterpreter'); +require('./src/Layer'); +require('./src/Mouse'); +require('./src/Namespace'); +require('./src/Object'); +require('./src/OnScreenKeyboard'); +require('./src/OutputStream'); +require('./src/Parser'); +require('./src/Position'); +require('./src/RawAudioFormat'); +require('./src/SessionRecording'); +require('./src/Status'); +require('./src/StringReader'); +require('./src/StringWriter'); +require('./src/Touch'); +require('./src/Tunnel'); +require('./src/UTF8Parser'); +require('./src/Version'); +require('./src/VideoPlayer'); + +if(module && module.exports) { + module.exports = Guacamole; +} else { + window.Guacamole = Guacamole; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..dca270f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10529 @@ +{ + "name": "@glokon/guacamole-common-js", + "version": "1.5.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@glokon/guacamole-common-js", + "version": "1.5.3", + "license": "Apache-2.0", + "devDependencies": { + "@babel/cli": "^7.22.9", + "@babel/core": "^7.22.9", + "@babel/eslint-parser": "^7.22.9", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/preset-env": "^7.22.9", + "babel-loader": "^9.1.3", + "babel-preset-minify": "^0.5.2", + "eslint": "^8.46.0", + "file-loader": "^6.2.0", + "jest": "^29.6.2", + "terser-webpack-plugin": "^5.3.9", + "url-loader": "^4.1.1", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "4.15.1" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/cli": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.9.tgz", + "integrity": "sha512-nb2O7AThqRo7/E53EGiuAkMaRbb7J5Qp3RvN+dmua1U+kydm0oznkhqbTEG15yk26G/C3yL6OdZjzgl+DMXVVA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.2.0", + "make-dir": "^2.1.0", + "slash": "^2.0.0" + }, + "bin": { + "babel": "bin/babel.js", + "babel-external-helpers": "bin/babel-external-helpers.js" + }, + "engines": { + "node": ">=6.9.0" + }, + "optionalDependencies": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", + "chokidar": "^3.4.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", + "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.8", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.22.9.tgz", + "integrity": "sha512-xdMkt39/nviO/4vpVdrEYPwXCsYIXSSAr6mC7WQsNIlGnuxKyKE7GZjalcnbSWiC4OXGNNN3UQPeHfjSC6sTDA==", + "dev": true, + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.11.0", + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", + "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", + "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", + "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz", + "integrity": "sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz", + "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", + "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", + "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", + "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-wrap-function": "^7.22.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", + "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz", + "integrity": "sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", + "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", + "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz", + "integrity": "sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", + "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", + "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", + "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", + "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", + "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", + "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", + "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", + "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", + "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", + "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", + "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", + "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", + "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", + "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", + "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", + "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz", + "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", + "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", + "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", + "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", + "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.9.tgz", + "integrity": "sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.7", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.6", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.5", + "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", + "@babel/plugin-transform-numeric-separator": "^7.22.5", + "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.6", + "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.4", + "babel-plugin-polyfill-corejs3": "^0.8.2", + "babel-plugin-polyfill-regenerator": "^0.5.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", + "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", + "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.7", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", + "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", + "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", + "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.2.tgz", + "integrity": "sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.2.tgz", + "integrity": "sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.6.2", + "@jest/reporters": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.6.2", + "jest-haste-map": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.2", + "jest-resolve-dependencies": "^29.6.2", + "jest-runner": "^29.6.2", + "jest-runtime": "^29.6.2", + "jest-snapshot": "^29.6.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", + "jest-watcher": "^29.6.2", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.2", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.2.tgz", + "integrity": "sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.2.tgz", + "integrity": "sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg==", + "dev": true, + "dependencies": { + "expect": "^29.6.2", + "jest-snapshot": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.2.tgz", + "integrity": "sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.2.tgz", + "integrity": "sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.2", + "jest-mock": "^29.6.2", + "jest-util": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.2.tgz", + "integrity": "sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.2", + "@jest/expect": "^29.6.2", + "@jest/types": "^29.6.1", + "jest-mock": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.2.tgz", + "integrity": "sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2", + "jest-worker": "^29.6.2", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.2.tgz", + "integrity": "sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw==", + "dev": true, + "dependencies": { + "@jest/console": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz", + "integrity": "sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.6.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz", + "integrity": "sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.2", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.2", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", + "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", + "dev": true, + "optional": true + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", + "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.44.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", + "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.4.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.7.tgz", + "integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/babel-helper-evaluate-path": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz", + "integrity": "sha512-mUh0UhS607bGh5wUMAQfOpt2JX2ThXMtppHRdRU1kL7ZLRWIXxoV2UIV1r2cAeeNeU1M5SB5/RSUgUxrK8yOkA==", + "dev": true + }, + "node_modules/babel-helper-flip-expressions": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz", + "integrity": "sha512-rSrkRW4YQ2ETCWww9gbsWk4N0x1BOtln349Tk0dlCS90oT68WMLyGR7WvaMp3eAnsVrCqdUtC19lo1avyGPejA==", + "dev": true + }, + "node_modules/babel-helper-is-nodes-equiv": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz", + "integrity": "sha512-ri/nsMFVRqXn7IyT5qW4/hIAGQxuYUFHa3qsxmPtbk6spZQcYlyDogfVpNm2XYOslH/ULS4VEJGUqQX5u7ACQw==", + "dev": true + }, + "node_modules/babel-helper-is-void-0": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz", + "integrity": "sha512-07rBV0xPRM3TM5NVJEOQEkECX3qnHDjaIbFvWYPv+T1ajpUiVLiqTfC+MmiZxY5KOL/Ec08vJdJD9kZiP9UkUg==", + "dev": true + }, + "node_modules/babel-helper-mark-eval-scopes": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz", + "integrity": "sha512-+d/mXPP33bhgHkdVOiPkmYoeXJ+rXRWi7OdhwpyseIqOS8CmzHQXHUp/+/Qr8baXsT0kjGpMHHofHs6C3cskdA==", + "dev": true + }, + "node_modules/babel-helper-remove-or-void": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz", + "integrity": "sha512-eYNceYtcGKpifHDir62gHJadVXdg9fAhuZEXiRQnJJ4Yi4oUTpqpNY//1pM4nVyjjDMPYaC2xSf0I+9IqVzwdA==", + "dev": true + }, + "node_modules/babel-helper-to-multiple-sequence-expressions": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz", + "integrity": "sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==", + "dev": true + }, + "node_modules/babel-jest": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", + "integrity": "sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.6.2", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dev": true, + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-minify-builtins": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz", + "integrity": "sha512-wpqbN7Ov5hsNwGdzuzvFcjgRlzbIeVv1gMIlICbPj0xkexnfoIDe7q+AZHMkQmAE/F9R5jkrB6TLfTegImlXag==", + "dev": true + }, + "node_modules/babel-plugin-minify-constant-folding": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-constant-folding/-/babel-plugin-minify-constant-folding-0.5.0.tgz", + "integrity": "sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==", + "dev": true, + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0" + } + }, + "node_modules/babel-plugin-minify-dead-code-elimination": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.2.tgz", + "integrity": "sha512-krq9Lwi0QIzyAlcNBXTL4usqUvevB4BzktdEsb8srcXC1AaYqRJiAQw6vdKdJSaXbz6snBvziGr6ch/aoRCfpA==", + "dev": true, + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0", + "babel-helper-mark-eval-scopes": "^0.4.3", + "babel-helper-remove-or-void": "^0.4.3", + "lodash": "^4.17.11" + } + }, + "node_modules/babel-plugin-minify-flip-comparisons": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz", + "integrity": "sha512-8hNwgLVeJzpeLVOVArag2DfTkbKodzOHU7+gAZ8mGBFGPQHK6uXVpg3jh5I/F6gfi5Q5usWU2OKcstn1YbAV7A==", + "dev": true, + "dependencies": { + "babel-helper-is-void-0": "^0.4.3" + } + }, + "node_modules/babel-plugin-minify-guarded-expressions": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-guarded-expressions/-/babel-plugin-minify-guarded-expressions-0.4.4.tgz", + "integrity": "sha512-RMv0tM72YuPPfLT9QLr3ix9nwUIq+sHT6z8Iu3sLbqldzC1Dls8DPCywzUIzkTx9Zh1hWX4q/m9BPoPed9GOfA==", + "dev": true, + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0", + "babel-helper-flip-expressions": "^0.4.3" + } + }, + "node_modules/babel-plugin-minify-infinity": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz", + "integrity": "sha512-X0ictxCk8y+NvIf+bZ1HJPbVZKMlPku3lgYxPmIp62Dp8wdtbMLSekczty3MzvUOlrk5xzWYpBpQprXUjDRyMA==", + "dev": true + }, + "node_modules/babel-plugin-minify-mangle-names": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.1.tgz", + "integrity": "sha512-8KMichAOae2FHlipjNDTo2wz97MdEb2Q0jrn4NIRXzHH7SJ3c5TaNNBkeTHbk9WUsMnqpNUx949ugM9NFWewzw==", + "dev": true, + "dependencies": { + "babel-helper-mark-eval-scopes": "^0.4.3" + } + }, + "node_modules/babel-plugin-minify-numeric-literals": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz", + "integrity": "sha512-5D54hvs9YVuCknfWywq0eaYDt7qYxlNwCqW9Ipm/kYeS9gYhJd0Rr/Pm2WhHKJ8DC6aIlDdqSBODSthabLSX3A==", + "dev": true + }, + "node_modules/babel-plugin-minify-replace": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-replace/-/babel-plugin-minify-replace-0.5.0.tgz", + "integrity": "sha512-aXZiaqWDNUbyNNNpWs/8NyST+oU7QTpK7J9zFEFSA0eOmtUNMU3fczlTTTlnCxHmq/jYNFEmkkSG3DDBtW3Y4Q==", + "dev": true + }, + "node_modules/babel-plugin-minify-simplify": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-simplify/-/babel-plugin-minify-simplify-0.5.1.tgz", + "integrity": "sha512-OSYDSnoCxP2cYDMk9gxNAed6uJDiDz65zgL6h8d3tm8qXIagWGMLWhqysT6DY3Vs7Fgq7YUDcjOomhVUb+xX6A==", + "dev": true, + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0", + "babel-helper-flip-expressions": "^0.4.3", + "babel-helper-is-nodes-equiv": "^0.0.1", + "babel-helper-to-multiple-sequence-expressions": "^0.5.0" + } + }, + "node_modules/babel-plugin-minify-type-constructors": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz", + "integrity": "sha512-4ADB0irJ/6BeXWHubjCJmrPbzhxDgjphBMjIjxCc25n4NGJ00NsYqwYt+F/OvE9RXx8KaSW7cJvp+iZX436tnQ==", + "dev": true, + "dependencies": { + "babel-helper-is-void-0": "^0.4.3" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", + "integrity": "sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", + "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.2", + "core-js-compat": "^3.31.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz", + "integrity": "sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-inline-consecutive-adds": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz", + "integrity": "sha512-8D104wbzzI5RlxeVPYeQb9QsUyepiH1rAO5hpPpQ6NPRgQLpIVwkS/Nbx944pm4K8Z+rx7CgjPsFACz/VCBN0Q==", + "dev": true + }, + "node_modules/babel-plugin-transform-member-expression-literals": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz", + "integrity": "sha512-Xq9/Rarpj+bjOZSl1nBbZYETsNEDDJSrb6Plb1sS3/36FukWFLLRysgecva5KZECjUJTrJoQqjJgtWToaflk5Q==", + "dev": true + }, + "node_modules/babel-plugin-transform-merge-sibling-variables": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.5.tgz", + "integrity": "sha512-xj/KrWi6/uP+DrD844h66Qh2cZN++iugEIgH8QcIxhmZZPNP6VpOE9b4gP2FFW39xDAY43kCmYMM6U0QNKN8fw==", + "dev": true + }, + "node_modules/babel-plugin-transform-minify-booleans": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz", + "integrity": "sha512-9pW9ePng6DZpzGPalcrULuhSCcauGAbn8AeU3bE34HcDkGm8Ldt0ysjGkyb64f0K3T5ilV4mriayOVv5fg0ASA==", + "dev": true + }, + "node_modules/babel-plugin-transform-property-literals": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz", + "integrity": "sha512-Pf8JHTjTPxecqVyL6KSwD/hxGpoTZjiEgV7nCx0KFQsJYM0nuuoCajbg09KRmZWeZbJ5NGTySABYv8b/hY1eEA==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + } + }, + "node_modules/babel-plugin-transform-regexp-constructors": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz", + "integrity": "sha512-JjymDyEyRNhAoNFp09y/xGwYVYzT2nWTGrBrWaL6eCg2m+B24qH2jR0AA8V8GzKJTgC8NW6joJmc6nabvWBD/g==", + "dev": true + }, + "node_modules/babel-plugin-transform-remove-console": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz", + "integrity": "sha512-88blrUrMX3SPiGkT1GnvVY8E/7A+k6oj3MNvUtTIxJflFzXTw1bHkuJ/y039ouhFMp2prRn5cQGzokViYi1dsg==", + "dev": true + }, + "node_modules/babel-plugin-transform-remove-debugger": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz", + "integrity": "sha512-Kd+eTBYlXfwoFzisburVwrngsrz4xh9I0ppoJnU/qlLysxVBRgI4Pj+dk3X8F5tDiehp3hhP8oarRMT9v2Z3lw==", + "dev": true + }, + "node_modules/babel-plugin-transform-remove-undefined": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-undefined/-/babel-plugin-transform-remove-undefined-0.5.0.tgz", + "integrity": "sha512-+M7fJYFaEE/M9CXa0/IRkDbiV3wRELzA1kKQFCJ4ifhrzLKn/9VCCgj9OFmYWwBd8IB48YdgPkHYtbYq+4vtHQ==", + "dev": true, + "dependencies": { + "babel-helper-evaluate-path": "^0.5.0" + } + }, + "node_modules/babel-plugin-transform-simplify-comparison-operators": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz", + "integrity": "sha512-GLInxhGAQWJ9YIdjwF6dAFlmh4U+kN8pL6Big7nkDzHoZcaDQOtBm28atEhQJq6m9GpAovbiGEShKqXv4BSp0A==", + "dev": true + }, + "node_modules/babel-plugin-transform-undefined-to-void": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz", + "integrity": "sha512-D2UbwxawEY1xVc9svYAUZQM2xarwSNXue2qDIx6CeV2EuMGaes/0su78zlIDIAgE7BvnMw4UpmSo9fDy+znghg==", + "dev": true + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-minify": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.2.tgz", + "integrity": "sha512-v4GL+kk0TfovbRIKZnC3HPbu2cAGmPAby7BsOmuPdMJfHV+4FVdsGXTH/OOGQRKYdjemBuL1+MsE6mobobhe9w==", + "dev": true, + "dependencies": { + "babel-plugin-minify-builtins": "^0.5.0", + "babel-plugin-minify-constant-folding": "^0.5.0", + "babel-plugin-minify-dead-code-elimination": "^0.5.2", + "babel-plugin-minify-flip-comparisons": "^0.4.3", + "babel-plugin-minify-guarded-expressions": "^0.4.4", + "babel-plugin-minify-infinity": "^0.4.3", + "babel-plugin-minify-mangle-names": "^0.5.1", + "babel-plugin-minify-numeric-literals": "^0.4.3", + "babel-plugin-minify-replace": "^0.5.0", + "babel-plugin-minify-simplify": "^0.5.1", + "babel-plugin-minify-type-constructors": "^0.4.3", + "babel-plugin-transform-inline-consecutive-adds": "^0.4.3", + "babel-plugin-transform-member-expression-literals": "^6.9.4", + "babel-plugin-transform-merge-sibling-variables": "^6.9.5", + "babel-plugin-transform-minify-booleans": "^6.9.4", + "babel-plugin-transform-property-literals": "^6.9.4", + "babel-plugin-transform-regexp-constructors": "^0.4.3", + "babel-plugin-transform-remove-console": "^6.9.4", + "babel-plugin-transform-remove-debugger": "^6.9.4", + "babel-plugin-transform-remove-undefined": "^0.5.0", + "babel-plugin-transform-simplify-comparison-operators": "^6.9.4", + "babel-plugin-transform-undefined-to-void": "^6.9.4", + "lodash": "^4.17.11" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001519", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz", + "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/core-js-compat": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz", + "integrity": "sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.9" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", + "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.485", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.485.tgz", + "integrity": "sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", + "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", + "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.1", + "@eslint/js": "^8.46.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.2", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", + "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", + "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.2.tgz", + "integrity": "sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.6.2", + "@types/node": "*", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", + "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", + "dev": true + }, + "node_modules/fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.2.tgz", + "integrity": "sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.6.2", + "@jest/types": "^29.6.1", + "import-local": "^3.0.2", + "jest-cli": "^29.6.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.2.tgz", + "integrity": "sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.2", + "@jest/expect": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.6.2", + "jest-matcher-utils": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-runtime": "^29.6.2", + "jest-snapshot": "^29.6.2", + "jest-util": "^29.6.2", + "p-limit": "^3.1.0", + "pretty-format": "^29.6.2", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.2.tgz", + "integrity": "sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==", + "dev": true, + "dependencies": { + "@jest/core": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/types": "^29.6.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.6.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.2.tgz", + "integrity": "sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.6.2", + "@jest/types": "^29.6.1", + "babel-jest": "^29.6.2", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.6.2", + "jest-environment-node": "^29.6.2", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.2", + "jest-runner": "^29.6.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.6.2", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.2.tgz", + "integrity": "sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.2.tgz", + "integrity": "sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.6.2", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-node": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.2.tgz", + "integrity": "sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.2", + "@jest/fake-timers": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.2", + "jest-util": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz", + "integrity": "sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.2", + "jest-worker": "^29.6.2", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz", + "integrity": "sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz", + "integrity": "sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.6.2", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.2.tgz", + "integrity": "sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.2.tgz", + "integrity": "sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.2.tgz", + "integrity": "sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.2", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz", + "integrity": "sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.2.tgz", + "integrity": "sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w==", + "dev": true, + "dependencies": { + "@jest/console": "^29.6.2", + "@jest/environment": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.6.2", + "jest-haste-map": "^29.6.2", + "jest-leak-detector": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-resolve": "^29.6.2", + "jest-runtime": "^29.6.2", + "jest-util": "^29.6.2", + "jest-watcher": "^29.6.2", + "jest-worker": "^29.6.2", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.2.tgz", + "integrity": "sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.2", + "@jest/fake-timers": "^29.6.2", + "@jest/globals": "^29.6.2", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-mock": "^29.6.2", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.2", + "jest-snapshot": "^29.6.2", + "jest-util": "^29.6.2", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.2.tgz", + "integrity": "sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.6.2", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.6.2", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^29.6.2", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", + "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.2.tgz", + "integrity": "sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.2.tgz", + "integrity": "sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.6.2", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz", + "integrity": "sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.6.2", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/launch-editor": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", + "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", + "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.19.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", + "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "5.88.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", + "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", + "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8bd3356 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "@glokon/guacamole-common-js", + "version": "1.5.3", + "description": "Guacamole common js with typescript type definitions", + "main": "lib/index.js", + "types": "guacamole.d.ts", + "scripts": { + "build": "webpack", + "test": "jest" + }, + "keywords": [ + "guacamole", + "guacamole-common-js", + "guacamole-client", + "typescript" + ], + "author": "Daniel McAssey ", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/glokon/guacamole-common-js/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/glokon/guacamole-common-js.git" + }, + "devDependencies": { + "@babel/cli": "^7.22.9", + "@babel/core": "^7.22.9", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/preset-env": "^7.22.9", + "@babel/eslint-parser": "^7.22.9", + "babel-loader": "^9.1.3", + "babel-preset-minify": "^0.5.2", + "eslint": "^8.46.0", + "file-loader": "^6.2.0", + "jest": "^29.6.2", + "terser-webpack-plugin": "^5.3.9", + "url-loader": "^4.1.1", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "4.15.1" + }, + "homepage": "https://github.com/glokon/guacamole-common-js" +} diff --git a/scripts/getPackageJson.js b/scripts/getPackageJson.js new file mode 100644 index 0000000..c06bd05 --- /dev/null +++ b/scripts/getPackageJson.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * A module to get package informations from package.json + * @module getPackageJson + * @param {...string} keys from package.json if no arguments passed it returns package.json content as object + * @returns {object} with given keys or content of package.json as object + */ + +/** + * Returns package info + */ +const getPackageJson = function(...args) { + const packageJSON = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'))); + if (!args.length) { + return packageJSON; + } + return args.reduce((out, key) => { + out[key] = packageJSON[key]; + return out; + }, {}); +}; + +module.exports = getPackageJson; diff --git a/src/ArrayBufferReader.js b/src/ArrayBufferReader.js new file mode 100644 index 0000000..2d542a6 --- /dev/null +++ b/src/ArrayBufferReader.js @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A reader which automatically handles the given input stream, returning + * strictly received packets as array buffers. Note that this object will + * overwrite any installed event handlers on the given Guacamole.InputStream. + * + * @constructor + * @param {!Guacamole.InputStream} stream + * The stream that data will be read from. + */ +Guacamole.ArrayBufferReader = function(stream) { + + /** + * Reference to this Guacamole.InputStream. + * @private + */ + var guac_reader = this; + + // Receive blobs as array buffers + stream.onblob = function(data) { + + // Convert to ArrayBuffer + var binary = window.atob(data); + var arrayBuffer = new ArrayBuffer(binary.length); + var bufferView = new Uint8Array(arrayBuffer); + + for (var i=0; istart and ending at end - 1. + */ + var slice = function slice(blob, start, end) { + + // Use prefixed implementations if necessary + var sliceImplementation = ( + blob.slice + || blob.webkitSlice + || blob.mozSlice + ).bind(blob); + + var length = end - start; + + // The old Blob.slice() was length-based (not end-based). Try the + // length version first, if the two calls are not equivalent. + if (length !== end) { + + // If the result of the slice() call matches the expected length, + // trust that result. It must be correct. + var sliceResult = sliceImplementation(start, length); + if (sliceResult.size === length) + return sliceResult; + + } + + // Otherwise, use the most-recent standard: end-based slice() + return sliceImplementation(start, end); + + }; + + /** + * Sends the contents of the given blob over the underlying stream. + * + * @param {!Blob} blob + * The blob to send. + */ + this.sendBlob = function sendBlob(blob) { + + var offset = 0; + var reader = new FileReader(); + + /** + * Reads the next chunk of the blob provided to + * [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. The chunk itself + * is read asynchronously, and will not be available until + * reader.onload fires. + * + * @private + */ + var readNextChunk = function readNextChunk() { + + // If no further chunks remain, inform of completion and stop + if (offset >= blob.size) { + + // Fire completion event for completed blob + if (guacWriter.oncomplete) + guacWriter.oncomplete(blob); + + // No further chunks to read + return; + + } + + // Obtain reference to next chunk as a new blob + var chunk = slice(blob, offset, offset + arrayBufferWriter.blobLength); + offset += arrayBufferWriter.blobLength; + + // Attempt to read the blob contents represented by the blob into + // a new array buffer + reader.readAsArrayBuffer(chunk); + + }; + + // Send each chunk over the stream, continue reading the next chunk + reader.onload = function chunkLoadComplete() { + + // Send the successfully-read chunk + arrayBufferWriter.sendData(reader.result); + + // Continue sending more chunks after the latest chunk is + // acknowledged + arrayBufferWriter.onack = function sendMoreChunks(status) { + + if (guacWriter.onack) + guacWriter.onack(status); + + // Abort transfer if an error occurs + if (status.isError()) + return; + + // Inform of blob upload progress via progress events + if (guacWriter.onprogress) + guacWriter.onprogress(blob, offset - arrayBufferWriter.blobLength); + + // Queue the next chunk for reading + readNextChunk(); + + }; + + }; + + // If an error prevents further reading, inform of error and stop + reader.onerror = function chunkLoadFailed() { + + // Fire error event, including the context of the error + if (guacWriter.onerror) + guacWriter.onerror(blob, offset, reader.error); + + }; + + // Begin reading the first chunk + readNextChunk(); + + }; + + /** + * Signals that no further text will be sent, effectively closing the + * stream. + */ + this.sendEnd = function sendEnd() { + arrayBufferWriter.sendEnd(); + }; + + /** + * Fired for received data, if acknowledged by the server. + * + * @event + * @param {!Guacamole.Status} status + * The status of the operation. + */ + this.onack = null; + + /** + * Fired when an error occurs reading a blob passed to + * [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. The transfer for the + * the given blob will cease, but the stream will remain open. + * + * @event + * @param {!Blob} blob + * The blob that was being read when the error occurred. + * + * @param {!number} offset + * The offset of the failed read attempt within the blob, in bytes. + * + * @param {!DOMError} error + * The error that occurred. + */ + this.onerror = null; + + /** + * Fired for each successfully-read chunk of data as a blob is being sent + * via [sendBlob()]{@link Guacamole.BlobWriter#sendBlob}. + * + * @event + * @param {!Blob} blob + * The blob that is being read. + * + * @param {!number} offset + * The offset of the read that just succeeded. + */ + this.onprogress = null; + + /** + * Fired when a blob passed to + * [sendBlob()]{@link Guacamole.BlobWriter#sendBlob} has finished being + * sent. + * + * @event + * @param {!Blob} blob + * The blob that was sent. + */ + this.oncomplete = null; + +}; diff --git a/src/Client.js b/src/Client.js new file mode 100644 index 0000000..bd59d65 --- /dev/null +++ b/src/Client.js @@ -0,0 +1,2080 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Guacamole protocol client. Given a {@link Guacamole.Tunnel}, + * automatically handles incoming and outgoing Guacamole instructions via the + * provided tunnel, updating its display using one or more canvas elements. + * + * @constructor + * @param {!Guacamole.Tunnel} tunnel + * The tunnel to use to send and receive Guacamole instructions. + */ +Guacamole.Client = function(tunnel) { + + var guac_client = this; + + var currentState = Guacamole.Client.State.IDLE; + + var currentTimestamp = 0; + + /** + * The rough number of milliseconds to wait between sending keep-alive + * pings. This may vary depending on how frequently the browser allows + * timers to run, as well as how frequently the client receives messages + * from the server. + * + * @private + * @constant + * @type {!number} + */ + var KEEP_ALIVE_FREQUENCY = 5000; + + /** + * The current keep-alive ping timeout ID, if any. This will only be set + * upon connecting. + * + * @private + * @type {number} + */ + var keepAliveTimeout = null; + + /** + * The timestamp of the point in time that the last keep-live ping was + * sent, in milliseconds elapsed since midnight of January 1, 1970 UTC. + * + * @private + * @type {!number} + */ + var lastSentKeepAlive = 0; + + /** + * Translation from Guacamole protocol line caps to Layer line caps. + * + * @private + * @type {!Object.} + */ + var lineCap = { + 0: "butt", + 1: "round", + 2: "square" + }; + + /** + * Translation from Guacamole protocol line caps to Layer line caps. + * + * @private + * @type {!Object.} + */ + var lineJoin = { + 0: "bevel", + 1: "miter", + 2: "round" + }; + + /** + * The underlying Guacamole display. + * + * @private + * @type {!Guacamole.Display} + */ + var display = new Guacamole.Display(); + + /** + * All available layers and buffers + * + * @private + * @type {!Object.} + */ + var layers = {}; + + /** + * All audio players currently in use by the client. Initially, this will + * be empty, but audio players may be allocated by the server upon request. + * + * @private + * @type {!Object.} + */ + var audioPlayers = {}; + + /** + * All video players currently in use by the client. Initially, this will + * be empty, but video players may be allocated by the server upon request. + * + * @private + * @type {!Object.} + */ + var videoPlayers = {}; + + // No initial parsers + var parsers = []; + + // No initial streams + var streams = []; + + /** + * All current objects. The index of each object is dictated by the + * Guacamole server. + * + * @private + * @type {!Guacamole.Object[]} + */ + var objects = []; + + // Pool of available stream indices + var stream_indices = new Guacamole.IntegerPool(); + + // Array of allocated output streams by index + var output_streams = []; + + function setState(state) { + if (state != currentState) { + currentState = state; + if (guac_client.onstatechange) + guac_client.onstatechange(currentState); + } + } + + function isConnected() { + return currentState == Guacamole.Client.State.CONNECTED + || currentState == Guacamole.Client.State.WAITING; + } + + /** + * Produces an opaque representation of Guacamole.Client state which can be + * later imported through a call to importState(). This object is + * effectively an independent, compressed snapshot of protocol and display + * state. Invoking this function implicitly flushes the display. + * + * @param {!function} callback + * Callback which should be invoked once the state object is ready. The + * state object will be passed to the callback as the sole parameter. + * This callback may be invoked immediately, or later as the display + * finishes rendering and becomes ready. + */ + this.exportState = function exportState(callback) { + + // Start with empty state + var state = { + 'currentState' : currentState, + 'currentTimestamp' : currentTimestamp, + 'layers' : {} + }; + + var layersSnapshot = {}; + + // Make a copy of all current layers (protocol state) + for (var key in layers) { + layersSnapshot[key] = layers[key]; + } + + // Populate layers once data is available (display state, requires flush) + display.flush(function populateLayers() { + + // Export each defined layer/buffer + for (var key in layersSnapshot) { + + var index = parseInt(key); + var layer = layersSnapshot[key]; + var canvas = layer.toCanvas(); + + // Store layer/buffer dimensions + var exportLayer = { + 'width' : layer.width, + 'height' : layer.height + }; + + // Store layer/buffer image data, if it can be generated + if (layer.width && layer.height) + exportLayer.url = canvas.toDataURL('image/png'); + + // Add layer properties if not a buffer nor the default layer + if (index > 0) { + exportLayer.x = layer.x; + exportLayer.y = layer.y; + exportLayer.z = layer.z; + exportLayer.alpha = layer.alpha; + exportLayer.matrix = layer.matrix; + exportLayer.parent = getLayerIndex(layer.parent); + } + + // Store exported layer + state.layers[key] = exportLayer; + + } + + // Invoke callback now that the state is ready + callback(state); + + }); + + }; + + /** + * Restores Guacamole.Client protocol and display state based on an opaque + * object from a prior call to exportState(). The Guacamole.Client instance + * used to export that state need not be the same as this instance. + * + * @param {!object} state + * An opaque representation of Guacamole.Client state from a prior call + * to exportState(). + * + * @param {function} [callback] + * The function to invoke when state has finished being imported. This + * may happen immediately, or later as images within the provided state + * object are loaded. + */ + this.importState = function importState(state, callback) { + + var key; + var index; + + currentState = state.currentState; + currentTimestamp = state.currentTimestamp; + + // Cancel any pending display operations/frames + display.cancel(); + + // Dispose of all layers + for (key in layers) { + index = parseInt(key); + if (index > 0) + layers[key].dispose(); + } + + layers = {}; + + // Import state of each layer/buffer + for (key in state.layers) { + + index = parseInt(key); + + var importLayer = state.layers[key]; + var layer = getLayer(index); + + // Reset layer size + display.resize(layer, importLayer.width, importLayer.height); + + // Initialize new layer if it has associated data + if (importLayer.url) { + display.setChannelMask(layer, Guacamole.Layer.SRC); + display.draw(layer, 0, 0, importLayer.url); + } + + // Set layer-specific properties if not a buffer nor the default layer + if (index > 0 && importLayer.parent >= 0) { + + // Apply layer position and set parent + var parent = getLayer(importLayer.parent); + display.move(layer, parent, importLayer.x, importLayer.y, importLayer.z); + + // Set layer transparency + display.shade(layer, importLayer.alpha); + + // Apply matrix transform + var matrix = importLayer.matrix; + display.distort(layer, + matrix[0], matrix[1], matrix[2], + matrix[3], matrix[4], matrix[5]); + + } + + } + + // Flush changes to display + display.flush(callback); + + }; + + /** + * Returns the underlying display of this Guacamole.Client. The display + * contains an Element which can be added to the DOM, causing the + * display to become visible. + * + * @return {!Guacamole.Display} + * The underlying display of this Guacamole.Client. + */ + this.getDisplay = function() { + return display; + }; + + /** + * Sends the current size of the screen. + * + * @param {!number} width + * The width of the screen. + * + * @param {!number} height + * The height of the screen. + */ + this.sendSize = function(width, height) { + + // Do not send requests if not connected + if (!isConnected()) + return; + + tunnel.sendMessage("size", width, height); + + }; + + /** + * Sends a key event having the given properties as if the user + * pressed or released a key. + * + * @param {!boolean} pressed + * Whether the key is pressed (true) or released (false). + * + * @param {!number} keysym + * The keysym of the key being pressed or released. + */ + this.sendKeyEvent = function(pressed, keysym) { + // Do not send requests if not connected + if (!isConnected()) + return; + + tunnel.sendMessage("key", keysym, pressed); + }; + + /** + * Sends a mouse event having the properties provided by the given mouse + * state. + * + * @param {!Guacamole.Mouse.State} mouseState + * The state of the mouse to send in the mouse event. + * + * @param {boolean} [applyDisplayScale=false] + * Whether the provided mouse state uses local display units, rather + * than remote display units, and should be scaled to match the + * {@link Guacamole.Display}. + */ + this.sendMouseState = function sendMouseState(mouseState, applyDisplayScale) { + + // Do not send requests if not connected + if (!isConnected()) + return; + + var x = mouseState.x; + var y = mouseState.y; + + // Translate for display units if requested + if (applyDisplayScale) { + x /= display.getScale(); + y /= display.getScale(); + } + + // Update client-side cursor + display.moveCursor( + Math.floor(x), + Math.floor(y) + ); + + // Build mask + var buttonMask = 0; + if (mouseState.left) buttonMask |= 1; + if (mouseState.middle) buttonMask |= 2; + if (mouseState.right) buttonMask |= 4; + if (mouseState.up) buttonMask |= 8; + if (mouseState.down) buttonMask |= 16; + + // Send message + tunnel.sendMessage("mouse", Math.floor(x), Math.floor(y), buttonMask); + }; + + /** + * Sends a touch event having the properties provided by the given touch + * state. + * + * @param {!Guacamole.Touch.State} touchState + * The state of the touch contact to send in the touch event. + * + * @param {boolean} [applyDisplayScale=false] + * Whether the provided touch state uses local display units, rather + * than remote display units, and should be scaled to match the + * {@link Guacamole.Display}. + */ + this.sendTouchState = function sendTouchState(touchState, applyDisplayScale) { + + // Do not send requests if not connected + if (!isConnected()) + return; + + var x = touchState.x; + var y = touchState.y; + + // Translate for display units if requested + if (applyDisplayScale) { + x /= display.getScale(); + y /= display.getScale(); + } + + tunnel.sendMessage('touch', touchState.id, Math.floor(x), Math.floor(y), + Math.floor(touchState.radiusX), Math.floor(touchState.radiusY), + touchState.angle, touchState.force); + + }; + + /** + * Allocates an available stream index and creates a new + * Guacamole.OutputStream using that index, associating the resulting + * stream with this Guacamole.Client. Note that this stream will not yet + * exist as far as the other end of the Guacamole connection is concerned. + * Streams exist within the Guacamole protocol only when referenced by an + * instruction which creates the stream, such as a "clipboard", "file", or + * "pipe" instruction. + * + * @returns {!Guacamole.OutputStream} + * A new Guacamole.OutputStream with a newly-allocated index and + * associated with this Guacamole.Client. + */ + this.createOutputStream = function createOutputStream() { + + // Allocate index + var index = stream_indices.next(); + + // Return new stream + var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index); + return stream; + + }; + + /** + * Opens a new audio stream for writing, where audio data having the give + * mimetype will be sent along the returned stream. The instruction + * necessary to create this stream will automatically be sent. + * + * @param {!string} mimetype + * The mimetype of the audio data that will be sent along the returned + * stream. + * + * @return {!Guacamole.OutputStream} + * The created audio stream. + */ + this.createAudioStream = function(mimetype) { + + // Allocate and associate stream with audio metadata + var stream = guac_client.createOutputStream(); + tunnel.sendMessage("audio", stream.index, mimetype); + return stream; + + }; + + /** + * Opens a new file for writing, having the given index, mimetype and + * filename. The instruction necessary to create this stream will + * automatically be sent. + * + * @param {!string} mimetype + * The mimetype of the file being sent. + * + * @param {!string} filename + * The filename of the file being sent. + * + * @return {!Guacamole.OutputStream} + * The created file stream. + */ + this.createFileStream = function(mimetype, filename) { + + // Allocate and associate stream with file metadata + var stream = guac_client.createOutputStream(); + tunnel.sendMessage("file", stream.index, mimetype, filename); + return stream; + + }; + + /** + * Opens a new pipe for writing, having the given name and mimetype. The + * instruction necessary to create this stream will automatically be sent. + * + * @param {!string} mimetype + * The mimetype of the data being sent. + * + * @param {!string} name + * The name of the pipe. + * + * @return {!Guacamole.OutputStream} + * The created file stream. + */ + this.createPipeStream = function(mimetype, name) { + + // Allocate and associate stream with pipe metadata + var stream = guac_client.createOutputStream(); + tunnel.sendMessage("pipe", stream.index, mimetype, name); + return stream; + + }; + + /** + * Opens a new clipboard object for writing, having the given mimetype. The + * instruction necessary to create this stream will automatically be sent. + * + * @param {!string} mimetype + * The mimetype of the data being sent. + * + * @param {!string} name + * The name of the pipe. + * + * @return {!Guacamole.OutputStream} + * The created file stream. + */ + this.createClipboardStream = function(mimetype) { + + // Allocate and associate stream with clipboard metadata + var stream = guac_client.createOutputStream(); + tunnel.sendMessage("clipboard", stream.index, mimetype); + return stream; + + }; + + /** + * Opens a new argument value stream for writing, having the given + * parameter name and mimetype, requesting that the connection parameter + * with the given name be updated to the value described by the contents + * of the following stream. The instruction necessary to create this stream + * will automatically be sent. + * + * @param {!string} mimetype + * The mimetype of the data being sent. + * + * @param {!string} name + * The name of the connection parameter to attempt to update. + * + * @return {!Guacamole.OutputStream} + * The created argument value stream. + */ + this.createArgumentValueStream = function createArgumentValueStream(mimetype, name) { + + // Allocate and associate stream with argument value metadata + var stream = guac_client.createOutputStream(); + tunnel.sendMessage("argv", stream.index, mimetype, name); + return stream; + + }; + + /** + * Creates a new output stream associated with the given object and having + * the given mimetype and name. The legality of a mimetype and name is + * dictated by the object itself. The instruction necessary to create this + * stream will automatically be sent. + * + * @param {!number} index + * The index of the object for which the output stream is being + * created. + * + * @param {!string} mimetype + * The mimetype of the data which will be sent to the output stream. + * + * @param {!string} name + * The defined name of an output stream within the given object. + * + * @returns {!Guacamole.OutputStream} + * An output stream which will write blobs to the named output stream + * of the given object. + */ + this.createObjectOutputStream = function createObjectOutputStream(index, mimetype, name) { + + // Allocate and associate stream with object metadata + var stream = guac_client.createOutputStream(); + tunnel.sendMessage("put", index, stream.index, mimetype, name); + return stream; + + }; + + /** + * Requests read access to the input stream having the given name. If + * successful, a new input stream will be created. + * + * @param {!number} index + * The index of the object from which the input stream is being + * requested. + * + * @param {!string} name + * The name of the input stream to request. + */ + this.requestObjectInputStream = function requestObjectInputStream(index, name) { + + // Do not send requests if not connected + if (!isConnected()) + return; + + tunnel.sendMessage("get", index, name); + }; + + /** + * Acknowledge receipt of a blob on the stream with the given index. + * + * @param {!number} index + * The index of the stream associated with the received blob. + * + * @param {!string} message + * A human-readable message describing the error or status. + * + * @param {!number} code + * The error code, if any, or 0 for success. + */ + this.sendAck = function(index, message, code) { + + // Do not send requests if not connected + if (!isConnected()) + return; + + tunnel.sendMessage("ack", index, message, code); + }; + + /** + * Given the index of a file, writes a blob of data to that file. + * + * @param {!number} index + * The index of the file to write to. + * + * @param {!string} data + * Base64-encoded data to write to the file. + */ + this.sendBlob = function(index, data) { + + // Do not send requests if not connected + if (!isConnected()) + return; + + tunnel.sendMessage("blob", index, data); + }; + + /** + * Marks a currently-open stream as complete. The other end of the + * Guacamole connection will be notified via an "end" instruction that the + * stream is closed, and the index will be made available for reuse in + * future streams. + * + * @param {!number} index + * The index of the stream to end. + */ + this.endStream = function(index) { + + // Do not send requests if not connected + if (!isConnected()) + return; + + // Explicitly close stream by sending "end" instruction + tunnel.sendMessage("end", index); + + // Free associated index and stream if they exist + if (output_streams[index]) { + stream_indices.free(index); + delete output_streams[index]; + } + + }; + + /** + * Fired whenever the state of this Guacamole.Client changes. + * + * @event + * @param {!number} state + * The new state of the client. + */ + this.onstatechange = null; + + /** + * Fired when the remote client sends a name update. + * + * @event + * @param {!string} name + * The new name of this client. + */ + this.onname = null; + + /** + * Fired when an error is reported by the remote client, and the connection + * is being closed. + * + * @event + * @param {!Guacamole.Status} status + * A status object which describes the error. + */ + this.onerror = null; + + /** + * Fired when an arbitrary message is received from the tunnel that should + * be processed by the client. By default, additional message-specific + * events such as "onjoin" and "onleave" will fire for the received message + * after this event has been processed. An event handler for "onmsg" need + * not be supplied if "onjoin" and/or "onleave" will be used. + * + * @event + * @param {!number} msgcode + * A status code sent by the remote server that indicates the nature of + * the message that is being sent to the client. + * + * @param {string[]} args + * An array of arguments to be processed with the message sent to the + * client. + * + * @return {boolean} + * true if message-specific events such as "onjoin" and + * "onleave" should be fired for this message, false otherwise. If + * no value is returned, message-specific events will be allowed to + * fire. + */ + this.onmsg = null; + + /** + * Fired when a user joins a shared connection. + * + * @event + * @param {!string} userID + * A unique value representing this specific user's connection to the + * shared connection. This value is generated by the server and is + * guaranteed to be unique relative to other users of the connection. + * + * @param {!string} name + * A human-readable name representing the user that joined, such as + * their username. This value is provided by the web application during + * the connection handshake and is not necessarily unique relative to + * other users of the connection. + */ + this.onjoin = null; + + /** + * Fired when a user leaves a shared connection. + * + * @event + * @param {!string} userID + * A unique value representing this specific user's connection to the + * shared connection. This value is generated by the server and is + * guaranteed to be unique relative to other users of the connection. + * + * @param {!string} name + * A human-readable name representing the user that left, such as their + * username. This value is provided by the web application during the + * connection handshake and is not necessarily unique relative to other + * users of the connection. + */ + this.onleave = null; + + /** + * Fired when a audio stream is created. The stream provided to this event + * handler will contain its own event handlers for received data. + * + * @event + * @param {!Guacamole.InputStream} stream + * The stream that will receive audio data from the server. + * + * @param {!string} mimetype + * The mimetype of the audio data which will be received. + * + * @return {Guacamole.AudioPlayer} + * An object which implements the Guacamole.AudioPlayer interface and + * has been initialized to play the data in the provided stream, or null + * if the built-in audio players of the Guacamole client should be + * used. + */ + this.onaudio = null; + + /** + * Fired when a video stream is created. The stream provided to this event + * handler will contain its own event handlers for received data. + * + * @event + * @param {!Guacamole.InputStream} stream + * The stream that will receive video data from the server. + * + * @param {!Guacamole.Display.VisibleLayer} layer + * The destination layer on which the received video data should be + * played. It is the responsibility of the Guacamole.VideoPlayer + * implementation to play the received data within this layer. + * + * @param {!string} mimetype + * The mimetype of the video data which will be received. + * + * @return {Guacamole.VideoPlayer} + * An object which implements the Guacamole.VideoPlayer interface and + * has been initialized to play the data in the provided stream, or null + * if the built-in video players of the Guacamole client should be + * used. + */ + this.onvideo = null; + + /** + * Fired when the remote client is explicitly declaring the level of + * multi-touch support provided by a particular display layer. + * + * @event + * @param {!Guacamole.Display.VisibleLayer} layer + * The layer whose multi-touch support level is being declared. + * + * @param {!number} touches + * The maximum number of simultaneous touches supported by the given + * layer, where 0 indicates that touch events are not supported at all. + */ + this.onmultitouch = null; + + /** + * Fired when the current value of a connection parameter is being exposed + * by the server. + * + * @event + * @param {!Guacamole.InputStream} stream + * The stream that will receive connection parameter data from the + * server. + * + * @param {!string} mimetype + * The mimetype of the data which will be received. + * + * @param {!string} name + * The name of the connection parameter whose value is being exposed. + */ + this.onargv = null; + + /** + * Fired when the clipboard of the remote client is changing. + * + * @event + * @param {!Guacamole.InputStream} stream + * The stream that will receive clipboard data from the server. + * + * @param {!string} mimetype + * The mimetype of the data which will be received. + */ + this.onclipboard = null; + + /** + * Fired when a file stream is created. The stream provided to this event + * handler will contain its own event handlers for received data. + * + * @event + * @param {!Guacamole.InputStream} stream + * The stream that will receive data from the server. + * + * @param {!string} mimetype + * The mimetype of the file received. + * + * @param {!string} filename + * The name of the file received. + */ + this.onfile = null; + + /** + * Fired when a filesystem object is created. The object provided to this + * event handler will contain its own event handlers and functions for + * requesting and handling data. + * + * @event + * @param {!Guacamole.Object} object + * The created filesystem object. + * + * @param {!string} name + * The name of the filesystem. + */ + this.onfilesystem = null; + + /** + * Fired when a pipe stream is created. The stream provided to this event + * handler will contain its own event handlers for received data; + * + * @event + * @param {!Guacamole.InputStream} stream + * The stream that will receive data from the server. + * + * @param {!string} mimetype + * The mimetype of the data which will be received. + * + * @param {!string} name + * The name of the pipe. + */ + this.onpipe = null; + + /** + * Fired when a "required" instruction is received. A required instruction + * indicates that additional parameters are required for the connection to + * continue, such as user credentials. + * + * @event + * @param {!string[]} parameters + * The names of the connection parameters that are required to be + * provided for the connection to continue. + */ + this.onrequired = null; + + /** + * Fired whenever a sync instruction is received from the server, indicating + * that the server is finished processing any input from the client and + * has sent any results. + * + * @event + * @param {!number} timestamp + * The timestamp associated with the sync instruction. + * + * @param {!number} frames + * The number of frames that were considered or combined to produce the + * frame associated with this sync instruction, or zero if this value + * is not known or the remote desktop server provides no concept of + * frames. + */ + this.onsync = null; + + /** + * Returns the layer with the given index, creating it if necessary. + * Positive indices refer to visible layers, an index of zero refers to + * the default layer, and negative indices refer to buffers. + * + * @private + * @param {!number} index + * The index of the layer to retrieve. + * + * @return {!(Guacamole.Display.VisibleLayer|Guacamole.Layer)} + * The layer having the given index. + */ + var getLayer = function getLayer(index) { + + // Get layer, create if necessary + var layer = layers[index]; + if (!layer) { + + // Create layer based on index + if (index === 0) + layer = display.getDefaultLayer(); + else if (index > 0) + layer = display.createLayer(); + else + layer = display.createBuffer(); + + // Add new layer + layers[index] = layer; + + } + + return layer; + + }; + + /** + * Returns the index passed to getLayer() when the given layer was created. + * Positive indices refer to visible layers, an index of zero refers to the + * default layer, and negative indices refer to buffers. + * + * @param {!(Guacamole.Display.VisibleLayer|Guacamole.Layer)} layer + * The layer whose index should be determined. + * + * @returns {number} + * The index of the given layer, or null if no such layer is associated + * with this client. + */ + var getLayerIndex = function getLayerIndex(layer) { + + // Avoid searching if there clearly is no such layer + if (!layer) + return null; + + // Search through each layer, returning the index of the given layer + // once found + for (var key in layers) { + if (layer === layers[key]) + return parseInt(key); + } + + // Otherwise, no such index + return null; + + }; + + function getParser(index) { + + var parser = parsers[index]; + + // If parser not yet created, create it, and tie to the + // oninstruction handler of the tunnel. + if (parser == null) { + parser = parsers[index] = new Guacamole.Parser(); + parser.oninstruction = tunnel.oninstruction; + } + + return parser; + + } + + /** + * Handlers for all defined layer properties. + * + * @private + * @type {!Object.} + */ + var layerPropertyHandlers = { + + "miter-limit": function(layer, value) { + display.setMiterLimit(layer, parseFloat(value)); + }, + + "multi-touch" : function layerSupportsMultiTouch(layer, value) { + + // Process "multi-touch" property only for true visible layers (not off-screen buffers) + if (guac_client.onmultitouch && layer instanceof Guacamole.Display.VisibleLayer) + guac_client.onmultitouch(layer, parseInt(value)); + + } + + }; + + /** + * Handlers for all instruction opcodes receivable by a Guacamole protocol + * client. + * + * @private + * @type {!Object.} + */ + var instructionHandlers = { + + "ack": function(parameters) { + + var stream_index = parseInt(parameters[0]); + var reason = parameters[1]; + var code = parseInt(parameters[2]); + + // Get stream + var stream = output_streams[stream_index]; + if (stream) { + + // Signal ack if handler defined + if (stream.onack) + stream.onack(new Guacamole.Status(code, reason)); + + // If code is an error, invalidate stream if not already + // invalidated by onack handler + if (code >= 0x0100 && output_streams[stream_index] === stream) { + stream_indices.free(stream_index); + delete output_streams[stream_index]; + } + + } + + }, + + "arc": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + var x = parseInt(parameters[1]); + var y = parseInt(parameters[2]); + var radius = parseInt(parameters[3]); + var startAngle = parseFloat(parameters[4]); + var endAngle = parseFloat(parameters[5]); + var negative = parseInt(parameters[6]); + + display.arc(layer, x, y, radius, startAngle, endAngle, negative != 0); + + }, + + "argv": function(parameters) { + + var stream_index = parseInt(parameters[0]); + var mimetype = parameters[1]; + var name = parameters[2]; + + // Create stream + if (guac_client.onargv) { + var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); + guac_client.onargv(stream, mimetype, name); + } + + // Otherwise, unsupported + else + guac_client.sendAck(stream_index, "Receiving argument values unsupported", 0x0100); + + }, + + "audio": function(parameters) { + + var stream_index = parseInt(parameters[0]); + var mimetype = parameters[1]; + + // Create stream + var stream = streams[stream_index] = + new Guacamole.InputStream(guac_client, stream_index); + + // Get player instance via callback + var audioPlayer = null; + if (guac_client.onaudio) + audioPlayer = guac_client.onaudio(stream, mimetype); + + // If unsuccessful, try to use a default implementation + if (!audioPlayer) + audioPlayer = Guacamole.AudioPlayer.getInstance(stream, mimetype); + + // If we have successfully retrieved an audio player, send success response + if (audioPlayer) { + audioPlayers[stream_index] = audioPlayer; + guac_client.sendAck(stream_index, "OK", 0x0000); + } + + // Otherwise, mimetype must be unsupported + else + guac_client.sendAck(stream_index, "BAD TYPE", 0x030F); + + }, + + "blob": function(parameters) { + + // Get stream + var stream_index = parseInt(parameters[0]); + var data = parameters[1]; + var stream = streams[stream_index]; + + // Write data + if (stream && stream.onblob) + stream.onblob(data); + + }, + + "body" : function handleBody(parameters) { + + // Get object + var objectIndex = parseInt(parameters[0]); + var object = objects[objectIndex]; + + var streamIndex = parseInt(parameters[1]); + var mimetype = parameters[2]; + var name = parameters[3]; + + // Create stream if handler defined + if (object && object.onbody) { + var stream = streams[streamIndex] = new Guacamole.InputStream(guac_client, streamIndex); + object.onbody(stream, mimetype, name); + } + + // Otherwise, unsupported + else + guac_client.sendAck(streamIndex, "Receipt of body unsupported", 0x0100); + + }, + + "cfill": function(parameters) { + + var channelMask = parseInt(parameters[0]); + var layer = getLayer(parseInt(parameters[1])); + var r = parseInt(parameters[2]); + var g = parseInt(parameters[3]); + var b = parseInt(parameters[4]); + var a = parseInt(parameters[5]); + + display.setChannelMask(layer, channelMask); + display.fillColor(layer, r, g, b, a); + + }, + + "clip": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + + display.clip(layer); + + }, + + "clipboard": function(parameters) { + + var stream_index = parseInt(parameters[0]); + var mimetype = parameters[1]; + + // Create stream + if (guac_client.onclipboard) { + var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); + guac_client.onclipboard(stream, mimetype); + } + + // Otherwise, unsupported + else + guac_client.sendAck(stream_index, "Clipboard unsupported", 0x0100); + + }, + + "close": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + + display.close(layer); + + }, + + "copy": function(parameters) { + + var srcL = getLayer(parseInt(parameters[0])); + var srcX = parseInt(parameters[1]); + var srcY = parseInt(parameters[2]); + var srcWidth = parseInt(parameters[3]); + var srcHeight = parseInt(parameters[4]); + var channelMask = parseInt(parameters[5]); + var dstL = getLayer(parseInt(parameters[6])); + var dstX = parseInt(parameters[7]); + var dstY = parseInt(parameters[8]); + + display.setChannelMask(dstL, channelMask); + display.copy(srcL, srcX, srcY, srcWidth, srcHeight, + dstL, dstX, dstY); + + }, + + "cstroke": function(parameters) { + + var channelMask = parseInt(parameters[0]); + var layer = getLayer(parseInt(parameters[1])); + var cap = lineCap[parseInt(parameters[2])]; + var join = lineJoin[parseInt(parameters[3])]; + var thickness = parseInt(parameters[4]); + var r = parseInt(parameters[5]); + var g = parseInt(parameters[6]); + var b = parseInt(parameters[7]); + var a = parseInt(parameters[8]); + + display.setChannelMask(layer, channelMask); + display.strokeColor(layer, cap, join, thickness, r, g, b, a); + + }, + + "cursor": function(parameters) { + + var cursorHotspotX = parseInt(parameters[0]); + var cursorHotspotY = parseInt(parameters[1]); + var srcL = getLayer(parseInt(parameters[2])); + var srcX = parseInt(parameters[3]); + var srcY = parseInt(parameters[4]); + var srcWidth = parseInt(parameters[5]); + var srcHeight = parseInt(parameters[6]); + + display.setCursor(cursorHotspotX, cursorHotspotY, + srcL, srcX, srcY, srcWidth, srcHeight); + + }, + + "curve": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + var cp1x = parseInt(parameters[1]); + var cp1y = parseInt(parameters[2]); + var cp2x = parseInt(parameters[3]); + var cp2y = parseInt(parameters[4]); + var x = parseInt(parameters[5]); + var y = parseInt(parameters[6]); + + display.curveTo(layer, cp1x, cp1y, cp2x, cp2y, x, y); + + }, + + "disconnect" : function handleDisconnect(parameters) { + + // Explicitly tear down connection + guac_client.disconnect(); + + }, + + "dispose": function(parameters) { + + var layer_index = parseInt(parameters[0]); + + // If visible layer, remove from parent + if (layer_index > 0) { + + // Remove from parent + var layer = getLayer(layer_index); + display.dispose(layer); + + // Delete reference + delete layers[layer_index]; + + } + + // If buffer, just delete reference + else if (layer_index < 0) + delete layers[layer_index]; + + // Attempting to dispose the root layer currently has no effect. + + }, + + "distort": function(parameters) { + + var layer_index = parseInt(parameters[0]); + var a = parseFloat(parameters[1]); + var b = parseFloat(parameters[2]); + var c = parseFloat(parameters[3]); + var d = parseFloat(parameters[4]); + var e = parseFloat(parameters[5]); + var f = parseFloat(parameters[6]); + + // Only valid for visible layers (not buffers) + if (layer_index >= 0) { + var layer = getLayer(layer_index); + display.distort(layer, a, b, c, d, e, f); + } + + }, + + "error": function(parameters) { + + var reason = parameters[0]; + var code = parseInt(parameters[1]); + + // Call handler if defined + if (guac_client.onerror) + guac_client.onerror(new Guacamole.Status(code, reason)); + + guac_client.disconnect(); + + }, + + "end": function(parameters) { + + var stream_index = parseInt(parameters[0]); + + // Get stream + var stream = streams[stream_index]; + if (stream) { + + // Signal end of stream if handler defined + if (stream.onend) + stream.onend(); + + // Invalidate stream + delete streams[stream_index]; + + } + + }, + + "file": function(parameters) { + + var stream_index = parseInt(parameters[0]); + var mimetype = parameters[1]; + var filename = parameters[2]; + + // Create stream + if (guac_client.onfile) { + var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); + guac_client.onfile(stream, mimetype, filename); + } + + // Otherwise, unsupported + else + guac_client.sendAck(stream_index, "File transfer unsupported", 0x0100); + + }, + + "filesystem" : function handleFilesystem(parameters) { + + var objectIndex = parseInt(parameters[0]); + var name = parameters[1]; + + // Create object, if supported + if (guac_client.onfilesystem) { + var object = objects[objectIndex] = new Guacamole.Object(guac_client, objectIndex); + guac_client.onfilesystem(object, name); + } + + // If unsupported, simply ignore the availability of the filesystem + + }, + + "identity": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + + display.setTransform(layer, 1, 0, 0, 1, 0, 0); + + }, + + "img": function(parameters) { + + var stream_index = parseInt(parameters[0]); + var channelMask = parseInt(parameters[1]); + var layer = getLayer(parseInt(parameters[2])); + var mimetype = parameters[3]; + var x = parseInt(parameters[4]); + var y = parseInt(parameters[5]); + + // Create stream + var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); + + // Draw received contents once decoded + display.setChannelMask(layer, channelMask); + display.drawStream(layer, x, y, stream, mimetype); + + }, + + "jpeg": function(parameters) { + + var channelMask = parseInt(parameters[0]); + var layer = getLayer(parseInt(parameters[1])); + var x = parseInt(parameters[2]); + var y = parseInt(parameters[3]); + var data = parameters[4]; + + display.setChannelMask(layer, channelMask); + display.draw(layer, x, y, "data:image/jpeg;base64," + data); + + }, + + "lfill": function(parameters) { + + var channelMask = parseInt(parameters[0]); + var layer = getLayer(parseInt(parameters[1])); + var srcLayer = getLayer(parseInt(parameters[2])); + + display.setChannelMask(layer, channelMask); + display.fillLayer(layer, srcLayer); + + }, + + "line": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + var x = parseInt(parameters[1]); + var y = parseInt(parameters[2]); + + display.lineTo(layer, x, y); + + }, + + "lstroke": function(parameters) { + + var channelMask = parseInt(parameters[0]); + var layer = getLayer(parseInt(parameters[1])); + var srcLayer = getLayer(parseInt(parameters[2])); + + display.setChannelMask(layer, channelMask); + display.strokeLayer(layer, srcLayer); + + }, + + "mouse" : function handleMouse(parameters) { + + var x = parseInt(parameters[0]); + var y = parseInt(parameters[1]); + + // Display and move software cursor to received coordinates + display.showCursor(true); + display.moveCursor(x, y); + + }, + + "move": function(parameters) { + + var layer_index = parseInt(parameters[0]); + var parent_index = parseInt(parameters[1]); + var x = parseInt(parameters[2]); + var y = parseInt(parameters[3]); + var z = parseInt(parameters[4]); + + // Only valid for non-default layers + if (layer_index > 0 && parent_index >= 0) { + var layer = getLayer(layer_index); + var parent = getLayer(parent_index); + display.move(layer, parent, x, y, z); + } + + }, + + "msg" : function(parameters) { + + var userID; + var username; + + // Fire general message handling event first + var allowDefault = true; + var msgid = parseInt(parameters[0]); + if (guac_client.onmsg) { + allowDefault = guac_client.onmsg(msgid, parameters.slice(1)); + if (allowDefault === undefined) + allowDefault = true; + } + + // Fire message-specific convenience events if not prevented by the + // "onmsg" handler + if (allowDefault) { + switch (msgid) { + + case Guacamole.Client.Message.USER_JOINED: + userID = parameters[1]; + username = parameters[2]; + if (guac_client.onjoin) + guac_client.onjoin(userID, username); + break; + + case Guacamole.Client.Message.USER_LEFT: + userID = parameters[1]; + username = parameters[2]; + if (guac_client.onleave) + guac_client.onleave(userID, username); + break; + + } + } + + }, + + "name": function(parameters) { + if (guac_client.onname) guac_client.onname(parameters[0]); + }, + + "nest": function(parameters) { + var parser = getParser(parseInt(parameters[0])); + parser.receive(parameters[1]); + }, + + "pipe": function(parameters) { + + var stream_index = parseInt(parameters[0]); + var mimetype = parameters[1]; + var name = parameters[2]; + + // Create stream + if (guac_client.onpipe) { + var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); + guac_client.onpipe(stream, mimetype, name); + } + + // Otherwise, unsupported + else + guac_client.sendAck(stream_index, "Named pipes unsupported", 0x0100); + + }, + + "png": function(parameters) { + + var channelMask = parseInt(parameters[0]); + var layer = getLayer(parseInt(parameters[1])); + var x = parseInt(parameters[2]); + var y = parseInt(parameters[3]); + var data = parameters[4]; + + display.setChannelMask(layer, channelMask); + display.draw(layer, x, y, "data:image/png;base64," + data); + + }, + + "pop": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + + display.pop(layer); + + }, + + "push": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + + display.push(layer); + + }, + + "rect": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + var x = parseInt(parameters[1]); + var y = parseInt(parameters[2]); + var w = parseInt(parameters[3]); + var h = parseInt(parameters[4]); + + display.rect(layer, x, y, w, h); + + }, + + "required": function required(parameters) { + if (guac_client.onrequired) guac_client.onrequired(parameters); + }, + + "reset": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + + display.reset(layer); + + }, + + "set": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + var name = parameters[1]; + var value = parameters[2]; + + // Call property handler if defined + var handler = layerPropertyHandlers[name]; + if (handler) + handler(layer, value); + + }, + + "shade": function(parameters) { + + var layer_index = parseInt(parameters[0]); + var a = parseInt(parameters[1]); + + // Only valid for visible layers (not buffers) + if (layer_index >= 0) { + var layer = getLayer(layer_index); + display.shade(layer, a); + } + + }, + + "size": function(parameters) { + + var layer_index = parseInt(parameters[0]); + var layer = getLayer(layer_index); + var width = parseInt(parameters[1]); + var height = parseInt(parameters[2]); + + display.resize(layer, width, height); + + }, + + "start": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + var x = parseInt(parameters[1]); + var y = parseInt(parameters[2]); + + display.moveTo(layer, x, y); + + }, + + "sync": function(parameters) { + + var timestamp = parseInt(parameters[0]); + var frames = parameters[1] ? parseInt(parameters[1]) : 0; + + // Flush display, send sync when done + display.flush(function displaySyncComplete() { + + // Synchronize all audio players + for (var index in audioPlayers) { + var audioPlayer = audioPlayers[index]; + if (audioPlayer) + audioPlayer.sync(); + } + + // Send sync response to server + if (timestamp !== currentTimestamp) { + tunnel.sendMessage("sync", timestamp); + currentTimestamp = timestamp; + } + + }, timestamp, frames); + + // If received first update, no longer waiting. + if (currentState === Guacamole.Client.State.WAITING) + setState(Guacamole.Client.State.CONNECTED); + + // Call sync handler if defined + if (guac_client.onsync) + guac_client.onsync(timestamp, frames); + + }, + + "transfer": function(parameters) { + + var srcL = getLayer(parseInt(parameters[0])); + var srcX = parseInt(parameters[1]); + var srcY = parseInt(parameters[2]); + var srcWidth = parseInt(parameters[3]); + var srcHeight = parseInt(parameters[4]); + var function_index = parseInt(parameters[5]); + var dstL = getLayer(parseInt(parameters[6])); + var dstX = parseInt(parameters[7]); + var dstY = parseInt(parameters[8]); + + /* SRC */ + if (function_index === 0x3) + display.put(srcL, srcX, srcY, srcWidth, srcHeight, + dstL, dstX, dstY); + + /* Anything else that isn't a NO-OP */ + else if (function_index !== 0x5) + display.transfer(srcL, srcX, srcY, srcWidth, srcHeight, + dstL, dstX, dstY, Guacamole.Client.DefaultTransferFunction[function_index]); + + }, + + "transform": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + var a = parseFloat(parameters[1]); + var b = parseFloat(parameters[2]); + var c = parseFloat(parameters[3]); + var d = parseFloat(parameters[4]); + var e = parseFloat(parameters[5]); + var f = parseFloat(parameters[6]); + + display.transform(layer, a, b, c, d, e, f); + + }, + + "undefine" : function handleUndefine(parameters) { + + // Get object + var objectIndex = parseInt(parameters[0]); + var object = objects[objectIndex]; + + // Signal end of object definition + if (object && object.onundefine) + object.onundefine(); + + }, + + "video": function(parameters) { + + var stream_index = parseInt(parameters[0]); + var layer = getLayer(parseInt(parameters[1])); + var mimetype = parameters[2]; + + // Create stream + var stream = streams[stream_index] = + new Guacamole.InputStream(guac_client, stream_index); + + // Get player instance via callback + var videoPlayer = null; + if (guac_client.onvideo) + videoPlayer = guac_client.onvideo(stream, layer, mimetype); + + // If unsuccessful, try to use a default implementation + if (!videoPlayer) + videoPlayer = Guacamole.VideoPlayer.getInstance(stream, layer, mimetype); + + // If we have successfully retrieved an video player, send success response + if (videoPlayer) { + videoPlayers[stream_index] = videoPlayer; + guac_client.sendAck(stream_index, "OK", 0x0000); + } + + // Otherwise, mimetype must be unsupported + else + guac_client.sendAck(stream_index, "BAD TYPE", 0x030F); + + } + + }; + + /** + * Sends a keep-alive ping to the Guacamole server, advising the server + * that the client is still connected and responding. The lastSentKeepAlive + * timestamp is automatically updated as a result of calling this function. + * + * @private + */ + var sendKeepAlive = function sendKeepAlive() { + tunnel.sendMessage('nop'); + lastSentKeepAlive = new Date().getTime(); + }; + + /** + * Schedules the next keep-alive ping based on the KEEP_ALIVE_FREQUENCY and + * the time that the last ping was sent, if ever. If enough time has + * elapsed that a ping should have already been sent, calling this function + * will send that ping immediately. + * + * @private + */ + var scheduleKeepAlive = function scheduleKeepAlive() { + + window.clearTimeout(keepAliveTimeout); + + var currentTime = new Date().getTime(); + var keepAliveDelay = Math.max(lastSentKeepAlive + KEEP_ALIVE_FREQUENCY - currentTime, 0); + + // Ping server regularly to keep connection alive, but send the ping + // immediately if enough time has elapsed that it should have already + // been sent + if (keepAliveDelay > 0) + keepAliveTimeout = window.setTimeout(sendKeepAlive, keepAliveDelay); + else + sendKeepAlive(); + + }; + + /** + * Stops sending any further keep-alive pings. If a keep-alive ping was + * scheduled to be sent, that ping is cancelled. + * + * @private + */ + var stopKeepAlive = function stopKeepAlive() { + window.clearTimeout(keepAliveTimeout); + }; + + tunnel.oninstruction = function(opcode, parameters) { + + var handler = instructionHandlers[opcode]; + if (handler) + handler(parameters); + + // Leverage network activity to ensure the next keep-alive ping is + // sent, even if the browser is currently throttling timers + scheduleKeepAlive(); + + }; + + /** + * Sends a disconnect instruction to the server and closes the tunnel. + */ + this.disconnect = function() { + + // Only attempt disconnection not disconnected. + if (currentState != Guacamole.Client.State.DISCONNECTED + && currentState != Guacamole.Client.State.DISCONNECTING) { + + setState(Guacamole.Client.State.DISCONNECTING); + + // Stop sending keep-alive messages + stopKeepAlive(); + + // Send disconnect message and disconnect + tunnel.sendMessage("disconnect"); + tunnel.disconnect(); + setState(Guacamole.Client.State.DISCONNECTED); + + } + + }; + + /** + * Connects the underlying tunnel of this Guacamole.Client, passing the + * given arbitrary data to the tunnel during the connection process. + * + * @param {string} data + * Arbitrary connection data to be sent to the underlying tunnel during + * the connection process. + * + * @throws {!Guacamole.Status} + * If an error occurs during connection. + */ + this.connect = function(data) { + + setState(Guacamole.Client.State.CONNECTING); + + try { + tunnel.connect(data); + } + catch (status) { + setState(Guacamole.Client.State.IDLE); + throw status; + } + + // Regularly send keep-alive ping to ensure the server knows we're + // still here, even if not active + scheduleKeepAlive(); + + setState(Guacamole.Client.State.WAITING); + }; + +}; + +/** + * All possible Guacamole Client states. + * + * @type {!Object.} + */ +Guacamole.Client.State = { + + /** + * The client is idle, with no active connection. + * + * @type number + */ + "IDLE" : 0, + + /** + * The client is in the process of establishing a connection. + * + * @type {!number} + */ + "CONNECTING" : 1, + + /** + * The client is waiting on further information or a remote server to + * establish the connection. + * + * @type {!number} + */ + "WAITING" : 2, + + /** + * The client is actively connected to a remote server. + * + * @type {!number} + */ + "CONNECTED" : 3, + + /** + * The client is in the process of disconnecting from the remote server. + * + * @type {!number} + */ + "DISCONNECTING" : 4, + + /** + * The client has completed the connection and is no longer connected. + * + * @type {!number} + */ + "DISCONNECTED" : 5 + +}; + +/** + * Map of all Guacamole binary raster operations to transfer functions. + * + * @private + * @type {!Object.} + */ +Guacamole.Client.DefaultTransferFunction = { + + /* BLACK */ + 0x0: function (src, dst) { + dst.red = dst.green = dst.blue = 0x00; + }, + + /* WHITE */ + 0xF: function (src, dst) { + dst.red = dst.green = dst.blue = 0xFF; + }, + + /* SRC */ + 0x3: function (src, dst) { + dst.red = src.red; + dst.green = src.green; + dst.blue = src.blue; + dst.alpha = src.alpha; + }, + + /* DEST (no-op) */ + 0x5: function (src, dst) { + // Do nothing + }, + + /* Invert SRC */ + 0xC: function (src, dst) { + dst.red = 0xFF & ~src.red; + dst.green = 0xFF & ~src.green; + dst.blue = 0xFF & ~src.blue; + dst.alpha = src.alpha; + }, + + /* Invert DEST */ + 0xA: function (src, dst) { + dst.red = 0xFF & ~dst.red; + dst.green = 0xFF & ~dst.green; + dst.blue = 0xFF & ~dst.blue; + }, + + /* AND */ + 0x1: function (src, dst) { + dst.red = ( src.red & dst.red); + dst.green = ( src.green & dst.green); + dst.blue = ( src.blue & dst.blue); + }, + + /* NAND */ + 0xE: function (src, dst) { + dst.red = 0xFF & ~( src.red & dst.red); + dst.green = 0xFF & ~( src.green & dst.green); + dst.blue = 0xFF & ~( src.blue & dst.blue); + }, + + /* OR */ + 0x7: function (src, dst) { + dst.red = ( src.red | dst.red); + dst.green = ( src.green | dst.green); + dst.blue = ( src.blue | dst.blue); + }, + + /* NOR */ + 0x8: function (src, dst) { + dst.red = 0xFF & ~( src.red | dst.red); + dst.green = 0xFF & ~( src.green | dst.green); + dst.blue = 0xFF & ~( src.blue | dst.blue); + }, + + /* XOR */ + 0x6: function (src, dst) { + dst.red = ( src.red ^ dst.red); + dst.green = ( src.green ^ dst.green); + dst.blue = ( src.blue ^ dst.blue); + }, + + /* XNOR */ + 0x9: function (src, dst) { + dst.red = 0xFF & ~( src.red ^ dst.red); + dst.green = 0xFF & ~( src.green ^ dst.green); + dst.blue = 0xFF & ~( src.blue ^ dst.blue); + }, + + /* AND inverted source */ + 0x4: function (src, dst) { + dst.red = 0xFF & (~src.red & dst.red); + dst.green = 0xFF & (~src.green & dst.green); + dst.blue = 0xFF & (~src.blue & dst.blue); + }, + + /* OR inverted source */ + 0xD: function (src, dst) { + dst.red = 0xFF & (~src.red | dst.red); + dst.green = 0xFF & (~src.green | dst.green); + dst.blue = 0xFF & (~src.blue | dst.blue); + }, + + /* AND inverted destination */ + 0x2: function (src, dst) { + dst.red = 0xFF & ( src.red & ~dst.red); + dst.green = 0xFF & ( src.green & ~dst.green); + dst.blue = 0xFF & ( src.blue & ~dst.blue); + }, + + /* OR inverted destination */ + 0xB: function (src, dst) { + dst.red = 0xFF & ( src.red | ~dst.red); + dst.green = 0xFF & ( src.green | ~dst.green); + dst.blue = 0xFF & ( src.blue | ~dst.blue); + } + +}; + +/** + * A list of possible messages that can be sent by the server for processing + * by the client. + * + * @type {!Object.} + */ +Guacamole.Client.Message = { + + /** + * A client message that indicates that a user has joined an existing + * connection. This message expects a single additional argument - the + * name of the user who has joined the connection. + * + * @type {!number} + */ + "USER_JOINED": 0x0001, + + /** + * A client message that indicates that a user has left an existing + * connection. This message expects a single additional argument - the + * name of the user who has left the connection. + * + * @type {!number} + */ + "USER_LEFT": 0x0002 + +}; diff --git a/src/DataURIReader.js b/src/DataURIReader.js new file mode 100644 index 0000000..e95f79a --- /dev/null +++ b/src/DataURIReader.js @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A reader which automatically handles the given input stream, returning + * received blobs as a single data URI built over the course of the stream. + * Note that this object will overwrite any installed event handlers on the + * given Guacamole.InputStream. + * + * @constructor + * @param {!Guacamole.InputStream} stream + * The stream that data will be read from. + * + * @param {!string} mimetype + * The mimetype of the data being received. + */ +Guacamole.DataURIReader = function(stream, mimetype) { + + /** + * Reference to this Guacamole.DataURIReader. + * + * @private + * @type {!Guacamole.DataURIReader} + */ + var guac_reader = this; + + /** + * Current data URI. + * + * @private + * @type {!string} + */ + var uri = 'data:' + mimetype + ';base64,'; + + // Receive blobs as array buffers + stream.onblob = function dataURIReaderBlob(data) { + + // Currently assuming data will ALWAYS be safe to simply append. This + // will not be true if the received base64 data encodes a number of + // bytes that isn't a multiple of three (as base64 expands in a ratio + // of exactly 3:4). + uri += data; + + }; + + // Simply call onend when end received + stream.onend = function dataURIReaderEnd() { + if (guac_reader.onend) + guac_reader.onend(); + }; + + /** + * Returns the data URI of all data received through the underlying stream + * thus far. + * + * @returns {!string} + * The data URI of all data received through the underlying stream thus + * far. + */ + this.getURI = function getURI() { + return uri; + }; + + /** + * Fired once this stream is finished and no further data will be written. + * + * @event + */ + this.onend = null; + +}; \ No newline at end of file diff --git a/src/Display.js b/src/Display.js new file mode 100644 index 0000000..374a762 --- /dev/null +++ b/src/Display.js @@ -0,0 +1,2192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * The Guacamole display. The display does not deal with the Guacamole + * protocol, and instead implements a set of graphical operations which + * embody the set of operations present in the protocol. The order operations + * are executed is guaranteed to be in the same order as their corresponding + * functions are called. + * + * @constructor + */ +Guacamole.Display = function() { + + /** + * Reference to this Guacamole.Display. + * @private + */ + var guac_display = this; + + var displayWidth = 0; + var displayHeight = 0; + var displayScale = 1; + + // Create display + var display = document.createElement("div"); + display.style.position = "relative"; + display.style.width = displayWidth + "px"; + display.style.height = displayHeight + "px"; + + // Ensure transformations on display originate at 0,0 + display.style.transformOrigin = + display.style.webkitTransformOrigin = + display.style.MozTransformOrigin = + display.style.OTransformOrigin = + display.style.msTransformOrigin = + "0 0"; + + // Create default layer + var default_layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight); + + // Create cursor layer + var cursor = new Guacamole.Display.VisibleLayer(0, 0); + cursor.setChannelMask(Guacamole.Layer.SRC); + + // Add default layer and cursor to display + display.appendChild(default_layer.getElement()); + display.appendChild(cursor.getElement()); + + // Create bounding div + var bounds = document.createElement("div"); + bounds.style.position = "relative"; + bounds.style.width = (displayWidth*displayScale) + "px"; + bounds.style.height = (displayHeight*displayScale) + "px"; + + // Add display to bounds + bounds.appendChild(display); + + /** + * The X coordinate of the hotspot of the mouse cursor. The hotspot is + * the relative location within the image of the mouse cursor at which + * each click occurs. + * + * @type {!number} + */ + this.cursorHotspotX = 0; + + /** + * The Y coordinate of the hotspot of the mouse cursor. The hotspot is + * the relative location within the image of the mouse cursor at which + * each click occurs. + * + * @type {!number} + */ + this.cursorHotspotY = 0; + + /** + * The current X coordinate of the local mouse cursor. This is not + * necessarily the location of the actual mouse - it refers only to + * the location of the cursor image within the Guacamole display, as + * last set by moveCursor(). + * + * @type {!number} + */ + this.cursorX = 0; + + /** + * The current X coordinate of the local mouse cursor. This is not + * necessarily the location of the actual mouse - it refers only to + * the location of the cursor image within the Guacamole display, as + * last set by moveCursor(). + * + * @type {!number} + */ + this.cursorY = 0; + + /** + * The number of milliseconds over which display rendering statistics + * should be gathered, dispatching {@link #onstatistics} events as those + * statistics are available. If set to zero, no statistics will be + * gathered. + * + * @default 0 + * @type {!number} + */ + this.statisticWindow = 0; + + /** + * Fired when the default layer (and thus the entire Guacamole display) + * is resized. + * + * @event + * @param {!number} width + * The new width of the Guacamole display. + * + * @param {!number} height + * The new height of the Guacamole display. + */ + this.onresize = null; + + /** + * Fired whenever the local cursor image is changed. This can be used to + * implement special handling of the client-side cursor, or to override + * the default use of a software cursor layer. + * + * @event + * @param {!HTMLCanvasElement} canvas + * The cursor image. + * + * @param {!number} x + * The X-coordinate of the cursor hotspot. + * + * @param {!number} y + * The Y-coordinate of the cursor hotspot. + */ + this.oncursor = null; + + /** + * Fired whenever performance statistics are available for recently- + * rendered frames. This event will fire only if {@link #statisticWindow} + * is non-zero. + * + * @event + * @param {!Guacamole.Display.Statistics} stats + * An object containing general rendering performance statistics for + * the remote desktop, Guacamole server, and Guacamole client. + */ + this.onstatistics = null; + + /** + * The queue of all pending Tasks. Tasks will be run in order, with new + * tasks added at the end of the queue and old tasks removed from the + * front of the queue (FIFO). These tasks will eventually be grouped + * into a Frame. + * + * @private + * @type {!Task[]} + */ + var tasks = []; + + /** + * The queue of all frames. Each frame is a pairing of an array of tasks + * and a callback which must be called when the frame is rendered. + * + * @private + * @type {!Frame[]} + */ + var frames = []; + + /** + * The ID of the animation frame request returned by the last call to + * requestAnimationFrame(). This value will only be set if the browser + * supports requestAnimationFrame(), if a frame render is currently + * pending, and if the current browser tab is currently focused (likely to + * handle requests for animation frames). In all other cases, this will be + * null. + * + * @private + * @type {number} + */ + var inProgressFrame = null; + + /** + * Flushes all pending frames synchronously. This function will block until + * all pending frames have rendered. If a frame is currently blocked by an + * asynchronous operation like an image load, this function will return + * after reaching that operation and the flush operation will + * automamtically resume after that operation completes. + * + * @private + */ + var syncFlush = function syncFlush() { + + var localTimestamp = 0; + var remoteTimestamp = 0; + + var renderedLogicalFrames = 0; + var rendered_frames = 0; + + // Draw all pending frames, if ready + while (rendered_frames < frames.length) { + + var frame = frames[rendered_frames]; + if (!frame.isReady()) + break; + + frame.flush(); + + localTimestamp = frame.localTimestamp; + remoteTimestamp = frame.remoteTimestamp; + renderedLogicalFrames += frame.logicalFrames; + rendered_frames++; + + } + + // Remove rendered frames from array + frames.splice(0, rendered_frames); + + if (rendered_frames) + notifyFlushed(localTimestamp, remoteTimestamp, renderedLogicalFrames); + + }; + + /** + * Flushes all pending frames asynchronously. This function returns + * immediately, relying on requestAnimationFrame() to dictate when each + * frame should be flushed. + * + * @private + */ + var asyncFlush = function asyncFlush() { + + var continueFlush = function continueFlush() { + + // We're no longer waiting to render a frame + inProgressFrame = null; + + // Nothing to do if there are no frames remaining + if (!frames.length) + return; + + // Flush the next frame only if it is ready (not awaiting + // completion of some asynchronous operation like an image load) + if (frames[0].isReady()) { + var frame = frames.shift(); + frame.flush(); + notifyFlushed(frame.localTimestamp, frame.remoteTimestamp, frame.logicalFrames); + } + + // Request yet another animation frame if frames remain to be + // flushed + if (frames.length) + inProgressFrame = window.requestAnimationFrame(continueFlush); + + }; + + // Begin flushing frames if not already waiting to render a frame + if (!inProgressFrame) + inProgressFrame = window.requestAnimationFrame(continueFlush); + + }; + + /** + * Recently-gathered display render statistics, as made available by calls + * to notifyFlushed(). The contents of this array will be trimmed to + * contain only up to {@link #statisticWindow} milliseconds of statistics. + * + * @private + * @type {Guacamole.Display.Statistics[]} + */ + var statistics = []; + + /** + * Notifies that one or more frames have been successfully rendered + * (flushed) to the display. + * + * @private + * @param {!number} localTimestamp + * The local timestamp of the point in time at which the most recent, + * flushed frame was received by the display, in milliseconds since the + * Unix Epoch. + * + * @param {!number} remoteTimestamp + * The remote timestamp of sync instruction associated with the most + * recent, flushed frame received by the display. This timestamp is in + * milliseconds, but is arbitrary, having meaning only relative to + * other timestamps in the same connection. + * + * @param {!number} logicalFrames + * The number of remote desktop frames that were flushed. + */ + var notifyFlushed = function notifyFlushed(localTimestamp, remoteTimestamp, logicalFrames) { + + // Ignore if statistics are not being gathered + if (!guac_display.statisticWindow) + return; + + var current = new Date().getTime(); + + // Find the first statistic that is still within the configured time + // window + for (var first = 0; first < statistics.length; first++) { + if (current - statistics[first].timestamp <= guac_display.statisticWindow) + break; + } + + // Remove all statistics except those within the time window + statistics.splice(0, first - 1); + + // Record statistics for latest frame + statistics.push({ + localTimestamp : localTimestamp, + remoteTimestamp : remoteTimestamp, + timestamp : current, + frames : logicalFrames + }); + + // Determine the actual time interval of the available statistics (this + // will not perfectly match the configured interval, which is an upper + // bound) + var statDuration = (statistics[statistics.length - 1].timestamp - statistics[0].timestamp) / 1000; + + // Determine the amount of time that elapsed remotely (within the + // remote desktop) + var remoteDuration = (statistics[statistics.length - 1].remoteTimestamp - statistics[0].remoteTimestamp) / 1000; + + // Calculate the number of frames that have been rendered locally + // within the configured time interval + var localFrames = statistics.length; + + // Calculate the number of frames actually received from the remote + // desktop by the Guacamole server + var remoteFrames = statistics.reduce(function sumFrames(prev, stat) { + return prev + stat.frames; + }, 0); + + // Calculate the number of frames that the Guacamole server had to + // drop or combine with other frames + var drops = statistics.reduce(function sumDrops(prev, stat) { + return prev + Math.max(0, stat.frames - 1); + }, 0); + + // Produce lag and FPS statistics from above raw measurements + var stats = new Guacamole.Display.Statistics({ + processingLag : current - localTimestamp, + desktopFps : (remoteDuration && remoteFrames) ? remoteFrames / remoteDuration : null, + clientFps : statDuration ? localFrames / statDuration : null, + serverFps : remoteDuration ? localFrames / remoteDuration : null, + dropRate : remoteDuration ? drops / remoteDuration : null + }); + + // Notify of availability of new statistics + if (guac_display.onstatistics) + guac_display.onstatistics(stats); + + }; + + // Switch from asynchronous frame handling to synchronous frame handling if + // requestAnimationFrame() is unlikely to be usable (browsers may not + // invoke the animation frame callback if the relevant tab is not focused) + window.addEventListener('blur', function switchToSyncFlush() { + if (inProgressFrame && !document.hasFocus()) { + + // Cancel pending asynchronous processing of frame ... + window.cancelAnimationFrame(inProgressFrame); + inProgressFrame = null; + + // ... and instead process it synchronously + syncFlush(); + + } + }, true); + + /** + * Flushes all pending frames. + * @private + */ + function __flush_frames() { + + if (window.requestAnimationFrame && document.hasFocus()) + asyncFlush(); + else + syncFlush(); + + } + + /** + * An ordered list of tasks which must be executed atomically. Once + * executed, an associated (and optional) callback will be called. + * + * @private + * @constructor + * @param {function} [callback] + * The function to call when this frame is rendered. + * + * @param {!Task[]} tasks + * The set of tasks which must be executed to render this frame. + * + * @param {number} [timestamp] + * The remote timestamp of sync instruction associated with this frame. + * This timestamp is in milliseconds, but is arbitrary, having meaning + * only relative to other remote timestamps in the same connection. If + * omitted, a compatible but local timestamp will be used instead. + * + * @param {number} [logicalFrames=0] + * The number of remote desktop frames that were combined to produce + * this frame, or zero if this value is unknown or inapplicable. + */ + var Frame = function Frame(callback, tasks, timestamp, logicalFrames) { + + /** + * The local timestamp of the point in time at which this frame was + * received by the display, in milliseconds since the Unix Epoch. + * + * @type {!number} + */ + this.localTimestamp = new Date().getTime(); + + /** + * The remote timestamp of sync instruction associated with this frame. + * This timestamp is in milliseconds, but is arbitrary, having meaning + * only relative to other remote timestamps in the same connection. + * + * @type {!number} + */ + this.remoteTimestamp = timestamp || this.localTimestamp; + + /** + * The number of remote desktop frames that were combined to produce + * this frame. If unknown or not applicable, this will be zero. + * + * @type {!number} + */ + this.logicalFrames = logicalFrames || 0; + + /** + * Cancels rendering of this frame and all associated tasks. The + * callback provided at construction time, if any, is not invoked. + */ + this.cancel = function cancel() { + + callback = null; + + tasks.forEach(function cancelTask(task) { + task.cancel(); + }); + + tasks = []; + + }; + + /** + * Returns whether this frame is ready to be rendered. This function + * returns true if and only if ALL underlying tasks are unblocked. + * + * @returns {!boolean} + * true if all underlying tasks are unblocked, false otherwise. + */ + this.isReady = function() { + + // Search for blocked tasks + for (var i=0; i < tasks.length; i++) { + if (tasks[i].blocked) + return false; + } + + // If no blocked tasks, the frame is ready + return true; + + }; + + /** + * Renders this frame, calling the associated callback, if any, after + * the frame is complete. This function MUST only be called when no + * blocked tasks exist. Calling this function with blocked tasks + * will result in undefined behavior. + */ + this.flush = function() { + + // Draw all pending tasks. + for (var i=0; i < tasks.length; i++) + tasks[i].execute(); + + // Call callback + if (callback) callback(); + + }; + + }; + + /** + * A container for an task handler. Each operation which must be ordered + * is associated with a Task that goes into a task queue. Tasks in this + * queue are executed in order once their handlers are set, while Tasks + * without handlers block themselves and any following Tasks from running. + * + * @constructor + * @private + * @param {function} [taskHandler] + * The function to call when this task runs, if any. + * + * @param {boolean} [blocked] + * Whether this task should start blocked. + */ + function Task(taskHandler, blocked) { + + /** + * Reference to this Task. + * + * @private + * @type {!Guacamole.Display.Task} + */ + var task = this; + + /** + * Whether this Task is blocked. + * + * @type {boolean} + */ + this.blocked = blocked; + + /** + * Cancels this task such that it will not run. The task handler + * provided at construction time, if any, is not invoked. Calling + * execute() after calling this function has no effect. + */ + this.cancel = function cancel() { + task.blocked = false; + taskHandler = null; + }; + + /** + * Unblocks this Task, allowing it to run. + */ + this.unblock = function() { + if (task.blocked) { + task.blocked = false; + __flush_frames(); + } + }; + + /** + * Calls the handler associated with this task IMMEDIATELY. This + * function does not track whether this task is marked as blocked. + * Enforcing the blocked status of tasks is up to the caller. + */ + this.execute = function() { + if (taskHandler) taskHandler(); + }; + + } + + /** + * Schedules a task for future execution. The given handler will execute + * immediately after all previous tasks upon frame flush, unless this + * task is blocked. If any tasks is blocked, the entire frame will not + * render (and no tasks within will execute) until all tasks are unblocked. + * + * @private + * @param {function} [handler] + * The function to call when possible, if any. + * + * @param {boolean} [blocked] + * Whether the task should start blocked. + * + * @returns {!Task} + * The Task created and added to the queue for future running. + */ + function scheduleTask(handler, blocked) { + var task = new Task(handler, blocked); + tasks.push(task); + return task; + } + + /** + * Returns the element which contains the Guacamole display. + * + * @return {!Element} + * The element containing the Guacamole display. + */ + this.getElement = function() { + return bounds; + }; + + /** + * Returns the width of this display. + * + * @return {!number} + * The width of this display; + */ + this.getWidth = function() { + return displayWidth; + }; + + /** + * Returns the height of this display. + * + * @return {!number} + * The height of this display; + */ + this.getHeight = function() { + return displayHeight; + }; + + /** + * Returns the default layer of this display. Each Guacamole display always + * has at least one layer. Other layers can optionally be created within + * this layer, but the default layer cannot be removed and is the absolute + * ancestor of all other layers. + * + * @return {!Guacamole.Display.VisibleLayer} + * The default layer. + */ + this.getDefaultLayer = function() { + return default_layer; + }; + + /** + * Returns the cursor layer of this display. Each Guacamole display contains + * a layer for the image of the mouse cursor. This layer is a special case + * and exists above all other layers, similar to the hardware mouse cursor. + * + * @return {!Guacamole.Display.VisibleLayer} + * The cursor layer. + */ + this.getCursorLayer = function() { + return cursor; + }; + + /** + * Creates a new layer. The new layer will be a direct child of the default + * layer, but can be moved to be a child of any other layer. Layers returned + * by this function are visible. + * + * @return {!Guacamole.Display.VisibleLayer} + * The newly-created layer. + */ + this.createLayer = function() { + var layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight); + layer.move(default_layer, 0, 0, 0); + return layer; + }; + + /** + * Creates a new buffer. Buffers are invisible, off-screen surfaces. They + * are implemented in the same manner as layers, but do not provide the + * same nesting semantics. + * + * @return {!Guacamole.Layer} + * The newly-created buffer. + */ + this.createBuffer = function() { + var buffer = new Guacamole.Layer(0, 0); + buffer.autosize = 1; + return buffer; + }; + + /** + * Flush all pending draw tasks, if possible, as a new frame. If the entire + * frame is not ready, the flush will wait until all required tasks are + * unblocked. + * + * @param {function} [callback] + * The function to call when this frame is flushed. This may happen + * immediately, or later when blocked tasks become unblocked. + * + * @param {number} timestamp + * The remote timestamp of sync instruction associated with this frame. + * This timestamp is in milliseconds, but is arbitrary, having meaning + * only relative to other remote timestamps in the same connection. + * + * @param {number} logicalFrames + * The number of remote desktop frames that were combined to produce + * this frame. + */ + this.flush = function(callback, timestamp, logicalFrames) { + + // Add frame, reset tasks + frames.push(new Frame(callback, tasks, timestamp, logicalFrames)); + tasks = []; + + // Attempt flush + __flush_frames(); + + }; + + /** + * Cancels rendering of all pending frames and associated rendering + * operations. The callbacks provided to outstanding past calls to flush(), + * if any, are not invoked. + */ + this.cancel = function cancel() { + + frames.forEach(function cancelFrame(frame) { + frame.cancel(); + }); + + frames = []; + + tasks.forEach(function cancelTask(task) { + task.cancel(); + }); + + tasks = []; + + }; + + /** + * Sets the hotspot and image of the mouse cursor displayed within the + * Guacamole display. + * + * @param {!number} hotspotX + * The X coordinate of the cursor hotspot. + * + * @param {!number} hotspotY + * The Y coordinate of the cursor hotspot. + * + * @param {!Guacamole.Layer} layer + * The source layer containing the data which should be used as the + * mouse cursor image. + * + * @param {!number} srcx + * The X coordinate of the upper-left corner of the rectangle within + * the source layer's coordinate space to copy data from. + * + * @param {!number} srcy + * The Y coordinate of the upper-left corner of the rectangle within + * the source layer's coordinate space to copy data from. + * + * @param {!number} srcw + * The width of the rectangle within the source layer's coordinate + * space to copy data from. + * + * @param {!number} srch + * The height of the rectangle within the source layer's coordinate + * space to copy data from. + */ + this.setCursor = function(hotspotX, hotspotY, layer, srcx, srcy, srcw, srch) { + scheduleTask(function __display_set_cursor() { + + // Set hotspot + guac_display.cursorHotspotX = hotspotX; + guac_display.cursorHotspotY = hotspotY; + + // Reset cursor size + cursor.resize(srcw, srch); + + // Draw cursor to cursor layer + cursor.copy(layer, srcx, srcy, srcw, srch, 0, 0); + guac_display.moveCursor(guac_display.cursorX, guac_display.cursorY); + + // Fire cursor change event + if (guac_display.oncursor) + guac_display.oncursor(cursor.toCanvas(), hotspotX, hotspotY); + + }); + }; + + /** + * Sets whether the software-rendered cursor is shown. This cursor differs + * from the hardware cursor in that it is built into the Guacamole.Display, + * and relies on its own Guacamole layer to render. + * + * @param {boolean} [shown=true] + * Whether to show the software cursor. + */ + this.showCursor = function(shown) { + + var element = cursor.getElement(); + var parent = element.parentNode; + + // Remove from DOM if hidden + if (shown === false) { + if (parent) + parent.removeChild(element); + } + + // Otherwise, ensure cursor is child of display + else if (parent !== display) + display.appendChild(element); + + }; + + /** + * Sets the location of the local cursor to the given coordinates. For the + * sake of responsiveness, this function performs its action immediately. + * Cursor motion is not maintained within atomic frames. + * + * @param {!number} x + * The X coordinate to move the cursor to. + * + * @param {!number} y + * The Y coordinate to move the cursor to. + */ + this.moveCursor = function(x, y) { + + // Move cursor layer + cursor.translate(x - guac_display.cursorHotspotX, + y - guac_display.cursorHotspotY); + + // Update stored position + guac_display.cursorX = x; + guac_display.cursorY = y; + + }; + + /** + * Changes the size of the given Layer to the given width and height. + * Resizing is only attempted if the new size provided is actually different + * from the current size. + * + * @param {!Guacamole.Layer} layer + * The layer to resize. + * + * @param {!number} width + * The new width. + * + * @param {!number} height + * The new height. + */ + this.resize = function(layer, width, height) { + scheduleTask(function __display_resize() { + + layer.resize(width, height); + + // Resize display if default layer is resized + if (layer === default_layer) { + + // Update (set) display size + displayWidth = width; + displayHeight = height; + display.style.width = displayWidth + "px"; + display.style.height = displayHeight + "px"; + + // Update bounds size + bounds.style.width = (displayWidth*displayScale) + "px"; + bounds.style.height = (displayHeight*displayScale) + "px"; + + // Notify of resize + if (guac_display.onresize) + guac_display.onresize(width, height); + + } + + }); + }; + + /** + * Draws the specified image at the given coordinates. The image specified + * must already be loaded. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + * + * @param {!CanvasImageSource} image + * The image to draw. Note that this not a URL. + */ + this.drawImage = function(layer, x, y, image) { + scheduleTask(function __display_drawImage() { + layer.drawImage(x, y, image); + }); + }; + + /** + * Draws the image contained within the specified Blob at the given + * coordinates. The Blob specified must already be populated with image + * data. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + * + * @param {!Blob} blob + * The Blob containing the image data to draw. + */ + this.drawBlob = function(layer, x, y, blob) { + + var task; + + // Prefer createImageBitmap() over blob URLs if available + if (window.createImageBitmap) { + + var bitmap; + + // Draw image once loaded + task = scheduleTask(function drawImageBitmap() { + layer.drawImage(x, y, bitmap); + }, true); + + // Load image from provided blob + window.createImageBitmap(blob).then(function bitmapLoaded(decoded) { + bitmap = decoded; + task.unblock(); + }); + + } + + // Use blob URLs and the Image object if createImageBitmap() is + // unavailable + else { + + // Create URL for blob + var url = URL.createObjectURL(blob); + + // Draw and free blob URL when ready + task = scheduleTask(function __display_drawBlob() { + + // Draw the image only if it loaded without errors + if (image.width && image.height) + layer.drawImage(x, y, image); + + // Blob URL no longer needed + URL.revokeObjectURL(url); + + }, true); + + // Load image from URL + var image = new Image(); + image.onload = task.unblock; + image.onerror = task.unblock; + image.src = url; + + } + + }; + + /** + * Draws the image within the given stream at the given coordinates. The + * image will be loaded automatically, and this and any future operations + * will wait for the image to finish loading. This function will + * automatically choose an appropriate method for reading and decoding the + * given image stream, and should be preferred for received streams except + * where manual decoding of the stream is unavoidable. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + * + * @param {!Guacamole.InputStream} stream + * The stream along which image data will be received. + * + * @param {!string} mimetype + * The mimetype of the image within the stream. + */ + this.drawStream = function drawStream(layer, x, y, stream, mimetype) { + + // If createImageBitmap() is available, load the image as a blob so + // that function can be used + if (window.createImageBitmap) { + var reader = new Guacamole.BlobReader(stream, mimetype); + reader.onend = function drawImageBlob() { + guac_display.drawBlob(layer, x, y, reader.getBlob()); + }; + } + + // Lacking createImageBitmap(), fall back to data URIs and the Image + // object + else { + var reader = new Guacamole.DataURIReader(stream, mimetype); + reader.onend = function drawImageDataURI() { + guac_display.draw(layer, x, y, reader.getURI()); + }; + } + + }; + + /** + * Draws the image at the specified URL at the given coordinates. The image + * will be loaded automatically, and this and any future operations will + * wait for the image to finish loading. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + * + * @param {!string} url + * The URL of the image to draw. + */ + this.draw = function(layer, x, y, url) { + + var task = scheduleTask(function __display_draw() { + + // Draw the image only if it loaded without errors + if (image.width && image.height) + layer.drawImage(x, y, image); + + }, true); + + var image = new Image(); + image.onload = task.unblock; + image.onerror = task.unblock; + image.src = url; + + }; + + /** + * Plays the video at the specified URL within this layer. The video + * will be loaded automatically, and this and any future operations will + * wait for the video to finish loading. Future operations will not be + * executed until the video finishes playing. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!string} mimetype + * The mimetype of the video to play. + * + * @param {!number} duration + * The duration of the video in milliseconds. + * + * @param {!string} url + * The URL of the video to play. + */ + this.play = function(layer, mimetype, duration, url) { + + // Start loading the video + var video = document.createElement("video"); + video.type = mimetype; + video.src = url; + + // Start copying frames when playing + video.addEventListener("play", function() { + + function render_callback() { + layer.drawImage(0, 0, video); + if (!video.ended) + window.setTimeout(render_callback, 20); + } + + render_callback(); + + }, false); + + scheduleTask(video.play); + + }; + + /** + * Transfer a rectangle of image data from one Layer to this Layer using the + * specified transfer function. + * + * @param {!Guacamole.Layer} srcLayer + * The Layer to copy image data from. + * + * @param {!number} srcx + * The X coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcy + * The Y coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcw + * The width of the rectangle within the source Layer's coordinate + * space to copy data from. + * + * @param {!number} srch + * The height of the rectangle within the source Layer's coordinate + * space to copy data from. + * + * @param {!Guacamole.Layer} dstLayer + * The layer to draw upon. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + * + * @param {!function} transferFunction + * The transfer function to use to transfer data from source to + * destination. + */ + this.transfer = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y, transferFunction) { + scheduleTask(function __display_transfer() { + dstLayer.transfer(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction); + }); + }; + + /** + * Put a rectangle of image data from one Layer to this Layer directly + * without performing any alpha blending. Simply copy the data. + * + * @param {!Guacamole.Layer} srcLayer + * The Layer to copy image data from. + * + * @param {!number} srcx + * The X coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcy + * The Y coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcw + * The width of the rectangle within the source Layer's coordinate + * space to copy data from. + * + * @param {!number} srch + * The height of the rectangle within the source Layer's coordinate + * space to copy data from. + * + * @param {!Guacamole.Layer} dstLayer + * The layer to draw upon. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + */ + this.put = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) { + scheduleTask(function __display_put() { + dstLayer.put(srcLayer, srcx, srcy, srcw, srch, x, y); + }); + }; + + /** + * Copy a rectangle of image data from one Layer to this Layer. This + * operation will copy exactly the image data that will be drawn once all + * operations of the source Layer that were pending at the time this + * function was called are complete. This operation will not alter the + * size of the source Layer even if its autosize property is set to true. + * + * @param {!Guacamole.Layer} srcLayer + * The Layer to copy image data from. + * + * @param {!number} srcx + * The X coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcy + * The Y coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcw + * The width of the rectangle within the source Layer's coordinate + * space to copy data from. + * + * @param {!number} srch + * The height of the rectangle within the source Layer's coordinate space to copy data from. + * + * @param {!Guacamole.Layer} dstLayer + * The layer to draw upon. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + */ + this.copy = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) { + scheduleTask(function __display_copy() { + dstLayer.copy(srcLayer, srcx, srcy, srcw, srch, x, y); + }); + }; + + /** + * Starts a new path at the specified point. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} x + * The X coordinate of the point to draw. + * + * @param {!number} y + * The Y coordinate of the point to draw. + */ + this.moveTo = function(layer, x, y) { + scheduleTask(function __display_moveTo() { + layer.moveTo(x, y); + }); + }; + + /** + * Add the specified line to the current path. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} x + * The X coordinate of the endpoint of the line to draw. + * + * @param {!number} y + * The Y coordinate of the endpoint of the line to draw. + */ + this.lineTo = function(layer, x, y) { + scheduleTask(function __display_lineTo() { + layer.lineTo(x, y); + }); + }; + + /** + * Add the specified arc to the current path. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} x + * The X coordinate of the center of the circle which will contain the + * arc. + * + * @param {!number} y + * The Y coordinate of the center of the circle which will contain the + * arc. + * + * @param {!number} radius + * The radius of the circle. + * + * @param {!number} startAngle + * The starting angle of the arc, in radians. + * + * @param {!number} endAngle + * The ending angle of the arc, in radians. + * + * @param {!boolean} negative + * Whether the arc should be drawn in order of decreasing angle. + */ + this.arc = function(layer, x, y, radius, startAngle, endAngle, negative) { + scheduleTask(function __display_arc() { + layer.arc(x, y, radius, startAngle, endAngle, negative); + }); + }; + + /** + * Starts a new path at the specified point. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} cp1x + * The X coordinate of the first control point. + * + * @param {!number} cp1y + * The Y coordinate of the first control point. + * + * @param {!number} cp2x + * The X coordinate of the second control point. + * + * @param {!number} cp2y + * The Y coordinate of the second control point. + * + * @param {!number} x + * The X coordinate of the endpoint of the curve. + * + * @param {!number} y + * The Y coordinate of the endpoint of the curve. + */ + this.curveTo = function(layer, cp1x, cp1y, cp2x, cp2y, x, y) { + scheduleTask(function __display_curveTo() { + layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y); + }); + }; + + /** + * Closes the current path by connecting the end point with the start + * point (if any) with a straight line. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + */ + this.close = function(layer) { + scheduleTask(function __display_close() { + layer.close(); + }); + }; + + /** + * Add the specified rectangle to the current path. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} x + * The X coordinate of the upper-left corner of the rectangle to draw. + * + * @param {!number} y + * The Y coordinate of the upper-left corner of the rectangle to draw. + * + * @param {!number} w + * The width of the rectangle to draw. + * + * @param {!number} h + * The height of the rectangle to draw. + */ + this.rect = function(layer, x, y, w, h) { + scheduleTask(function __display_rect() { + layer.rect(x, y, w, h); + }); + }; + + /** + * Clip all future drawing operations by the current path. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as fillColor()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {!Guacamole.Layer} layer + * The layer to affect. + */ + this.clip = function(layer) { + scheduleTask(function __display_clip() { + layer.clip(); + }); + }; + + /** + * Stroke the current path with the specified color. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!string} cap + * The line cap style. Can be "round", "square", or "butt". + * + * @param {!string} join + * The line join style. Can be "round", "bevel", or "miter". + * + * @param {!number} thickness + * The line thickness in pixels. + * + * @param {!number} r + * The red component of the color to fill. + * + * @param {!number} g + * The green component of the color to fill. + * + * @param {!number} b + * The blue component of the color to fill. + * + * @param {!number} a + * The alpha component of the color to fill. + */ + this.strokeColor = function(layer, cap, join, thickness, r, g, b, a) { + scheduleTask(function __display_strokeColor() { + layer.strokeColor(cap, join, thickness, r, g, b, a); + }); + }; + + /** + * Fills the current path with the specified color. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!number} r + * The red component of the color to fill. + * + * @param {!number} g + * The green component of the color to fill. + * + * @param {!number} b + * The blue component of the color to fill. + * + * @param {!number} a + * The alpha component of the color to fill. + */ + this.fillColor = function(layer, r, g, b, a) { + scheduleTask(function __display_fillColor() { + layer.fillColor(r, g, b, a); + }); + }; + + /** + * Stroke the current path with the image within the specified layer. The + * image data will be tiled infinitely within the stroke. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!string} cap + * The line cap style. Can be "round", "square", or "butt". + * + * @param {!string} join + * The line join style. Can be "round", "bevel", or "miter". + * + * @param {!number} thickness + * The line thickness in pixels. + * + * @param {!Guacamole.Layer} srcLayer + * The layer to use as a repeating pattern within the stroke. + */ + this.strokeLayer = function(layer, cap, join, thickness, srcLayer) { + scheduleTask(function __display_strokeLayer() { + layer.strokeLayer(cap, join, thickness, srcLayer); + }); + }; + + /** + * Fills the current path with the image within the specified layer. The + * image data will be tiled infinitely within the stroke. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + * + * @param {!Guacamole.Layer} srcLayer + * The layer to use as a repeating pattern within the fill. + */ + this.fillLayer = function(layer, srcLayer) { + scheduleTask(function __display_fillLayer() { + layer.fillLayer(srcLayer); + }); + }; + + /** + * Push current layer state onto stack. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + */ + this.push = function(layer) { + scheduleTask(function __display_push() { + layer.push(); + }); + }; + + /** + * Pop layer state off stack. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + */ + this.pop = function(layer) { + scheduleTask(function __display_pop() { + layer.pop(); + }); + }; + + /** + * Reset the layer, clearing the stack, the current path, and any transform + * matrix. + * + * @param {!Guacamole.Layer} layer + * The layer to draw upon. + */ + this.reset = function(layer) { + scheduleTask(function __display_reset() { + layer.reset(); + }); + }; + + /** + * Sets the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {!Guacamole.Layer} layer + * The layer to modify. + * + * @param {!number} a + * The first value in the affine transform's matrix. + * + * @param {!number} b + * The second value in the affine transform's matrix. + * + * @param {!number} c + * The third value in the affine transform's matrix. + * + * @param {!number} d + * The fourth value in the affine transform's matrix. + * + * @param {!number} e + * The fifth value in the affine transform's matrix. + * + * @param {!number} f + * The sixth value in the affine transform's matrix. + */ + this.setTransform = function(layer, a, b, c, d, e, f) { + scheduleTask(function __display_setTransform() { + layer.setTransform(a, b, c, d, e, f); + }); + }; + + /** + * Applies the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {!Guacamole.Layer} layer + * The layer to modify. + * + * @param {!number} a + * The first value in the affine transform's matrix. + * + * @param {!number} b + * The second value in the affine transform's matrix. + * + * @param {!number} c + * The third value in the affine transform's matrix. + * + * @param {!number} d + * The fourth value in the affine transform's matrix. + * + * @param {!number} e + * The fifth value in the affine transform's matrix. + * + * @param {!number} f + * The sixth value in the affine transform's matrix. + * + */ + this.transform = function(layer, a, b, c, d, e, f) { + scheduleTask(function __display_transform() { + layer.transform(a, b, c, d, e, f); + }); + }; + + /** + * Sets the channel mask for future operations on this Layer. + * + * The channel mask is a Guacamole-specific compositing operation identifier + * with a single bit representing each of four channels (in order): source + * image where destination transparent, source where destination opaque, + * destination where source transparent, and destination where source + * opaque. + * + * @param {!Guacamole.Layer} layer + * The layer to modify. + * + * @param {!number} mask + * The channel mask for future operations on this Layer. + */ + this.setChannelMask = function(layer, mask) { + scheduleTask(function __display_setChannelMask() { + layer.setChannelMask(mask); + }); + }; + + /** + * Sets the miter limit for stroke operations using the miter join. This + * limit is the maximum ratio of the size of the miter join to the stroke + * width. If this ratio is exceeded, the miter will not be drawn for that + * joint of the path. + * + * @param {!Guacamole.Layer} layer + * The layer to modify. + * + * @param {!number} limit + * The miter limit for stroke operations using the miter join. + */ + this.setMiterLimit = function(layer, limit) { + scheduleTask(function __display_setMiterLimit() { + layer.setMiterLimit(limit); + }); + }; + + /** + * Removes the given layer container entirely, such that it is no longer + * contained within its parent layer, if any. + * + * @param {!Guacamole.Display.VisibleLayer} layer + * The layer being removed from its parent. + */ + this.dispose = function dispose(layer) { + scheduleTask(function disposeLayer() { + layer.dispose(); + }); + }; + + /** + * Applies the given affine transform (defined with six values from the + * transform's matrix) to the given layer. + * + * @param {!Guacamole.Display.VisibleLayer} layer + * The layer being distorted. + * + * @param {!number} a + * The first value in the affine transform's matrix. + * + * @param {!number} b + * The second value in the affine transform's matrix. + * + * @param {!number} c + * The third value in the affine transform's matrix. + * + * @param {!number} d + * The fourth value in the affine transform's matrix. + * + * @param {!number} e + * The fifth value in the affine transform's matrix. + * + * @param {!number} f + * The sixth value in the affine transform's matrix. + */ + this.distort = function distort(layer, a, b, c, d, e, f) { + scheduleTask(function distortLayer() { + layer.distort(a, b, c, d, e, f); + }); + }; + + /** + * Moves the upper-left corner of the given layer to the given X and Y + * coordinate, sets the Z stacking order, and reparents the layer + * to the given parent layer. + * + * @param {!Guacamole.Display.VisibleLayer} layer + * The layer being moved. + * + * @param {!Guacamole.Display.VisibleLayer} parent + * The parent to set. + * + * @param {!number} x + * The X coordinate to move to. + * + * @param {!number} y + * The Y coordinate to move to. + * + * @param {!number} z + * The Z coordinate to move to. + */ + this.move = function move(layer, parent, x, y, z) { + scheduleTask(function moveLayer() { + layer.move(parent, x, y, z); + }); + }; + + /** + * Sets the opacity of the given layer to the given value, where 255 is + * fully opaque and 0 is fully transparent. + * + * @param {!Guacamole.Display.VisibleLayer} layer + * The layer whose opacity should be set. + * + * @param {!number} alpha + * The opacity to set. + */ + this.shade = function shade(layer, alpha) { + scheduleTask(function shadeLayer() { + layer.shade(alpha); + }); + }; + + /** + * Sets the scale of the client display element such that it renders at + * a relatively smaller or larger size, without affecting the true + * resolution of the display. + * + * @param {!number} scale + * The scale to resize to, where 1.0 is normal size (1:1 scale). + */ + this.scale = function(scale) { + + display.style.transform = + display.style.WebkitTransform = + display.style.MozTransform = + display.style.OTransform = + display.style.msTransform = + + "scale(" + scale + "," + scale + ")"; + + displayScale = scale; + + // Update bounds size + bounds.style.width = (displayWidth*displayScale) + "px"; + bounds.style.height = (displayHeight*displayScale) + "px"; + + }; + + /** + * Returns the scale of the display. + * + * @return {!number} + * The scale of the display. + */ + this.getScale = function() { + return displayScale; + }; + + /** + * Returns a canvas element containing the entire display, with all child + * layers composited within. + * + * @return {!HTMLCanvasElement} + * A new canvas element containing a copy of the display. + */ + this.flatten = function() { + + // Get destination canvas + var canvas = document.createElement("canvas"); + canvas.width = default_layer.width; + canvas.height = default_layer.height; + + var context = canvas.getContext("2d"); + + // Returns sorted array of children + function get_children(layer) { + + // Build array of children + var children = []; + for (var index in layer.children) + children.push(layer.children[index]); + + // Sort + children.sort(function children_comparator(a, b) { + + // Compare based on Z order + var diff = a.z - b.z; + if (diff !== 0) + return diff; + + // If Z order identical, use document order + var a_element = a.getElement(); + var b_element = b.getElement(); + var position = b_element.compareDocumentPosition(a_element); + + if (position & Node.DOCUMENT_POSITION_PRECEDING) return -1; + if (position & Node.DOCUMENT_POSITION_FOLLOWING) return 1; + + // Otherwise, assume same + return 0; + + }); + + // Done + return children; + + } + + // Draws the contents of the given layer at the given coordinates + function draw_layer(layer, x, y) { + + // Draw layer + if (layer.width > 0 && layer.height > 0) { + + // Save and update alpha + var initial_alpha = context.globalAlpha; + context.globalAlpha *= layer.alpha / 255.0; + + // Copy data + context.drawImage(layer.getCanvas(), x, y); + + // Draw all children + var children = get_children(layer); + for (var i=0; i} + */ + this.children = {}; + + // Set layer position + var canvas = layer.getCanvas(); + canvas.style.position = "absolute"; + canvas.style.left = "0px"; + canvas.style.top = "0px"; + + // Create div with given size + var div = document.createElement("div"); + div.appendChild(canvas); + div.style.width = width + "px"; + div.style.height = height + "px"; + div.style.position = "absolute"; + div.style.left = "0px"; + div.style.top = "0px"; + div.style.overflow = "hidden"; + + /** + * Superclass resize() function. + * @private + */ + var __super_resize = this.resize; + + this.resize = function(width, height) { + + // Resize containing div + div.style.width = width + "px"; + div.style.height = height + "px"; + + __super_resize(width, height); + + }; + + /** + * Returns the element containing the canvas and any other elements + * associated with this layer. + * + * @returns {!Element} + * The element containing this layer's canvas. + */ + this.getElement = function() { + return div; + }; + + /** + * The translation component of this layer's transform. + * + * @private + * @type {!string} + */ + var translate = "translate(0px, 0px)"; // (0, 0) + + /** + * The arbitrary matrix component of this layer's transform. + * + * @private + * @type {!string} + */ + var matrix = "matrix(1, 0, 0, 1, 0, 0)"; // Identity + + /** + * Moves the upper-left corner of this layer to the given X and Y + * coordinate. + * + * @param {!number} x + * The X coordinate to move to. + * + * @param {!number} y + * The Y coordinate to move to. + */ + this.translate = function(x, y) { + + layer.x = x; + layer.y = y; + + // Generate translation + translate = "translate(" + + x + "px," + + y + "px)"; + + // Set layer transform + div.style.transform = + div.style.WebkitTransform = + div.style.MozTransform = + div.style.OTransform = + div.style.msTransform = + + translate + " " + matrix; + + }; + + /** + * Moves the upper-left corner of this VisibleLayer to the given X and Y + * coordinate, sets the Z stacking order, and reparents this VisibleLayer + * to the given VisibleLayer. + * + * @param {!Guacamole.Display.VisibleLayer} parent + * The parent to set. + * + * @param {!number} x + * The X coordinate to move to. + * + * @param {!number} y + * The Y coordinate to move to. + * + * @param {!number} z + * The Z coordinate to move to. + */ + this.move = function(parent, x, y, z) { + + // Set parent if necessary + if (layer.parent !== parent) { + + // Maintain relationship + if (layer.parent) + delete layer.parent.children[layer.__unique_id]; + layer.parent = parent; + parent.children[layer.__unique_id] = layer; + + // Reparent element + var parent_element = parent.getElement(); + parent_element.appendChild(div); + + } + + // Set location + layer.translate(x, y); + layer.z = z; + div.style.zIndex = z; + + }; + + /** + * Sets the opacity of this layer to the given value, where 255 is fully + * opaque and 0 is fully transparent. + * + * @param {!number} a + * The opacity to set. + */ + this.shade = function(a) { + layer.alpha = a; + div.style.opacity = a/255.0; + }; + + /** + * Removes this layer container entirely, such that it is no longer + * contained within its parent layer, if any. + */ + this.dispose = function() { + + // Remove from parent container + if (layer.parent) { + delete layer.parent.children[layer.__unique_id]; + layer.parent = null; + } + + // Remove from parent element + if (div.parentNode) + div.parentNode.removeChild(div); + + }; + + /** + * Applies the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {!number} a + * The first value in the affine transform's matrix. + * + * @param {!number} b + * The second value in the affine transform's matrix. + * + * @param {!number} c + * The third value in the affine transform's matrix. + * + * @param {!number} d + * The fourth value in the affine transform's matrix. + * + * @param {!number} e + * The fifth value in the affine transform's matrix. + * + * @param {!number} f + * The sixth value in the affine transform's matrix. + */ + this.distort = function(a, b, c, d, e, f) { + + // Store matrix + layer.matrix = [a, b, c, d, e, f]; + + // Generate matrix transformation + matrix = + + /* a c e + * b d f + * 0 0 1 + */ + + "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")"; + + // Set layer transform + div.style.transform = + div.style.WebkitTransform = + div.style.MozTransform = + div.style.OTransform = + div.style.msTransform = + + translate + " " + matrix; + + }; + +}; + +/** + * The next identifier to be assigned to the layer container. This identifier + * uniquely identifies each VisibleLayer, but is unrelated to the index of + * the layer, which exists at the protocol/client level only. + * + * @private + * @type {!number} + */ +Guacamole.Display.VisibleLayer.__next_id = 0; + +/** + * A set of Guacamole display performance statistics, describing the speed at + * which the remote desktop, Guacamole server, and Guacamole client are + * rendering frames. + * + * @constructor + * @param {Guacamole.Display.Statistics|Object} [template={}] + * The object whose properties should be copied within the new + * Guacamole.Display.Statistics. + */ +Guacamole.Display.Statistics = function Statistics(template) { + + template = template || {}; + + /** + * The amount of time that the Guacamole client is taking to render + * individual frames, in milliseconds, if known. If this value is unknown, + * such as if the there are insufficient frame statistics recorded to + * calculate this value, this will be null. + * + * @type {?number} + */ + this.processingLag = template.processingLag; + + /** + * The framerate of the remote desktop currently being viewed within the + * relevant Gucamole.Display, independent of Guacamole, in frames per + * second. This represents the speed at which the remote desktop is + * producing frame data for the Guacamole server to consume. If this + * value is unknown, such as if the remote desktop server does not actually + * define frame boundaries, this will be null. + * + * @type {?number} + */ + this.desktopFps = template.desktopFps; + + /** + * The rate at which the Guacamole server is generating frames for the + * Guacamole client to consume, in frames per second. If the Guacamole + * server is correctly adjusting for variance in client/browser processing + * power, this rate should closely match the client rate, and should remain + * independent of any network latency. If this value is unknown, such as if + * the there are insufficient frame statistics recorded to calculate this + * value, this will be null. + * + * @type {?number} + */ + this.serverFps = template.serverFps; + + /** + * The rate at which the Guacamole client is consuming frames generated by + * the Guacamole server, in frames per second. If the Guacamole server is + * correctly adjusting for variance in client/browser processing power, + * this rate should closely match the server rate, regardless of any + * latency on the network between the server and client. If this value is + * unknown, such as if the there are insufficient frame statistics recorded + * to calculate this value, this will be null. + * + * @type {?number} + */ + this.clientFps = template.clientFps; + + /** + * The rate at which the Guacamole server is dropping or combining frames + * received from the remote desktop server to compensate for variance in + * client/browser processing power, in frames per second. This value may + * also be non-zero if the server is compensating for variances in its own + * processing power, or relative slowness in image compression vs. the rate + * that inbound frames are received. If this value is unknown, such as if + * the remote desktop server does not actually define frame boundaries, + * this will be null. + */ + this.dropRate = template.dropRate; + +}; diff --git a/src/Event.js b/src/Event.js new file mode 100644 index 0000000..04a4148 --- /dev/null +++ b/src/Event.js @@ -0,0 +1,326 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * An arbitrary event, emitted by a {@link Guacamole.Event.Target}. This object + * should normally serve as the base class for a different object that is more + * specific to the event type. + * + * @constructor + * @param {!string} type + * The unique name of this event type. + */ +Guacamole.Event = function Event(type) { + + /** + * The unique name of this event type. + * + * @type {!string} + */ + this.type = type; + + /** + * An arbitrary timestamp in milliseconds, indicating this event's + * position in time relative to other events. + * + * @type {!number} + */ + this.timestamp = new Date().getTime(); + + /** + * Returns the number of milliseconds elapsed since this event was created. + * + * @return {!number} + * The number of milliseconds elapsed since this event was created. + */ + this.getAge = function getAge() { + return new Date().getTime() - this.timestamp; + }; + + /** + * Requests that the legacy event handler associated with this event be + * invoked on the given event target. This function will be invoked + * automatically by implementations of {@link Guacamole.Event.Target} + * whenever {@link Guacamole.Event.Target#emit emit()} is invoked. + *

+ * Older versions of Guacamole relied on single event handlers with the + * prefix "on", such as "onmousedown" or "onkeyup". If a Guacamole.Event + * implementation is replacing the event previously represented by one of + * these handlers, this function gives the implementation the opportunity + * to provide backward compatibility with the old handler. + *

+ * Unless overridden, this function does nothing. + * + * @param {!Guacamole.Event.Target} eventTarget + * The {@link Guacamole.Event.Target} that emitted this event. + */ + this.invokeLegacyHandler = function invokeLegacyHandler(eventTarget) { + // Do nothing + }; + +}; + +/** + * A {@link Guacamole.Event} that may relate to one or more DOM events. + * Continued propagation and default behavior of the related DOM events may be + * prevented with {@link Guacamole.Event.DOMEvent#stopPropagation stopPropagation()} + * and {@link Guacamole.Event.DOMEvent#preventDefault preventDefault()} + * respectively. + * + * @constructor + * @augments Guacamole.Event + * + * @param {!string} type + * The unique name of this event type. + * + * @param {Event|Event[]} [events=[]] + * The DOM events that are related to this event, if any. Future calls to + * {@link Guacamole.Event.DOMEvent#preventDefault preventDefault()} and + * {@link Guacamole.Event.DOMEvent#stopPropagation stopPropagation()} will + * affect these events. + */ +Guacamole.Event.DOMEvent = function DOMEvent(type, events) { + + Guacamole.Event.call(this, type); + + // Default to empty array + events = events || []; + + // Automatically wrap non-array single Event in an array + if (!Array.isArray(events)) + events = [ events ]; + + /** + * Requests that the default behavior of related DOM events be prevented. + * Whether this request will be honored by the browser depends on the + * nature of those events and the timing of the request. + */ + this.preventDefault = function preventDefault() { + events.forEach(function applyPreventDefault(event) { + if (event.preventDefault) event.preventDefault(); + event.returnValue = false; + }); + }; + + /** + * Stops further propagation of related events through the DOM. Only events + * that are directly related to this event will be stopped. + */ + this.stopPropagation = function stopPropagation() { + events.forEach(function applyStopPropagation(event) { + event.stopPropagation(); + }); + }; + +}; + +/** + * Convenience function for cancelling all further processing of a given DOM + * event. Invoking this function prevents the default behavior of the event and + * stops any further propagation. + * + * @param {!Event} event + * The DOM event to cancel. + */ +Guacamole.Event.DOMEvent.cancelEvent = function cancelEvent(event) { + event.stopPropagation(); + if (event.preventDefault) event.preventDefault(); + event.returnValue = false; +}; + +/** + * An object which can dispatch {@link Guacamole.Event} objects. Listeners + * registered with {@link Guacamole.Event.Target#on on()} will automatically + * be invoked based on the type of {@link Guacamole.Event} passed to + * {@link Guacamole.Event.Target#dispatch dispatch()}. It is normally + * subclasses of Guacamole.Event.Target that will dispatch events, and usages + * of those subclasses that will catch dispatched events with on(). + * + * @constructor + */ +Guacamole.Event.Target = function Target() { + + /** + * A callback function which handles an event dispatched by an event + * target. + * + * @callback Guacamole.Event.Target~listener + * @param {!Guacamole.Event} event + * The event that was dispatched. + * + * @param {!Guacamole.Event.Target} target + * The object that dispatched the event. + */ + + /** + * All listeners (callback functions) registered for each event type passed + * to {@link Guacamole.Event.Targer#on on()}. + * + * @private + * @type {!Object.} + */ + var listeners = {}; + + /** + * Registers a listener for events having the given type, as dictated by + * the {@link Guacamole.Event#type type} property of {@link Guacamole.Event} + * provided to {@link Guacamole.Event.Target#dispatch dispatch()}. + * + * @param {!string} type + * The unique name of this event type. + * + * @param {!Guacamole.Event.Target~listener} listener + * The function to invoke when an event having the given type is + * dispatched. The {@link Guacamole.Event} object provided to + * {@link Guacamole.Event.Target#dispatch dispatch()} will be passed to + * this function, along with the dispatching Guacamole.Event.Target. + */ + this.on = function on(type, listener) { + + var relevantListeners = listeners[type]; + if (!relevantListeners) + listeners[type] = relevantListeners = []; + + relevantListeners.push(listener); + + }; + + /** + * Registers a listener for events having the given types, as dictated by + * the {@link Guacamole.Event#type type} property of {@link Guacamole.Event} + * provided to {@link Guacamole.Event.Target#dispatch dispatch()}. + *

+ * Invoking this function is equivalent to manually invoking + * {@link Guacamole.Event.Target#on on()} for each of the provided types. + * + * @param {!string[]} types + * The unique names of the event types to associate with the given + * listener. + * + * @param {!Guacamole.Event.Target~listener} listener + * The function to invoke when an event having any of the given types + * is dispatched. The {@link Guacamole.Event} object provided to + * {@link Guacamole.Event.Target#dispatch dispatch()} will be passed to + * this function, along with the dispatching Guacamole.Event.Target. + */ + this.onEach = function onEach(types, listener) { + types.forEach(function addListener(type) { + this.on(type, listener); + }, this); + }; + + /** + * Dispatches the given event, invoking all event handlers registered with + * this Guacamole.Event.Target for that event's + * {@link Guacamole.Event#type type}. + * + * @param {!Guacamole.Event} event + * The event to dispatch. + */ + this.dispatch = function dispatch(event) { + + // Invoke any relevant legacy handler for the event + event.invokeLegacyHandler(this); + + // Invoke all registered listeners + var relevantListeners = listeners[event.type]; + if (relevantListeners) { + for (var i = 0; i < relevantListeners.length; i++) { + relevantListeners[i](event, this); + } + } + + }; + + /** + * Unregisters a listener that was previously registered with + * {@link Guacamole.Event.Target#on on()} or + * {@link Guacamole.Event.Target#onEach onEach()}. If no such listener was + * registered, this function has no effect. If multiple copies of the same + * listener were registered, the first listener still registered will be + * removed. + * + * @param {!string} type + * The unique name of the event type handled by the listener being + * removed. + * + * @param {!Guacamole.Event.Target~listener} listener + * The listener function previously provided to + * {@link Guacamole.Event.Target#on on()}or + * {@link Guacamole.Event.Target#onEach onEach()}. + * + * @returns {!boolean} + * true if the specified listener was removed, false otherwise. + */ + this.off = function off(type, listener) { + + var relevantListeners = listeners[type]; + if (!relevantListeners) + return false; + + for (var i = 0; i < relevantListeners.length; i++) { + if (relevantListeners[i] === listener) { + relevantListeners.splice(i, 1); + return true; + } + } + + return false; + + }; + + /** + * Unregisters listeners that were previously registered with + * {@link Guacamole.Event.Target#on on()} or + * {@link Guacamole.Event.Target#onEach onEach()}. If no such listeners + * were registered, this function has no effect. If multiple copies of the + * same listener were registered for the same event type, the first + * listener still registered will be removed. + *

+ * Invoking this function is equivalent to manually invoking + * {@link Guacamole.Event.Target#off off()} for each of the provided types. + * + * @param {!string[]} types + * The unique names of the event types handled by the listeners being + * removed. + * + * @param {!Guacamole.Event.Target~listener} listener + * The listener function previously provided to + * {@link Guacamole.Event.Target#on on()} or + * {@link Guacamole.Event.Target#onEach onEach()}. + * + * @returns {!boolean} + * true if any of the specified listeners were removed, false + * otherwise. + */ + this.offEach = function offEach(types, listener) { + + var changed = false; + + types.forEach(function removeListener(type) { + changed |= this.off(type, listener); + }, this); + + return changed; + + }; + +}; diff --git a/src/InputSink.js b/src/InputSink.js new file mode 100644 index 0000000..616fe26 --- /dev/null +++ b/src/InputSink.js @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A hidden input field which attempts to keep itself focused at all times, + * except when another input field has been intentionally focused, whether + * programatically or by the user. The actual underlying input field, returned + * by getElement(), may be used as a reliable source of keyboard-related events, + * particularly composition and input events which may require a focused input + * field to be dispatched at all. + * + * @constructor + */ +Guacamole.InputSink = function InputSink() { + + /** + * Reference to this instance of Guacamole.InputSink. + * + * @private + * @type {!Guacamole.InputSink} + */ + var sink = this; + + /** + * The underlying input field, styled to be invisible. + * + * @private + * @type {!Element} + */ + var field = document.createElement('textarea'); + field.style.position = 'fixed'; + field.style.outline = 'none'; + field.style.border = 'none'; + field.style.margin = '0'; + field.style.padding = '0'; + field.style.height = '0'; + field.style.width = '0'; + field.style.left = '0'; + field.style.bottom = '0'; + field.style.resize = 'none'; + field.style.background = 'transparent'; + field.style.color = 'transparent'; + + // Keep field clear when modified via normal keypresses + field.addEventListener("keypress", function clearKeypress(e) { + field.value = ''; + }, false); + + // Keep field clear when modofied via composition events + field.addEventListener("compositionend", function clearCompletedComposition(e) { + if (e.data) + field.value = ''; + }, false); + + // Keep field clear when modofied via input events + field.addEventListener("input", function clearCompletedInput(e) { + if (e.data && !e.isComposing) + field.value = ''; + }, false); + + // Whenever focus is gained, automatically click to ensure cursor is + // actually placed within the field (the field may simply be highlighted or + // outlined otherwise) + field.addEventListener("focus", function focusReceived() { + window.setTimeout(function deferRefocus() { + field.click(); + field.select(); + }, 0); + }, true); + + /** + * Attempts to focus the underlying input field. The focus attempt occurs + * asynchronously, and may silently fail depending on browser restrictions. + */ + this.focus = function focus() { + window.setTimeout(function deferRefocus() { + field.focus(); // Focus must be deferred to work reliably across browsers + }, 0); + }; + + /** + * Returns the underlying input field. This input field MUST be manually + * added to the DOM for the Guacamole.InputSink to have any effect. + * + * @returns {!Element} + * The underlying input field. + */ + this.getElement = function getElement() { + return field; + }; + + // Automatically refocus input sink if part of DOM + document.addEventListener("keydown", function refocusSink(e) { + + // Do not refocus if focus is on an input field + var focused = document.activeElement; + if (focused && focused !== document.body) { + + // Only consider focused input fields which are actually visible + var rect = focused.getBoundingClientRect(); + if (rect.left + rect.width > 0 && rect.top + rect.height > 0) + return; + + } + + // Refocus input sink instead of handling click + sink.focus(); + + }, true); + +}; diff --git a/src/InputStream.js b/src/InputStream.js new file mode 100644 index 0000000..7bda133 --- /dev/null +++ b/src/InputStream.js @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * An input stream abstraction used by the Guacamole client to facilitate + * transfer of files or other binary data. + * + * @constructor + * @param {!Guacamole.Client} client + * The client owning this stream. + * + * @param {!number} index + * The index of this stream. + */ +Guacamole.InputStream = function(client, index) { + + /** + * Reference to this stream. + * + * @private + * @type {!Guacamole.InputStream} + */ + var guac_stream = this; + + /** + * The index of this stream. + * + * @type {!number} + */ + this.index = index; + + /** + * Called when a blob of data is received. + * + * @event + * @param {!string} data + * The received base64 data. + */ + this.onblob = null; + + /** + * Called when this stream is closed. + * + * @event + */ + this.onend = null; + + /** + * Acknowledges the receipt of a blob. + * + * @param {!string} message + * A human-readable message describing the error or status. + * + * @param {!number} code + * The error code, if any, or 0 for success. + */ + this.sendAck = function(message, code) { + client.sendAck(guac_stream.index, message, code); + }; + +}; diff --git a/src/IntegerPool.js b/src/IntegerPool.js new file mode 100644 index 0000000..6916cab --- /dev/null +++ b/src/IntegerPool.js @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Integer pool which returns consistently increasing integers while integers + * are in use, and previously-used integers when possible. + * @constructor + */ +Guacamole.IntegerPool = function() { + + /** + * Reference to this integer pool. + * + * @private + */ + var guac_pool = this; + + /** + * Array of available integers. + * + * @private + * @type {!number[]} + */ + var pool = []; + + /** + * The next integer to return if no more integers remain. + * + * @type {!number} + */ + this.next_int = 0; + + /** + * Returns the next available integer in the pool. If possible, a previously + * used integer will be returned. + * + * @return {!number} + * The next available integer. + */ + this.next = function() { + + // If free'd integers exist, return one of those + if (pool.length > 0) + return pool.shift(); + + // Otherwise, return a new integer + return guac_pool.next_int++; + + }; + + /** + * Frees the given integer, allowing it to be reused. + * + * @param {!number} integer + * The integer to free. + */ + this.free = function(integer) { + pool.push(integer); + }; + +}; diff --git a/src/JSONReader.js b/src/JSONReader.js new file mode 100644 index 0000000..ef71d78 --- /dev/null +++ b/src/JSONReader.js @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A reader which automatically handles the given input stream, assembling all + * received blobs into a JavaScript object by appending them to each other, in + * order, and decoding the result as JSON. Note that this object will overwrite + * any installed event handlers on the given Guacamole.InputStream. + * + * @constructor + * @param {Guacamole.InputStream} stream + * The stream that JSON will be read from. + */ +Guacamole.JSONReader = function guacamoleJSONReader(stream) { + + /** + * Reference to this Guacamole.JSONReader. + * + * @private + * @type {!Guacamole.JSONReader} + */ + var guacReader = this; + + /** + * Wrapped Guacamole.StringReader. + * + * @private + * @type {!Guacamole.StringReader} + */ + var stringReader = new Guacamole.StringReader(stream); + + /** + * All JSON read thus far. + * + * @private + * @type {!string} + */ + var json = ''; + + /** + * Returns the current length of this Guacamole.JSONReader, in characters. + * + * @return {!number} + * The current length of this Guacamole.JSONReader. + */ + this.getLength = function getLength() { + return json.length; + }; + + /** + * Returns the contents of this Guacamole.JSONReader as a JavaScript + * object. + * + * @return {object} + * The contents of this Guacamole.JSONReader, as parsed from the JSON + * contents of the input stream. + */ + this.getJSON = function getJSON() { + return JSON.parse(json); + }; + + // Append all received text + stringReader.ontext = function ontext(text) { + + // Append received text + json += text; + + // Call handler, if present + if (guacReader.onprogress) + guacReader.onprogress(text.length); + + }; + + // Simply call onend when end received + stringReader.onend = function onend() { + if (guacReader.onend) + guacReader.onend(); + }; + + /** + * Fired once for every blob of data received. + * + * @event + * @param {!number} length + * The number of characters received. + */ + this.onprogress = null; + + /** + * Fired once this stream is finished and no further data will be written. + * + * @event + */ + this.onend = null; + +}; diff --git a/src/KeyEventInterpreter.js b/src/KeyEventInterpreter.js new file mode 100644 index 0000000..44354fb --- /dev/null +++ b/src/KeyEventInterpreter.js @@ -0,0 +1,550 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * An object that will accept raw key events and produce human readable text + * batches, seperated by at least `batchSeperation` milliseconds, which can be + * retrieved through the onbatch callback or by calling getCurrentBatch(). + * + * NOTE: The event processing logic and output format is based on the `guaclog` + * tool, with the addition of batching support. + * + * @constructor + * + * @param {number} [batchSeperation=5000] + * The minimum number of milliseconds that must elapse between subsequent + * batches of key-event-generated text. If 0 or negative, no splitting will + * occur, resulting in a single batch for all provided key events. + * + * @param {number} [startTimestamp=0] + * The starting timestamp for the recording being intepreted. If provided, + * the timestamp of each intepreted event will be relative to this timestamp. + * If not provided, the raw recording timestamp will be used. + */ +Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) { + + /** + * Reference to this Guacamole.KeyEventInterpreter. + * + * @private + * @type {!Guacamole.SessionRecording} + */ + var interpreter = this; + + // Default to 5 seconds if the batch seperation was not provided + if (batchSeperation === undefined || batchSeperation === null) + batchSeperation = 5000; + + // Default to 0 seconds to keep the raw timestamps + if (startTimestamp === undefined || startTimestamp === null) + startTimestamp = 0; + + /** + * A definition for a known key. + * + * @constructor + * @private + * @param {KeyDefinition|object} [template={}] + * The object whose properties should be copied within the new + * KeyDefinition. + */ + var KeyDefinition = function KeyDefinition(template) { + + /** + * The X11 keysym of the key. + * @type {!number} + */ + this.keysym = parseInt(template.keysym); + + /** + * A human-readable name for the key. + * @type {!String} + */ + this.name = template.name; + + /** + * The value which would be typed in a typical text editor, if any. If the + * key is not associated with any typable value, or if the typable value is + * not generally useful in an auditing context, this will be undefined. + * @type {String} + */ + this.value = template.value; + + /** + * Whether this key is a modifier which may affect the interpretation of + * other keys, and thus should be tracked as it is held down. + * @type {!boolean} + * @default false + */ + this.modifier = template.modifier || false; + + }; + + /** + * A precursor array to the KNOWN_KEYS map. The objects contained within + * will be constructed into full KeyDefinition objects. + * + * @constant + * @private + * @type {Object[]} + */ + var _KNOWN_KEYS = [ + {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true }, + {keysym: 0xFF08, name: 'Backspace' }, + {keysym: 0xFF09, name: 'Tab' }, + {keysym: 0xFF0B, name: 'Clear' }, + {keysym: 0xFF0D, name: 'Return', value: "\n" }, + {keysym: 0xFF13, name: 'Pause' }, + {keysym: 0xFF14, name: 'Scroll' }, + {keysym: 0xFF15, name: 'SysReq' }, + {keysym: 0xFF1B, name: 'Escape' }, + {keysym: 0xFF50, name: 'Home' }, + {keysym: 0xFF51, name: 'Left' }, + {keysym: 0xFF52, name: 'Up' }, + {keysym: 0xFF53, name: 'Right' }, + {keysym: 0xFF54, name: 'Down' }, + {keysym: 0xFF55, name: 'Page Up' }, + {keysym: 0xFF56, name: 'Page Down' }, + {keysym: 0xFF57, name: 'End' }, + {keysym: 0xFF63, name: 'Insert' }, + {keysym: 0xFF65, name: 'Undo' }, + {keysym: 0xFF6A, name: 'Help' }, + {keysym: 0xFF7F, name: 'Num' }, + {keysym: 0xFF80, name: 'Space', value: " " }, + {keysym: 0xFF8D, name: 'Enter', value: "\n" }, + {keysym: 0xFF95, name: 'Home' }, + {keysym: 0xFF96, name: 'Left' }, + {keysym: 0xFF97, name: 'Up' }, + {keysym: 0xFF98, name: 'Right' }, + {keysym: 0xFF99, name: 'Down' }, + {keysym: 0xFF9A, name: 'Page Up' }, + {keysym: 0xFF9B, name: 'Page Down' }, + {keysym: 0xFF9C, name: 'End' }, + {keysym: 0xFF9E, name: 'Insert' }, + {keysym: 0xFFAA, name: '*', value: "*" }, + {keysym: 0xFFAB, name: '+', value: "+" }, + {keysym: 0xFFAD, name: '-', value: "-" }, + {keysym: 0xFFAE, name: '.', value: "." }, + {keysym: 0xFFAF, name: '/', value: "/" }, + {keysym: 0xFFB0, name: '0', value: "0" }, + {keysym: 0xFFB1, name: '1', value: "1" }, + {keysym: 0xFFB2, name: '2', value: "2" }, + {keysym: 0xFFB3, name: '3', value: "3" }, + {keysym: 0xFFB4, name: '4', value: "4" }, + {keysym: 0xFFB5, name: '5', value: "5" }, + {keysym: 0xFFB6, name: '6', value: "6" }, + {keysym: 0xFFB7, name: '7', value: "7" }, + {keysym: 0xFFB8, name: '8', value: "8" }, + {keysym: 0xFFB9, name: '9', value: "9" }, + {keysym: 0xFFBE, name: 'F1' }, + {keysym: 0xFFBF, name: 'F2' }, + {keysym: 0xFFC0, name: 'F3' }, + {keysym: 0xFFC1, name: 'F4' }, + {keysym: 0xFFC2, name: 'F5' }, + {keysym: 0xFFC3, name: 'F6' }, + {keysym: 0xFFC4, name: 'F7' }, + {keysym: 0xFFC5, name: 'F8' }, + {keysym: 0xFFC6, name: 'F9' }, + {keysym: 0xFFC7, name: 'F10' }, + {keysym: 0xFFC8, name: 'F11' }, + {keysym: 0xFFC9, name: 'F12' }, + {keysym: 0xFFCA, name: 'F13' }, + {keysym: 0xFFCB, name: 'F14' }, + {keysym: 0xFFCC, name: 'F15' }, + {keysym: 0xFFCD, name: 'F16' }, + {keysym: 0xFFCE, name: 'F17' }, + {keysym: 0xFFCF, name: 'F18' }, + {keysym: 0xFFD0, name: 'F19' }, + {keysym: 0xFFD1, name: 'F20' }, + {keysym: 0xFFD2, name: 'F21' }, + {keysym: 0xFFD3, name: 'F22' }, + {keysym: 0xFFD4, name: 'F23' }, + {keysym: 0xFFD5, name: 'F24' }, + {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true }, + {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true }, + {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true }, + {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true }, + {keysym: 0xFFE5, name: 'Caps' }, + {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true }, + {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true }, + {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true }, + {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true }, + {keysym: 0xFFEB, name: 'Super', value: null, modifier: true }, + {keysym: 0xFFEC, name: 'Super', value: null, modifier: true }, + {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true }, + {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true }, + {keysym: 0xFFFF, name: 'Delete' } + ]; + + /** + * All known keys, as a map of X11 keysym to KeyDefinition. + * + * @constant + * @private + * @type {Object.} + */ + var KNOWN_KEYS = {}; + _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) { + + // Construct a map of keysym to KeyDefinition object + KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition) + + }); + + /** + * A map of X11 keysyms to a KeyDefinition object, if the corresponding + * key is currently pressed. If a keysym has no entry in this map at all, + * it means that the key is not being pressed. Note that not all keysyms + * are necessarily tracked within this map - only those that are explicitly + * tracked. + * + * @private + * @type {Object. } + */ + var pressedKeys = {}; + + /** + * The current key event batch, containing a representation of all key + * events processed since the end of the last batch passed to onbatch. + * Null if no key events have been processed yet. + * + * @private + * @type {!KeyEventBatch} + */ + var currentBatch = null; + + /** + * The timestamp of the most recent key event processed. + * + * @private + * @type {Number} + */ + var lastKeyEvent = 0; + + /** + * Returns true if the currently-pressed keys are part of a shortcut, or + * false otherwise. + * + * @private + * @returns {!boolean} + * True if the currently-pressed keys are part of a shortcut, or false + * otherwise. + */ + function isShortcut() { + + // If one of the currently-pressed keys is non-printable, a shortcut + // is being typed + for (var keysym in pressedKeys) { + if (pressedKeys[keysym].value === null) + return true; + } + + return false; + } + + /** + * If the provided keysym corresponds to a valid UTF-8 character, return + * a KeyDefinition for that keysym. Otherwise, return null. + * + * @private + * @param {Number} keysym + * The keysym to produce a UTF-8 KeyDefinition for, if valid. + * + * @returns {KeyDefinition} + * A KeyDefinition for the provided keysym, if it's a valid UTF-8 + * keysym, or null otherwise. + */ + function getUnicodeKeyDefinition(keysym) { + + // Translate only if keysym maps to Unicode + if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF)) + return null; + + // Convert to UTF8 string + var codepoint = keysym & 0xFFFF; + var name = String.fromCharCode(codepoint); + + // Create and return the definition + return new KeyDefinition({keysym: keysym, name: name, value: name, modifier: false}); + + } + + /** + * Return a KeyDefinition corresponding to the provided keysym. + * + * @private + * @param {Number} keysym + * The keysym to return a KeyDefinition for. + * + * @returns {KeyDefinition} + * A KeyDefinition corresponding to the provided keysym. + */ + function getKeyDefinitionByKeysym(keysym) { + + // If it's a known type, return the existing definition + if (keysym in KNOWN_KEYS) + return KNOWN_KEYS[keysym]; + + // Return a UTF-8 KeyDefinition, if valid + var definition = getUnicodeKeyDefinition(keysym); + if (definition != null) + return definition; + + // If it's not UTF-8, return an unknown definition, with the name + // just set to the hex value of the keysym + return new KeyDefinition({ + keysym: keysym, + name: '0x' + String(keysym.toString(16)) + }) + + } + + /** + * Fired whenever a new batch of typed text extracted from key events + * is available. A new batch will be provided every time a new key event + * is processed after more than batchSeperation milliseconds after the + * previous key event. + * + * @event + * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch} + */ + this.onbatch = null; + + /** + * Handles a raw key event, potentially appending typed text to the + * current batch, and calling onbatch with the current batch, if the + * callback is set and a new batch is about to be started. + * + * @param {!string[]} args + * The arguments of the key event. + */ + this.handleKeyEvent = function handleKeyEvent(args) { + + // The X11 keysym + var keysym = parseInt(args[0]); + + // Either 1 or 0 for pressed or released, respectively + var pressed = parseInt(args[1]); + + // The timestamp when this key event occured + var timestamp = parseInt(args[2]); + + // If no current batch exists, start a new one now + if (!currentBatch) + currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch(); + + // Only switch to a new batch of text if sufficient time has passed + // since the last key event + var newBatch = (batchSeperation >= 0 + && (timestamp - lastKeyEvent) >= batchSeperation); + lastKeyEvent = timestamp; + + if (newBatch) { + + // Call the handler with the current batch of text and the timestamp + // at which the current batch started + if (currentBatch.events.length && interpreter.onbatch) + interpreter.onbatch(currentBatch); + + // Move on to the next batch of text + currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch(); + + } + + var keyDefinition = getKeyDefinitionByKeysym(keysym); + + // Mark down whether the key was pressed or released + if (keyDefinition.modifier) { + if (pressed) + pressedKeys[keysym] = keyDefinition; + else + delete pressedKeys[keysym]; + } + + // Append to the current typed value when a printable + // (non-modifier) key is pressed + else if (pressed) { + + var relativeTimestap = timestamp - startTimestamp; + + if (isShortcut()) { + + var shortcutText = '<'; + + var firstKey = true; + + // Compose entry by inspecting the state of each tracked key. + // At least one key must be pressed when in a shortcut. + for (var keysym in pressedKeys) { + + var pressedKeyDefinition = pressedKeys[keysym]; + + // Print name of key + if (firstKey) { + shortcutText += pressedKeyDefinition.name; + firstKey = false; + } + + else + shortcutText += ('+' + pressedKeyDefinition.name); + + } + + // Finally, append the printable key to close the shortcut + shortcutText += ('+' + keyDefinition.name + '>') + + // Add the shortcut to the current batch + currentBatch.simpleValue += shortcutText; + currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent( + shortcutText, false, relativeTimestap)); + + } + + // Print the key itself + else { + + var keyText; + var typed; + + // Print the value if explicitly defined + if (keyDefinition.value != null) { + + keyText = keyDefinition.value; + typed = true; + + } + + // Otherwise print the name + else { + + keyText = ('<' + keyDefinition.name + '>'); + + // While this is a representation for a single character, + // the key text is the name of the key, not the actual + // character itself + typed = false; + + } + + // Add the key to the current batch + currentBatch.simpleValue += keyText; + currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent( + keyText, typed, relativeTimestap)); + + } + + } + + }; + + /** + * Return the current batch of typed text. Note that the batch may be + * incomplete, as more key events might be processed before the next + * batch starts. + * + * @returns {Guacamole.KeyEventInterpreter.KeyEventBatch} + * The current batch of text. + */ + this.getCurrentBatch = function getCurrentBatch() { + return currentBatch; + }; +}; + +/** + * A granular description of an extracted key event, including a human-readable + * text representation of the event, whether the event is directly typed or not, + * and the timestamp when the event occured. + * + * @constructor + * @param {!String} text + * A human-readable representation of the event. + * + * @param {!boolean} typed + * True if this event represents a directly-typed character, or false + * otherwise. + * + * @param {!Number} timestamp + * The timestamp from the recording when this event occured. + */ +Guacamole.KeyEventInterpreter.KeyEvent = function KeyEvent(text, typed, timestamp) { + + /** + * A human-readable representation of the event. If a printable character + * was directly typed, this will just be that character. Otherwise it will + * be a string describing the event. + * + * @type {!String} + */ + this.text = text; + + /** + * True if this text of this event is exactly a typed character, or false + * otherwise. + * + * @type {!boolean} + */ + this.typed = typed; + + /** + * The timestamp from the recording when this event occured. If a + * `startTimestamp` value was provided to the interpreter constructor, this + * will be relative to start of the recording. If not, it will be the raw + * timestamp from the key event. + * + * @type {!Number} + */ + this.timestamp = timestamp; + +}; + +/** + * A series of intepreted key events, seperated by at least the configured + * batchSeperation value from any other key events in the recording corresponding + * to the interpreted key events. A batch will always consist of at least one key + * event, and an associated simplified representation of the event(s). + * + * @constructor + * @param {!Guacamole.KeyEventInterpreter.KeyEvent[]} events + * The interpreted key events for this batch. + * + * @param {!String} simpleValue + * The simplified, human-readable value representing the key events for + * this batch. + */ +Guacamole.KeyEventInterpreter.KeyEventBatch = function KeyEventBatch(events, simpleValue) { + + /** + * All key events for this batch. + * + * @type {!Guacamole.KeyEventInterpreter.KeyEvent[]} + */ + this.events = events || []; + + /** + * The simplified, human-readable value representing the key events for + * this batch, equivalent to concatenating the `text` field of all key + * events in the batch. + * + * @type {!String} + */ + this.simpleValue = simpleValue || ''; + +}; diff --git a/src/Keyboard.js b/src/Keyboard.js new file mode 100644 index 0000000..62d709c --- /dev/null +++ b/src/Keyboard.js @@ -0,0 +1,1525 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Provides cross-browser and cross-keyboard keyboard for a specific element. + * Browser and keyboard layout variation is abstracted away, providing events + * which represent keys as their corresponding X11 keysym. + * + * @constructor + * @param {Element|Document} [element] + * The Element to use to provide keyboard events. If omitted, at least one + * Element must be manually provided through the listenTo() function for + * the Guacamole.Keyboard instance to have any effect. + */ +Guacamole.Keyboard = function Keyboard(element) { + + /** + * Reference to this Guacamole.Keyboard. + * + * @private + * @type {!Guacamole.Keyboard} + */ + var guac_keyboard = this; + + /** + * An integer value which uniquely identifies this Guacamole.Keyboard + * instance with respect to other Guacamole.Keyboard instances. + * + * @private + * @type {!number} + */ + var guacKeyboardID = Guacamole.Keyboard._nextID++; + + /** + * The name of the property which is added to event objects via markEvent() + * to note that they have already been handled by this Guacamole.Keyboard. + * + * @private + * @constant + * @type {!string} + */ + var EVENT_MARKER = '_GUAC_KEYBOARD_HANDLED_BY_' + guacKeyboardID; + + /** + * Fired whenever the user presses a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {!number} keysym + * The keysym of the key being pressed. + * + * @return {!boolean} + * true if the key event should be allowed through to the browser, + * false otherwise. + */ + this.onkeydown = null; + + /** + * Fired whenever the user releases a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {!number} keysym + * The keysym of the key being released. + */ + this.onkeyup = null; + + /** + * Set of known platform-specific or browser-specific quirks which must be + * accounted for to properly interpret key events, even if the only way to + * reliably detect that quirk is to platform/browser-sniff. + * + * @private + * @type {!Object.} + */ + var quirks = { + + /** + * Whether keyup events are universally unreliable. + * + * @type {!boolean} + */ + keyupUnreliable: false, + + /** + * Whether the Alt key is actually a modifier for typable keys and is + * thus never used for keyboard shortcuts. + * + * @type {!boolean} + */ + altIsTypableOnly: false, + + /** + * Whether we can rely on receiving a keyup event for the Caps Lock + * key. + * + * @type {!boolean} + */ + capsLockKeyupUnreliable: false + + }; + + // Set quirk flags depending on platform/browser, if such information is + // available + if (navigator && navigator.platform) { + + // All keyup events are unreliable on iOS (sadly) + if (navigator.platform.match(/ipad|iphone|ipod/i)) + quirks.keyupUnreliable = true; + + // The Alt key on Mac is never used for keyboard shortcuts, and the + // Caps Lock key never dispatches keyup events + else if (navigator.platform.match(/^mac/i)) { + quirks.altIsTypableOnly = true; + quirks.capsLockKeyupUnreliable = true; + } + + } + + /** + * A key event having a corresponding timestamp. This event is non-specific. + * Its subclasses should be used instead when recording specific key + * events. + * + * @private + * @constructor + * @param {KeyboardEvent} [orig] + * The relevant DOM keyboard event. + */ + var KeyEvent = function KeyEvent(orig) { + + /** + * Reference to this key event. + * + * @private + * @type {!KeyEvent} + */ + var key_event = this; + + /** + * The JavaScript key code of the key pressed. For most events (keydown + * and keyup), this is a scancode-like value related to the position of + * the key on the US English "Qwerty" keyboard. For keypress events, + * this is the Unicode codepoint of the character that would be typed + * by the key pressed. + * + * @type {!number} + */ + this.keyCode = orig ? (orig.which || orig.keyCode) : 0; + + /** + * The legacy DOM3 "keyIdentifier" of the key pressed, as defined at: + * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent + * + * @type {!string} + */ + this.keyIdentifier = orig && orig.keyIdentifier; + + /** + * The standard name of the key pressed, as defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + * + * @type {!string} + */ + this.key = orig && orig.key; + + /** + * The location on the keyboard corresponding to the key pressed, as + * defined at: + * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + * + * @type {!number} + */ + this.location = orig ? getEventLocation(orig) : 0; + + /** + * The state of all local keyboard modifiers at the time this event was + * received. + * + * @type {!Guacamole.Keyboard.ModifierState} + */ + this.modifiers = orig ? Guacamole.Keyboard.ModifierState.fromKeyboardEvent(orig) : new Guacamole.Keyboard.ModifierState(); + + /** + * An arbitrary timestamp in milliseconds, indicating this event's + * position in time relative to other events. + * + * @type {!number} + */ + this.timestamp = new Date().getTime(); + + /** + * Whether the default action of this key event should be prevented. + * + * @type {!boolean} + */ + this.defaultPrevented = false; + + /** + * The keysym of the key associated with this key event, as determined + * by a best-effort guess using available event properties and keyboard + * state. + * + * @type {number} + */ + this.keysym = null; + + /** + * Whether the keysym value of this key event is known to be reliable. + * If false, the keysym may still be valid, but it's only a best guess, + * and future key events may be a better source of information. + * + * @type {!boolean} + */ + this.reliable = false; + + /** + * Returns the number of milliseconds elapsed since this event was + * received. + * + * @return {!number} + * The number of milliseconds elapsed since this event was + * received. + */ + this.getAge = function() { + return new Date().getTime() - key_event.timestamp; + }; + + }; + + /** + * Information related to the pressing of a key, which need not be a key + * associated with a printable character. The presence or absence of any + * information within this object is browser-dependent. + * + * @private + * @constructor + * @augments Guacamole.Keyboard.KeyEvent + * @param {!KeyboardEvent} orig + * The relevant DOM "keydown" event. + */ + var KeydownEvent = function KeydownEvent(orig) { + + // We extend KeyEvent + KeyEvent.call(this, orig); + + // If key is known from keyCode or DOM3 alone, use that + this.keysym = keysym_from_key_identifier(this.key, this.location) + || keysym_from_keycode(this.keyCode, this.location); + + /** + * Whether the keyup following this keydown event is known to be + * reliable. If false, we cannot rely on the keyup event to occur. + * + * @type {!boolean} + */ + this.keyupReliable = !quirks.keyupUnreliable; + + // DOM3 and keyCode are reliable sources if the corresponding key is + // not a printable key + if (this.keysym && !isPrintable(this.keysym)) + this.reliable = true; + + // Use legacy keyIdentifier as a last resort, if it looks sane + if (!this.keysym && key_identifier_sane(this.keyCode, this.keyIdentifier)) + this.keysym = keysym_from_key_identifier(this.keyIdentifier, this.location, this.modifiers.shift); + + // If a key is pressed while meta is held down, the keyup will + // never be sent in Chrome (bug #108404) + if (this.modifiers.meta && this.keysym !== 0xFFE7 && this.keysym !== 0xFFE8) + this.keyupReliable = false; + + // We cannot rely on receiving keyup for Caps Lock on certain platforms + else if (this.keysym === 0xFFE5 && quirks.capsLockKeyupUnreliable) + this.keyupReliable = false; + + // Determine whether default action for Alt+combinations must be prevented + var prevent_alt = !this.modifiers.ctrl && !quirks.altIsTypableOnly; + + // If alt is typeable only, and this is actually an alt key event, treat as AltGr instead + if (quirks.altIsTypableOnly && (this.keysym === 0xFFE9 || this.keysym === 0xFFEA)) + this.keysym = 0xFE03; + + // Determine whether default action for Ctrl+combinations must be prevented + var prevent_ctrl = !this.modifiers.alt; + + // We must rely on the (potentially buggy) keyIdentifier if preventing + // the default action is important + if ((prevent_ctrl && this.modifiers.ctrl) + || (prevent_alt && this.modifiers.alt) + || this.modifiers.meta + || this.modifiers.hyper) + this.reliable = true; + + // Record most recently known keysym by associated key code + recentKeysym[this.keyCode] = this.keysym; + + }; + + KeydownEvent.prototype = new KeyEvent(); + + /** + * Information related to the pressing of a key, which MUST be + * associated with a printable character. The presence or absence of any + * information within this object is browser-dependent. + * + * @private + * @constructor + * @augments Guacamole.Keyboard.KeyEvent + * @param {!KeyboardEvent} orig + * The relevant DOM "keypress" event. + */ + var KeypressEvent = function KeypressEvent(orig) { + + // We extend KeyEvent + KeyEvent.call(this, orig); + + // Pull keysym from char code + this.keysym = keysym_from_charcode(this.keyCode); + + // Keypress is always reliable + this.reliable = true; + + }; + + KeypressEvent.prototype = new KeyEvent(); + + /** + * Information related to the releasing of a key, which need not be a key + * associated with a printable character. The presence or absence of any + * information within this object is browser-dependent. + * + * @private + * @constructor + * @augments Guacamole.Keyboard.KeyEvent + * @param {!KeyboardEvent} orig + * The relevant DOM "keyup" event. + */ + var KeyupEvent = function KeyupEvent(orig) { + + // We extend KeyEvent + KeyEvent.call(this, orig); + + // If key is known from keyCode or DOM3 alone, use that (keyCode is + // still more reliable for keyup when dead keys are in use) + this.keysym = keysym_from_keycode(this.keyCode, this.location) + || keysym_from_key_identifier(this.key, this.location); + + // Fall back to the most recently pressed keysym associated with the + // keyCode if the inferred key doesn't seem to actually be pressed + if (!guac_keyboard.pressed[this.keysym]) + this.keysym = recentKeysym[this.keyCode] || this.keysym; + + // Keyup is as reliable as it will ever be + this.reliable = true; + + }; + + KeyupEvent.prototype = new KeyEvent(); + + /** + * An array of recorded events, which can be instances of the private + * KeydownEvent, KeypressEvent, and KeyupEvent classes. + * + * @private + * @type {!KeyEvent[]} + */ + var eventLog = []; + + /** + * Map of known JavaScript keycodes which do not map to typable characters + * to their X11 keysym equivalents. + * + * @private + * @type {!Object.} + */ + var keycodeKeysyms = { + 8: [0xFF08], // backspace + 9: [0xFF09], // tab + 12: [0xFF0B, 0xFF0B, 0xFF0B, 0xFFB5], // clear / KP 5 + 13: [0xFF0D], // enter + 16: [0xFFE1, 0xFFE1, 0xFFE2], // shift + 17: [0xFFE3, 0xFFE3, 0xFFE4], // ctrl + 18: [0xFFE9, 0xFFE9, 0xFFEA], // alt + 19: [0xFF13], // pause/break + 20: [0xFFE5], // caps lock + 27: [0xFF1B], // escape + 32: [0x0020], // space + 33: [0xFF55, 0xFF55, 0xFF55, 0xFFB9], // page up / KP 9 + 34: [0xFF56, 0xFF56, 0xFF56, 0xFFB3], // page down / KP 3 + 35: [0xFF57, 0xFF57, 0xFF57, 0xFFB1], // end / KP 1 + 36: [0xFF50, 0xFF50, 0xFF50, 0xFFB7], // home / KP 7 + 37: [0xFF51, 0xFF51, 0xFF51, 0xFFB4], // left arrow / KP 4 + 38: [0xFF52, 0xFF52, 0xFF52, 0xFFB8], // up arrow / KP 8 + 39: [0xFF53, 0xFF53, 0xFF53, 0xFFB6], // right arrow / KP 6 + 40: [0xFF54, 0xFF54, 0xFF54, 0xFFB2], // down arrow / KP 2 + 45: [0xFF63, 0xFF63, 0xFF63, 0xFFB0], // insert / KP 0 + 46: [0xFFFF, 0xFFFF, 0xFFFF, 0xFFAE], // delete / KP decimal + 91: [0xFFE7], // left windows/command key (meta_l) + 92: [0xFFE8], // right window/command key (meta_r) + 93: [0xFF67], // menu key + 96: [0xFFB0], // KP 0 + 97: [0xFFB1], // KP 1 + 98: [0xFFB2], // KP 2 + 99: [0xFFB3], // KP 3 + 100: [0xFFB4], // KP 4 + 101: [0xFFB5], // KP 5 + 102: [0xFFB6], // KP 6 + 103: [0xFFB7], // KP 7 + 104: [0xFFB8], // KP 8 + 105: [0xFFB9], // KP 9 + 106: [0xFFAA], // KP multiply + 107: [0xFFAB], // KP add + 109: [0xFFAD], // KP subtract + 110: [0xFFAE], // KP decimal + 111: [0xFFAF], // KP divide + 112: [0xFFBE], // f1 + 113: [0xFFBF], // f2 + 114: [0xFFC0], // f3 + 115: [0xFFC1], // f4 + 116: [0xFFC2], // f5 + 117: [0xFFC3], // f6 + 118: [0xFFC4], // f7 + 119: [0xFFC5], // f8 + 120: [0xFFC6], // f9 + 121: [0xFFC7], // f10 + 122: [0xFFC8], // f11 + 123: [0xFFC9], // f12 + 144: [0xFF7F], // num lock + 145: [0xFF14], // scroll lock + 225: [0xFE03] // altgraph (iso_level3_shift) + }; + + /** + * Map of known JavaScript keyidentifiers which do not map to typable + * characters to their unshifted X11 keysym equivalents. + * + * @private + * @type {!Object.} + */ + var keyidentifier_keysym = { + "Again": [0xFF66], + "AllCandidates": [0xFF3D], + "Alphanumeric": [0xFF30], + "Alt": [0xFFE9, 0xFFE9, 0xFFEA], + "Attn": [0xFD0E], + "AltGraph": [0xFE03], + "ArrowDown": [0xFF54], + "ArrowLeft": [0xFF51], + "ArrowRight": [0xFF53], + "ArrowUp": [0xFF52], + "Backspace": [0xFF08], + "CapsLock": [0xFFE5], + "Cancel": [0xFF69], + "Clear": [0xFF0B], + "Convert": [0xFF21], + "Copy": [0xFD15], + "Crsel": [0xFD1C], + "CrSel": [0xFD1C], + "CodeInput": [0xFF37], + "Compose": [0xFF20], + "Control": [0xFFE3, 0xFFE3, 0xFFE4], + "ContextMenu": [0xFF67], + "Delete": [0xFFFF], + "Down": [0xFF54], + "End": [0xFF57], + "Enter": [0xFF0D], + "EraseEof": [0xFD06], + "Escape": [0xFF1B], + "Execute": [0xFF62], + "Exsel": [0xFD1D], + "ExSel": [0xFD1D], + "F1": [0xFFBE], + "F2": [0xFFBF], + "F3": [0xFFC0], + "F4": [0xFFC1], + "F5": [0xFFC2], + "F6": [0xFFC3], + "F7": [0xFFC4], + "F8": [0xFFC5], + "F9": [0xFFC6], + "F10": [0xFFC7], + "F11": [0xFFC8], + "F12": [0xFFC9], + "F13": [0xFFCA], + "F14": [0xFFCB], + "F15": [0xFFCC], + "F16": [0xFFCD], + "F17": [0xFFCE], + "F18": [0xFFCF], + "F19": [0xFFD0], + "F20": [0xFFD1], + "F21": [0xFFD2], + "F22": [0xFFD3], + "F23": [0xFFD4], + "F24": [0xFFD5], + "Find": [0xFF68], + "GroupFirst": [0xFE0C], + "GroupLast": [0xFE0E], + "GroupNext": [0xFE08], + "GroupPrevious": [0xFE0A], + "FullWidth": null, + "HalfWidth": null, + "HangulMode": [0xFF31], + "Hankaku": [0xFF29], + "HanjaMode": [0xFF34], + "Help": [0xFF6A], + "Hiragana": [0xFF25], + "HiraganaKatakana": [0xFF27], + "Home": [0xFF50], + "Hyper": [0xFFED, 0xFFED, 0xFFEE], + "Insert": [0xFF63], + "JapaneseHiragana": [0xFF25], + "JapaneseKatakana": [0xFF26], + "JapaneseRomaji": [0xFF24], + "JunjaMode": [0xFF38], + "KanaMode": [0xFF2D], + "KanjiMode": [0xFF21], + "Katakana": [0xFF26], + "Left": [0xFF51], + "Meta": [0xFFE7, 0xFFE7, 0xFFE8], + "ModeChange": [0xFF7E], + "NumLock": [0xFF7F], + "PageDown": [0xFF56], + "PageUp": [0xFF55], + "Pause": [0xFF13], + "Play": [0xFD16], + "PreviousCandidate": [0xFF3E], + "PrintScreen": [0xFF61], + "Redo": [0xFF66], + "Right": [0xFF53], + "RomanCharacters": null, + "Scroll": [0xFF14], + "Select": [0xFF60], + "Separator": [0xFFAC], + "Shift": [0xFFE1, 0xFFE1, 0xFFE2], + "SingleCandidate": [0xFF3C], + "Super": [0xFFEB, 0xFFEB, 0xFFEC], + "Tab": [0xFF09], + "UIKeyInputDownArrow": [0xFF54], + "UIKeyInputEscape": [0xFF1B], + "UIKeyInputLeftArrow": [0xFF51], + "UIKeyInputRightArrow": [0xFF53], + "UIKeyInputUpArrow": [0xFF52], + "Up": [0xFF52], + "Undo": [0xFF65], + "Win": [0xFFE7, 0xFFE7, 0xFFE8], + "Zenkaku": [0xFF28], + "ZenkakuHankaku": [0xFF2A] + }; + + /** + * All keysyms which should not repeat when held down. + * + * @private + * @type {!Object.} + */ + var no_repeat = { + 0xFE03: true, // ISO Level 3 Shift (AltGr) + 0xFFE1: true, // Left shift + 0xFFE2: true, // Right shift + 0xFFE3: true, // Left ctrl + 0xFFE4: true, // Right ctrl + 0xFFE5: true, // Caps Lock + 0xFFE7: true, // Left meta + 0xFFE8: true, // Right meta + 0xFFE9: true, // Left alt + 0xFFEA: true, // Right alt + 0xFFEB: true, // Left super/hyper + 0xFFEC: true // Right super/hyper + }; + + /** + * All modifiers and their states. + * + * @type {!Guacamole.Keyboard.ModifierState} + */ + this.modifiers = new Guacamole.Keyboard.ModifierState(); + + /** + * The state of every key, indexed by keysym. If a particular key is + * pressed, the value of pressed for that keysym will be true. If a key + * is not currently pressed, it will not be defined. + * + * @type {!Object.} + */ + this.pressed = {}; + + /** + * The state of every key, indexed by keysym, for strictly those keys whose + * status has been indirectly determined thorugh observation of other key + * events. If a particular key is implicitly pressed, the value of + * implicitlyPressed for that keysym will be true. If a key + * is not currently implicitly pressed (the key is not pressed OR the state + * of the key is explicitly known), it will not be defined. + * + * @private + * @type {!Object.} + */ + var implicitlyPressed = {}; + + /** + * The last result of calling the onkeydown handler for each key, indexed + * by keysym. This is used to prevent/allow default actions for key events, + * even when the onkeydown handler cannot be called again because the key + * is (theoretically) still pressed. + * + * @private + * @type {!Object.} + */ + var last_keydown_result = {}; + + /** + * The keysym most recently associated with a given keycode when keydown + * fired. This object maps keycodes to keysyms. + * + * @private + * @type {!Object.} + */ + var recentKeysym = {}; + + /** + * Timeout before key repeat starts. + * + * @private + * @type {number} + */ + var key_repeat_timeout = null; + + /** + * Interval which presses and releases the last key pressed while that + * key is still being held down. + * + * @private + * @type {number} + */ + var key_repeat_interval = null; + + /** + * Given an array of keysyms indexed by location, returns the keysym + * for the given location, or the keysym for the standard location if + * undefined. + * + * @private + * @param {number[]} keysyms + * An array of keysyms, where the index of the keysym in the array is + * the location value. + * + * @param {!number} location + * The location on the keyboard corresponding to the key pressed, as + * defined at: http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent + */ + var get_keysym = function get_keysym(keysyms, location) { + + if (!keysyms) + return null; + + return keysyms[location] || keysyms[0]; + }; + + /** + * Returns true if the given keysym corresponds to a printable character, + * false otherwise. + * + * @param {!number} keysym + * The keysym to check. + * + * @returns {!boolean} + * true if the given keysym corresponds to a printable character, + * false otherwise. + */ + var isPrintable = function isPrintable(keysym) { + + // Keysyms with Unicode equivalents are printable + return (keysym >= 0x00 && keysym <= 0xFF) + || (keysym & 0xFFFF0000) === 0x01000000; + + }; + + function keysym_from_key_identifier(identifier, location, shifted) { + + if (!identifier) + return null; + + var typedCharacter; + + // If identifier is U+xxxx, decode Unicode character + var unicodePrefixLocation = identifier.indexOf("U+"); + if (unicodePrefixLocation >= 0) { + var hex = identifier.substring(unicodePrefixLocation+2); + typedCharacter = String.fromCharCode(parseInt(hex, 16)); + } + + // If single character and not keypad, use that as typed character + else if (identifier.length === 1 && location !== 3) + typedCharacter = identifier; + + // Otherwise, look up corresponding keysym + else + return get_keysym(keyidentifier_keysym[identifier], location); + + // Alter case if necessary + if (shifted === true) + typedCharacter = typedCharacter.toUpperCase(); + else if (shifted === false) + typedCharacter = typedCharacter.toLowerCase(); + + // Get codepoint + var codepoint = typedCharacter.charCodeAt(0); + return keysym_from_charcode(codepoint); + + } + + function isControlCharacter(codepoint) { + return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F); + } + + function keysym_from_charcode(codepoint) { + + // Keysyms for control characters + if (isControlCharacter(codepoint)) return 0xFF00 | codepoint; + + // Keysyms for ASCII chars + if (codepoint >= 0x0000 && codepoint <= 0x00FF) + return codepoint; + + // Keysyms for Unicode + if (codepoint >= 0x0100 && codepoint <= 0x10FFFF) + return 0x01000000 | codepoint; + + return null; + + } + + function keysym_from_keycode(keyCode, location) { + return get_keysym(keycodeKeysyms[keyCode], location); + } + + /** + * Heuristically detects if the legacy keyIdentifier property of + * a keydown/keyup event looks incorrectly derived. Chrome, and + * presumably others, will produce the keyIdentifier by assuming + * the keyCode is the Unicode codepoint for that key. This is not + * correct in all cases. + * + * @private + * @param {!number} keyCode + * The keyCode from a browser keydown/keyup event. + * + * @param {string} keyIdentifier + * The legacy keyIdentifier from a browser keydown/keyup event. + * + * @returns {!boolean} + * true if the keyIdentifier looks sane, false if the keyIdentifier + * appears incorrectly derived or is missing entirely. + */ + var key_identifier_sane = function key_identifier_sane(keyCode, keyIdentifier) { + + // Missing identifier is not sane + if (!keyIdentifier) + return false; + + // Assume non-Unicode keyIdentifier values are sane + var unicodePrefixLocation = keyIdentifier.indexOf("U+"); + if (unicodePrefixLocation === -1) + return true; + + // If the Unicode codepoint isn't identical to the keyCode, + // then the identifier is likely correct + var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16); + if (keyCode !== codepoint) + return true; + + // The keyCodes for A-Z and 0-9 are actually identical to their + // Unicode codepoints + if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57)) + return true; + + // The keyIdentifier does NOT appear sane + return false; + + }; + + /** + * Marks a key as pressed, firing the keydown event if registered. Key + * repeat for the pressed key will start after a delay if that key is + * not a modifier. The return value of this function depends on the + * return value of the keydown event handler, if any. + * + * @param {number} keysym + * The keysym of the key to press. + * + * @return {boolean} + * true if event should NOT be canceled, false otherwise. + */ + this.press = function(keysym) { + + // Don't bother with pressing the key if the key is unknown + if (keysym === null) return; + + // Only press if released + if (!guac_keyboard.pressed[keysym]) { + + // Mark key as pressed + guac_keyboard.pressed[keysym] = true; + + // Send key event + if (guac_keyboard.onkeydown) { + var result = guac_keyboard.onkeydown(keysym); + last_keydown_result[keysym] = result; + + // Stop any current repeat + window.clearTimeout(key_repeat_timeout); + window.clearInterval(key_repeat_interval); + + // Repeat after a delay as long as pressed + if (!no_repeat[keysym]) + key_repeat_timeout = window.setTimeout(function() { + key_repeat_interval = window.setInterval(function() { + guac_keyboard.onkeyup(keysym); + guac_keyboard.onkeydown(keysym); + }, 50); + }, 500); + + return result; + } + } + + // Return the last keydown result by default, resort to false if unknown + return last_keydown_result[keysym] || false; + + }; + + /** + * Marks a key as released, firing the keyup event if registered. + * + * @param {number} keysym + * The keysym of the key to release. + */ + this.release = function(keysym) { + + // Only release if pressed + if (guac_keyboard.pressed[keysym]) { + + // Mark key as released + delete guac_keyboard.pressed[keysym]; + delete implicitlyPressed[keysym]; + + // Stop repeat + window.clearTimeout(key_repeat_timeout); + window.clearInterval(key_repeat_interval); + + // Send key event + if (keysym !== null && guac_keyboard.onkeyup) + guac_keyboard.onkeyup(keysym); + + } + + }; + + /** + * Presses and releases the keys necessary to type the given string of + * text. + * + * @param {!string} str + * The string to type. + */ + this.type = function type(str) { + + // Press/release the key corresponding to each character in the string + for (var i = 0; i < str.length; i++) { + + // Determine keysym of current character + var codepoint = str.codePointAt ? str.codePointAt(i) : str.charCodeAt(i); + var keysym = keysym_from_charcode(codepoint); + + // Press and release key for current character + guac_keyboard.press(keysym); + guac_keyboard.release(keysym); + + } + + }; + + /** + * Resets the state of this keyboard, releasing all keys, and firing keyup + * events for each released key. + */ + this.reset = function() { + + // Release all pressed keys + for (var keysym in guac_keyboard.pressed) + guac_keyboard.release(parseInt(keysym)); + + // Clear event log + eventLog = []; + + }; + + /** + * Resynchronizes the remote state of the given modifier with its + * corresponding local modifier state, as dictated by + * {@link KeyEvent#modifiers} within the given key event, by pressing or + * releasing keysyms. + * + * @private + * @param {!string} modifier + * The name of the {@link Guacamole.Keyboard.ModifierState} property + * being updated. + * + * @param {!number[]} keysyms + * The keysyms which represent the modifier being updated. + * + * @param {!KeyEvent} keyEvent + * Guacamole's current best interpretation of the key event being + * processed. + */ + var updateModifierState = function updateModifierState(modifier, + keysyms, keyEvent) { + + var localState = keyEvent.modifiers[modifier]; + var remoteState = guac_keyboard.modifiers[modifier]; + + var i; + + // Do not trust changes in modifier state for events directly involving + // that modifier: (1) the flag may erroneously be cleared despite + // another version of the same key still being held and (2) the change + // in flag may be due to the current event being processed, thus + // updating things here is at best redundant and at worst incorrect + if (keysyms.indexOf(keyEvent.keysym) !== -1) + return; + + // Release all related keys if modifier is implicitly released + if (remoteState && localState === false) { + for (i = 0; i < keysyms.length; i++) { + guac_keyboard.release(keysyms[i]); + } + } + + // Press if modifier is implicitly pressed + else if (!remoteState && localState) { + + // Verify that modifier flag isn't already pressed or already set + // due to another version of the same key being held down + for (i = 0; i < keysyms.length; i++) { + if (guac_keyboard.pressed[keysyms[i]]) + return; + } + + // Mark as implicitly pressed only if there is other information + // within the key event relating to a different key. Some + // platforms, such as iOS, will send essentially empty key events + // for modifier keys, using only the modifier flags to signal the + // identity of the key. + var keysym = keysyms[0]; + if (keyEvent.keysym) + implicitlyPressed[keysym] = true; + + guac_keyboard.press(keysym); + + } + + }; + + /** + * Given a keyboard event, updates the remote key state to match the local + * modifier state and remote based on the modifier flags within the event. + * This function pays no attention to keycodes. + * + * @private + * @param {!KeyEvent} keyEvent + * Guacamole's current best interpretation of the key event being + * processed. + */ + var syncModifierStates = function syncModifierStates(keyEvent) { + + // Resync state of alt + updateModifierState('alt', [ + 0xFFE9, // Left alt + 0xFFEA, // Right alt + 0xFE03 // AltGr + ], keyEvent); + + // Resync state of shift + updateModifierState('shift', [ + 0xFFE1, // Left shift + 0xFFE2 // Right shift + ], keyEvent); + + // Resync state of ctrl + updateModifierState('ctrl', [ + 0xFFE3, // Left ctrl + 0xFFE4 // Right ctrl + ], keyEvent); + + // Resync state of meta + updateModifierState('meta', [ + 0xFFE7, // Left meta + 0xFFE8 // Right meta + ], keyEvent); + + // Resync state of hyper + updateModifierState('hyper', [ + 0xFFEB, // Left super/hyper + 0xFFEC // Right super/hyper + ], keyEvent); + + // Update state + guac_keyboard.modifiers = keyEvent.modifiers; + + }; + + /** + * Returns whether all currently pressed keys were implicitly pressed. A + * key is implicitly pressed if its status was inferred indirectly from + * inspection of other key events. + * + * @private + * @returns {!boolean} + * true if all currently pressed keys were implicitly pressed, false + * otherwise. + */ + var isStateImplicit = function isStateImplicit() { + + for (var keysym in guac_keyboard.pressed) { + if (!implicitlyPressed[keysym]) + return false; + } + + return true; + + }; + + /** + * Reads through the event log, removing events from the head of the log + * when the corresponding true key presses are known (or as known as they + * can be). + * + * @private + * @return {boolean} + * Whether the default action of the latest event should be prevented. + */ + function interpret_events() { + + // Do not prevent default if no event could be interpreted + var handled_event = interpret_event(); + if (!handled_event) + return false; + + // Interpret as much as possible + var last_event; + do { + last_event = handled_event; + handled_event = interpret_event(); + } while (handled_event !== null); + + // Reset keyboard state if we cannot expect to receive any further + // keyup events + if (isStateImplicit()) + guac_keyboard.reset(); + + return last_event.defaultPrevented; + + } + + /** + * Releases Ctrl+Alt, if both are currently pressed and the given keysym + * looks like a key that may require AltGr. + * + * @private + * @param {!number} keysym + * The key that was just pressed. + */ + var release_simulated_altgr = function release_simulated_altgr(keysym) { + + // Both Ctrl+Alt must be pressed if simulated AltGr is in use + if (!guac_keyboard.modifiers.ctrl || !guac_keyboard.modifiers.alt) + return; + + // Assume [A-Z] never require AltGr + if (keysym >= 0x0041 && keysym <= 0x005A) + return; + + // Assume [a-z] never require AltGr + if (keysym >= 0x0061 && keysym <= 0x007A) + return; + + // Release Ctrl+Alt if the keysym is printable + if (keysym <= 0xFF || (keysym & 0xFF000000) === 0x01000000) { + guac_keyboard.release(0xFFE3); // Left ctrl + guac_keyboard.release(0xFFE4); // Right ctrl + guac_keyboard.release(0xFFE9); // Left alt + guac_keyboard.release(0xFFEA); // Right alt + } + + }; + + /** + * Reads through the event log, interpreting the first event, if possible, + * and returning that event. If no events can be interpreted, due to a + * total lack of events or the need for more events, null is returned. Any + * interpreted events are automatically removed from the log. + * + * @private + * @return {KeyEvent} + * The first key event in the log, if it can be interpreted, or null + * otherwise. + */ + var interpret_event = function interpret_event() { + + // Peek at first event in log + var first = eventLog[0]; + if (!first) + return null; + + // Keydown event + if (first instanceof KeydownEvent) { + + var keysym = null; + var accepted_events = []; + + // Defer handling of Meta until it is known to be functioning as a + // modifier (it may otherwise actually be an alternative method for + // pressing a single key, such as Meta+Left for Home on ChromeOS) + if (first.keysym === 0xFFE7 || first.keysym === 0xFFE8) { + + // Defer handling until further events exist to provide context + if (eventLog.length === 1) + return null; + + // Drop keydown if it turns out Meta does not actually apply + if (eventLog[1].keysym !== first.keysym) { + if (!eventLog[1].modifiers.meta) + return eventLog.shift(); + } + + // Drop duplicate keydown events while waiting to determine + // whether to acknowledge Meta (browser may repeat keydown + // while the key is held) + else if (eventLog[1] instanceof KeydownEvent) + return eventLog.shift(); + + } + + // If event itself is reliable, no need to wait for other events + if (first.reliable) { + keysym = first.keysym; + accepted_events = eventLog.splice(0, 1); + } + + // If keydown is immediately followed by a keypress, use the indicated character + else if (eventLog[1] instanceof KeypressEvent) { + keysym = eventLog[1].keysym; + accepted_events = eventLog.splice(0, 2); + } + + // If keydown is immediately followed by anything else, then no + // keypress can possibly occur to clarify this event, and we must + // handle it now + else if (eventLog[1]) { + keysym = first.keysym; + accepted_events = eventLog.splice(0, 1); + } + + // Fire a key press if valid events were found + if (accepted_events.length > 0) { + + syncModifierStates(first); + + if (keysym) { + + // Fire event + release_simulated_altgr(keysym); + var defaultPrevented = !guac_keyboard.press(keysym); + recentKeysym[first.keyCode] = keysym; + + // Release the key now if we cannot rely on the associated + // keyup event + if (!first.keyupReliable) + guac_keyboard.release(keysym); + + // Record whether default was prevented + for (var i=0; i} + */ + var compositeOperation = { + /* 0x0 NOT IMPLEMENTED */ + 0x1: "destination-in", + 0x2: "destination-out", + /* 0x3 NOT IMPLEMENTED */ + 0x4: "source-in", + /* 0x5 NOT IMPLEMENTED */ + 0x6: "source-atop", + /* 0x7 NOT IMPLEMENTED */ + 0x8: "source-out", + 0x9: "destination-atop", + 0xA: "xor", + 0xB: "destination-over", + 0xC: "copy", + /* 0xD NOT IMPLEMENTED */ + 0xE: "source-over", + 0xF: "lighter" + }; + + /** + * Resizes the canvas element backing this Layer. This function should only + * be used internally. + * + * @private + * @param {number} [newWidth=0] + * The new width to assign to this Layer. + * + * @param {number} [newHeight=0] + * The new height to assign to this Layer. + */ + var resize = function resize(newWidth, newHeight) { + + // Default size to zero + newWidth = newWidth || 0; + newHeight = newHeight || 0; + + // Calculate new dimensions of internal canvas + var canvasWidth = Math.ceil(newWidth / CANVAS_SIZE_FACTOR) * CANVAS_SIZE_FACTOR; + var canvasHeight = Math.ceil(newHeight / CANVAS_SIZE_FACTOR) * CANVAS_SIZE_FACTOR; + + // Resize only if canvas dimensions are actually changing + if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) { + + // Copy old data only if relevant and non-empty + var oldData = null; + if (!empty && canvas.width !== 0 && canvas.height !== 0) { + + // Create canvas and context for holding old data + oldData = document.createElement("canvas"); + oldData.width = Math.min(layer.width, newWidth); + oldData.height = Math.min(layer.height, newHeight); + + var oldDataContext = oldData.getContext("2d"); + + // Copy image data from current + oldDataContext.drawImage(canvas, + 0, 0, oldData.width, oldData.height, + 0, 0, oldData.width, oldData.height); + + } + + // Preserve composite operation + var oldCompositeOperation = context.globalCompositeOperation; + + // Resize canvas + canvas.width = canvasWidth; + canvas.height = canvasHeight; + + // Redraw old data, if any + if (oldData) + context.drawImage(oldData, + 0, 0, oldData.width, oldData.height, + 0, 0, oldData.width, oldData.height); + + // Restore composite operation + context.globalCompositeOperation = oldCompositeOperation; + + // Acknowledge reset of stack (happens on resize of canvas) + stackSize = 0; + context.save(); + + } + + // If the canvas size is not changing, manually force state reset + else + layer.reset(); + + // Assign new layer dimensions + layer.width = newWidth; + layer.height = newHeight; + + }; + + /** + * Given the X and Y coordinates of the upper-left corner of a rectangle + * and the rectangle's width and height, resize the backing canvas element + * as necessary to ensure that the rectangle fits within the canvas + * element's coordinate space. This function will only make the canvas + * larger. If the rectangle already fits within the canvas element's + * coordinate space, the canvas is left unchanged. + * + * @private + * @param {!number} x + * The X coordinate of the upper-left corner of the rectangle to fit. + * + * @param {!number} y + * The Y coordinate of the upper-left corner of the rectangle to fit. + * + * @param {!number} w + * The width of the rectangle to fit. + * + * @param {!number} h + * The height of the rectangle to fit. + */ + function fitRect(x, y, w, h) { + + // Calculate bounds + var opBoundX = w + x; + var opBoundY = h + y; + + // Determine max width + var resizeWidth; + if (opBoundX > layer.width) + resizeWidth = opBoundX; + else + resizeWidth = layer.width; + + // Determine max height + var resizeHeight; + if (opBoundY > layer.height) + resizeHeight = opBoundY; + else + resizeHeight = layer.height; + + // Resize if necessary + layer.resize(resizeWidth, resizeHeight); + + } + + /** + * Set to true if this Layer should resize itself to accommodate the + * dimensions of any drawing operation, and false (the default) otherwise. + * + * Note that setting this property takes effect immediately, and thus may + * take effect on operations that were started in the past but have not + * yet completed. If you wish the setting of this flag to only modify + * future operations, you will need to make the setting of this flag an + * operation with sync(). + * + * @example + * // Set autosize to true for all future operations + * layer.sync(function() { + * layer.autosize = true; + * }); + * + * @type {!boolean} + * @default false + */ + this.autosize = false; + + /** + * The current width of this layer. + * + * @type {!number} + */ + this.width = width; + + /** + * The current height of this layer. + * + * @type {!number} + */ + this.height = height; + + /** + * Returns the canvas element backing this Layer. Note that the dimensions + * of the canvas may not exactly match those of the Layer, as resizing a + * canvas while maintaining its state is an expensive operation. + * + * @returns {!HTMLCanvasElement} + * The canvas element backing this Layer. + */ + this.getCanvas = function getCanvas() { + return canvas; + }; + + /** + * Returns a new canvas element containing the same image as this Layer. + * Unlike getCanvas(), the canvas element returned is guaranteed to have + * the exact same dimensions as the Layer. + * + * @returns {!HTMLCanvasElement} + * A new canvas element containing a copy of the image content this + * Layer. + */ + this.toCanvas = function toCanvas() { + + // Create new canvas having same dimensions + var canvas = document.createElement('canvas'); + canvas.width = layer.width; + canvas.height = layer.height; + + // Copy image contents to new canvas + var context = canvas.getContext('2d'); + context.drawImage(layer.getCanvas(), 0, 0); + + return canvas; + + }; + + /** + * Changes the size of this Layer to the given width and height. Resizing + * is only attempted if the new size provided is actually different from + * the current size. + * + * @param {!number} newWidth + * The new width to assign to this Layer. + * + * @param {!number} newHeight + * The new height to assign to this Layer. + */ + this.resize = function(newWidth, newHeight) { + if (newWidth !== layer.width || newHeight !== layer.height) + resize(newWidth, newHeight); + }; + + /** + * Draws the specified image at the given coordinates. The image specified + * must already be loaded. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + * + * @param {!CanvasImageSource} image + * The image to draw. Note that this is not a URL. + */ + this.drawImage = function(x, y, image) { + if (layer.autosize) fitRect(x, y, image.width, image.height); + context.drawImage(image, x, y); + empty = false; + }; + + /** + * Transfer a rectangle of image data from one Layer to this Layer using the + * specified transfer function. + * + * @param {!Guacamole.Layer} srcLayer + * The Layer to copy image data from. + * + * @param {!number} srcx + * The X coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcy + * The Y coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcw + * The width of the rectangle within the source Layer's coordinate + * space to copy data from. + * + * @param {!number} srch + * The height of the rectangle within the source Layer's coordinate + * space to copy data from. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + * + * @param {!function} transferFunction + * The transfer function to use to transfer data from source to + * destination. + */ + this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) { + + var srcCanvas = srcLayer.getCanvas(); + + // If entire rectangle outside source canvas, stop + if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return; + + // Otherwise, clip rectangle to area + if (srcx + srcw > srcCanvas.width) + srcw = srcCanvas.width - srcx; + + if (srcy + srch > srcCanvas.height) + srch = srcCanvas.height - srcy; + + // Stop if nothing to draw. + if (srcw === 0 || srch === 0) return; + + if (layer.autosize) fitRect(x, y, srcw, srch); + + // Get image data from src and dst + var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch); + var dst = context.getImageData(x , y, srcw, srch); + + // Apply transfer for each pixel + for (var i=0; i= srcCanvas.width || srcy >= srcCanvas.height) return; + + // Otherwise, clip rectangle to area + if (srcx + srcw > srcCanvas.width) + srcw = srcCanvas.width - srcx; + + if (srcy + srch > srcCanvas.height) + srch = srcCanvas.height - srcy; + + // Stop if nothing to draw. + if (srcw === 0 || srch === 0) return; + + if (layer.autosize) fitRect(x, y, srcw, srch); + + // Get image data from src and dst + var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch); + context.putImageData(src, x, y); + empty = false; + + }; + + /** + * Copy a rectangle of image data from one Layer to this Layer. This + * operation will copy exactly the image data that will be drawn once all + * operations of the source Layer that were pending at the time this + * function was called are complete. This operation will not alter the + * size of the source Layer even if its autosize property is set to true. + * + * @param {!Guacamole.Layer} srcLayer + * The Layer to copy image data from. + * + * @param {!number} srcx + * The X coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcy + * The Y coordinate of the upper-left corner of the rectangle within + * the source Layer's coordinate space to copy data from. + * + * @param {!number} srcw + * The width of the rectangle within the source Layer's coordinate + * space to copy data from. + * + * @param {!number} srch + * The height of the rectangle within the source Layer's coordinate + * space to copy data from. + * + * @param {!number} x + * The destination X coordinate. + * + * @param {!number} y + * The destination Y coordinate. + */ + this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) { + + var srcCanvas = srcLayer.getCanvas(); + + // If entire rectangle outside source canvas, stop + if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return; + + // Otherwise, clip rectangle to area + if (srcx + srcw > srcCanvas.width) + srcw = srcCanvas.width - srcx; + + if (srcy + srch > srcCanvas.height) + srch = srcCanvas.height - srcy; + + // Stop if nothing to draw. + if (srcw === 0 || srch === 0) return; + + if (layer.autosize) fitRect(x, y, srcw, srch); + context.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch); + empty = false; + + }; + + /** + * Starts a new path at the specified point. + * + * @param {!number} x + * The X coordinate of the point to draw. + * + * @param {!number} y + * The Y coordinate of the point to draw. + */ + this.moveTo = function(x, y) { + + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, 0, 0); + context.moveTo(x, y); + + }; + + /** + * Add the specified line to the current path. + * + * @param {!number} x + * The X coordinate of the endpoint of the line to draw. + * + * @param {!number} y + * The Y coordinate of the endpoint of the line to draw. + */ + this.lineTo = function(x, y) { + + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, 0, 0); + context.lineTo(x, y); + + }; + + /** + * Add the specified arc to the current path. + * + * @param {!number} x + * The X coordinate of the center of the circle which will contain the + * arc. + * + * @param {!number} y + * The Y coordinate of the center of the circle which will contain the + * arc. + * + * @param {!number} radius + * The radius of the circle. + * + * @param {!number} startAngle + * The starting angle of the arc, in radians. + * + * @param {!number} endAngle + * The ending angle of the arc, in radians. + * + * @param {!boolean} negative + * Whether the arc should be drawn in order of decreasing angle. + */ + this.arc = function(x, y, radius, startAngle, endAngle, negative) { + + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, 0, 0); + context.arc(x, y, radius, startAngle, endAngle, negative); + + }; + + /** + * Starts a new path at the specified point. + * + * @param {!number} cp1x + * The X coordinate of the first control point. + * + * @param {!number} cp1y + * The Y coordinate of the first control point. + * + * @param {!number} cp2x + * The X coordinate of the second control point. + * + * @param {!number} cp2y + * The Y coordinate of the second control point. + * + * @param {!number} x + * The X coordinate of the endpoint of the curve. + * + * @param {!number} y + * The Y coordinate of the endpoint of the curve. + */ + this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) { + + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, 0, 0); + context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); + + }; + + /** + * Closes the current path by connecting the end point with the start + * point (if any) with a straight line. + */ + this.close = function() { + context.closePath(); + pathClosed = true; + }; + + /** + * Add the specified rectangle to the current path. + * + * @param {!number} x + * The X coordinate of the upper-left corner of the rectangle to draw. + * + * @param {!number} y + * The Y coordinate of the upper-left corner of the rectangle to draw. + * + * @param {!number} w + * The width of the rectangle to draw. + * + * @param {!number} h + * The height of the rectangle to draw. + */ + this.rect = function(x, y, w, h) { + + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, w, h); + context.rect(x, y, w, h); + + }; + + /** + * Clip all future drawing operations by the current path. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as fillColor()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + */ + this.clip = function() { + + // Set new clipping region + context.clip(); + + // Path now implicitly closed + pathClosed = true; + + }; + + /** + * Stroke the current path with the specified color. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {!string} cap + * The line cap style. Can be "round", "square", or "butt". + * + * @param {!string} join + * The line join style. Can be "round", "bevel", or "miter". + * + * @param {!number} thickness + * The line thickness in pixels. + * + * @param {!number} r + * The red component of the color to fill. + * + * @param {!number} g + * The green component of the color to fill. + * + * @param {!number} b + * The blue component of the color to fill. + * + * @param {!number} a + * The alpha component of the color to fill. + */ + this.strokeColor = function(cap, join, thickness, r, g, b, a) { + + // Stroke with color + context.lineCap = cap; + context.lineJoin = join; + context.lineWidth = thickness; + context.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")"; + context.stroke(); + empty = false; + + // Path now implicitly closed + pathClosed = true; + + }; + + /** + * Fills the current path with the specified color. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {!number} r + * The red component of the color to fill. + * + * @param {!number} g + * The green component of the color to fill. + * + * @param {!number} b + * The blue component of the color to fill. + * + * @param {!number} a + * The alpha component of the color to fill. + */ + this.fillColor = function(r, g, b, a) { + + // Fill with color + context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")"; + context.fill(); + empty = false; + + // Path now implicitly closed + pathClosed = true; + + }; + + /** + * Stroke the current path with the image within the specified layer. The + * image data will be tiled infinitely within the stroke. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {!string} cap + * The line cap style. Can be "round", "square", or "butt". + * + * @param {!string} join + * The line join style. Can be "round", "bevel", or "miter". + * + * @param {!number} thickness + * The line thickness in pixels. + * + * @param {!Guacamole.Layer} srcLayer + * The layer to use as a repeating pattern within the stroke. + */ + this.strokeLayer = function(cap, join, thickness, srcLayer) { + + // Stroke with image data + context.lineCap = cap; + context.lineJoin = join; + context.lineWidth = thickness; + context.strokeStyle = context.createPattern( + srcLayer.getCanvas(), + "repeat" + ); + context.stroke(); + empty = false; + + // Path now implicitly closed + pathClosed = true; + + }; + + /** + * Fills the current path with the image within the specified layer. The + * image data will be tiled infinitely within the stroke. The current path + * is implicitly closed. The current path can continue to be reused + * for other operations (such as clip()) but a new path will be started + * once a path drawing operation (path() or rect()) is used. + * + * @param {!Guacamole.Layer} srcLayer + * The layer to use as a repeating pattern within the fill. + */ + this.fillLayer = function(srcLayer) { + + // Fill with image data + context.fillStyle = context.createPattern( + srcLayer.getCanvas(), + "repeat" + ); + context.fill(); + empty = false; + + // Path now implicitly closed + pathClosed = true; + + }; + + /** + * Push current layer state onto stack. + */ + this.push = function() { + + // Save current state onto stack + context.save(); + stackSize++; + + }; + + /** + * Pop layer state off stack. + */ + this.pop = function() { + + // Restore current state from stack + if (stackSize > 0) { + context.restore(); + stackSize--; + } + + }; + + /** + * Reset the layer, clearing the stack, the current path, and any transform + * matrix. + */ + this.reset = function() { + + // Clear stack + while (stackSize > 0) { + context.restore(); + stackSize--; + } + + // Restore to initial state + context.restore(); + context.save(); + + // Clear path + context.beginPath(); + pathClosed = false; + + }; + + /** + * Sets the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {!number} a + * The first value in the affine transform's matrix. + * + * @param {!number} b + * The second value in the affine transform's matrix. + * + * @param {!number} c + * The third value in the affine transform's matrix. + * + * @param {!number} d + * The fourth value in the affine transform's matrix. + * + * @param {!number} e + * The fifth value in the affine transform's matrix. + * + * @param {!number} f + * The sixth value in the affine transform's matrix. + */ + this.setTransform = function(a, b, c, d, e, f) { + context.setTransform( + a, b, c, + d, e, f + /*0, 0, 1*/ + ); + }; + + /** + * Applies the given affine transform (defined with six values from the + * transform's matrix). + * + * @param {!number} a + * The first value in the affine transform's matrix. + * + * @param {!number} b + * The second value in the affine transform's matrix. + * + * @param {!number} c + * The third value in the affine transform's matrix. + * + * @param {!number} d + * The fourth value in the affine transform's matrix. + * + * @param {!number} e + * The fifth value in the affine transform's matrix. + * + * @param {!number} f + * The sixth value in the affine transform's matrix. + */ + this.transform = function(a, b, c, d, e, f) { + context.transform( + a, b, c, + d, e, f + /*0, 0, 1*/ + ); + }; + + /** + * Sets the channel mask for future operations on this Layer. + * + * The channel mask is a Guacamole-specific compositing operation identifier + * with a single bit representing each of four channels (in order): source + * image where destination transparent, source where destination opaque, + * destination where source transparent, and destination where source + * opaque. + * + * @param {!number} mask + * The channel mask for future operations on this Layer. + */ + this.setChannelMask = function(mask) { + context.globalCompositeOperation = compositeOperation[mask]; + }; + + /** + * Sets the miter limit for stroke operations using the miter join. This + * limit is the maximum ratio of the size of the miter join to the stroke + * width. If this ratio is exceeded, the miter will not be drawn for that + * joint of the path. + * + * @param {!number} limit + * The miter limit for stroke operations using the miter join. + */ + this.setMiterLimit = function(limit) { + context.miterLimit = limit; + }; + + // Initialize canvas dimensions + resize(width, height); + + // Explicitly render canvas below other elements in the layer (such as + // child layers). Chrome and others may fail to render layers properly + // without this. + canvas.style.zIndex = -1; + +}; + +/** + * Channel mask for the composite operation "rout". + * + * @type {!number} + */ +Guacamole.Layer.ROUT = 0x2; + +/** + * Channel mask for the composite operation "atop". + * + * @type {!number} + */ +Guacamole.Layer.ATOP = 0x6; + +/** + * Channel mask for the composite operation "xor". + * + * @type {!number} + */ +Guacamole.Layer.XOR = 0xA; + +/** + * Channel mask for the composite operation "rover". + * + * @type {!number} + */ +Guacamole.Layer.ROVER = 0xB; + +/** + * Channel mask for the composite operation "over". + * + * @type {!number} + */ +Guacamole.Layer.OVER = 0xE; + +/** + * Channel mask for the composite operation "plus". + * + * @type {!number} + */ +Guacamole.Layer.PLUS = 0xF; + +/** + * Channel mask for the composite operation "rin". + * Beware that WebKit-based browsers may leave the contents of the destination + * layer where the source layer is transparent, despite the definition of this + * operation. + * + * @type {!number} + */ +Guacamole.Layer.RIN = 0x1; + +/** + * Channel mask for the composite operation "in". + * Beware that WebKit-based browsers may leave the contents of the destination + * layer where the source layer is transparent, despite the definition of this + * operation. + * + * @type {!number} + */ +Guacamole.Layer.IN = 0x4; + +/** + * Channel mask for the composite operation "out". + * Beware that WebKit-based browsers may leave the contents of the destination + * layer where the source layer is transparent, despite the definition of this + * operation. + * + * @type {!number} + */ +Guacamole.Layer.OUT = 0x8; + +/** + * Channel mask for the composite operation "ratop". + * Beware that WebKit-based browsers may leave the contents of the destination + * layer where the source layer is transparent, despite the definition of this + * operation. + * + * @type {!number} + */ +Guacamole.Layer.RATOP = 0x9; + +/** + * Channel mask for the composite operation "src". + * Beware that WebKit-based browsers may leave the contents of the destination + * layer where the source layer is transparent, despite the definition of this + * operation. + * + * @type {!number} + */ +Guacamole.Layer.SRC = 0xC; + +/** + * Represents a single pixel of image data. All components have a minimum value + * of 0 and a maximum value of 255. + * + * @constructor + * + * @param {!number} r + * The red component of this pixel. + * + * @param {!number} g + * The green component of this pixel. + * + * @param {!number} b + * The blue component of this pixel. + * + * @param {!number} a + * The alpha component of this pixel. + */ +Guacamole.Layer.Pixel = function(r, g, b, a) { + + /** + * The red component of this pixel, where 0 is the minimum value, + * and 255 is the maximum. + * + * @type {!number} + */ + this.red = r; + + /** + * The green component of this pixel, where 0 is the minimum value, + * and 255 is the maximum. + * + * @type {!number} + */ + this.green = g; + + /** + * The blue component of this pixel, where 0 is the minimum value, + * and 255 is the maximum. + * + * @type {!number} + */ + this.blue = b; + + /** + * The alpha component of this pixel, where 0 is the minimum value, + * and 255 is the maximum. + * + * @type {!number} + */ + this.alpha = a; + +}; diff --git a/src/Mouse.js b/src/Mouse.js new file mode 100644 index 0000000..2f8bc28 --- /dev/null +++ b/src/Mouse.js @@ -0,0 +1,1265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Provides cross-browser mouse events for a given element. The events of + * the given element are automatically populated with handlers that translate + * mouse events into a non-browser-specific event provided by the + * Guacamole.Mouse instance. + * + * @example + * var mouse = new Guacamole.Mouse(client.getDisplay().getElement()); + * + * // Forward all mouse interaction over Guacamole connection + * mouse.onEach(['mousedown', 'mousemove', 'mouseup'], function sendMouseEvent(e) { + * client.sendMouseState(e.state, true); + * }); + * + * @example + * // Hide software cursor when mouse leaves display + * mouse.on('mouseout', function hideCursor() { + * client.getDisplay().showCursor(false); + * }); + * + * @constructor + * @augments Guacamole.Mouse.Event.Target + * @param {!Element} element + * The Element to use to provide mouse events. + */ +Guacamole.Mouse = function Mouse(element) { + + Guacamole.Mouse.Event.Target.call(this); + + /** + * Reference to this Guacamole.Mouse. + * + * @private + * @type {!Guacamole.Mouse} + */ + var guac_mouse = this; + + /** + * The number of mousemove events to require before re-enabling mouse + * event handling after receiving a touch event. + * + * @type {!number} + */ + this.touchMouseThreshold = 3; + + /** + * The minimum amount of pixels scrolled required for a single scroll button + * click. + * + * @type {!number} + */ + this.scrollThreshold = 53; + + /** + * The number of pixels to scroll per line. + * + * @type {!number} + */ + this.PIXELS_PER_LINE = 18; + + /** + * The number of pixels to scroll per page. + * + * @type {!number} + */ + this.PIXELS_PER_PAGE = this.PIXELS_PER_LINE * 16; + + /** + * Array of {@link Guacamole.Mouse.State} button names corresponding to the + * mouse button indices used by DOM mouse events. + * + * @private + * @type {!string[]} + */ + var MOUSE_BUTTONS = [ + Guacamole.Mouse.State.Buttons.LEFT, + Guacamole.Mouse.State.Buttons.MIDDLE, + Guacamole.Mouse.State.Buttons.RIGHT + ]; + + /** + * Counter of mouse events to ignore. This decremented by mousemove, and + * while non-zero, mouse events will have no effect. + * + * @private + * @type {!number} + */ + var ignore_mouse = 0; + + /** + * Cumulative scroll delta amount. This value is accumulated through scroll + * events and results in scroll button clicks if it exceeds a certain + * threshold. + * + * @private + * @type {!number} + */ + var scroll_delta = 0; + + // Block context menu so right-click gets sent properly + element.addEventListener("contextmenu", function(e) { + Guacamole.Event.DOMEvent.cancelEvent(e); + }, false); + + element.addEventListener("mousemove", function(e) { + + // If ignoring events, decrement counter + if (ignore_mouse) { + Guacamole.Event.DOMEvent.cancelEvent(e); + ignore_mouse--; + return; + } + + guac_mouse.move(Guacamole.Position.fromClientPosition(element, e.clientX, e.clientY), e); + + }, false); + + element.addEventListener("mousedown", function(e) { + + // Do not handle if ignoring events + if (ignore_mouse) { + Guacamole.Event.DOMEvent.cancelEvent(e); + return; + } + + var button = MOUSE_BUTTONS[e.button]; + if (button) + guac_mouse.press(button, e); + + }, false); + + element.addEventListener("mouseup", function(e) { + + // Do not handle if ignoring events + if (ignore_mouse) { + Guacamole.Event.DOMEvent.cancelEvent(e); + return; + } + + var button = MOUSE_BUTTONS[e.button]; + if (button) + guac_mouse.release(button, e); + + }, false); + + element.addEventListener("mouseout", function(e) { + + // Get parent of the element the mouse pointer is leaving + if (!e) e = window.event; + + // Check that mouseout is due to actually LEAVING the element + var target = e.relatedTarget || e.toElement; + while (target) { + if (target === element) + return; + target = target.parentNode; + } + + // Release all buttons and fire mouseout + guac_mouse.reset(e); + guac_mouse.out(e); + + }, false); + + // Override selection on mouse event element. + element.addEventListener("selectstart", function(e) { + Guacamole.Event.DOMEvent.cancelEvent(e); + }, false); + + // Ignore all pending mouse events when touch events are the apparent source + function ignorePendingMouseEvents() { ignore_mouse = guac_mouse.touchMouseThreshold; } + + element.addEventListener("touchmove", ignorePendingMouseEvents, false); + element.addEventListener("touchstart", ignorePendingMouseEvents, false); + element.addEventListener("touchend", ignorePendingMouseEvents, false); + + // Scroll wheel support + function mousewheel_handler(e) { + + // Determine approximate scroll amount (in pixels) + var delta = e.deltaY || -e.wheelDeltaY || -e.wheelDelta; + + // If successfully retrieved scroll amount, convert to pixels if not + // already in pixels + if (delta) { + + // Convert to pixels if delta was lines + if (e.deltaMode === 1) + delta = e.deltaY * guac_mouse.PIXELS_PER_LINE; + + // Convert to pixels if delta was pages + else if (e.deltaMode === 2) + delta = e.deltaY * guac_mouse.PIXELS_PER_PAGE; + + } + + // Otherwise, assume legacy mousewheel event and line scrolling + else + delta = e.detail * guac_mouse.PIXELS_PER_LINE; + + // Update overall delta + scroll_delta += delta; + + // Up + if (scroll_delta <= -guac_mouse.scrollThreshold) { + + // Repeatedly click the up button until insufficient delta remains + do { + guac_mouse.click(Guacamole.Mouse.State.Buttons.UP); + scroll_delta += guac_mouse.scrollThreshold; + } while (scroll_delta <= -guac_mouse.scrollThreshold); + + // Reset delta + scroll_delta = 0; + + } + + // Down + if (scroll_delta >= guac_mouse.scrollThreshold) { + + // Repeatedly click the down button until insufficient delta remains + do { + guac_mouse.click(Guacamole.Mouse.State.Buttons.DOWN); + scroll_delta -= guac_mouse.scrollThreshold; + } while (scroll_delta >= guac_mouse.scrollThreshold); + + // Reset delta + scroll_delta = 0; + + } + + // All scroll/wheel events must currently be cancelled regardless of + // whether the dispatched event is cancelled, as there is no Guacamole + // scroll event and thus no way to cancel scroll events that are + // smaller than required to produce an up/down click + Guacamole.Event.DOMEvent.cancelEvent(e); + + } + + element.addEventListener('DOMMouseScroll', mousewheel_handler, false); + element.addEventListener('mousewheel', mousewheel_handler, false); + element.addEventListener('wheel', mousewheel_handler, false); + + /** + * Whether the browser supports CSS3 cursor styling, including hotspot + * coordinates. + * + * @private + * @type {!boolean} + */ + var CSS3_CURSOR_SUPPORTED = (function() { + + var div = document.createElement("div"); + + // If no cursor property at all, then no support + if (!("cursor" in div.style)) + return false; + + try { + // Apply simple 1x1 PNG + div.style.cursor = "url(data:image/png;base64," + + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB" + + "AQMAAAAl21bKAAAAA1BMVEX///+nxBvI" + + "AAAACklEQVQI12NgAAAAAgAB4iG8MwAA" + + "AABJRU5ErkJggg==) 0 0, auto"; + } + catch (e) { + return false; + } + + // Verify cursor property is set to URL with hotspot + return /\burl\([^()]*\)\s+0\s+0\b/.test(div.style.cursor || ""); + + })(); + + /** + * Changes the local mouse cursor to the given canvas, having the given + * hotspot coordinates. This affects styling of the element backing this + * Guacamole.Mouse only, and may fail depending on browser support for + * setting the mouse cursor. + * + * If setting the local cursor is desired, it is up to the implementation + * to do something else, such as use the software cursor built into + * Guacamole.Display, if the local cursor cannot be set. + * + * @param {!HTMLCanvasElement} canvas + * The cursor image. + * + * @param {!number} x + * The X-coordinate of the cursor hotspot. + * + * @param {!number} y + * The Y-coordinate of the cursor hotspot. + * + * @return {!boolean} + * true if the cursor was successfully set, false if the cursor could + * not be set for any reason. + */ + this.setCursor = function(canvas, x, y) { + + // Attempt to set via CSS3 cursor styling + if (CSS3_CURSOR_SUPPORTED) { + var dataURL = canvas.toDataURL('image/png'); + element.style.cursor = "url(" + dataURL + ") " + x + " " + y + ", auto"; + return true; + } + + // Otherwise, setting cursor failed + return false; + + }; + +}; + +/** + * The current state of a mouse, including position and buttons. + * + * @constructor + * @augments Guacamole.Position + * @param {Guacamole.Mouse.State|object} [template={}] + * The object whose properties should be copied within the new + * Guacamole.Mouse.State. + */ +Guacamole.Mouse.State = function State(template) { + + /** + * Returns the template object that would be provided to the + * Guacamole.Mouse.State constructor to produce a new Guacamole.Mouse.State + * object with the properties specified. The order and type of arguments + * used by this function are identical to those accepted by the + * Guacamole.Mouse.State constructor of Apache Guacamole 1.3.0 and older. + * + * @private + * @param {!number} x + * The X position of the mouse pointer in pixels. + * + * @param {!number} y + * The Y position of the mouse pointer in pixels. + * + * @param {!boolean} left + * Whether the left mouse button is pressed. + * + * @param {!boolean} middle + * Whether the middle mouse button is pressed. + * + * @param {!boolean} right + * Whether the right mouse button is pressed. + * + * @param {!boolean} up + * Whether the up mouse button is pressed (the fourth button, usually + * part of a scroll wheel). + * + * @param {!boolean} down + * Whether the down mouse button is pressed (the fifth button, usually + * part of a scroll wheel). + * + * @return {!object} + * The equivalent template object that would be passed to the new + * Guacamole.Mouse.State constructor. + */ + var legacyConstructor = function legacyConstructor(x, y, left, middle, right, up, down) { + return { + x : x, + y : y, + left : left, + middle : middle, + right : right, + up : up, + down : down + }; + }; + + // Accept old-style constructor, as well + if (arguments.length > 1) + template = legacyConstructor.apply(this, arguments); + else + template = template || {}; + + Guacamole.Position.call(this, template); + + /** + * Whether the left mouse button is currently pressed. + * + * @type {!boolean} + * @default false + */ + this.left = template.left || false; + + /** + * Whether the middle mouse button is currently pressed. + * + * @type {!boolean} + * @default false + */ + this.middle = template.middle || false; + + /** + * Whether the right mouse button is currently pressed. + * + * @type {!boolean} + * @default false + */ + this.right = template.right || false; + + /** + * Whether the up mouse button is currently pressed. This is the fourth + * mouse button, associated with upward scrolling of the mouse scroll + * wheel. + * + * @type {!boolean} + * @default false + */ + this.up = template.up || false; + + /** + * Whether the down mouse button is currently pressed. This is the fifth + * mouse button, associated with downward scrolling of the mouse scroll + * wheel. + * + * @type {!boolean} + * @default false + */ + this.down = template.down || false; + +}; + +/** + * All mouse buttons that may be represented by a + * {@link Guacamole.Mouse.State}. + * + * @readonly + * @enum + */ +Guacamole.Mouse.State.Buttons = { + + /** + * The name of the {@link Guacamole.Mouse.State} property representing the + * left mouse button. + * + * @constant + * @type {!string} + */ + LEFT : 'left', + + /** + * The name of the {@link Guacamole.Mouse.State} property representing the + * middle mouse button. + * + * @constant + * @type {!string} + */ + MIDDLE : 'middle', + + /** + * The name of the {@link Guacamole.Mouse.State} property representing the + * right mouse button. + * + * @constant + * @type {!string} + */ + RIGHT : 'right', + + /** + * The name of the {@link Guacamole.Mouse.State} property representing the + * up mouse button (the fourth mouse button, clicked when the mouse scroll + * wheel is scrolled up). + * + * @constant + * @type {!string} + */ + UP : 'up', + + /** + * The name of the {@link Guacamole.Mouse.State} property representing the + * down mouse button (the fifth mouse button, clicked when the mouse scroll + * wheel is scrolled up). + * + * @constant + * @type {!string} + */ + DOWN : 'down' + +}; + +/** + * Base event type for all mouse events. The mouse producing the event may be + * the user's local mouse (as with {@link Guacamole.Mouse}) or an emulated + * mouse (as with {@link Guacamole.Mouse.Touchpad}). + * + * @constructor + * @augments Guacamole.Event.DOMEvent + * @param {!string} type + * The type name of the event ("mousedown", "mouseup", etc.) + * + * @param {!Guacamole.Mouse.State} state + * The current mouse state. + * + * @param {Event|Event[]} [events=[]] + * The DOM events that are related to this event, if any. + */ +Guacamole.Mouse.Event = function MouseEvent(type, state, events) { + + Guacamole.Event.DOMEvent.call(this, type, events); + + /** + * The name of the event handler used by the Guacamole JavaScript API for + * this event prior to the migration to Guacamole.Event.Target. + * + * @private + * @constant + * @type {!string} + */ + var legacyHandlerName = 'on' + this.type; + + /** + * The current mouse state at the time this event was fired. + * + * @type {!Guacamole.Mouse.State} + */ + this.state = state; + + /** + * @inheritdoc + */ + this.invokeLegacyHandler = function invokeLegacyHandler(target) { + if (target[legacyHandlerName]) { + + this.preventDefault(); + this.stopPropagation(); + + target[legacyHandlerName](this.state); + + } + }; + +}; + +/** + * An object which can dispatch {@link Guacamole.Mouse.Event} objects + * representing mouse events. These mouse events may be produced from an actual + * mouse device (as with {@link Guacamole.Mouse}), from an emulated mouse + * device (as with {@link Guacamole.Mouse.Touchpad}, or may be programmatically + * generated (using functions like [dispatch()]{@link Guacamole.Mouse.Event.Target#dispatch}, + * [press()]{@link Guacamole.Mouse.Event.Target#press}, and + * [release()]{@link Guacamole.Mouse.Event.Target#release}). + * + * @constructor + * @augments Guacamole.Event.Target + */ +Guacamole.Mouse.Event.Target = function MouseEventTarget() { + + Guacamole.Event.Target.call(this); + + /** + * The current mouse state. The properties of this state are updated when + * mouse events fire. This state object is also passed in as a parameter to + * the handler of any mouse events. + * + * @type {!Guacamole.Mouse.State} + */ + this.currentState = new Guacamole.Mouse.State(); + + /** + * Fired whenever a mouse button is effectively pressed. Depending on the + * object dispatching the event, this can be due to a true mouse button + * press ({@link Guacamole.Mouse}), an emulated mouse button press from a + * touch gesture ({@link Guacamole.Mouse.Touchpad} and + * {@link Guacamole.Mouse.Touchscreen}), or may be programmatically + * generated through [dispatch()]{@link Guacamole.Mouse.Event.Target#dispatch}, + * [press()]{@link Guacamole.Mouse.Event.Target#press}, or + * [click()]{@link Guacamole.Mouse.Event.Target#click}. + * + * @event Guacamole.Mouse.Event.Target#mousedown + * @param {!Guacamole.Mouse.Event} event + * The mousedown event that was fired. + */ + + /** + * Fired whenever a mouse button is effectively released. Depending on the + * object dispatching the event, this can be due to a true mouse button + * release ({@link Guacamole.Mouse}), an emulated mouse button release from + * a touch gesture ({@link Guacamole.Mouse.Touchpad} and + * {@link Guacamole.Mouse.Touchscreen}), or may be programmatically + * generated through [dispatch()]{@link Guacamole.Mouse.Event.Target#dispatch}, + * [release()]{@link Guacamole.Mouse.Event.Target#release}, or + * [click()]{@link Guacamole.Mouse.Event.Target#click}. + * + * @event Guacamole.Mouse.Event.Target#mouseup + * @param {!Guacamole.Mouse.Event} event + * The mouseup event that was fired. + */ + + /** + * Fired whenever the mouse pointer is effectively moved. Depending on the + * object dispatching the event, this can be due to true mouse movement + * ({@link Guacamole.Mouse}), emulated mouse movement from + * a touch gesture ({@link Guacamole.Mouse.Touchpad} and + * {@link Guacamole.Mouse.Touchscreen}), or may be programmatically + * generated through [dispatch()]{@link Guacamole.Mouse.Event.Target#dispatch}, + * or [move()]{@link Guacamole.Mouse.Event.Target#move}. + * + * @event Guacamole.Mouse.Event.Target#mousemove + * @param {!Guacamole.Mouse.Event} event + * The mousemove event that was fired. + */ + + /** + * Fired whenever the mouse pointer leaves the boundaries of the element + * being monitored for interaction. This will only ever be automatically + * fired due to movement of an actual mouse device via + * {@link Guacamole.Mouse} unless programmatically generated through + * [dispatch()]{@link Guacamole.Mouse.Event.Target#dispatch}, + * or [out()]{@link Guacamole.Mouse.Event.Target#out}. + * + * @event Guacamole.Mouse.Event.Target#mouseout + * @param {!Guacamole.Mouse.Event} event + * The mouseout event that was fired. + */ + + /** + * Presses the given mouse button, if it isn't already pressed. Valid + * button names are defined by {@link Guacamole.Mouse.State.Buttons} and + * correspond to the button-related properties of + * {@link Guacamole.Mouse.State}. + * + * @fires Guacamole.Mouse.Event.Target#mousedown + * + * @param {!string} button + * The name of the mouse button to press, as defined by + * {@link Guacamole.Mouse.State.Buttons}. + * + * @param {Event|Event[]} [events=[]] + * The DOM events that are related to the mouse button press, if any. + */ + this.press = function press(button, events) { + if (!this.currentState[button]) { + this.currentState[button] = true; + this.dispatch(new Guacamole.Mouse.Event('mousedown', this.currentState, events)); + } + }; + + /** + * Releases the given mouse button, if it isn't already released. Valid + * button names are defined by {@link Guacamole.Mouse.State.Buttons} and + * correspond to the button-related properties of + * {@link Guacamole.Mouse.State}. + * + * @fires Guacamole.Mouse.Event.Target#mouseup + * + * @param {!string} button + * The name of the mouse button to release, as defined by + * {@link Guacamole.Mouse.State.Buttons}. + * + * @param {Event|Event[]} [events=[]] + * The DOM events related to the mouse button release, if any. + */ + this.release = function release(button, events) { + if (this.currentState[button]) { + this.currentState[button] = false; + this.dispatch(new Guacamole.Mouse.Event('mouseup', this.currentState, events)); + } + }; + + /** + * Clicks (presses and releases) the given mouse button. Valid button + * names are defined by {@link Guacamole.Mouse.State.Buttons} and + * correspond to the button-related properties of + * {@link Guacamole.Mouse.State}. + * + * @fires Guacamole.Mouse.Event.Target#mousedown + * @fires Guacamole.Mouse.Event.Target#mouseup + * + * @param {!string} button + * The name of the mouse button to click, as defined by + * {@link Guacamole.Mouse.State.Buttons}. + * + * @param {Event|Event[]} [events=[]] + * The DOM events related to the click, if any. + */ + this.click = function click(button, events) { + this.press(button, events); + this.release(button, events); + }; + + /** + * Moves the mouse to the given coordinates. + * + * @fires Guacamole.Mouse.Event.Target#mousemove + * + * @param {!(Guacamole.Position|object)} position + * The new coordinates of the mouse pointer. This object may be a + * {@link Guacamole.Position} or any object with "x" and "y" + * properties. + * + * @param {Event|Event[]} [events=[]] + * The DOM events related to the mouse movement, if any. + */ + this.move = function move(position, events) { + + if (this.currentState.x !== position.x || this.currentState.y !== position.y) { + this.currentState.x = position.x; + this.currentState.y = position.y; + this.dispatch(new Guacamole.Mouse.Event('mousemove', this.currentState, events)); + } + + }; + + /** + * Notifies event listeners that the mouse pointer has left the boundaries + * of the area being monitored for mouse events. + * + * @fires Guacamole.Mouse.Event.Target#mouseout + * + * @param {Event|Event[]} [events=[]] + * The DOM events related to the mouse leaving the boundaries of the + * monitored object, if any. + */ + this.out = function out(events) { + this.dispatch(new Guacamole.Mouse.Event('mouseout', this.currentState, events)); + }; + + /** + * Releases all mouse buttons that are currently pressed. If all mouse + * buttons have already been released, this function has no effect. + * + * @fires Guacamole.Mouse.Event.Target#mouseup + * + * @param {Event|Event[]} [events=[]] + * The DOM event related to all mouse buttons being released, if any. + */ + this.reset = function reset(events) { + for (var button in Guacamole.Mouse.State.Buttons) { + this.release(Guacamole.Mouse.State.Buttons[button], events); + } + }; + +}; + +/** + * Provides cross-browser relative touch event translation for a given element. + * + * Touch events are translated into mouse events as if the touches occurred + * on a touchpad (drag to push the mouse pointer, tap to click). + * + * @example + * var touchpad = new Guacamole.Mouse.Touchpad(client.getDisplay().getElement()); + * + * // Emulate a mouse using touchpad-style gestures, forwarding all mouse + * // interaction over Guacamole connection + * touchpad.onEach(['mousedown', 'mousemove', 'mouseup'], function sendMouseEvent(e) { + * + * // Re-show software mouse cursor if possibly hidden by a prior call to + * // showCursor(), such as a "mouseout" event handler that hides the + * // cursor + * client.getDisplay().showCursor(true); + * + * client.sendMouseState(e.state, true); + * + * }); + * + * @constructor + * @augments Guacamole.Mouse.Event.Target + * @param {!Element} element + * The Element to use to provide touch events. + */ +Guacamole.Mouse.Touchpad = function Touchpad(element) { + + Guacamole.Mouse.Event.Target.call(this); + + /** + * The "mouseout" event will never be fired by Guacamole.Mouse.Touchpad. + * + * @ignore + * @event Guacamole.Mouse.Touchpad#mouseout + */ + + /** + * Reference to this Guacamole.Mouse.Touchpad. + * + * @private + * @type {!Guacamole.Mouse.Touchpad} + */ + var guac_touchpad = this; + + /** + * The distance a two-finger touch must move per scrollwheel event, in + * pixels. + * + * @type {!number} + */ + this.scrollThreshold = 20 * (window.devicePixelRatio || 1); + + /** + * The maximum number of milliseconds to wait for a touch to end for the + * gesture to be considered a click. + * + * @type {!number} + */ + this.clickTimingThreshold = 250; + + /** + * The maximum number of pixels to allow a touch to move for the gesture to + * be considered a click. + * + * @type {!number} + */ + this.clickMoveThreshold = 10 * (window.devicePixelRatio || 1); + + /** + * The current mouse state. The properties of this state are updated when + * mouse events fire. This state object is also passed in as a parameter to + * the handler of any mouse events. + * + * @type {!Guacamole.Mouse.State} + */ + this.currentState = new Guacamole.Mouse.State(); + + var touch_count = 0; + var last_touch_x = 0; + var last_touch_y = 0; + var last_touch_time = 0; + var pixels_moved = 0; + + var touch_buttons = { + 1: "left", + 2: "right", + 3: "middle" + }; + + var gesture_in_progress = false; + var click_release_timeout = null; + + element.addEventListener("touchend", function(e) { + + e.preventDefault(); + + // If we're handling a gesture AND this is the last touch + if (gesture_in_progress && e.touches.length === 0) { + + var time = new Date().getTime(); + + // Get corresponding mouse button + var button = touch_buttons[touch_count]; + + // If mouse already down, release anad clear timeout + if (guac_touchpad.currentState[button]) { + + // Fire button up event + guac_touchpad.release(button, e); + + // Clear timeout, if set + if (click_release_timeout) { + window.clearTimeout(click_release_timeout); + click_release_timeout = null; + } + + } + + // If single tap detected (based on time and distance) + if (time - last_touch_time <= guac_touchpad.clickTimingThreshold + && pixels_moved < guac_touchpad.clickMoveThreshold) { + + // Fire button down event + guac_touchpad.press(button, e); + + // Delay mouse up - mouse up should be canceled if + // touchstart within timeout. + click_release_timeout = window.setTimeout(function() { + + // Fire button up event + guac_touchpad.release(button, e); + + // Gesture now over + gesture_in_progress = false; + + }, guac_touchpad.clickTimingThreshold); + + } + + // If we're not waiting to see if this is a click, stop gesture + if (!click_release_timeout) + gesture_in_progress = false; + + } + + }, false); + + element.addEventListener("touchstart", function(e) { + + e.preventDefault(); + + // Track number of touches, but no more than three + touch_count = Math.min(e.touches.length, 3); + + // Clear timeout, if set + if (click_release_timeout) { + window.clearTimeout(click_release_timeout); + click_release_timeout = null; + } + + // Record initial touch location and time for touch movement + // and tap gestures + if (!gesture_in_progress) { + + // Stop mouse events while touching + gesture_in_progress = true; + + // Record touch location and time + var starting_touch = e.touches[0]; + last_touch_x = starting_touch.clientX; + last_touch_y = starting_touch.clientY; + last_touch_time = new Date().getTime(); + pixels_moved = 0; + + } + + }, false); + + element.addEventListener("touchmove", function(e) { + + e.preventDefault(); + + // Get change in touch location + var touch = e.touches[0]; + var delta_x = touch.clientX - last_touch_x; + var delta_y = touch.clientY - last_touch_y; + + // Track pixels moved + pixels_moved += Math.abs(delta_x) + Math.abs(delta_y); + + // If only one touch involved, this is mouse move + if (touch_count === 1) { + + // Calculate average velocity in Manhatten pixels per millisecond + var velocity = pixels_moved / (new Date().getTime() - last_touch_time); + + // Scale mouse movement relative to velocity + var scale = 1 + velocity; + + // Update mouse location + var position = new Guacamole.Position(guac_touchpad.currentState); + position.x += delta_x*scale; + position.y += delta_y*scale; + + // Prevent mouse from leaving screen + position.x = Math.min(Math.max(0, position.x), element.offsetWidth - 1); + position.y = Math.min(Math.max(0, position.y), element.offsetHeight - 1); + + // Fire movement event, if defined + guac_touchpad.move(position, e); + + // Update touch location + last_touch_x = touch.clientX; + last_touch_y = touch.clientY; + + } + + // Interpret two-finger swipe as scrollwheel + else if (touch_count === 2) { + + // If change in location passes threshold for scroll + if (Math.abs(delta_y) >= guac_touchpad.scrollThreshold) { + + // Decide button based on Y movement direction + var button; + if (delta_y > 0) button = "down"; + else button = "up"; + + guac_touchpad.click(button, e); + + // Only update touch location after a scroll has been + // detected + last_touch_x = touch.clientX; + last_touch_y = touch.clientY; + + } + + } + + }, false); + +}; + +/** + * Provides cross-browser absolute touch event translation for a given element. + * + * Touch events are translated into mouse events as if the touches occurred + * on a touchscreen (tapping anywhere on the screen clicks at that point, + * long-press to right-click). + * + * @example + * var touchscreen = new Guacamole.Mouse.Touchscreen(client.getDisplay().getElement()); + * + * // Emulate a mouse using touchscreen-style gestures, forwarding all mouse + * // interaction over Guacamole connection + * touchscreen.onEach(['mousedown', 'mousemove', 'mouseup'], function sendMouseEvent(e) { + * + * // Re-show software mouse cursor if possibly hidden by a prior call to + * // showCursor(), such as a "mouseout" event handler that hides the + * // cursor + * client.getDisplay().showCursor(true); + * + * client.sendMouseState(e.state, true); + * + * }); + * + * @constructor + * @augments Guacamole.Mouse.Event.Target + * @param {!Element} element + * The Element to use to provide touch events. + */ +Guacamole.Mouse.Touchscreen = function Touchscreen(element) { + + Guacamole.Mouse.Event.Target.call(this); + + /** + * The "mouseout" event will never be fired by Guacamole.Mouse.Touchscreen. + * + * @ignore + * @event Guacamole.Mouse.Touchscreen#mouseout + */ + + /** + * Reference to this Guacamole.Mouse.Touchscreen. + * + * @private + * @type {!Guacamole.Mouse.Touchscreen} + */ + var guac_touchscreen = this; + + /** + * Whether a gesture is known to be in progress. If false, touch events + * will be ignored. + * + * @private + * @type {!boolean} + */ + var gesture_in_progress = false; + + /** + * The start X location of a gesture. + * + * @private + * @type {number} + */ + var gesture_start_x = null; + + /** + * The start Y location of a gesture. + * + * @private + * @type {number} + */ + var gesture_start_y = null; + + /** + * The timeout associated with the delayed, cancellable click release. + * + * @private + * @type {number} + */ + var click_release_timeout = null; + + /** + * The timeout associated with long-press for right click. + * + * @private + * @type {number} + */ + var long_press_timeout = null; + + /** + * The distance a two-finger touch must move per scrollwheel event, in + * pixels. + * + * @type {!number} + */ + this.scrollThreshold = 20 * (window.devicePixelRatio || 1); + + /** + * The maximum number of milliseconds to wait for a touch to end for the + * gesture to be considered a click. + * + * @type {!number} + */ + this.clickTimingThreshold = 250; + + /** + * The maximum number of pixels to allow a touch to move for the gesture to + * be considered a click. + * + * @type {!number} + */ + this.clickMoveThreshold = 16 * (window.devicePixelRatio || 1); + + /** + * The amount of time a press must be held for long press to be + * detected. + */ + this.longPressThreshold = 500; + + /** + * Returns whether the given touch event exceeds the movement threshold for + * clicking, based on where the touch gesture began. + * + * @private + * @param {!TouchEvent} e + * The touch event to check. + * + * @return {!boolean} + * true if the movement threshold is exceeded, false otherwise. + */ + function finger_moved(e) { + var touch = e.touches[0] || e.changedTouches[0]; + var delta_x = touch.clientX - gesture_start_x; + var delta_y = touch.clientY - gesture_start_y; + return Math.sqrt(delta_x*delta_x + delta_y*delta_y) >= guac_touchscreen.clickMoveThreshold; + } + + /** + * Begins a new gesture at the location of the first touch in the given + * touch event. + * + * @private + * @param {!TouchEvent} e + * The touch event beginning this new gesture. + */ + function begin_gesture(e) { + var touch = e.touches[0]; + gesture_in_progress = true; + gesture_start_x = touch.clientX; + gesture_start_y = touch.clientY; + } + + /** + * End the current gesture entirely. Wait for all touches to be done before + * resuming gesture detection. + * + * @private + */ + function end_gesture() { + window.clearTimeout(click_release_timeout); + window.clearTimeout(long_press_timeout); + gesture_in_progress = false; + } + + element.addEventListener("touchend", function(e) { + + // Do not handle if no gesture + if (!gesture_in_progress) + return; + + // Ignore if more than one touch + if (e.touches.length !== 0 || e.changedTouches.length !== 1) { + end_gesture(); + return; + } + + // Long-press, if any, is over + window.clearTimeout(long_press_timeout); + + // Always release mouse button if pressed + guac_touchscreen.release(Guacamole.Mouse.State.Buttons.LEFT, e); + + // If finger hasn't moved enough to cancel the click + if (!finger_moved(e)) { + + e.preventDefault(); + + // If not yet pressed, press and start delay release + if (!guac_touchscreen.currentState.left) { + + var touch = e.changedTouches[0]; + guac_touchscreen.move(Guacamole.Position.fromClientPosition(element, touch.clientX, touch.clientY)); + guac_touchscreen.press(Guacamole.Mouse.State.Buttons.LEFT, e); + + // Release button after a delay, if not canceled + click_release_timeout = window.setTimeout(function() { + guac_touchscreen.release(Guacamole.Mouse.State.Buttons.LEFT, e); + end_gesture(); + }, guac_touchscreen.clickTimingThreshold); + + } + + } // end if finger not moved + + }, false); + + element.addEventListener("touchstart", function(e) { + + // Ignore if more than one touch + if (e.touches.length !== 1) { + end_gesture(); + return; + } + + e.preventDefault(); + + // New touch begins a new gesture + begin_gesture(e); + + // Keep button pressed if tap after left click + window.clearTimeout(click_release_timeout); + + // Click right button if this turns into a long-press + long_press_timeout = window.setTimeout(function() { + var touch = e.touches[0]; + guac_touchscreen.move(Guacamole.Position.fromClientPosition(element, touch.clientX, touch.clientY)); + guac_touchscreen.click(Guacamole.Mouse.State.Buttons.RIGHT, e); + end_gesture(); + }, guac_touchscreen.longPressThreshold); + + }, false); + + element.addEventListener("touchmove", function(e) { + + // Do not handle if no gesture + if (!gesture_in_progress) + return; + + // Cancel long press if finger moved + if (finger_moved(e)) + window.clearTimeout(long_press_timeout); + + // Ignore if more than one touch + if (e.touches.length !== 1) { + end_gesture(); + return; + } + + // Update mouse position if dragging + if (guac_touchscreen.currentState.left) { + + e.preventDefault(); + + // Update state + var touch = e.touches[0]; + guac_touchscreen.move(Guacamole.Position.fromClientPosition(element, touch.clientX, touch.clientY), e); + + } + + }, false); + +}; diff --git a/src/Namespace.js b/src/Namespace.js new file mode 100644 index 0000000..227096f --- /dev/null +++ b/src/Namespace.js @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * The namespace used by the Guacamole JavaScript API. Absolutely all classes + * defined by the Guacamole JavaScript API will be within this namespace. + * + * @namespace + */ +var Guacamole = Guacamole || {}; diff --git a/src/Object.js b/src/Object.js new file mode 100644 index 0000000..5d65f57 --- /dev/null +++ b/src/Object.js @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * An object used by the Guacamole client to house arbitrarily-many named + * input and output streams. + * + * @constructor + * @param {!Guacamole.Client} client + * The client owning this object. + * + * @param {!number} index + * The index of this object. + */ +Guacamole.Object = function guacamoleObject(client, index) { + + /** + * Reference to this Guacamole.Object. + * + * @private + * @type {!Guacamole.Object} + */ + var guacObject = this; + + /** + * Map of stream name to corresponding queue of callbacks. The queue of + * callbacks is guaranteed to be in order of request. + * + * @private + * @type {!Object.} + */ + var bodyCallbacks = {}; + + /** + * Removes and returns the callback at the head of the callback queue for + * the stream having the given name. If no such callbacks exist, null is + * returned. + * + * @private + * @param {!string} name + * The name of the stream to retrieve a callback for. + * + * @returns {function} + * The next callback associated with the stream having the given name, + * or null if no such callback exists. + */ + var dequeueBodyCallback = function dequeueBodyCallback(name) { + + // If no callbacks defined, simply return null + var callbacks = bodyCallbacks[name]; + if (!callbacks) + return null; + + // Otherwise, pull off first callback, deleting the queue if empty + var callback = callbacks.shift(); + if (callbacks.length === 0) + delete bodyCallbacks[name]; + + // Return found callback + return callback; + + }; + + /** + * Adds the given callback to the tail of the callback queue for the stream + * having the given name. + * + * @private + * @param {!string} name + * The name of the stream to associate with the given callback. + * + * @param {!function} callback + * The callback to add to the queue of the stream with the given name. + */ + var enqueueBodyCallback = function enqueueBodyCallback(name, callback) { + + // Get callback queue by name, creating first if necessary + var callbacks = bodyCallbacks[name]; + if (!callbacks) { + callbacks = []; + bodyCallbacks[name] = callbacks; + } + + // Add callback to end of queue + callbacks.push(callback); + + }; + + /** + * The index of this object. + * + * @type {!number} + */ + this.index = index; + + /** + * Called when this object receives the body of a requested input stream. + * By default, all objects will invoke the callbacks provided to their + * requestInputStream() functions based on the name of the stream + * requested. This behavior can be overridden by specifying a different + * handler here. + * + * @event + * @param {!Guacamole.InputStream} inputStream + * The input stream of the received body. + * + * @param {!string} mimetype + * The mimetype of the data being received. + * + * @param {!string} name + * The name of the stream whose body has been received. + */ + this.onbody = function defaultBodyHandler(inputStream, mimetype, name) { + + // Call queued callback for the received body, if any + var callback = dequeueBodyCallback(name); + if (callback) + callback(inputStream, mimetype); + + }; + + /** + * Called when this object is being undefined. Once undefined, no further + * communication involving this object may occur. + * + * @event + */ + this.onundefine = null; + + /** + * Requests read access to the input stream having the given name. If + * successful, a new input stream will be created. + * + * @param {!string} name + * The name of the input stream to request. + * + * @param {function} [bodyCallback] + * The callback to invoke when the body of the requested input stream + * is received. This callback will be provided a Guacamole.InputStream + * and its mimetype as its two only arguments. If the onbody handler of + * this object is overridden, this callback will not be invoked. + */ + this.requestInputStream = function requestInputStream(name, bodyCallback) { + + // Queue body callback if provided + if (bodyCallback) + enqueueBodyCallback(name, bodyCallback); + + // Send request for input stream + client.requestObjectInputStream(guacObject.index, name); + + }; + + /** + * Creates a new output stream associated with this object and having the + * given mimetype and name. The legality of a mimetype and name is dictated + * by the object itself. + * + * @param {!string} mimetype + * The mimetype of the data which will be sent to the output stream. + * + * @param {!string} name + * The defined name of an output stream within this object. + * + * @returns {!Guacamole.OutputStream} + * An output stream which will write blobs to the named output stream + * of this object. + */ + this.createOutputStream = function createOutputStream(mimetype, name) { + return client.createObjectOutputStream(guacObject.index, mimetype, name); + }; + +}; + +/** + * The reserved name denoting the root stream of any object. The contents of + * the root stream MUST be a JSON map of stream name to mimetype. + * + * @constant + * @type {!string} + */ +Guacamole.Object.ROOT_STREAM = '/'; + +/** + * The mimetype of a stream containing JSON which maps available stream names + * to their corresponding mimetype. The root stream of a Guacamole.Object MUST + * have this mimetype. + * + * @constant + * @type {!string} + */ +Guacamole.Object.STREAM_INDEX_MIMETYPE = 'application/vnd.glyptodon.guacamole.stream-index+json'; diff --git a/src/OnScreenKeyboard.js b/src/OnScreenKeyboard.js new file mode 100644 index 0000000..221f3b2 --- /dev/null +++ b/src/OnScreenKeyboard.js @@ -0,0 +1,947 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Dynamic on-screen keyboard. Given the layout object for an on-screen + * keyboard, this object will construct a clickable on-screen keyboard with its + * own key events. + * + * @constructor + * @param {!Guacamole.OnScreenKeyboard.Layout} layout + * The layout of the on-screen keyboard to display. + */ +Guacamole.OnScreenKeyboard = function(layout) { + + /** + * Reference to this Guacamole.OnScreenKeyboard. + * + * @private + * @type {!Guacamole.OnScreenKeyboard} + */ + var osk = this; + + /** + * Map of currently-set modifiers to the keysym associated with their + * original press. When the modifier is cleared, this keysym must be + * released. + * + * @private + * @type {!Object.} + */ + var modifierKeysyms = {}; + + /** + * Map of all key names to their current pressed states. If a key is not + * pressed, it may not be in this map at all, but all pressed keys will + * have a corresponding mapping to true. + * + * @private + * @type {!Object.} + */ + var pressed = {}; + + /** + * All scalable elements which are part of the on-screen keyboard. Each + * scalable element is carefully controlled to ensure the interface layout + * and sizing remains constant, even on browsers that would otherwise + * experience rounding error due to unit conversions. + * + * @private + * @type {!ScaledElement[]} + */ + var scaledElements = []; + + /** + * Adds a CSS class to an element. + * + * @private + * @function + * @param {!Element} element + * The element to add a class to. + * + * @param {!string} classname + * The name of the class to add. + */ + var addClass = function addClass(element, classname) { + + // If classList supported, use that + if (element.classList) + element.classList.add(classname); + + // Otherwise, simply append the class + else + element.className += " " + classname; + + }; + + /** + * Removes a CSS class from an element. + * + * @private + * @function + * @param {!Element} element + * The element to remove a class from. + * + * @param {!string} classname + * The name of the class to remove. + */ + var removeClass = function removeClass(element, classname) { + + // If classList supported, use that + if (element.classList) + element.classList.remove(classname); + + // Otherwise, manually filter out classes with given name + else { + element.className = element.className.replace(/([^ ]+)[ ]*/g, + function removeMatchingClasses(match, testClassname) { + + // If same class, remove + if (testClassname === classname) + return ""; + + // Otherwise, allow + return match; + + } + ); + } + + }; + + /** + * Counter of mouse events to ignore. This decremented by mousemove, and + * while non-zero, mouse events will have no effect. + * + * @private + * @type {!number} + */ + var ignoreMouse = 0; + + /** + * Ignores all pending mouse events when touch events are the apparent + * source. Mouse events are ignored until at least touchMouseThreshold + * mouse events occur without corresponding touch events. + * + * @private + */ + var ignorePendingMouseEvents = function ignorePendingMouseEvents() { + ignoreMouse = osk.touchMouseThreshold; + }; + + /** + * An element whose dimensions are maintained according to an arbitrary + * scale. The conversion factor for these arbitrary units to pixels is + * provided later via a call to scale(). + * + * @private + * @constructor + * @param {!Element} element + * The element whose scale should be maintained. + * + * @param {!number} width + * The width of the element, in arbitrary units, relative to other + * ScaledElements. + * + * @param {!number} height + * The height of the element, in arbitrary units, relative to other + * ScaledElements. + * + * @param {boolean} [scaleFont=false] + * Whether the line height and font size should be scaled as well. + */ + var ScaledElement = function ScaledElement(element, width, height, scaleFont) { + + /** + * The width of this ScaledElement, in arbitrary units, relative to + * other ScaledElements. + * + * @type {!number} + */ + this.width = width; + + /** + * The height of this ScaledElement, in arbitrary units, relative to + * other ScaledElements. + * + * @type {!number} + */ + this.height = height; + + /** + * Resizes the associated element, updating its dimensions according to + * the given pixels per unit. + * + * @param {!number} pixels + * The number of pixels to assign per arbitrary unit. + */ + this.scale = function(pixels) { + + // Scale element width/height + element.style.width = (width * pixels) + "px"; + element.style.height = (height * pixels) + "px"; + + // Scale font, if requested + if (scaleFont) { + element.style.lineHeight = (height * pixels) + "px"; + element.style.fontSize = pixels + "px"; + } + + }; + + }; + + /** + * Returns whether all modifiers having the given names are currently + * active. + * + * @private + * @param {!string[]} names + * The names of all modifiers to test. + * + * @returns {!boolean} + * true if all specified modifiers are pressed, false otherwise. + */ + var modifiersPressed = function modifiersPressed(names) { + + // If any required modifiers are not pressed, return false + for (var i=0; i < names.length; i++) { + + // Test whether current modifier is pressed + var name = names[i]; + if (!(name in modifierKeysyms)) + return false; + + } + + // Otherwise, all required modifiers are pressed + return true; + + }; + + /** + * Returns the single matching Key object associated with the key of the + * given name, where that Key object's requirements (such as pressed + * modifiers) are all currently satisfied. + * + * @private + * @param {!string} keyName + * The name of the key to retrieve. + * + * @returns {Guacamole.OnScreenKeyboard.Key} + * The Key object associated with the given name, where that object's + * requirements are all currently satisfied, or null if no such Key + * can be found. + */ + var getActiveKey = function getActiveKey(keyName) { + + // Get key array for given name + var keys = osk.keys[keyName]; + if (!keys) + return null; + + // Find last matching key + for (var i = keys.length - 1; i >= 0; i--) { + + // Get candidate key + var candidate = keys[i]; + + // If all required modifiers are pressed, use that key + if (modifiersPressed(candidate.requires)) + return candidate; + + } + + // No valid key + return null; + + }; + + /** + * Presses the key having the given name, updating the associated key + * element with the "guac-keyboard-pressed" CSS class. If the key is + * already pressed, this function has no effect. + * + * @private + * @param {!string} keyName + * The name of the key to press. + * + * @param {!string} keyElement + * The element associated with the given key. + */ + var press = function press(keyName, keyElement) { + + // Press key if not yet pressed + if (!pressed[keyName]) { + + addClass(keyElement, "guac-keyboard-pressed"); + + // Get current key based on modifier state + var key = getActiveKey(keyName); + + // Update modifier state + if (key.modifier) { + + // Construct classname for modifier + var modifierClass = "guac-keyboard-modifier-" + getCSSName(key.modifier); + + // Retrieve originally-pressed keysym, if modifier was already pressed + var originalKeysym = modifierKeysyms[key.modifier]; + + // Activate modifier if not pressed + if (originalKeysym === undefined) { + + addClass(keyboard, modifierClass); + modifierKeysyms[key.modifier] = key.keysym; + + // Send key event only if keysym is meaningful + if (key.keysym && osk.onkeydown) + osk.onkeydown(key.keysym); + + } + + // Deactivate if not pressed + else { + + removeClass(keyboard, modifierClass); + delete modifierKeysyms[key.modifier]; + + // Send key event only if original keysym is meaningful + if (originalKeysym && osk.onkeyup) + osk.onkeyup(originalKeysym); + + } + + } + + // If not modifier, send key event now + else if (osk.onkeydown) + osk.onkeydown(key.keysym); + + // Mark key as pressed + pressed[keyName] = true; + + } + + }; + + /** + * Releases the key having the given name, removing the + * "guac-keyboard-pressed" CSS class from the associated element. If the + * key is already released, this function has no effect. + * + * @private + * @param {!string} keyName + * The name of the key to release. + * + * @param {!string} keyElement + * The element associated with the given key. + */ + var release = function release(keyName, keyElement) { + + // Release key if currently pressed + if (pressed[keyName]) { + + removeClass(keyElement, "guac-keyboard-pressed"); + + // Get current key based on modifier state + var key = getActiveKey(keyName); + + // Send key event if not a modifier key + if (!key.modifier && osk.onkeyup) + osk.onkeyup(key.keysym); + + // Mark key as released + pressed[keyName] = false; + + } + + }; + + // Create keyboard + var keyboard = document.createElement("div"); + keyboard.className = "guac-keyboard"; + + // Do not allow selection or mouse movement to propagate/register. + keyboard.onselectstart = + keyboard.onmousemove = + keyboard.onmouseup = + keyboard.onmousedown = function handleMouseEvents(e) { + + // If ignoring events, decrement counter + if (ignoreMouse) + ignoreMouse--; + + e.stopPropagation(); + return false; + + }; + + /** + * The number of mousemove events to require before re-enabling mouse + * event handling after receiving a touch event. + * + * @type {!number} + */ + this.touchMouseThreshold = 3; + + /** + * Fired whenever the user presses a key on this Guacamole.OnScreenKeyboard. + * + * @event + * @param {!number} keysym + * The keysym of the key being pressed. + */ + this.onkeydown = null; + + /** + * Fired whenever the user releases a key on this Guacamole.OnScreenKeyboard. + * + * @event + * @param {!number} keysym + * The keysym of the key being released. + */ + this.onkeyup = null; + + /** + * The keyboard layout provided at time of construction. + * + * @type {!Guacamole.OnScreenKeyboard.Layout} + */ + this.layout = new Guacamole.OnScreenKeyboard.Layout(layout); + + /** + * Returns the element containing the entire on-screen keyboard. + * + * @returns {!Element} + * The element containing the entire on-screen keyboard. + */ + this.getElement = function() { + return keyboard; + }; + + /** + * Resizes all elements within this Guacamole.OnScreenKeyboard such that + * the width is close to but does not exceed the specified width. The + * height of the keyboard is determined based on the width. + * + * @param {!number} width + * The width to resize this Guacamole.OnScreenKeyboard to, in pixels. + */ + this.resize = function(width) { + + // Get pixel size of a unit + var unit = Math.floor(width * 10 / osk.layout.width) / 10; + + // Resize all scaled elements + for (var i=0; i} keys + * A mapping of key name to key definition, where the key definition is + * the title of the key (a string), the keysym (a number), a single + * Key object, or an array of Key objects. + * + * @returns {!Object.} + * A more-predictable mapping of key name to key definition, where the + * key definition is always simply an array of Key objects. + */ + var getKeys = function getKeys(keys) { + + var keyArrays = {}; + + // Coerce all keys into individual key arrays + for (var name in layout.keys) { + keyArrays[name] = asKeyArray(name, keys[name]); + } + + return keyArrays; + + }; + + /** + * Map of all key names to their corresponding set of keys. Each key name + * may correspond to multiple keys due to the effect of modifiers. + * + * @type {!Object.} + */ + this.keys = getKeys(layout.keys); + + /** + * Given an arbitrary string representing the name of some component of the + * on-screen keyboard, returns a string formatted for use as a CSS class + * name. The result will be lowercase. Word boundaries previously denoted + * by CamelCase will be replaced by individual hyphens, as will all + * contiguous non-alphanumeric characters. + * + * @private + * @param {!string} name + * An arbitrary string representing the name of some component of the + * on-screen keyboard. + * + * @returns {!string} + * A string formatted for use as a CSS class name. + */ + var getCSSName = function getCSSName(name) { + + // Convert name from possibly-CamelCase to hyphenated lowercase + var cssName = name + .replace(/([a-z])([A-Z])/g, '$1-$2') + .replace(/[^A-Za-z0-9]+/g, '-') + .toLowerCase(); + + return cssName; + + }; + + /** + * Appends DOM elements to the given element as dictated by the layout + * structure object provided. If a name is provided, an additional CSS + * class, prepended with "guac-keyboard-", will be added to the top-level + * element. + * + * If the layout structure object is an array, all elements within that + * array will be recursively appended as children of a group, and the + * top-level element will be given the CSS class "guac-keyboard-group". + * + * If the layout structure object is an object, all properties within that + * object will be recursively appended as children of a group, and the + * top-level element will be given the CSS class "guac-keyboard-group". The + * name of each property will be applied as the name of each child object + * for the sake of CSS. Each property will be added in sorted order. + * + * If the layout structure object is a string, the key having that name + * will be appended. The key will be given the CSS class + * "guac-keyboard-key" and "guac-keyboard-key-NAME", where NAME is the name + * of the key. If the name of the key is a single character, this will + * first be transformed into the C-style hexadecimal literal for the + * Unicode codepoint of that character. For example, the key "A" would + * become "guac-keyboard-key-0x41". + * + * If the layout structure object is a number, a gap of that size will be + * inserted. The gap will be given the CSS class "guac-keyboard-gap", and + * will be scaled according to the same size units as each key. + * + * @private + * @param {!Element} element + * The element to append elements to. + * + * @param {!(Array|object|string|number)} object + * The layout structure object to use when constructing the elements to + * append. + * + * @param {string} [name] + * The name of the top-level element being appended, if any. + */ + var appendElements = function appendElements(element, object, name) { + + var i; + + // Create div which will become the group or key + var div = document.createElement('div'); + + // Add class based on name, if name given + if (name) + addClass(div, 'guac-keyboard-' + getCSSName(name)); + + // If an array, append each element + if (object instanceof Array) { + + // Add group class + addClass(div, 'guac-keyboard-group'); + + // Append all elements of array + for (i=0; i < object.length; i++) + appendElements(div, object[i]); + + } + + // If an object, append each property value + else if (object instanceof Object) { + + // Add group class + addClass(div, 'guac-keyboard-group'); + + // Append all children, sorted by name + var names = Object.keys(object).sort(); + for (i=0; i < names.length; i++) { + var name = names[i]; + appendElements(div, object[name], name); + } + + } + + // If a number, create as a gap + else if (typeof object === 'number') { + + // Add gap class + addClass(div, 'guac-keyboard-gap'); + + // Maintain scale + scaledElements.push(new ScaledElement(div, object, object)); + + } + + // If a string, create as a key + else if (typeof object === 'string') { + + // If key name is only one character, use codepoint for name + var keyName = object; + if (keyName.length === 1) + keyName = '0x' + keyName.charCodeAt(0).toString(16); + + // Add key container class + addClass(div, 'guac-keyboard-key-container'); + + // Create key element which will contain all possible caps + var keyElement = document.createElement('div'); + keyElement.className = 'guac-keyboard-key ' + + 'guac-keyboard-key-' + getCSSName(keyName); + + // Add all associated keys as caps within DOM + var keys = osk.keys[object]; + if (keys) { + for (i=0; i < keys.length; i++) { + + // Get current key + var key = keys[i]; + + // Create cap element for key + var capElement = document.createElement('div'); + capElement.className = 'guac-keyboard-cap'; + capElement.textContent = key.title; + + // Add classes for any requirements + for (var j=0; j < key.requires.length; j++) { + var requirement = key.requires[j]; + addClass(capElement, 'guac-keyboard-requires-' + getCSSName(requirement)); + addClass(keyElement, 'guac-keyboard-uses-' + getCSSName(requirement)); + } + + // Add cap to key within DOM + keyElement.appendChild(capElement); + + } + } + + // Add key to DOM, maintain scale + div.appendChild(keyElement); + scaledElements.push(new ScaledElement(div, osk.layout.keyWidths[object] || 1, 1, true)); + + /** + * Handles a touch event which results in the pressing of an OSK + * key. Touch events will result in mouse events being ignored for + * touchMouseThreshold events. + * + * @private + * @param {!TouchEvent} e + * The touch event being handled. + */ + var touchPress = function touchPress(e) { + e.preventDefault(); + ignoreMouse = osk.touchMouseThreshold; + press(object, keyElement); + }; + + /** + * Handles a touch event which results in the release of an OSK + * key. Touch events will result in mouse events being ignored for + * touchMouseThreshold events. + * + * @private + * @param {!TouchEvent} e + * The touch event being handled. + */ + var touchRelease = function touchRelease(e) { + e.preventDefault(); + ignoreMouse = osk.touchMouseThreshold; + release(object, keyElement); + }; + + /** + * Handles a mouse event which results in the pressing of an OSK + * key. If mouse events are currently being ignored, this handler + * does nothing. + * + * @private + * @param {!MouseEvent} e + * The touch event being handled. + */ + var mousePress = function mousePress(e) { + e.preventDefault(); + if (ignoreMouse === 0) + press(object, keyElement); + }; + + /** + * Handles a mouse event which results in the release of an OSK + * key. If mouse events are currently being ignored, this handler + * does nothing. + * + * @private + * @param {!MouseEvent} e + * The touch event being handled. + */ + var mouseRelease = function mouseRelease(e) { + e.preventDefault(); + if (ignoreMouse === 0) + release(object, keyElement); + }; + + // Handle touch events on key + keyElement.addEventListener("touchstart", touchPress, true); + keyElement.addEventListener("touchend", touchRelease, true); + + // Handle mouse events on key + keyElement.addEventListener("mousedown", mousePress, true); + keyElement.addEventListener("mouseup", mouseRelease, true); + keyElement.addEventListener("mouseout", mouseRelease, true); + + } // end if object is key name + + // Add newly-created group/key + element.appendChild(div); + + }; + + // Create keyboard layout in DOM + appendElements(keyboard, layout.layout); + +}; + +/** + * Represents an entire on-screen keyboard layout, including all available + * keys, their behaviors, and their relative position and sizing. + * + * @constructor + * @param {!(Guacamole.OnScreenKeyboard.Layout|object)} template + * The object whose identically-named properties will be used to initialize + * the properties of this layout. + */ +Guacamole.OnScreenKeyboard.Layout = function(template) { + + /** + * The language of keyboard layout, such as "en_US". This property is for + * informational purposes only, but it is recommend to conform to the + * [language code]_[country code] format. + * + * @type {!string} + */ + this.language = template.language; + + /** + * The type of keyboard layout, such as "qwerty". This property is for + * informational purposes only, and does not conform to any standard. + * + * @type {!string} + */ + this.type = template.type; + + /** + * Map of key name to corresponding keysym, title, or key object. If only + * the keysym or title is provided, the key object will be created + * implicitly. In all cases, the name property of the key object will be + * taken from the name given in the mapping. + * + * @type {!Object.} + */ + this.keys = template.keys; + + /** + * Arbitrarily nested, arbitrarily grouped key names. The contents of the + * layout will be traversed to produce an identically-nested grouping of + * keys in the DOM tree. All strings will be transformed into their + * corresponding sets of keys, while all objects and arrays will be + * transformed into named groups and anonymous groups respectively. Any + * numbers present will be transformed into gaps of that size, scaled + * according to the same units as each key. + * + * @type {!object} + */ + this.layout = template.layout; + + /** + * The width of the entire keyboard, in arbitrary units. The width of each + * key is relative to this width, as both width values are assumed to be in + * the same units. The conversion factor between these units and pixels is + * derived later via a call to resize() on the Guacamole.OnScreenKeyboard. + * + * @type {!number} + */ + this.width = template.width; + + /** + * The width of each key, in arbitrary units, relative to other keys in + * this layout. The true pixel size of each key will be determined by the + * overall size of the keyboard. If not defined here, the width of each + * key will default to 1. + * + * @type {!Object.} + */ + this.keyWidths = template.keyWidths || {}; + +}; + +/** + * Represents a single key, or a single possible behavior of a key. Each key + * on the on-screen keyboard must have at least one associated + * Guacamole.OnScreenKeyboard.Key, whether that key is explicitly defined or + * implied, and may have multiple Guacamole.OnScreenKeyboard.Key if behavior + * depends on modifier states. + * + * @constructor + * @param {!(Guacamole.OnScreenKeyboard.Key|object)} template + * The object whose identically-named properties will be used to initialize + * the properties of this key. + * + * @param {string} [name] + * The name to use instead of any name provided within the template, if + * any. If omitted, the name within the template will be used, assuming the + * template contains a name. + */ +Guacamole.OnScreenKeyboard.Key = function(template, name) { + + /** + * The unique name identifying this key within the keyboard layout. + * + * @type {!string} + */ + this.name = name || template.name; + + /** + * The human-readable title that will be displayed to the user within the + * key. If not provided, this will be derived from the key name. + * + * @type {!string} + */ + this.title = template.title || this.name; + + /** + * The keysym to be pressed/released when this key is pressed/released. If + * not provided, this will be derived from the title if the title is a + * single character. + * + * @type {number} + */ + this.keysym = template.keysym || (function deriveKeysym(title) { + + // Do not derive keysym if title is not exactly one character + if (!title || title.length !== 1) + return null; + + // For characters between U+0000 and U+00FF, the keysym is the codepoint + var charCode = title.charCodeAt(0); + if (charCode >= 0x0000 && charCode <= 0x00FF) + return charCode; + + // For characters between U+0100 and U+10FFFF, the keysym is the codepoint or'd with 0x01000000 + if (charCode >= 0x0100 && charCode <= 0x10FFFF) + return 0x01000000 | charCode; + + // Unable to derive keysym + return null; + + })(this.title); + + /** + * The name of the modifier set when the key is pressed and cleared when + * this key is released, if any. The names of modifiers are distinct from + * the names of keys; both the "RightShift" and "LeftShift" keys may set + * the "shift" modifier, for example. By default, the key will affect no + * modifiers. + * + * @type {string} + */ + this.modifier = template.modifier; + + /** + * An array containing the names of each modifier required for this key to + * have an effect. For example, a lowercase letter may require nothing, + * while an uppercase letter would require "shift", assuming the Shift key + * is named "shift" within the layout. By default, the key will require + * no modifiers. + * + * @type {!string[]} + */ + this.requires = template.requires || []; + +}; diff --git a/src/OutputStream.js b/src/OutputStream.js new file mode 100644 index 0000000..bb9bad6 --- /dev/null +++ b/src/OutputStream.js @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Abstract stream which can receive data. + * + * @constructor + * @param {!Guacamole.Client} client + * The client owning this stream. + * + * @param {!number} index + * The index of this stream. + */ +Guacamole.OutputStream = function(client, index) { + + /** + * Reference to this stream. + * + * @private + * @type {!Guacamole.OutputStream} + */ + var guac_stream = this; + + /** + * The index of this stream. + * @type {!number} + */ + this.index = index; + + /** + * Fired whenever an acknowledgement is received from the server, indicating + * that a stream operation has completed, or an error has occurred. + * + * @event + * @param {!Guacamole.Status} status + * The status of the operation. + */ + this.onack = null; + + /** + * Writes the given base64-encoded data to this stream as a blob. + * + * @param {!string} data + * The base64-encoded data to send. + */ + this.sendBlob = function(data) { + client.sendBlob(guac_stream.index, data); + }; + + /** + * Closes this stream. + */ + this.sendEnd = function() { + client.endStream(guac_stream.index); + }; + +}; diff --git a/src/Parser.js b/src/Parser.js new file mode 100644 index 0000000..8cd93f2 --- /dev/null +++ b/src/Parser.js @@ -0,0 +1,348 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Simple Guacamole protocol parser that invokes an oninstruction event when + * full instructions are available from data received via receive(). + * + * @constructor + */ +Guacamole.Parser = function Parser() { + + /** + * Reference to this parser. + * + * @private + * @type {!Guacamole.Parser} + */ + var parser = this; + + /** + * Current buffer of received data. This buffer grows until a full + * element is available. After a full element is available, that element + * is flushed into the element buffer. + * + * @private + * @type {!string} + */ + var buffer = ''; + + /** + * Buffer of all received, complete elements. After an entire instruction + * is read, this buffer is flushed, and a new instruction begins. + * + * @private + * @type {!string[]} + */ + var elementBuffer = []; + + /** + * The character offset within the buffer of the current or most recently + * parsed element's terminator. If sufficient characters have not yet been + * read via calls to receive(), this may point to an offset well beyond the + * end of the buffer. If no characters for an element have yet been read, + * this will be -1. + * + * @private + * @type {!number} + */ + var elementEnd = -1; + + /** + * The character offset within the buffer of the location that the parser + * should start looking for the next element length search or next element + * value. + * + * @private + * @type {!number} + */ + var startIndex = 0; + + /** + * The declared length of the current element being parsed, in Unicode + * codepoints. + * + * @private + * @type {!number} + */ + var elementCodepoints = 0; + + /** + * The number of parsed characters that must accumulate in the begining of + * the parse buffer before processing time is expended to truncate that + * buffer and conserve memory. + * + * @private + * @constant + * @type {!number} + */ + var BUFFER_TRUNCATION_THRESHOLD = 4096; + + /** + * The lowest Unicode codepoint to require a surrogate pair when encoded + * with UTF-16. In UTF-16, characters with codepoints at or above this + * value are represented with a surrogate pair, while characters with + * codepoints below this value are represented with a single character. + * + * @private + * @constant + * @type {!number} + */ + var MIN_CODEPOINT_REQUIRES_SURROGATE = 0x10000; + + /** + * Appends the given instruction data packet to the internal buffer of + * this Guacamole.Parser, executing all completed instructions at + * the beginning of this buffer, if any. + * + * @param {!string} packet + * The instruction data to receive. + * + * @param {!boolean} [isBuffer=false] + * Whether the provided data should be treated as an instruction buffer + * that grows continuously. If true, the data provided to receive() + * MUST always start with the data provided to the previous call. If + * false (the default), only the new data should be provided to + * receive(), and previously-received data will automatically be + * buffered by the parser as needed. + */ + this.receive = function receive(packet, isBuffer) { + + if (isBuffer) + buffer = packet; + + else { + + // Truncate buffer as necessary + if (startIndex > BUFFER_TRUNCATION_THRESHOLD && elementEnd >= startIndex) { + + buffer = buffer.substring(startIndex); + + // Reset parse relative to truncation + elementEnd -= startIndex; + startIndex = 0; + + } + + // Append data to buffer ONLY if there is outstanding data present. It + // is otherwise much faster to simply parse the received buffer as-is, + // and tunnel implementations can take advantage of this by preferring + // to send only complete instructions. Both the HTTP and WebSocket + // tunnel implementations included with Guacamole already do this. + if (buffer.length) + buffer += packet; + else + buffer = packet; + + } + + // While search is within currently received data + while (elementEnd < buffer.length) { + + // If we are waiting for element data + if (elementEnd >= startIndex) { + + // If we have enough data in the buffer to fill the element + // value, but the number of codepoints in the expected substring + // containing the element value value is less that its declared + // length, that can only be because the element contains + // characters split between high and low surrogates, and the + // actual end of the element value is further out. The minimum + // number of additional characters that must be read to satisfy + // the declared length is simply the difference between the + // number of codepoints actually present vs. the expected + // length. + var codepoints = Guacamole.Parser.codePointCount(buffer, startIndex, elementEnd); + if (codepoints < elementCodepoints) { + elementEnd += elementCodepoints - codepoints; + continue; + } + + // If the current element ends with a character involving both + // a high and low surrogate, elementEnd points to the low + // surrogate and NOT the element terminator. We must shift the + // end and reevaluate. + else if (elementCodepoints && buffer.codePointAt(elementEnd - 1) >= MIN_CODEPOINT_REQUIRES_SURROGATE) { + elementEnd++; + continue; + } + + // We now have enough data for the element. Parse. + var element = buffer.substring(startIndex, elementEnd); + var terminator = buffer.substring(elementEnd, elementEnd + 1); + + // Add element to array + elementBuffer.push(element); + + // If last element, handle instruction + if (terminator === ';') { + + // Get opcode + var opcode = elementBuffer.shift(); + + // Call instruction handler. + if (parser.oninstruction !== null) + parser.oninstruction(opcode, elementBuffer); + + // Clear elements + elementBuffer = []; + + // Immediately truncate buffer if its contents have been + // completely parsed, so that the next call to receive() + // need not append to the buffer unnecessarily + if (elementEnd + 1 === buffer.length) { + elementEnd = -1; + buffer = ''; + } + + } + else if (terminator !== ',') + throw new Error('Element terminator of instruction was not ";" nor ",".'); + + // Start searching for length at character after + // element terminator + startIndex = elementEnd + 1; + + } + + // Search for end of length + var lengthEnd = buffer.indexOf('.', startIndex); + if (lengthEnd !== -1) { + + // Parse length + elementCodepoints = parseInt(buffer.substring(elementEnd + 1, lengthEnd)); + if (isNaN(elementCodepoints)) + throw new Error('Non-numeric character in element length.'); + + // Calculate start of element + startIndex = lengthEnd + 1; + + // Calculate location of element terminator + elementEnd = startIndex + elementCodepoints; + + } + + // If no period yet, continue search when more data + // is received + else { + startIndex = buffer.length; + break; + } + + } // end parse loop + + }; + + /** + * Fired once for every complete Guacamole instruction received, in order. + * + * @event + * @param {!string} opcode + * The Guacamole instruction opcode. + * + * @param {!string[]} parameters + * The parameters provided for the instruction, if any. + */ + this.oninstruction = null; + +}; + +/** + * Returns the number of Unicode codepoints (not code units) within the given + * string. If character offsets are provided, only codepoints between those + * offsets are counted. Unlike the length property of a string, this function + * counts proper surrogate pairs as a single codepoint. High and low surrogate + * characters that are not part of a proper surrogate pair are counted + * separately as individual codepoints. + * + * @param {!string} str + * The string whose contents should be inspected. + * + * @param {number} [start=0] + * The index of the location in the given string where codepoint counting + * should start. If omitted, counting will begin at the start of the + * string. + * + * @param {number} [end] + * The index of the first location in the given string after where counting + * should stop (the character after the last character being counted). If + * omitted, all characters after the start location will be counted. + * + * @returns {!number} + * The number of Unicode codepoints within the requested portion of the + * given string. + */ +Guacamole.Parser.codePointCount = function codePointCount(str, start, end) { + + // Count only characters within the specified region + str = str.substring(start || 0, end); + + // Locate each proper Unicode surrogate pair (one high surrogate followed + // by one low surrogate) + var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g); + + // Each surrogate pair represents a single codepoint but is represented by + // two characters in a JavaScript string, and thus is counted twice toward + // string length. Subtracting the number of surrogate pairs adjusts that + // length value such that it gives us the number of codepoints. + return str.length - (surrogatePairs ? surrogatePairs.length : 0); + +}; + +/** + * Converts each of the values within the given array to strings, formatting + * those strings as length-prefixed elements of a complete Guacamole + * instruction. + * + * @param {!Array.<*>} elements + * The values that should be encoded as the elements of a Guacamole + * instruction. Order of these elements is preserved. This array MUST have + * at least one element. + * + * @returns {!string} + * A complete Guacamole instruction consisting of each of the provided + * element values, in order. + */ +Guacamole.Parser.toInstruction = function toInstruction(elements) { + + /** + * Converts the given value to a length/string pair for use as an + * element in a Guacamole instruction. + * + * @private + * @param {*} value + * The value to convert. + * + * @return {!string} + * The converted value. + */ + var toElement = function toElement(value) { + var str = '' + value; + return Guacamole.Parser.codePointCount(str) + "." + str; + }; + + var instr = toElement(elements[0]); + for (var i = 1; i < elements.length; i++) + instr += ',' + toElement(elements[i]); + + return instr + ';'; + +}; diff --git a/src/Position.js b/src/Position.js new file mode 100644 index 0000000..8c5c4fc --- /dev/null +++ b/src/Position.js @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A position in 2-D space. + * + * @constructor + * @param {Guacamole.Position|object} [template={}] + * The object whose properties should be copied within the new + * Guacamole.Position. + */ +Guacamole.Position = function Position(template) { + + template = template || {}; + + /** + * The current X position, in pixels. + * + * @type {!number} + * @default 0 + */ + this.x = template.x || 0; + + /** + * The current Y position, in pixels. + * + * @type {!number} + * @default 0 + */ + this.y = template.y || 0; + + /** + * Assigns the position represented by the given element and + * clientX/clientY coordinates. The clientX and clientY coordinates are + * relative to the browser viewport and are commonly available within + * JavaScript event objects. The final position is translated to + * coordinates that are relative the given element. + * + * @param {!Element} element + * The element the coordinates should be relative to. + * + * @param {!number} clientX + * The viewport-relative X coordinate to translate. + * + * @param {!number} clientY + * The viewport-relative Y coordinate to translate. + */ + this.fromClientPosition = function fromClientPosition(element, clientX, clientY) { + + this.x = clientX - element.offsetLeft; + this.y = clientY - element.offsetTop; + + // This is all JUST so we can get the position within the element + var parent = element.offsetParent; + while (parent && !(parent === document.body)) { + this.x -= parent.offsetLeft - parent.scrollLeft; + this.y -= parent.offsetTop - parent.scrollTop; + + parent = parent.offsetParent; + } + + // Element ultimately depends on positioning within document body, + // take document scroll into account. + if (parent) { + var documentScrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft; + var documentScrollTop = document.body.scrollTop || document.documentElement.scrollTop; + + this.x -= parent.offsetLeft - documentScrollLeft; + this.y -= parent.offsetTop - documentScrollTop; + } + + }; + +}; + +/** + * Returns a new {@link Guacamole.Position} representing the relative position + * of the given clientX/clientY coordinates within the given element. The + * clientX and clientY coordinates are relative to the browser viewport and are + * commonly available within JavaScript event objects. The final position is + * translated to coordinates that are relative the given element. + * + * @param {!Element} element + * The element the coordinates should be relative to. + * + * @param {!number} clientX + * The viewport-relative X coordinate to translate. + * + * @param {!number} clientY + * The viewport-relative Y coordinate to translate. + * + * @returns {!Guacamole.Position} + * A new Guacamole.Position representing the relative position of the given + * client coordinates. + */ +Guacamole.Position.fromClientPosition = function fromClientPosition(element, clientX, clientY) { + var position = new Guacamole.Position(); + position.fromClientPosition(element, clientX, clientY); + return position; +}; diff --git a/src/RawAudioFormat.js b/src/RawAudioFormat.js new file mode 100644 index 0000000..b2fd6ce --- /dev/null +++ b/src/RawAudioFormat.js @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A description of the format of raw PCM audio, such as that used by + * Guacamole.RawAudioPlayer and Guacamole.RawAudioRecorder. This object + * describes the number of bytes per sample, the number of channels, and the + * overall sample rate. + * + * @constructor + * @param {!(Guacamole.RawAudioFormat|object)} template + * The object whose properties should be copied into the corresponding + * properties of the new Guacamole.RawAudioFormat. + */ +Guacamole.RawAudioFormat = function RawAudioFormat(template) { + + /** + * The number of bytes in each sample of audio data. This value is + * independent of the number of channels. + * + * @type {!number} + */ + this.bytesPerSample = template.bytesPerSample; + + /** + * The number of audio channels (ie: 1 for mono, 2 for stereo). + * + * @type {!number} + */ + this.channels = template.channels; + + /** + * The number of samples per second, per channel. + * + * @type {!number} + */ + this.rate = template.rate; + +}; + +/** + * Parses the given mimetype, returning a new Guacamole.RawAudioFormat + * which describes the type of raw audio data represented by that mimetype. If + * the mimetype is not a supported raw audio data mimetype, null is returned. + * + * @param {!string} mimetype + * The audio mimetype to parse. + * + * @returns {Guacamole.RawAudioFormat} + * A new Guacamole.RawAudioFormat which describes the type of raw + * audio data represented by the given mimetype, or null if the given + * mimetype is not supported. + */ +Guacamole.RawAudioFormat.parse = function parseFormat(mimetype) { + + var bytesPerSample; + + // Rate is absolutely required - if null is still present later, the + // mimetype must not be supported + var rate = null; + + // Default for both "audio/L8" and "audio/L16" is one channel + var channels = 1; + + // "audio/L8" has one byte per sample + if (mimetype.substring(0, 9) === 'audio/L8;') { + mimetype = mimetype.substring(9); + bytesPerSample = 1; + } + + // "audio/L16" has two bytes per sample + else if (mimetype.substring(0, 10) === 'audio/L16;') { + mimetype = mimetype.substring(10); + bytesPerSample = 2; + } + + // All other types are unsupported + else + return null; + + // Parse all parameters + var parameters = mimetype.split(','); + for (var i = 0; i < parameters.length; i++) { + + var parameter = parameters[i]; + + // All parameters must have an equals sign separating name from value + var equals = parameter.indexOf('='); + if (equals === -1) + return null; + + // Parse name and value from parameter string + var name = parameter.substring(0, equals); + var value = parameter.substring(equals+1); + + // Handle each supported parameter + switch (name) { + + // Number of audio channels + case 'channels': + channels = parseInt(value); + break; + + // Sample rate + case 'rate': + rate = parseInt(value); + break; + + // All other parameters are unsupported + default: + return null; + + } + + }; + + // The rate parameter is required + if (rate === null) + return null; + + // Return parsed format details + return new Guacamole.RawAudioFormat({ + bytesPerSample : bytesPerSample, + channels : channels, + rate : rate + }); + +}; diff --git a/src/SessionRecording.js b/src/SessionRecording.js new file mode 100644 index 0000000..619f78f --- /dev/null +++ b/src/SessionRecording.js @@ -0,0 +1,1315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A recording of a Guacamole session. Given a {@link Guacamole.Tunnel} or Blob, + * the Guacamole.SessionRecording automatically parses Guacamole instructions + * within the recording source as it plays back the recording. Playback of the + * recording may be controlled through function calls to the + * Guacamole.SessionRecording, even while the recording has not yet finished + * being created or downloaded. Parsing of the contents of the recording will + * begin immediately and automatically after this constructor is invoked. + * + * @constructor + * @param {!Blob|Guacamole.Tunnel} source + * The Blob from which the instructions of the recording should + * be read. + * @param {number} [refreshInterval=1000] + * The minimum number of milliseconds between updates to the recording + * position through the provided onseek() callback. If non-positive, this + * parameter will be ignored, and the recording position will only be + * updated when seek requests are made, or when new frames are rendered. + * If not specified, refreshInterval will default to 1000 milliseconds. + */ +Guacamole.SessionRecording = function SessionRecording(source, refreshInterval) { + + // Default the refresh interval to 1 second if not specified otherwise + if (refreshInterval === undefined) + refreshInterval = 1000; + + /** + * Reference to this Guacamole.SessionRecording. + * + * @private + * @type {!Guacamole.SessionRecording} + */ + var recording = this; + + /** + * The Blob from which the instructions of the recording should be read. + * Note that this value is initialized far below. + * + * @private + * @type {!Blob} + */ + var recordingBlob; + + /** + * The tunnel from which the recording should be read, if the recording is + * being read from a tunnel. If the recording was supplied as a Blob, this + * will be null. + * + * @private + * @type {Guacamole.Tunnel} + */ + var tunnel = null; + + /** + * The number of bytes that this Guacamole.SessionRecording should attempt + * to read from the given blob in each read operation. Larger blocks will + * generally read the blob more quickly, but may result in excessive + * time being spent within the parser, making the page unresponsive + * while the recording is loading. + * + * @private + * @constant + * @type {Number} + */ + var BLOCK_SIZE = 262144; + + /** + * The minimum number of characters which must have been read between + * keyframes. + * + * @private + * @constant + * @type {Number} + */ + var KEYFRAME_CHAR_INTERVAL = 16384; + + /** + * The minimum number of milliseconds which must elapse between keyframes. + * + * @private + * @constant + * @type {Number} + */ + var KEYFRAME_TIME_INTERVAL = 5000; + + /** + * All frames parsed from the provided blob. + * + * @private + * @type {!Guacamole.SessionRecording._Frame[]} + */ + var frames = []; + + /** + * The timestamp of the last frame which was flagged for use as a keyframe. + * If no timestamp has yet been flagged, this will be 0. + * + * @private + * @type {!number} + */ + var lastKeyframe = 0; + + /** + * Tunnel which feeds arbitrary instructions to the client used by this + * Guacamole.SessionRecording for playback of the session recording. + * + * @private + * @type {!Guacamole.SessionRecording._PlaybackTunnel} + */ + var playbackTunnel = new Guacamole.SessionRecording._PlaybackTunnel(); + + /** + * Guacamole.Client instance used for visible playback of the session + * recording. + * + * @private + * @type {!Guacamole.Client} + */ + var playbackClient = new Guacamole.Client(playbackTunnel); + + /** + * The current frame rendered within the playback client. If no frame is + * yet rendered, this will be -1. + * + * @private + * @type {!number} + */ + var currentFrame = -1; + + /** + * The position of the recording when playback began, in milliseconds. If + * playback is not in progress, this will be null. + * + * @private + * @type {number} + */ + var startVideoPosition = null; + + /** + * The real-world timestamp when playback began, in milliseconds. If + * playback is not in progress, this will be null. + * + * @private + * @type {number} + */ + var startRealTimestamp = null; + + /** + * The current position within the recording, in milliseconds. + * + * @private + * @type {!number} + */ + var currentPosition = 0; + + /** + * An object containing a single "aborted" property which is set to + * true if the in-progress seek operation should be aborted. If no seek + * operation is in progress, this will be null. + * + * @private + * @type {object} + */ + var activeSeek = null; + + /** + * The byte offset within the recording blob of the first character of + * the first instruction of the current frame. Here, "current frame" + * refers to the frame currently being parsed when the provided + * recording is initially loading. If the recording is not being + * loaded, this value has no meaning. + * + * @private + * @type {!number} + */ + var frameStart = 0; + + /** + * The byte offset within the recording blob of the character which + * follows the last character of the most recently parsed instruction + * of the current frame. Here, "current frame" refers to the frame + * currently being parsed when the provided recording is initially + * loading. If the recording is not being loaded, this value has no + * meaning. + * + * @private + * @type {!number} + */ + var frameEnd = 0; + + /** + * Whether the initial loading process has been aborted. If the loading + * process has been aborted, no further blocks of data should be read + * from the recording. + * + * @private + * @type {!boolean} + */ + var aborted = false; + + /** + * The function to invoke when the seek operation initiated by a call + * to seek() is cancelled or successfully completed. If no seek + * operation is in progress, this will be null. + * + * @private + * @type {function} + */ + var seekCallback = null; + + /** + * Any current timeout associated with scheduling frame replay, or updating + * the current position, or null if no frame position increment is currently + * scheduled. + * + * @private + * @type {number} + */ + var updateTimeout = null; + + /** + * The browser timestamp of the last time that currentPosition was updated + * while playing, or null if the recording is not currently playing. + * + * @private + * @type {number} + */ + var lastUpdateTimestamp = null; + + /** + * Parses all Guacamole instructions within the given blob, invoking + * the provided instruction callback for each such instruction. Once + * the end of the blob has been reached (no instructions remain to be + * parsed), the provided completion callback is invoked. If a parse + * error prevents reading instructions from the blob, the onerror + * callback of the Guacamole.SessionRecording is invoked, and no further + * data is handled within the blob. + * + * @private + * @param {!Blob} blob + * The blob to parse Guacamole instructions from. + * + * @param {function} [instructionCallback] + * The callback to invoke for each Guacamole instruction read from + * the given blob. This function must accept the same arguments + * as the oninstruction handler of Guacamole.Parser. + * + * @param {function} [completionCallback] + * The callback to invoke once all instructions have been read from + * the given blob. + */ + var parseBlob = function parseBlob(blob, instructionCallback, completionCallback) { + + // Do not read any further blocks if loading has been aborted + if (aborted && blob === recordingBlob) + return; + + // Prepare a parser to handle all instruction data within the blob, + // automatically invoking the provided instruction callback for all + // parsed instructions + var parser = new Guacamole.Parser(); + parser.oninstruction = instructionCallback; + + var offset = 0; + var reader = new FileReader(); + + /** + * Reads the block of data at offset bytes within the blob. If no + * such block exists, then the completion callback provided to + * parseBlob() is invoked as all data has been read. + * + * @private + */ + var readNextBlock = function readNextBlock() { + + // Do not read any further blocks if loading has been aborted + if (aborted && blob === recordingBlob) + return; + + // Parse all instructions within the block, invoking the + // onerror handler if a parse error occurs + if (reader.readyState === 2 /* DONE */) { + try { + parser.receive(reader.result); + } + catch (parseError) { + if (recording.onerror) { + recording.onerror(parseError.message); + } + return; + } + } + + // If no data remains, the read operation is complete and no + // further blocks need to be read + if (offset >= blob.size) { + if (completionCallback) + completionCallback(); + } + + // Otherwise, read the next block + else { + var block = blob.slice(offset, offset + BLOCK_SIZE); + offset += block.size; + reader.readAsText(block); + } + + }; + + // Read blocks until the end of the given blob is reached + reader.onload = readNextBlock; + readNextBlock(); + + }; + + /** + * Calculates the size of the given Guacamole instruction element, in + * Unicode characters. The size returned includes the characters which + * make up the length, the "." separator between the length and the + * element itself, and the "," or ";" terminator which follows the + * element. + * + * @private + * @param {!string} value + * The value of the element which has already been parsed (lacks + * the initial length, "." separator, and "," or ";" terminator). + * + * @returns {!number} + * The number of Unicode characters which would make up the given + * element within a Guacamole instruction. + */ + var getElementSize = function getElementSize(value) { + + var valueLength = value.length; + + // Calculate base size, assuming at least one digit, the "." + // separator, and the "," or ";" terminator + var protocolSize = valueLength + 3; + + // Add one character for each additional digit that would occur + // in the element length prefix + while (valueLength >= 10) { + protocolSize++; + valueLength = Math.floor(valueLength / 10); + } + + return protocolSize; + + }; + + // Start playback client connected + playbackClient.connect(); + + // Hide cursor unless mouse position is received + playbackClient.getDisplay().showCursor(false); + + /** + * A key event interpreter to split all key events in this recording into + * human-readable batches of text. Constrcution is deferred until the first + * event is processed, to enable recording-relative timestamps. + * + * @type {!Guacamole.KeyEventInterpreter} + */ + var keyEventInterpreter = null; + + /** + * Initialize the key interpreter. This function should be called only once + * with the first timestamp in the recording as an argument. + * + * @private + * @param {!number} startTimestamp + * The timestamp of the first frame in the recording, i.e. the start of + * the recording. + */ + function initializeKeyInterpreter(startTimestamp) { + + keyEventInterpreter = new Guacamole.KeyEventInterpreter(null, startTimestamp); + + // Pass through any received batches to the recording ontext handler + keyEventInterpreter.onbatch = function onbatch(batch) { + + // Pass the batch through if a handler is set + if (recording.ontext) + recording.ontext(batch); + + }; + } + + /** + * Handles a newly-received instruction, whether from the main Blob or a + * tunnel, adding new frames and keyframes as necessary. Load progress is + * reported via onprogress automatically. + * + * @private + * @param {!string} opcode + * The opcode of the instruction to handle. + * + * @param {!string[]} args + * The arguments of the received instruction, if any. + */ + var loadInstruction = function loadInstruction(opcode, args) { + + // Advance end of frame by overall length of parsed instruction + frameEnd += getElementSize(opcode); + for (var i = 0; i < args.length; i++) + frameEnd += getElementSize(args[i]); + + // Once a sync is received, store all instructions since the last + // frame as a new frame + if (opcode === 'sync') { + + // Parse frame timestamp from sync instruction + var timestamp = parseInt(args[0]); + + // Add a new frame containing the instructions read since last frame + var frame = new Guacamole.SessionRecording._Frame(timestamp, frameStart, frameEnd); + frames.push(frame); + frameStart = frameEnd; + + // If this is the first frame, intialize the key event interpreter + // with the timestamp of the first frame + if (frames.length === 1) + initializeKeyInterpreter(timestamp); + + // This frame should eventually become a keyframe if enough data + // has been processed and enough recording time has elapsed, or if + // this is the absolute first frame + if (frames.length === 1 || (frameEnd - frames[lastKeyframe].start >= KEYFRAME_CHAR_INTERVAL + && timestamp - frames[lastKeyframe].timestamp >= KEYFRAME_TIME_INTERVAL)) { + frame.keyframe = true; + lastKeyframe = frames.length - 1; + + } + + // Notify that additional content is available + if (recording.onprogress) + recording.onprogress(recording.getDuration(), frameEnd); + + } + + else if (opcode === 'key') + keyEventInterpreter.handleKeyEvent(args); + }; + + /** + * Notifies that the session recording has been fully loaded. If the onload + * handler has not been defined, this function has no effect. + * + * @private + */ + var notifyLoaded = function notifyLoaded() { + if (recording.onload) + recording.onload(); + }; + + // Read instructions from provided blob, extracting each frame + if (source instanceof Blob) { + recordingBlob = source; + parseBlob(recordingBlob, loadInstruction, notifyLoaded); + } + + // If tunnel provided instead of Blob, extract frames, etc. as instructions + // are received, buffering things into a Blob for future seeks + else { + + tunnel = source; + recordingBlob = new Blob(); + + var errorEncountered = false; + var instructionBuffer = ''; + + // Read instructions from provided tunnel, extracting each frame + tunnel.oninstruction = function handleInstruction(opcode, args) { + + // Reconstitute received instruction + instructionBuffer += opcode.length + '.' + opcode; + args.forEach(function appendArg(arg) { + instructionBuffer += ',' + arg.length + '.' + arg; + }); + instructionBuffer += ';'; + + // Append to Blob (creating a new Blob in the process) + if (instructionBuffer.length >= BLOCK_SIZE) { + recordingBlob = new Blob([recordingBlob, instructionBuffer]); + instructionBuffer = ''; + } + + // Load parsed instruction into recording + loadInstruction(opcode, args); + + }; + + // Report any errors encountered + tunnel.onerror = function tunnelError(status) { + errorEncountered = true; + if (recording.onerror) + recording.onerror(status.message); + }; + + tunnel.onstatechange = function tunnelStateChanged(state) { + if (state === Guacamole.Tunnel.State.CLOSED) { + + // Append any remaining instructions + if (instructionBuffer.length) { + recordingBlob = new Blob([recordingBlob, instructionBuffer]); + instructionBuffer = ''; + } + + // If there's any typed text that's yet to be sent to the ontext + // handler, send it now + var batch = keyEventInterpreter.getCurrentBatch(); + if (batch && recording.ontext) + recording.ontext(batch); + + // Consider recording loaded if tunnel has closed without errors + if (!errorEncountered) + notifyLoaded(); + } + }; + + } + + /** + * Converts the given absolute timestamp to a timestamp which is relative + * to the first frame in the recording. + * + * @private + * @param {!number} timestamp + * The timestamp to convert to a relative timestamp. + * + * @returns {!number} + * The difference in milliseconds between the given timestamp and the + * first frame of the recording, or zero if no frames yet exist. + */ + var toRelativeTimestamp = function toRelativeTimestamp(timestamp) { + + // If no frames yet exist, all timestamps are zero + if (frames.length === 0) + return 0; + + // Calculate timestamp relative to first frame + return timestamp - frames[0].timestamp; + + }; + + /** + * Searches through the given region of frames for the closest frame + * having a relative timestamp less than or equal to the to the given + * relative timestamp. + * + * @private + * @param {!number} minIndex + * The index of the first frame in the region (the frame having the + * smallest timestamp). + * + * @param {!number} maxIndex + * The index of the last frame in the region (the frame having the + * largest timestamp). + * + * @param {!number} timestamp + * The relative timestamp to search for, where zero denotes the first + * frame in the recording. + * + * @returns {!number} + * The index of the frame having a relative timestamp closest to the + * given value. + */ + var findFrame = function findFrame(minIndex, maxIndex, timestamp) { + + // The region has only one frame - determine if it is before or after + // the requested timestamp + if (minIndex === maxIndex) { + + // Skip checking if this is the very first frame - no frame could + // possibly be earlier + if (minIndex === 0) + return minIndex; + + // If the closest frame occured after the requested timestamp, + // return the previous frame, which will be the closest with a + // timestamp before the requested timestamp + if (toRelativeTimestamp(frames[minIndex].timestamp) > timestamp) + return minIndex - 1; + + } + + // Split search region into two halves + var midIndex = Math.floor((minIndex + maxIndex) / 2); + var midTimestamp = toRelativeTimestamp(frames[midIndex].timestamp); + + // If timestamp is within lesser half, search again within that half + if (timestamp < midTimestamp && midIndex > minIndex) + return findFrame(minIndex, midIndex - 1, timestamp); + + // If timestamp is within greater half, search again within that half + if (timestamp > midTimestamp && midIndex < maxIndex) + return findFrame(midIndex + 1, maxIndex, timestamp); + + // Otherwise, we lucked out and found a frame with exactly the + // desired timestamp + return midIndex; + + }; + + /** + * Replays the instructions associated with the given frame, sending those + * instructions to the playback client. + * + * @private + * @param {!number} index + * The index of the frame within the frames array which should be + * replayed. + * + * @param {function} callback + * The callback to invoke once replay of the frame has completed. + */ + var replayFrame = function replayFrame(index, callback) { + + var frame = frames[index]; + + // Replay all instructions within the retrieved frame + parseBlob(recordingBlob.slice(frame.start, frame.end), function handleInstruction(opcode, args) { + playbackTunnel.receiveInstruction(opcode, args); + }, function replayCompleted() { + + // Store client state if frame is flagged as a keyframe + if (frame.keyframe && !frame.clientState) { + playbackClient.exportState(function storeClientState(state) { + frame.clientState = new Blob([JSON.stringify(state)]); + }); + } + + // Update state to correctly represent the current frame + currentFrame = index; + + if (callback) + callback(); + + }); + + }; + + /** + * Moves the playback position to the given frame, resetting the state of + * the playback client and replaying frames as necessary. The seek + * operation will proceed asynchronously. If a seek operation is already in + * progress, that seek is first aborted. The progress of the seek operation + * can be observed through the onseek handler and the provided callback. + * + * @private + * @param {!number} index + * The index of the frame which should become the new playback + * position. + * + * @param {function} callback + * The callback to invoke once the seek operation has completed. + * + * @param {number} [nextRealTimestamp] + * The timestamp of the point in time that the given frame should be + * displayed, as would be returned by new Date().getTime(). If omitted, + * the frame will be displayed as soon as possible. + */ + var seekToFrame = function seekToFrame(index, callback, nextRealTimestamp) { + + // Abort any in-progress seek + abortSeek(); + + // Note that a new seek operation is in progress + var thisSeek = activeSeek = { + aborted : false + }; + + var startIndex = index; + + // Replay any applicable incremental frames + var continueReplay = function continueReplay() { + + // Set the current position and notify changes + if (recording.onseek && currentFrame > startIndex) { + currentPosition = toRelativeTimestamp(frames[currentFrame].timestamp); + recording.onseek(currentPosition, currentFrame - startIndex, + index - startIndex); + } + + // Cancel seek if aborted + if (thisSeek.aborted) + return; + + // If frames remain, replay the next frame + if (currentFrame < index) + replayFrame(currentFrame + 1, continueReplay); + + // Otherwise, the seek operation is completed + else + callback(); + + }; + + // Continue replay after requested delay has elapsed, or + // immediately if no delay was requested + var continueAfterRequiredDelay = function continueAfterRequiredDelay() { + var delay = nextRealTimestamp ? Math.max(nextRealTimestamp - new Date().getTime(), 0) : 0; + if (delay) { + + // Clear any already-scheduled update before scheduling again + // to avoid multiple updates in flight at the same time + updateTimeout && clearTimeout(updateTimeout); + + // Schedule with the appropriate delay + updateTimeout = window.setTimeout(function timeoutComplete() { + updateTimeout = null; + continueReplay(); + }, delay); + } + else + continueReplay(); + }; + + // Back up until startIndex represents current state + for (; startIndex >= 0; startIndex--) { + + var frame = frames[startIndex]; + + // If we've reached the current frame, startIndex represents + // current state by definition + if (startIndex === currentFrame) + break; + + // If frame has associated absolute state, make that frame the + // current state + if (frame.clientState) { + frame.clientState.text().then(function textReady(text) { + playbackClient.importState(JSON.parse(text)); + currentFrame = startIndex; + continueAfterRequiredDelay(); + }); + return; + } + + } + + continueAfterRequiredDelay(); + + }; + + /** + * Aborts the seek operation currently in progress, if any. If no seek + * operation is in progress, this function has no effect. + * + * @private + */ + var abortSeek = function abortSeek() { + if (activeSeek) { + activeSeek.aborted = true; + activeSeek = null; + } + }; + + /** + * Advances playback to the next frame in the frames array and schedules + * playback of the frame following that frame based on their associated + * timestamps. If no frames exist after the next frame, playback is paused. + * + * @private + */ + var continuePlayback = function continuePlayback() { + + // Do not continue playback if the recording is paused + if (!recording.isPlaying()) + return; + + // If frames remain after advancing, schedule next frame + if (currentFrame + 1 < frames.length) { + + // Pull the upcoming frame + var next = frames[currentFrame + 1]; + + // The number of elapsed milliseconds on the clock since playback began + var realLifePlayTime = Date.now() - startRealTimestamp; + + // The number of milliseconds between the recording position when + // playback started and the position of the next frame + var timestampOffset = ( + toRelativeTimestamp(next.timestamp) - startVideoPosition); + + // The delay until the next frame should be rendered, taking into + // account any accumulated delays from rendering frames so far + var nextFrameDelay = timestampOffset - realLifePlayTime; + + // The delay until the refresh interval would induce an update to + // the current recording position, rounded to the nearest whole + // multiple of refreshInterval to ensure consistent timing for + // refresh intervals even with inconsistent frame timing + var nextRefreshDelay = refreshInterval >= 0 + ? (refreshInterval * (Math.floor( + (currentPosition + refreshInterval) / refreshInterval)) + ) - currentPosition + : nextFrameDelay; + + // If the next frame will occur before the next refresh interval, + // advance to the frame after the appropriate delay + if (nextFrameDelay <= nextRefreshDelay) + + seekToFrame(currentFrame + 1, function frameDelayElapsed() { + + // Record when the timestamp was updated and continue on + lastUpdateTimestamp = Date.now(); + continuePlayback(); + + }, Date.now() + nextFrameDelay); + + // The position needs to be incremented before the next frame + else { + + // Clear any existing update timeout + updateTimeout && window.clearTimeout(updateTimeout); + + updateTimeout = window.setTimeout(function incrementPosition() { + + updateTimeout = null; + + // Update the position + currentPosition += nextRefreshDelay; + + // Notifiy the new position using the onseek handler + if (recording.onseek) + recording.onseek(currentPosition); + + // Record when the timestamp was updated and continue on + lastUpdateTimestamp = Date.now(); + continuePlayback(); + + }, nextRefreshDelay); + } + + } + + // Otherwise stop playback + else + recording.pause(); + + }; + + /** + * Fired when loading of this recording has completed and all frames + * are available. + * + * @event + */ + this.onload = null; + + /** + * Fired when an error occurs which prevents the recording from being + * played back. + * + * @event + * @param {!string} message + * A human-readable message describing the error that occurred. + */ + this.onerror = null; + + /** + * Fired when further loading of this recording has been explicitly + * aborted through a call to abort(). + * + * @event + */ + this.onabort = null; + + /** + * Fired when new frames have become available while the recording is + * being downloaded. + * + * @event + * @param {!number} duration + * The new duration of the recording, in milliseconds. + * + * @param {!number} parsedSize + * The number of bytes that have been loaded/parsed. + */ + this.onprogress = null; + + /** + * Fired whenever playback of the recording has started. + * + * @event + */ + this.onplay = null; + + /** + * Fired whenever playback of the recording has been paused. This may + * happen when playback is explicitly paused with a call to pause(), or + * when playback is implicitly paused due to reaching the end of the + * recording. + * + * @event + */ + this.onpause = null; + + /** + * Fired whenever a new batch of typed text extracted from key events + * is available. + * + * @event + * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch} batch + * The batch of extracted text. + */ + this.ontext = null; + + /** + * Fired whenever the playback position within the recording changes. + * + * @event + * @param {!number} position + * The new position within the recording, in milliseconds. + * + * @param {!number} current + * The number of frames that have been seeked through. If not + * seeking through multiple frames due to a call to seek(), this + * will be 1. + * + * @param {!number} total + * The number of frames that are being seeked through in the + * current seek operation. If not seeking through multiple frames + * due to a call to seek(), this will be 1. + */ + this.onseek = null; + + /** + * Connects the underlying tunnel, beginning download of the Guacamole + * session. Playback of the Guacamole session cannot occur until at least + * one frame worth of instructions has been downloaded. If the underlying + * recording source is a Blob, this function has no effect. + * + * @param {string} [data] + * The data to send to the tunnel when connecting. + */ + this.connect = function connect(data) { + if (tunnel) + tunnel.connect(data); + }; + + /** + * Disconnects the underlying tunnel, stopping further download of the + * Guacamole session. If the underlying recording source is a Blob, this + * function has no effect. + */ + this.disconnect = function disconnect() { + if (tunnel) + tunnel.disconnect(); + }; + + /** + * Aborts the loading process, stopping further processing of the + * provided data. If the underlying recording source is a Guacamole tunnel, + * it will be disconnected. + */ + this.abort = function abort() { + if (!aborted) { + + aborted = true; + if (recording.onabort) + recording.onabort(); + + if (tunnel) + tunnel.disconnect(); + + } + }; + + /** + * Returns the underlying display of the Guacamole.Client used by this + * Guacamole.SessionRecording for playback. The display contains an Element + * which can be added to the DOM, causing the display (and thus playback of + * the recording) to become visible. + * + * @return {!Guacamole.Display} + * The underlying display of the Guacamole.Client used by this + * Guacamole.SessionRecording for playback. + */ + this.getDisplay = function getDisplay() { + return playbackClient.getDisplay(); + }; + + /** + * Returns whether playback is currently in progress. + * + * @returns {!boolean} + * true if playback is currently in progress, false otherwise. + */ + this.isPlaying = function isPlaying() { + return !!startRealTimestamp; + }; + + /** + * Returns the current playback position within the recording, in + * milliseconds, where zero is the start of the recording. + * + * @returns {!number} + * The current playback position within the recording, in milliseconds. + */ + this.getPosition = function getPosition() { + + return currentPosition; + + }; + + /** + * Returns the duration of this recording, in milliseconds. If the + * recording is still being downloaded, this value will gradually increase. + * + * @returns {!number} + * The duration of this recording, in milliseconds. + */ + this.getDuration = function getDuration() { + + // If no frames yet exist, duration is zero + if (frames.length === 0) + return 0; + + // Recording duration is simply the timestamp of the last frame + return toRelativeTimestamp(frames[frames.length - 1].timestamp); + + }; + + /** + * Begins continuous playback of the recording downloaded thus far. + * Playback of the recording will continue until pause() is invoked or + * until no further frames exist. Playback is initially paused when a + * Guacamole.SessionRecording is created, and must be explicitly started + * through a call to this function. If playback is already in progress, + * this function has no effect. If a seek operation is in progress, + * playback resumes at the current position, and the seek is aborted as if + * completed. + */ + this.play = function play() { + + // If playback is not already in progress and frames remain, + // begin playback + if (!recording.isPlaying() && currentFrame + 1 < frames.length) { + + // Notify that playback is starting + if (recording.onplay) + recording.onplay(); + + // Store timestamp of playback start for relative scheduling of + // future frames + startVideoPosition = currentPosition; + startRealTimestamp = Date.now(); + + // Begin playback of video + lastUpdateTimestamp = Date.now(); + continuePlayback(); + + } + + }; + + /** + * Seeks to the given position within the recording. If the recording is + * currently being played back, playback will continue after the seek is + * performed. If the recording is currently paused, playback will be + * paused after the seek is performed. If a seek operation is already in + * progress, that seek is first aborted. The seek operation will proceed + * asynchronously. + * + * @param {!number} position + * The position within the recording to seek to, in milliseconds. + * + * @param {function} [callback] + * The callback to invoke once the seek operation has completed. + */ + this.seek = function seek(position, callback) { + + // Do not seek if no frames exist + if (frames.length === 0) + return; + + // Abort active seek operation, if any + recording.cancel(); + + // Pause playback, preserving playback state + var originallyPlaying = recording.isPlaying(); + recording.pause(); + + // Restore playback when seek is completed or cancelled + seekCallback = function restorePlaybackState() { + + // Seek is no longer in progress + seekCallback = null; + + // Restore playback state + if (originallyPlaying) { + recording.play(); + originallyPlaying = null; + } + + // Notify that seek has completed + if (callback) + callback(); + + }; + + // Find the index of the closest frame at or before the requested position + var closestFrame = findFrame(0, frames.length - 1, position); + + // Seek to the closest frame before or at the requested position + seekToFrame(closestFrame, function seekComplete() { + + // Update the current position to the requested position + // and invoke the the onseek callback. Note that this is the + // position provided to this function, NOT the position of the + // frame that was just seeked + currentPosition = position; + if (recording.onseek) + recording.onseek(position); + + seekCallback(); + + }); + + }; + + /** + * Cancels the current seek operation, setting the current frame of the + * recording to wherever the seek operation was able to reach prior to + * being cancelled. If a callback was provided to seek(), that callback + * is invoked. If a seek operation is not currently underway, this + * function has no effect. + */ + this.cancel = function cancel() { + if (seekCallback) { + abortSeek(); + seekCallback(); + } + }; + + /** + * Pauses playback of the recording, if playback is currently in progress. + * If playback is not in progress, this function has no effect. If a seek + * operation is in progress, the seek is aborted. Playback is initially + * paused when a Guacamole.SessionRecording is created, and must be + * explicitly started through a call to play(). + */ + this.pause = function pause() { + + // Abort any in-progress seek / playback + abortSeek(); + + // Cancel any currently-scheduled updates + updateTimeout && clearTimeout(updateTimeout); + + // Increment the current position by the amount of time passed since the + // the last time it was updated + currentPosition += Date.now() - lastUpdateTimestamp; + + // Stop playback only if playback is in progress + if (recording.isPlaying()) { + + // Notify that playback is stopping + if (recording.onpause) + recording.onpause(); + + // Playback is stopped + lastUpdateTimestamp = null; + startVideoPosition = null; + startRealTimestamp = null; + + } + + }; + +}; + +/** + * A single frame of Guacamole session data. Each frame is made up of the set + * of instructions used to generate that frame, and the timestamp as dictated + * by the "sync" instruction terminating the frame. Optionally, a frame may + * also be associated with a snapshot of Guacamole client state, such that the + * frame can be rendered without replaying all previous frames. + * + * @private + * @constructor + * @param {!number} timestamp + * The timestamp of this frame, as dictated by the "sync" instruction which + * terminates the frame. + * + * @param {!number} start + * The byte offset within the blob of the first character of the first + * instruction of this frame. + * + * @param {!number} end + * The byte offset within the blob of character which follows the last + * character of the last instruction of this frame. + */ +Guacamole.SessionRecording._Frame = function _Frame(timestamp, start, end) { + + /** + * Whether this frame should be used as a keyframe if possible. This value + * is purely advisory. The stored clientState must eventually be manually + * set for the frame to be used as a keyframe. By default, frames are not + * keyframes. + * + * @type {!boolean} + * @default false + */ + this.keyframe = false; + + /** + * The timestamp of this frame, as dictated by the "sync" instruction which + * terminates the frame. + * + * @type {!number} + */ + this.timestamp = timestamp; + + /** + * The byte offset within the blob of the first character of the first + * instruction of this frame. + * + * @type {!number} + */ + this.start = start; + + /** + * The byte offset within the blob of character which follows the last + * character of the last instruction of this frame. + * + * @type {!number} + */ + this.end = end; + + /** + * A snapshot of client state after this frame was rendered, as returned by + * a call to exportState(), serialized as JSON, and stored within a Blob. + * Use of Blobs here is required to ensure the browser can make use of + * larger disk-backed storage if the size of the recording is large. If no + * such snapshot has been taken, this will be null. + * + * @type {Blob} + * @default null + */ + this.clientState = null; + +}; + +/** + * A read-only Guacamole.Tunnel implementation which streams instructions + * received through explicit calls to its receiveInstruction() function. + * + * @private + * @constructor + * @augments {Guacamole.Tunnel} + */ +Guacamole.SessionRecording._PlaybackTunnel = function _PlaybackTunnel() { + + /** + * Reference to this Guacamole.SessionRecording._PlaybackTunnel. + * + * @private + * @type {!Guacamole.SessionRecording._PlaybackTunnel} + */ + var tunnel = this; + + this.connect = function connect(data) { + // Do nothing + }; + + this.sendMessage = function sendMessage(elements) { + // Do nothing + }; + + this.disconnect = function disconnect() { + // Do nothing + }; + + /** + * Invokes this tunnel's oninstruction handler, notifying users of this + * tunnel (such as a Guacamole.Client instance) that an instruction has + * been received. If the oninstruction handler has not been set, this + * function has no effect. + * + * @param {!string} opcode + * The opcode of the Guacamole instruction. + * + * @param {!string[]} args + * All arguments associated with this Guacamole instruction. + */ + this.receiveInstruction = function receiveInstruction(opcode, args) { + if (tunnel.oninstruction) + tunnel.oninstruction(opcode, args); + }; + +}; diff --git a/src/Status.js b/src/Status.js new file mode 100644 index 0000000..19b872f --- /dev/null +++ b/src/Status.js @@ -0,0 +1,322 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A Guacamole status. Each Guacamole status consists of a status code, defined + * by the protocol, and an optional human-readable message, usually only + * included for debugging convenience. + * + * @constructor + * @param {!number} code + * The Guacamole status code, as defined by Guacamole.Status.Code. + * + * @param {string} [message] + * An optional human-readable message. + */ +Guacamole.Status = function(code, message) { + + /** + * Reference to this Guacamole.Status. + * + * @private + * @type {!Guacamole.Status} + */ + var guac_status = this; + + /** + * The Guacamole status code. + * + * @see Guacamole.Status.Code + * @type {!number} + */ + this.code = code; + + /** + * An arbitrary human-readable message associated with this status, if any. + * The human-readable message is not required, and is generally provided + * for debugging purposes only. For user feedback, it is better to translate + * the Guacamole status code into a message. + * + * @type {string} + */ + this.message = message; + + /** + * Returns whether this status represents an error. + * + * @returns {!boolean} + * true if this status represents an error, false otherwise. + */ + this.isError = function() { + return guac_status.code < 0 || guac_status.code > 0x00FF; + }; + +}; + +/** + * Enumeration of all Guacamole status codes. + */ +Guacamole.Status.Code = { + + /** + * The operation succeeded. + * + * @type {!number} + */ + "SUCCESS": 0x0000, + + /** + * The requested operation is unsupported. + * + * @type {!number} + */ + "UNSUPPORTED": 0x0100, + + /** + * The operation could not be performed due to an internal failure. + * + * @type {!number} + */ + "SERVER_ERROR": 0x0200, + + /** + * The operation could not be performed as the server is busy. + * + * @type {!number} + */ + "SERVER_BUSY": 0x0201, + + /** + * The operation could not be performed because the upstream server is not + * responding. + * + * @type {!number} + */ + "UPSTREAM_TIMEOUT": 0x0202, + + /** + * The operation was unsuccessful due to an error or otherwise unexpected + * condition of the upstream server. + * + * @type {!number} + */ + "UPSTREAM_ERROR": 0x0203, + + /** + * The operation could not be performed as the requested resource does not + * exist. + * + * @type {!number} + */ + "RESOURCE_NOT_FOUND": 0x0204, + + /** + * The operation could not be performed as the requested resource is + * already in use. + * + * @type {!number} + */ + "RESOURCE_CONFLICT": 0x0205, + + /** + * The operation could not be performed as the requested resource is now + * closed. + * + * @type {!number} + */ + "RESOURCE_CLOSED": 0x0206, + + /** + * The operation could not be performed because the upstream server does + * not appear to exist. + * + * @type {!number} + */ + "UPSTREAM_NOT_FOUND": 0x0207, + + /** + * The operation could not be performed because the upstream server is not + * available to service the request. + * + * @type {!number} + */ + "UPSTREAM_UNAVAILABLE": 0x0208, + + /** + * The session within the upstream server has ended because it conflicted + * with another session. + * + * @type {!number} + */ + "SESSION_CONFLICT": 0x0209, + + /** + * The session within the upstream server has ended because it appeared to + * be inactive. + * + * @type {!number} + */ + "SESSION_TIMEOUT": 0x020A, + + /** + * The session within the upstream server has been forcibly terminated. + * + * @type {!number} + */ + "SESSION_CLOSED": 0x020B, + + /** + * The operation could not be performed because bad parameters were given. + * + * @type {!number} + */ + "CLIENT_BAD_REQUEST": 0x0300, + + /** + * Permission was denied to perform the operation, as the user is not yet + * authorized (not yet logged in, for example). + * + * @type {!number} + */ + "CLIENT_UNAUTHORIZED": 0x0301, + + /** + * Permission was denied to perform the operation, and this permission will + * not be granted even if the user is authorized. + * + * @type {!number} + */ + "CLIENT_FORBIDDEN": 0x0303, + + /** + * The client took too long to respond. + * + * @type {!number} + */ + "CLIENT_TIMEOUT": 0x0308, + + /** + * The client sent too much data. + * + * @type {!number} + */ + "CLIENT_OVERRUN": 0x030D, + + /** + * The client sent data of an unsupported or unexpected type. + * + * @type {!number} + */ + "CLIENT_BAD_TYPE": 0x030F, + + /** + * The operation failed because the current client is already using too + * many resources. + * + * @type {!number} + */ + "CLIENT_TOO_MANY": 0x031D + +}; + +/** + * Returns the Guacamole protocol status code which most closely + * represents the given HTTP status code. + * + * @param {!number} status + * The HTTP status code to translate into a Guacamole protocol status + * code. + * + * @returns {!number} + * The Guacamole protocol status code which most closely represents the + * given HTTP status code. + */ +Guacamole.Status.Code.fromHTTPCode = function fromHTTPCode(status) { + + // Translate status codes with known equivalents + switch (status) { + + // HTTP 400 - Bad request + case 400: + return Guacamole.Status.Code.CLIENT_BAD_REQUEST; + + // HTTP 403 - Forbidden + case 403: + return Guacamole.Status.Code.CLIENT_FORBIDDEN; + + // HTTP 404 - Resource not found + case 404: + return Guacamole.Status.Code.RESOURCE_NOT_FOUND; + + // HTTP 429 - Too many requests + case 429: + return Guacamole.Status.Code.CLIENT_TOO_MANY; + + // HTTP 503 - Server unavailable + case 503: + return Guacamole.Status.Code.SERVER_BUSY; + + } + + // Default all other codes to generic internal error + return Guacamole.Status.Code.SERVER_ERROR; + +}; + +/** + * Returns the Guacamole protocol status code which most closely + * represents the given WebSocket status code. + * + * @param {!number} code + * The WebSocket status code to translate into a Guacamole protocol + * status code. + * + * @returns {!number} + * The Guacamole protocol status code which most closely represents the + * given WebSocket status code. + */ +Guacamole.Status.Code.fromWebSocketCode = function fromWebSocketCode(code) { + + // Translate status codes with known equivalents + switch (code) { + + // Successful disconnect (no error) + case 1000: // Normal Closure + return Guacamole.Status.Code.SUCCESS; + + // Codes which indicate the server is not reachable + case 1006: // Abnormal Closure (also signalled by JavaScript when the connection cannot be opened in the first place) + case 1015: // TLS Handshake + return Guacamole.Status.Code.UPSTREAM_NOT_FOUND; + + // Codes which indicate the server is reachable but busy/unavailable + case 1001: // Going Away + case 1012: // Service Restart + case 1013: // Try Again Later + case 1014: // Bad Gateway + return Guacamole.Status.Code.UPSTREAM_UNAVAILABLE; + + } + + // Default all other codes to generic internal error + return Guacamole.Status.Code.SERVER_ERROR; + +}; diff --git a/src/StringReader.js b/src/StringReader.js new file mode 100644 index 0000000..37b555c --- /dev/null +++ b/src/StringReader.js @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A reader which automatically handles the given input stream, returning + * strictly text data. Note that this object will overwrite any installed event + * handlers on the given Guacamole.InputStream. + * + * @constructor + * @param {!Guacamole.InputStream} stream + * The stream that data will be read from. + */ +Guacamole.StringReader = function(stream) { + + /** + * Reference to this Guacamole.InputStream. + * + * @private + * @type {!Guacamole.StringReader} + */ + var guac_reader = this; + + /** + * Parser for received UTF-8 data. + * + * @type {!Guacamole.UTF8Parser} + */ + var utf8Parser = new Guacamole.UTF8Parser(); + + /** + * Wrapped Guacamole.ArrayBufferReader. + * + * @private + * @type {!Guacamole.ArrayBufferReader} + */ + var array_reader = new Guacamole.ArrayBufferReader(stream); + + // Receive blobs as strings + array_reader.ondata = function(buffer) { + + // Decode UTF-8 + var text = utf8Parser.decode(buffer); + + // Call handler, if present + if (guac_reader.ontext) + guac_reader.ontext(text); + + }; + + // Simply call onend when end received + array_reader.onend = function() { + if (guac_reader.onend) + guac_reader.onend(); + }; + + /** + * Fired once for every blob of text data received. + * + * @event + * @param {!string} text + * The data packet received. + */ + this.ontext = null; + + /** + * Fired once this stream is finished and no further data will be written. + * @event + */ + this.onend = null; + +}; \ No newline at end of file diff --git a/src/StringWriter.js b/src/StringWriter.js new file mode 100644 index 0000000..6daeed2 --- /dev/null +++ b/src/StringWriter.js @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * A writer which automatically writes to the given output stream with text + * data. + * + * @constructor + * @param {!Guacamole.OutputStream} stream + * The stream that data will be written to. + */ +Guacamole.StringWriter = function(stream) { + + /** + * Reference to this Guacamole.StringWriter. + * + * @private + * @type {!Guacamole.StringWriter} + */ + var guac_writer = this; + + /** + * Wrapped Guacamole.ArrayBufferWriter. + * + * @private + * @type {!Guacamole.ArrayBufferWriter} + */ + var array_writer = new Guacamole.ArrayBufferWriter(stream); + + /** + * Internal buffer for UTF-8 output. + * + * @private + * @type {!Uint8Array} + */ + var buffer = new Uint8Array(8192); + + /** + * The number of bytes currently in the buffer. + * + * @private + * @type {!number} + */ + var length = 0; + + // Simply call onack for acknowledgements + array_writer.onack = function(status) { + if (guac_writer.onack) + guac_writer.onack(status); + }; + + /** + * Expands the size of the underlying buffer by the given number of bytes, + * updating the length appropriately. + * + * @private + * @param {!number} bytes + * The number of bytes to add to the underlying buffer. + */ + function __expand(bytes) { + + // Resize buffer if more space needed + if (length+bytes >= buffer.length) { + var new_buffer = new Uint8Array((length+bytes)*2); + new_buffer.set(buffer); + buffer = new_buffer; + } + + length += bytes; + + } + + /** + * Appends a single Unicode character to the current buffer, resizing the + * buffer if necessary. The character will be encoded as UTF-8. + * + * @private + * @param {!number} codepoint + * The codepoint of the Unicode character to append. + */ + function __append_utf8(codepoint) { + + var mask; + var bytes; + + // 1 byte + if (codepoint <= 0x7F) { + mask = 0x00; + bytes = 1; + } + + // 2 byte + else if (codepoint <= 0x7FF) { + mask = 0xC0; + bytes = 2; + } + + // 3 byte + else if (codepoint <= 0xFFFF) { + mask = 0xE0; + bytes = 3; + } + + // 4 byte + else if (codepoint <= 0x1FFFFF) { + mask = 0xF0; + bytes = 4; + } + + // If invalid codepoint, append replacement character + else { + __append_utf8(0xFFFD); + return; + } + + // Offset buffer by size + __expand(bytes); + var offset = length - 1; + + // Add trailing bytes, if any + for (var i=1; i>= 6; + } + + // Set initial byte + buffer[offset] = mask | codepoint; + + } + + /** + * Encodes the given string as UTF-8, returning an ArrayBuffer containing + * the resulting bytes. + * + * @private + * @param {!string} text + * The string to encode as UTF-8. + * + * @return {!Uint8Array} + * The encoded UTF-8 data. + */ + function __encode_utf8(text) { + + // Fill buffer with UTF-8 + for (var i=0; i 0) { + var out_buffer = buffer.subarray(0, length); + length = 0; + return out_buffer; + } + + } + + /** + * Sends the given text. + * + * @param {!string} text + * The text to send. + */ + this.sendText = function(text) { + if (text.length) + array_writer.sendData(__encode_utf8(text)); + }; + + /** + * Signals that no further text will be sent, effectively closing the + * stream. + */ + this.sendEnd = function() { + array_writer.sendEnd(); + }; + + /** + * Fired for received data, if acknowledged by the server. + * + * @event + * @param {!Guacamole.Status} status + * The status of the operation. + */ + this.onack = null; + +}; \ No newline at end of file diff --git a/src/Touch.js b/src/Touch.js new file mode 100644 index 0000000..19aa76b --- /dev/null +++ b/src/Touch.js @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Provides cross-browser multi-touch events for a given element. The events of + * the given element are automatically populated with handlers that translate + * touch events into a non-browser-specific event provided by the + * Guacamole.Touch instance. + * + * @constructor + * @augments Guacamole.Event.Target + * @param {!Element} element + * The Element to use to provide touch events. + */ +Guacamole.Touch = function Touch(element) { + + Guacamole.Event.Target.call(this); + + /** + * Reference to this Guacamole.Touch. + * + * @private + * @type {!Guacamole.Touch} + */ + var guacTouch = this; + + /** + * The default X/Y radius of each touch if the device or browser does not + * expose the size of the contact area. + * + * @private + * @constant + * @type {!number} + */ + var DEFAULT_CONTACT_RADIUS = Math.floor(16 * window.devicePixelRatio); + + /** + * The set of all active touches, stored by their unique identifiers. + * + * @type {!Object.} + */ + this.touches = {}; + + /** + * The number of active touches currently stored within + * {@link Guacamole.Touch#touches touches}. + */ + this.activeTouches = 0; + + /** + * Fired whenever a new touch contact is initiated on the element + * associated with this Guacamole.Touch. + * + * @event Guacamole.Touch#touchstart + * @param {!Guacamole.Touch.Event} event + * A {@link Guacamole.Touch.Event} object representing the "touchstart" + * event. + */ + + /** + * Fired whenever an established touch contact moves within the element + * associated with this Guacamole.Touch. + * + * @event Guacamole.Touch#touchmove + * @param {!Guacamole.Touch.Event} event + * A {@link Guacamole.Touch.Event} object representing the "touchmove" + * event. + */ + + /** + * Fired whenever an established touch contact is lifted from the element + * associated with this Guacamole.Touch. + * + * @event Guacamole.Touch#touchend + * @param {!Guacamole.Touch.Event} event + * A {@link Guacamole.Touch.Event} object representing the "touchend" + * event. + */ + + element.addEventListener('touchstart', function touchstart(e) { + + // Fire "ontouchstart" events for all new touches + for (var i = 0; i < e.changedTouches.length; i++) { + + var changedTouch = e.changedTouches[i]; + var identifier = changedTouch.identifier; + + // Ignore duplicated touches + if (guacTouch.touches[identifier]) + continue; + + var touch = guacTouch.touches[identifier] = new Guacamole.Touch.State({ + id : identifier, + radiusX : changedTouch.radiusX || DEFAULT_CONTACT_RADIUS, + radiusY : changedTouch.radiusY || DEFAULT_CONTACT_RADIUS, + angle : changedTouch.angle || 0.0, + force : changedTouch.force || 1.0 /* Within JavaScript changedTouch events, a force of 0.0 indicates the device does not support reporting changedTouch force */ + }); + + guacTouch.activeTouches++; + + touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY); + guacTouch.dispatch(new Guacamole.Touch.Event('touchmove', e, touch)); + + } + + }, false); + + element.addEventListener('touchmove', function touchstart(e) { + + // Fire "ontouchmove" events for all updated touches + for (var i = 0; i < e.changedTouches.length; i++) { + + var changedTouch = e.changedTouches[i]; + var identifier = changedTouch.identifier; + + // Ignore any unrecognized touches + var touch = guacTouch.touches[identifier]; + if (!touch) + continue; + + // Update force only if supported by browser (otherwise, assume + // force is unchanged) + if (changedTouch.force) + touch.force = changedTouch.force; + + // Update touch area, if supported by browser and device + touch.angle = changedTouch.angle || 0.0; + touch.radiusX = changedTouch.radiusX || DEFAULT_CONTACT_RADIUS; + touch.radiusY = changedTouch.radiusY || DEFAULT_CONTACT_RADIUS; + + // Update with any change in position + touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY); + guacTouch.dispatch(new Guacamole.Touch.Event('touchmove', e, touch)); + + } + + }, false); + + element.addEventListener('touchend', function touchstart(e) { + + // Fire "ontouchend" events for all updated touches + for (var i = 0; i < e.changedTouches.length; i++) { + + var changedTouch = e.changedTouches[i]; + var identifier = changedTouch.identifier; + + // Ignore any unrecognized touches + var touch = guacTouch.touches[identifier]; + if (!touch) + continue; + + // Stop tracking this particular touch + delete guacTouch.touches[identifier]; + guacTouch.activeTouches--; + + // Touch has ended + touch.force = 0.0; + + // Update with final position + touch.fromClientPosition(element, changedTouch.clientX, changedTouch.clientY); + guacTouch.dispatch(new Guacamole.Touch.Event('touchend', e, touch)); + + } + + }, false); + +}; + +/** + * The current state of a touch contact. + * + * @constructor + * @augments Guacamole.Position + * @param {Guacamole.Touch.State|object} [template={}] + * The object whose properties should be copied within the new + * Guacamole.Touch.State. + */ +Guacamole.Touch.State = function State(template) { + + template = template || {}; + + Guacamole.Position.call(this, template); + + /** + * An arbitrary integer ID which uniquely identifies this contact relative + * to other active contacts. + * + * @type {!number} + * @default 0 + */ + this.id = template.id || 0; + + /** + * The Y radius of the ellipse covering the general area of the touch + * contact, in pixels. + * + * @type {!number} + * @default 0 + */ + this.radiusX = template.radiusX || 0; + + /** + * The X radius of the ellipse covering the general area of the touch + * contact, in pixels. + * + * @type {!number} + * @default 0 + */ + this.radiusY = template.radiusY || 0; + + /** + * The rough angle of clockwise rotation of the general area of the touch + * contact, in degrees. + * + * @type {!number} + * @default 0.0 + */ + this.angle = template.angle || 0.0; + + /** + * The relative force exerted by the touch contact, where 0 is no force + * (the touch has been lifted) and 1 is maximum force (the maximum amount + * of force representable by the device). + * + * @type {!number} + * @default 1.0 + */ + this.force = template.force || 1.0; + +}; + +/** + * An event which represents a change in state of a single touch contact, + * including the creation or removal of that contact. If multiple contacts are + * involved in a touch interaction, each contact will be associated with its + * own event. + * + * @constructor + * @augments Guacamole.Event.DOMEvent + * @param {!string} type + * The name of the touch event type. Possible values are "touchstart", + * "touchmove", and "touchend". + * + * @param {!TouchEvent} event + * The DOM touch event that produced this Guacamole.Touch.Event. + * + * @param {!Guacamole.Touch.State} state + * The state of the touch contact associated with this event. + */ +Guacamole.Touch.Event = function TouchEvent(type, event, state) { + + Guacamole.Event.DOMEvent.call(this, type, [ event ]); + + /** + * The state of the touch contact associated with this event. + * + * @type {!Guacamole.Touch.State} + */ + this.state = state; + +}; diff --git a/src/Tunnel.js b/src/Tunnel.js new file mode 100644 index 0000000..7f2ee0f --- /dev/null +++ b/src/Tunnel.js @@ -0,0 +1,1377 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +var Guacamole = Guacamole || {}; + +/** + * Core object providing abstract communication for Guacamole. This object + * is a null implementation whose functions do nothing. Guacamole applications + * should use {@link Guacamole.HTTPTunnel} instead, or implement their own tunnel based + * on this one. + * + * @constructor + * @see Guacamole.HTTPTunnel + */ +Guacamole.Tunnel = function() { + + /** + * Connect to the tunnel with the given optional data. This data is + * typically used for authentication. The format of data accepted is + * up to the tunnel implementation. + * + * @param {string} [data] + * The data to send to the tunnel when connecting. + */ + this.connect = function(data) {}; + + /** + * Disconnect from the tunnel. + */ + this.disconnect = function() {}; + + /** + * Send the given message through the tunnel to the service on the other + * side. All messages are guaranteed to be received in the order sent. + * + * @param {...*} elements + * The elements of the message to send to the service on the other side + * of the tunnel. + */ + this.sendMessage = function(elements) {}; + + /** + * Changes the stored numeric state of this tunnel, firing the onstatechange + * event if the new state is different and a handler has been defined. + * + * @private + * @param {!number} state + * The new state of this tunnel. + */ + this.setState = function(state) { + + // Notify only if state changes + if (state !== this.state) { + this.state = state; + if (this.onstatechange) + this.onstatechange(state); + } + + }; + + /** + * Changes the stored UUID that uniquely identifies this tunnel, firing the + * onuuid event if a handler has been defined. + * + * @private + * @param {string} uuid + * The new state of this tunnel. + */ + this.setUUID = function setUUID(uuid) { + this.uuid = uuid; + if (this.onuuid) + this.onuuid(uuid); + }; + + /** + * Returns whether this tunnel is currently connected. + * + * @returns {!boolean} + * true if this tunnel is currently connected, false otherwise. + */ + this.isConnected = function isConnected() { + return this.state === Guacamole.Tunnel.State.OPEN + || this.state === Guacamole.Tunnel.State.UNSTABLE; + }; + + /** + * The current state of this tunnel. + * + * @type {!number} + */ + this.state = Guacamole.Tunnel.State.CLOSED; + + /** + * The maximum amount of time to wait for data to be received, in + * milliseconds. If data is not received within this amount of time, + * the tunnel is closed with an error. The default value is 15000. + * + * @type {!number} + */ + this.receiveTimeout = 15000; + + /** + * The amount of time to wait for data to be received before considering + * the connection to be unstable, in milliseconds. If data is not received + * within this amount of time, the tunnel status is updated to warn that + * the connection appears unresponsive and may close. The default value is + * 1500. + * + * @type {!number} + */ + this.unstableThreshold = 1500; + + /** + * The UUID uniquely identifying this tunnel. If not yet known, this will + * be null. + * + * @type {string} + */ + this.uuid = null; + + /** + * Fired when the UUID that uniquely identifies this tunnel is known. + * + * @event + * @param {!string} + * The UUID uniquely identifying this tunnel. + */ + this.onuuid = null; + + /** + * Fired whenever an error is encountered by the tunnel. + * + * @event + * @param {!Guacamole.Status} status + * A status object which describes the error. + */ + this.onerror = null; + + /** + * Fired whenever the state of the tunnel changes. + * + * @event + * @param {!number} state + * The new state of the client. + */ + this.onstatechange = null; + + /** + * Fired once for every complete Guacamole instruction received, in order. + * + * @event + * @param {!string} opcode + * The Guacamole instruction opcode. + * + * @param {!string[]} parameters + * The parameters provided for the instruction, if any. + */ + this.oninstruction = null; + +}; + +/** + * The Guacamole protocol instruction opcode reserved for arbitrary internal + * use by tunnel implementations. The value of this opcode is guaranteed to be + * the empty string (""). Tunnel implementations may use this opcode for any + * purpose. It is currently used by the HTTP tunnel to mark the end of the HTTP + * response, and by the WebSocket tunnel to transmit the tunnel UUID and send + * connection stability test pings/responses. + * + * @constant + * @type {!string} + */ +Guacamole.Tunnel.INTERNAL_DATA_OPCODE = ''; + +/** + * All possible tunnel states. + * + * @type {!Object.} + */ +Guacamole.Tunnel.State = { + + /** + * A connection is in pending. It is not yet known whether connection was + * successful. + * + * @type {!number} + */ + "CONNECTING": 0, + + /** + * Connection was successful, and data is being received. + * + * @type {!number} + */ + "OPEN": 1, + + /** + * The connection is closed. Connection may not have been successful, the + * tunnel may have been explicitly closed by either side, or an error may + * have occurred. + * + * @type {!number} + */ + "CLOSED": 2, + + /** + * The connection is open, but communication through the tunnel appears to + * be disrupted, and the connection may close as a result. + * + * @type {!number} + */ + "UNSTABLE" : 3 + +}; + +/** + * Guacamole Tunnel implemented over HTTP via XMLHttpRequest. + * + * @constructor + * @augments Guacamole.Tunnel + * + * @param {!string} tunnelURL + * The URL of the HTTP tunneling service. + * + * @param {boolean} [crossDomain=false] + * Whether tunnel requests will be cross-domain, and thus must use CORS + * mechanisms and headers. By default, it is assumed that tunnel requests + * will be made to the same domain. + * + * @param {object} [extraTunnelHeaders={}] + * Key value pairs containing the header names and values of any additional + * headers to be sent in tunnel requests. By default, no extra headers will + * be added. + */ +Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) { + + /** + * Reference to this HTTP tunnel. + * + * @private + * @type {!Guacamole.HTTPTunnel} + */ + var tunnel = this; + + var TUNNEL_CONNECT = tunnelURL + "?connect"; + var TUNNEL_READ = tunnelURL + "?read:"; + var TUNNEL_WRITE = tunnelURL + "?write:"; + + var POLLING_ENABLED = 1; + var POLLING_DISABLED = 0; + + // Default to polling - will be turned off automatically if not needed + var pollingMode = POLLING_ENABLED; + + var sendingMessages = false; + var outputMessageBuffer = ""; + + // If requests are expected to be cross-domain, the cookie that the HTTP + // tunnel depends on will only be sent if withCredentials is true + var withCredentials = !!crossDomain; + + /** + * The current receive timeout ID, if any. + * + * @private + * @type {number} + */ + var receive_timeout = null; + + /** + * The current connection stability timeout ID, if any. + * + * @private + * @type {number} + */ + var unstableTimeout = null; + + /** + * The current connection stability test ping interval ID, if any. This + * will only be set upon successful connection. + * + * @private + * @type {number} + */ + var pingInterval = null; + + /** + * The number of milliseconds to wait between connection stability test + * pings. + * + * @private + * @constant + * @type {!number} + */ + var PING_FREQUENCY = 500; + + /** + * Additional headers to be sent in tunnel requests. This dictionary can be + * populated with key/value header pairs to pass information such as authentication + * tokens, etc. + * + * @private + * @type {!object} + */ + var extraHeaders = extraTunnelHeaders || {}; + + /** + * The name of the HTTP header containing the session token specific to the + * HTTP tunnel implementation. + * + * @private + * @constant + * @type {!string} + */ + var TUNNEL_TOKEN_HEADER = 'Guacamole-Tunnel-Token'; + + /** + * The session token currently assigned to this HTTP tunnel. All distinct + * HTTP tunnel connections will have their own dedicated session token. + * + * @private + * @type {string} + */ + var tunnelSessionToken = null; + + /** + * Adds the configured additional headers to the given request. + * + * @private + * @param {!XMLHttpRequest} request + * The request where the configured extra headers will be added. + * + * @param {!object} headers + * The headers to be added to the request. + */ + function addExtraHeaders(request, headers) { + for (var name in headers) { + request.setRequestHeader(name, headers[name]); + } + } + + /** + * Resets the state of timers tracking network activity and stability. If + * those timers are not yet started, invoking this function starts them. + * This function should be invoked when the tunnel is established and every + * time there is network activity on the tunnel, such that the timers can + * safely assume the network and/or server are not responding if this + * function has not been invoked for a significant period of time. + * + * @private + */ + var resetTimers = function resetTimers() { + + // Get rid of old timeouts (if any) + window.clearTimeout(receive_timeout); + window.clearTimeout(unstableTimeout); + + // Clear unstable status + if (tunnel.state === Guacamole.Tunnel.State.UNSTABLE) + tunnel.setState(Guacamole.Tunnel.State.OPEN); + + // Set new timeout for tracking overall connection timeout + receive_timeout = window.setTimeout(function () { + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_TIMEOUT, "Server timeout.")); + }, tunnel.receiveTimeout); + + // Set new timeout for tracking suspected connection instability + unstableTimeout = window.setTimeout(function() { + tunnel.setState(Guacamole.Tunnel.State.UNSTABLE); + }, tunnel.unstableThreshold); + + }; + + /** + * Closes this tunnel, signaling the given status and corresponding + * message, which will be sent to the onerror handler if the status is + * an error status. + * + * @private + * @param {!Guacamole.Status} status + * The status causing the connection to close; + */ + function close_tunnel(status) { + + // Get rid of old timeouts (if any) + window.clearTimeout(receive_timeout); + window.clearTimeout(unstableTimeout); + + // Cease connection test pings + window.clearInterval(pingInterval); + + // Ignore if already closed + if (tunnel.state === Guacamole.Tunnel.State.CLOSED) + return; + + // If connection closed abnormally, signal error. + if (status.code !== Guacamole.Status.Code.SUCCESS && tunnel.onerror) { + + // Ignore RESOURCE_NOT_FOUND if we've already connected, as that + // only signals end-of-stream for the HTTP tunnel. + if (tunnel.state === Guacamole.Tunnel.State.CONNECTING + || status.code !== Guacamole.Status.Code.RESOURCE_NOT_FOUND) + tunnel.onerror(status); + + } + + // Reset output message buffer + sendingMessages = false; + + // Mark as closed + tunnel.setState(Guacamole.Tunnel.State.CLOSED); + + } + + + this.sendMessage = function() { + + // Do not attempt to send messages if not connected + if (!tunnel.isConnected()) + return; + + // Do not attempt to send empty messages + if (!arguments.length) + return; + + // Add message to buffer + outputMessageBuffer += Guacamole.Parser.toInstruction(arguments); + + // Send if not currently sending + if (!sendingMessages) + sendPendingMessages(); + + }; + + function sendPendingMessages() { + + // Do not attempt to send messages if not connected + if (!tunnel.isConnected()) + return; + + if (outputMessageBuffer.length > 0) { + + sendingMessages = true; + + var message_xmlhttprequest = new XMLHttpRequest(); + message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel.uuid); + message_xmlhttprequest.withCredentials = withCredentials; + addExtraHeaders(message_xmlhttprequest, extraHeaders); + message_xmlhttprequest.setRequestHeader("Content-type", "application/octet-stream"); + message_xmlhttprequest.setRequestHeader(TUNNEL_TOKEN_HEADER, tunnelSessionToken); + + // Once response received, send next queued event. + message_xmlhttprequest.onreadystatechange = function() { + if (message_xmlhttprequest.readyState === 4) { + + resetTimers(); + + // If an error occurs during send, handle it + if (message_xmlhttprequest.status !== 200) + handleHTTPTunnelError(message_xmlhttprequest); + + // Otherwise, continue the send loop + else + sendPendingMessages(); + + } + }; + + message_xmlhttprequest.send(outputMessageBuffer); + outputMessageBuffer = ""; // Clear buffer + + } + else + sendingMessages = false; + + } + + function handleHTTPTunnelError(xmlhttprequest) { + + // Pull status code directly from headers provided by Guacamole + var code = parseInt(xmlhttprequest.getResponseHeader("Guacamole-Status-Code")); + if (code) { + var message = xmlhttprequest.getResponseHeader("Guacamole-Error-Message"); + close_tunnel(new Guacamole.Status(code, message)); + } + + // Failing that, derive a Guacamole status code from the HTTP status + // code provided by the browser + else if (xmlhttprequest.status) + close_tunnel(new Guacamole.Status( + Guacamole.Status.Code.fromHTTPCode(xmlhttprequest.status), + xmlhttprequest.statusText)); + + // Otherwise, assume server is unreachable + else + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_NOT_FOUND)); + + } + + function handleResponse(xmlhttprequest) { + + var interval = null; + var nextRequest = null; + + var dataUpdateEvents = 0; + + var parser = new Guacamole.Parser(); + parser.oninstruction = function instructionReceived(opcode, args) { + + // Switch to next request if end-of-stream is signalled + if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE && args.length === 0) { + + // Reset parser state by simply switching to an entirely new + // parser + parser = new Guacamole.Parser(); + parser.oninstruction = instructionReceived; + + // Clean up interval if polling + if (interval) + clearInterval(interval); + + // Clean up object + xmlhttprequest.onreadystatechange = null; + xmlhttprequest.abort(); + + // Start handling next request + if (nextRequest) + handleResponse(nextRequest); + + } + + // Call instruction handler. + else if (opcode !== Guacamole.Tunnel.INTERNAL_DATA_OPCODE && tunnel.oninstruction) + tunnel.oninstruction(opcode, args); + + }; + + function parseResponse() { + + // Do not handle responses if not connected + if (!tunnel.isConnected()) { + + // Clean up interval if polling + if (interval !== null) + clearInterval(interval); + + return; + } + + // Do not parse response yet if not ready + if (xmlhttprequest.readyState < 2) return; + + // Attempt to read status + var status; + try { status = xmlhttprequest.status; } + + // If status could not be read, assume successful. + catch (e) { status = 200; } + + // Start next request as soon as possible IF request was successful + if (!nextRequest && status === 200) + nextRequest = makeRequest(); + + // Parse stream when data is received and when complete. + if (xmlhttprequest.readyState === 3 || + xmlhttprequest.readyState === 4) { + + resetTimers(); + + // Also poll every 30ms (some browsers don't repeatedly call onreadystatechange for new data) + if (pollingMode === POLLING_ENABLED) { + if (xmlhttprequest.readyState === 3 && !interval) + interval = setInterval(parseResponse, 30); + else if (xmlhttprequest.readyState === 4 && interval) + clearInterval(interval); + } + + // If canceled, stop transfer + if (xmlhttprequest.status === 0) { + tunnel.disconnect(); + return; + } + + // Halt on error during request + else if (xmlhttprequest.status !== 200) { + handleHTTPTunnelError(xmlhttprequest); + return; + } + + // Attempt to read in-progress data + var current; + try { current = xmlhttprequest.responseText; } + + // Do not attempt to parse if data could not be read + catch (e) { return; } + + try { + parser.receive(current, true); + } + catch (e) { + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SERVER_ERROR, e.message)); + return; + } + + } + + } + + // If response polling enabled, attempt to detect if still + // necessary (via wrapping parseResponse()) + if (pollingMode === POLLING_ENABLED) { + xmlhttprequest.onreadystatechange = function() { + + // If we receive two or more readyState==3 events, + // there is no need to poll. + if (xmlhttprequest.readyState === 3) { + dataUpdateEvents++; + if (dataUpdateEvents >= 2) { + pollingMode = POLLING_DISABLED; + xmlhttprequest.onreadystatechange = parseResponse; + } + } + + parseResponse(); + }; + } + + // Otherwise, just parse + else + xmlhttprequest.onreadystatechange = parseResponse; + + parseResponse(); + + } + + /** + * Arbitrary integer, unique for each tunnel read request. + * @private + */ + var request_id = 0; + + function makeRequest() { + + // Make request, increment request ID + var xmlhttprequest = new XMLHttpRequest(); + xmlhttprequest.open("GET", TUNNEL_READ + tunnel.uuid + ":" + (request_id++)); + xmlhttprequest.setRequestHeader(TUNNEL_TOKEN_HEADER, tunnelSessionToken); + xmlhttprequest.withCredentials = withCredentials; + addExtraHeaders(xmlhttprequest, extraHeaders); + xmlhttprequest.send(null); + + return xmlhttprequest; + + } + + this.connect = function(data) { + + // Start waiting for connect + resetTimers(); + + // Mark the tunnel as connecting + tunnel.setState(Guacamole.Tunnel.State.CONNECTING); + + // Start tunnel and connect + var connect_xmlhttprequest = new XMLHttpRequest(); + connect_xmlhttprequest.onreadystatechange = function() { + + if (connect_xmlhttprequest.readyState !== 4) + return; + + // If failure, throw error + if (connect_xmlhttprequest.status !== 200) { + handleHTTPTunnelError(connect_xmlhttprequest); + return; + } + + resetTimers(); + + // Get UUID and HTTP-specific tunnel session token from response + tunnel.setUUID(connect_xmlhttprequest.responseText); + tunnelSessionToken = connect_xmlhttprequest.getResponseHeader(TUNNEL_TOKEN_HEADER); + + // Fail connect attempt if token is not successfully assigned + if (!tunnelSessionToken) { + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_NOT_FOUND)); + return; + } + + // Mark as open + tunnel.setState(Guacamole.Tunnel.State.OPEN); + + // Ping tunnel endpoint regularly to test connection stability + pingInterval = setInterval(function sendPing() { + tunnel.sendMessage("nop"); + }, PING_FREQUENCY); + + // Start reading data + handleResponse(makeRequest()); + + }; + + connect_xmlhttprequest.open("POST", TUNNEL_CONNECT, true); + connect_xmlhttprequest.withCredentials = withCredentials; + addExtraHeaders(connect_xmlhttprequest, extraHeaders); + connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8"); + connect_xmlhttprequest.send(data); + + }; + + this.disconnect = function() { + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SUCCESS, "Manually closed.")); + }; + +}; + +Guacamole.HTTPTunnel.prototype = new Guacamole.Tunnel(); + +/** + * Guacamole Tunnel implemented over WebSocket via XMLHttpRequest. + * + * @constructor + * @augments Guacamole.Tunnel + * @param {!string} tunnelURL + * The URL of the WebSocket tunneling service. + */ +Guacamole.WebSocketTunnel = function(tunnelURL) { + + /** + * Reference to this WebSocket tunnel. + * + * @private + * @type {Guacamole.WebSocketTunnel} + */ + var tunnel = this; + + /** + * The parser that this tunnel will use to parse received Guacamole + * instructions. The parser is created when the tunnel is (re-)connected. + * Initially, this will be null. + * + * @private + * @type {Guacamole.Parser} + */ + var parser = null; + + /** + * The WebSocket used by this tunnel. + * + * @private + * @type {WebSocket} + */ + var socket = null; + + /** + * The current receive timeout ID, if any. + * + * @private + * @type {number} + */ + var receive_timeout = null; + + /** + * The current connection stability timeout ID, if any. + * + * @private + * @type {number} + */ + var unstableTimeout = null; + + /** + * The current connection stability test ping timeout ID, if any. This + * will only be set upon successful connection. + * + * @private + * @type {number} + */ + var pingTimeout = null; + + /** + * The WebSocket protocol corresponding to the protocol used for the current + * location. + * + * @private + * @type {!Object.} + */ + var ws_protocol = { + "http:": "ws:", + "https:": "wss:" + }; + + /** + * The number of milliseconds to wait between connection stability test + * pings. + * + * @private + * @constant + * @type {!number} + */ + var PING_FREQUENCY = 500; + + /** + * The timestamp of the point in time that the last connection stability + * test ping was sent, in milliseconds elapsed since midnight of January 1, + * 1970 UTC. + * + * @private + * @type {!number} + */ + var lastSentPing = 0; + + // Transform current URL to WebSocket URL + + // If not already a websocket URL + if ( tunnelURL.substring(0, 3) !== "ws:" + && tunnelURL.substring(0, 4) !== "wss:") { + + var protocol = ws_protocol[window.location.protocol]; + + // If absolute URL, convert to absolute WS URL + if (tunnelURL.substring(0, 1) === "/") + tunnelURL = + protocol + + "//" + window.location.host + + tunnelURL; + + // Otherwise, construct absolute from relative URL + else { + + // Get path from pathname + var slash = window.location.pathname.lastIndexOf("/"); + var path = window.location.pathname.substring(0, slash + 1); + + // Construct absolute URL + tunnelURL = + protocol + + "//" + window.location.host + + path + + tunnelURL; + + } + + } + + /** + * Sends an internal "ping" instruction to the Guacamole WebSocket + * endpoint, verifying network connection stability. If the network is + * stable, the Guacamole server will receive this instruction and respond + * with an identical ping. + * + * @private + */ + var sendPing = function sendPing() { + var currentTime = new Date().getTime(); + tunnel.sendMessage(Guacamole.Tunnel.INTERNAL_DATA_OPCODE, 'ping', currentTime); + lastSentPing = currentTime; + }; + + /** + * Resets the state of timers tracking network activity and stability. If + * those timers are not yet started, invoking this function starts them. + * This function should be invoked when the tunnel is established and every + * time there is network activity on the tunnel, such that the timers can + * safely assume the network and/or server are not responding if this + * function has not been invoked for a significant period of time. + * + * @private + */ + var resetTimers = function resetTimers() { + + // Get rid of old timeouts (if any) + window.clearTimeout(receive_timeout); + window.clearTimeout(unstableTimeout); + window.clearTimeout(pingTimeout); + + // Clear unstable status + if (tunnel.state === Guacamole.Tunnel.State.UNSTABLE) + tunnel.setState(Guacamole.Tunnel.State.OPEN); + + // Set new timeout for tracking overall connection timeout + receive_timeout = window.setTimeout(function () { + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_TIMEOUT, "Server timeout.")); + }, tunnel.receiveTimeout); + + // Set new timeout for tracking suspected connection instability + unstableTimeout = window.setTimeout(function() { + tunnel.setState(Guacamole.Tunnel.State.UNSTABLE); + }, tunnel.unstableThreshold); + + var currentTime = new Date().getTime(); + var pingDelay = Math.max(lastSentPing + PING_FREQUENCY - currentTime, 0); + + // Ping tunnel endpoint regularly to test connection stability, sending + // the ping immediately if enough time has already elapsed + if (pingDelay > 0) + pingTimeout = window.setTimeout(sendPing, pingDelay); + else + sendPing(); + + }; + + /** + * Closes this tunnel, signaling the given status and corresponding + * message, which will be sent to the onerror handler if the status is + * an error status. + * + * @private + * @param {!Guacamole.Status} status + * The status causing the connection to close; + */ + function close_tunnel(status) { + + // Get rid of old timeouts (if any) + window.clearTimeout(receive_timeout); + window.clearTimeout(unstableTimeout); + window.clearTimeout(pingTimeout); + + // Ignore if already closed + if (tunnel.state === Guacamole.Tunnel.State.CLOSED) + return; + + // If connection closed abnormally, signal error. + if (status.code !== Guacamole.Status.Code.SUCCESS && tunnel.onerror) + tunnel.onerror(status); + + // Mark as closed + tunnel.setState(Guacamole.Tunnel.State.CLOSED); + + socket.close(); + + } + + this.sendMessage = function(elements) { + + // Do not attempt to send messages if not connected + if (!tunnel.isConnected()) + return; + + // Do not attempt to send empty messages + if (!arguments.length) + return; + + socket.send(Guacamole.Parser.toInstruction(arguments)); + + }; + + this.connect = function(data) { + + resetTimers(); + + // Mark the tunnel as connecting + tunnel.setState(Guacamole.Tunnel.State.CONNECTING); + + parser = new Guacamole.Parser(); + parser.oninstruction = function instructionReceived(opcode, args) { + + // Update state and UUID when first instruction received + if (tunnel.uuid === null) { + + // Associate tunnel UUID if received + if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE && args.length === 1) + tunnel.setUUID(args[0]); + + // Tunnel is now open and UUID is available + tunnel.setState(Guacamole.Tunnel.State.OPEN); + + } + + // Call instruction handler. + if (opcode !== Guacamole.Tunnel.INTERNAL_DATA_OPCODE && tunnel.oninstruction) + tunnel.oninstruction(opcode, args); + + }; + + // Connect socket + socket = new WebSocket(tunnelURL + "?" + data, "guacamole"); + + socket.onopen = function(event) { + resetTimers(); + }; + + socket.onclose = function(event) { + + // Pull status code directly from closure reason provided by Guacamole + if (event.reason) + close_tunnel(new Guacamole.Status(parseInt(event.reason), event.reason)); + + // Failing that, derive a Guacamole status code from the WebSocket + // status code provided by the browser + else if (event.code) + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.fromWebSocketCode(event.code))); + + // Otherwise, assume server is unreachable + else + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.UPSTREAM_NOT_FOUND)); + + }; + + socket.onmessage = function(event) { + + resetTimers(); + + try { + parser.receive(event.data); + } + catch (e) { + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SERVER_ERROR, e.message)); + } + + }; + + }; + + this.disconnect = function() { + close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SUCCESS, "Manually closed.")); + }; + +}; + +Guacamole.WebSocketTunnel.prototype = new Guacamole.Tunnel(); + +/** + * Guacamole Tunnel which cycles between all specified tunnels until + * no tunnels are left. Another tunnel is used if an error occurs but + * no instructions have been received. If an instruction has been + * received, or no tunnels remain, the error is passed directly out + * through the onerror handler (if defined). + * + * @constructor + * @augments Guacamole.Tunnel + * @param {...Guacamole.Tunnel} tunnelChain + * The tunnels to use, in order of priority. + */ +Guacamole.ChainedTunnel = function(tunnelChain) { + + /** + * Reference to this chained tunnel. + * @private + */ + var chained_tunnel = this; + + /** + * Data passed in via connect(), to be used for + * wrapped calls to other tunnels' connect() functions. + * @private + */ + var connect_data; + + /** + * Array of all tunnels passed to this ChainedTunnel through the + * constructor arguments. + * @private + */ + var tunnels = []; + + /** + * The tunnel committed via commit_tunnel(), if any, or null if no tunnel + * has yet been committed. + * + * @private + * @type {Guacamole.Tunnel} + */ + var committedTunnel = null; + + // Load all tunnels into array + for (var i=0; i */g, " ")} and project contributors. + + This source code is licensed under the ${license} license found in the + LICENSE file in the root directory of this source tree. +`; + +module.exports = { + mode: 'production', + devtool: 'source-map', + entry: './index.js', + output: { + filename: 'index.js', + path: path.resolve(__dirname, 'lib'), + library: 'GuacamoleCommonJS', + libraryTarget: 'umd', + clean: true + }, + optimization: { + minimize: true, + minimizer: [ + new TerserPlugin({ extractComments: false}), + ] + }, + module: { + rules: [ + { + test: /\.m?js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader' + } + }, + ] + }, + plugins: [ + new webpack.BannerPlugin(banner) + ] +};