[
  {
    "path": ".gitignore",
    "content": "# Local .terraform directories\n**/.terraform/*\n\n# .tfstate files\n*.tfstate\n*.tfstate.*\n\n# Crash log files\ncrash.log\ncrash.*.log\n\n# Exclude all .tfvars files, which are likely to contain sensitive data\n*.tfvars\n*.tfvars.json\n\n# Ignore override files as they are usually used for local development\noverride.tf\noverride.tf.json\n*_override.tf\n*_override.tf.json\n\n# Ignore CLI configuration files\n.terraformrc\nterraform.rc\n\n# Ignore kubeconfig\nkubeconfig.yaml \n\n# Ignore testing-do-not-push\ntesting-do-not-push/*\n\nuser-data.yaml\nuser-data-base64.txt\n\n# Kubernetes secrets - DO NOT COMMIT REAL CREDENTIALS\ninfra/kubernetes/cyberdesk-secret.yaml\ninfra/kubernetes/checkpoint-cyberdesk-secret.yaml\ninfra/kubernetes/golden-vm-deploy.yaml\n\n# Local development environment variables\n.env\n\ninfra/kubernetes/test-secret.yaml\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:\n\n(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and\n\n(b) You must cause any modified files to carry prominent notices stating that You changed the files; and\n\n(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and\n\n(d) If the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\nTo apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets \"[ ]\" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same \"printed page\" as the copyright notice for easier identification within third-party archives.\n\nCopyright 2024 Cyberdesk Team\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License. "
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"assets/cyberdesk-logo-with-text.png\" width=\"400\" alt=\"Cyberdesk Logo\" />\n</p>\n\n<p align=\"center\">\n  <b>The open source infra for virtual desktop orchestration, tailored for computer agents</b>\n</p>\n\n<p align=\"center\">\n  <!-- NPM Version -->\n  <a href=\"https://www.npmjs.com/package/cyberdesk\">\n    <img src=\"https://img.shields.io/npm/v/cyberdesk?color=cb3837&logo=npm\" alt=\"NPM Version\" />\n  </a>\n  <!-- NPM Downloads -->\n  <a href=\"https://www.npmjs.com/package/cyberdesk\">\n    <img src=\"https://img.shields.io/npm/dw/cyberdesk?color=cb3837&logo=npm\" alt=\"NPM Downloads\" />\n  </a>\n  <!-- PyPI Version -->\n  <a href=\"https://pypi.org/project/cyberdesk/\">\n    <img src=\"https://img.shields.io/pypi/v/cyberdesk?color=3776ab&logo=pypi\" alt=\"PyPI Version\" />\n  </a>\n  <!-- PyPI Downloads -->\n  <a href=\"https://pypi.org/project/cyberdesk/\">\n    <img src=\"https://img.shields.io/pypi/dw/cyberdesk?color=3776ab&logo=pypi\" alt=\"PyPI Downloads\" />\n  </a>\n</p>\n<p align=\"center\">\n  <!-- Discord -->\n  <a href=\"https://discord.gg/ws5ddx5yZ8\">\n    <img src=\"https://img.shields.io/discord/1228348939648004096?label=discord&logo=discord&color=5865F2\" alt=\"Discord\" />\n  </a>\n  <!-- License -->\n  <a href=\"LICENSE\">\n    <img src=\"https://img.shields.io/badge/license-Apache%202.0-blue.svg\" alt=\"License: Apache 2.0\" />\n  </a>\n  <!-- GitHub Stars (optional) -->\n  <a href=\"https://github.com/cyberdesk-hq/cyberdesk\">\n    <img src=\"https://img.shields.io/github/stars/cyberdesk-hq/cyberdesk?style=social\" alt=\"GitHub Stars\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <img src=\"assets/QuickDemo.gif\" alt=\"Cyberdesk Demo\" width=\"600\" />\n  <br>\n  <i>A computer agent operating a Cyberdesk virtual desktop from a user prompt</i>\n</p>\n\n---\n\n## 🚀 Quick Start\n\n### TypeScript\n\n```bash\nnpm install cyberdesk@0.2.1\n```\n\n```typescript\nimport { createCyberdeskClient } from 'cyberdesk';\n\nconst cyberdesk = createCyberdeskClient({ apiKey: 'YOUR_API_KEY' });\nconst launchResult = await cyberdesk.launchDesktop({ body: { timeout_ms: 10000 } });\nconst desktopId = launchResult.id;\n\n// Take a screenshot\nconst screenshot = await cyberdesk.executeComputerAction({\n  path: { id: desktopId },\n  body: { type: 'screenshot' }\n});\n\n// Left click at (100, 150)\nawait cyberdesk.executeComputerAction({\n  path: { id: desktopId },\n  body: { type: 'click_mouse', x: 100, y: 150, button: 'left' }\n});\n```\n\n### Python\n\n```bash\npip install cyberdesk==0.2.7\n```\n\n```python\nfrom cyberdesk import CyberdeskClient\nfrom cyberdesk.actions import click_mouse, screenshot, ClickMouseButton\n\nclient = CyberdeskClient(api_key=\"YOUR_API_KEY\")\nresult = client.launch_desktop(timeout_ms=10000)\ndesktop_id = result.id\n\n# Take a screenshot\nscreenshot_action = screenshot()\nscreenshot_result = client.execute_computer_action(desktop_id, screenshot_action)\n\n# Left click at (100, 150)\nclick_action = click_mouse(x=100, y=150, button=ClickMouseButton.LEFT)\nclient.execute_computer_action(desktop_id, click_action)\n```\n\n👉 For more details and advanced usage, see the [Quickstart Guide](https://docs.cyberdesk.io/docs/quickstart) and [Official Documentation](#-official-documentation).\n\n---\n\n## ✨ Features\n\n<div align=\"center\">\n\n<table>\n  <tr>\n    <td align=\"center\" width=\"260\"><br><b>🚀 Fast Launch</b><br><sub>Spin up virtual desktops in seconds, ready for automation or remote use.</sub><br><br></td>\n    <td align=\"center\" width=\"260\"><br><b>🖱️ Full Automation</b><br><sub>Control mouse, keyboard, and more—perfect for computer agents.</sub><br><br></td>\n    <td align=\"center\" width=\"260\"><br><b>🖥️ Cloud Native</b><br><sub>Runs on AKS, or self-hosted on your own infrastructure.</sub><br><br></td>\n  </tr>\n  <tr>\n    <td align=\"center\" width=\"260\"><br><b>🔒 Secure & Auditable</b><br><sub>Session logs, API keys, and enterprise-grade security.</sub><br><br></td>\n    <td align=\"center\" width=\"260\"><br><b>🧩 Type-Safe SDKs</b><br><sub>Official Python & TypeScript SDKs with full type hints.</sub><br><br></td>\n    <td align=\"center\" width=\"260\"><br><b>🤖 AI-Ready</b><br><sub>Tailor built for the next generation of computer use agents</sub><br><br></td>\n  </tr>\n</table>\n\n</div>\n\n---\n\n## 📚 Official Documentation\n\n- [Quickstart Guide](https://docs.cyberdesk.io/docs/quickstart)\n- [API Reference](https://docs.cyberdesk.io/docs/api-reference)\n- [TypeScript SDK](sdks/ts-sdk/README.md)\n- [Python SDK](sdks/py-sdk/README.md)\n\n---\n\n## 🛠️ Project Structure\n\n### /apps\n- **web**: Landing page and dashboard ([README](apps/web/README.md))\n- **api**: Developer-facing API ([README](apps/api/README.md))\n- **docs**: Documentation site ([README](apps/docs/README.md))\n\n### /services\n- **cyberdesk-operator**: Kubernetes operator for managing Cyberdesk Custom Resources, and starting/stopping Kubevirt virtual machines ([README](services/cyberdesk-operator/README.md))\n- **gateway**: HTTP service that proxies requests to the Kubevirt API, and routes them to the correct virtual machine ([README](services/gateway/README.md))\n\n### /sdks\n- **ts-sdk**: TypeScript SDK ([README](sdks/ts-sdk/README.md))\n- **py-sdk**: Python SDK ([README](sdks/py-sdk/README.md))\n\n### /infrastructure\n- **terraform**: AKS Cluster Setup (Terraform) ([README](infrastructure/README.md))\n- **kubernetes**: Kubernetes resources for the Cyberdesk operator\n\n---\n\n## 🤝 Contributing\n\nWe welcome contributions!\n- Join the [Discord](https://discord.gg/ws5ddx5yZ8) for discussion and support\n- Get a personal 1-1 walkthrough of how to self host the project by contacting us on [Discord](https://discord.gg/ws5ddx5yZ8)\n\n---\n\n## 📣 Community & Support\n\n- [Discord](https://discord.gg/ws5ddx5yZ8) for help and chat\n- [Good First Issues](https://github.com/cyberdesk-hq/cyberdesk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)\n- [Open an Issue](https://github.com/cyberdesk-hq/cyberdesk/issues)\n\n---\n\n## 💡 Philosophy\n\n> At **Cyberdesk** our mission is to make building computer agents as easy as playing with legos. We believe in open, simple, and extensible tools for the new generation of developers: *computer agent developers*.\n\n---\n\n## 📄 License\n\nApache License 2.0. See [LICENSE](LICENSE).\n\n---\n\n<p align=\"center\">\n  <sub>Made with ❤️ by the Cyberdesk Team</sub>\n</p>\n"
  },
  {
    "path": "apps/api/.dockerignore",
    "content": "/.git\n/node_modules\n.dockerignore\n.env\nDockerfile\nfly.toml\n"
  },
  {
    "path": "apps/api/.gitignore",
    "content": "# prod\ndist/\n\n# dev\n.yarn/\n!.yarn/releases\n.vscode/*\n!.vscode/launch.json\n!.vscode/*.code-snippets\n.idea/workspace.xml\n.idea/usage.statistics.xml\n.idea/shelf\n\n# deps\nnode_modules/\n.wrangler\n\n# env\n.env\n.env.production\n.dev.vars\n\n# logs\nlogs/\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\n# misc\n.DS_Store\n\n# turbo\n.turbo\n"
  },
  {
    "path": "apps/api/Dockerfile",
    "content": "# syntax = docker/dockerfile:1\n\n# Adjust NODE_VERSION as desired\nARG NODE_VERSION=20.18.0\nFROM node:${NODE_VERSION}-slim AS base\n\nLABEL fly_launch_runtime=\"Node.js\"\n\n# Node.js app lives here\nWORKDIR /app\n\n# Set production environment \nENV NODE_ENV=\"production\"\n\n\n# Throw-away build stage to reduce size of final image\nFROM base AS build\n\n# Install packages needed to build node modules\nRUN apt-get update -qq && \\\n    apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3\n\n# Install node modules\nCOPY package.json ./\nRUN npm install --include=dev\n\n# Copy application code\nCOPY . .\n\n# Build application\nRUN npm run build\n\n# Remove development dependencies\nRUN npm prune --omit=dev\n\n\n# Final stage for app image\nFROM base\n\n# Copy built application\nCOPY --from=build /app /app\n\n# Start the server by default, this can be overwritten at runtime\nEXPOSE 3000\nCMD [ \"npm\", \"run\", \"start\" ]\n"
  },
  {
    "path": "apps/api/README.md",
    "content": "# Created by Unkey's toolbox\n\nThis API is built with speed, and security in mind. The API is built with [Hono](https://hono.dev), [Unkey](https://unkey.com) and [Supabase](https://supabase.com) with hosting on [Fly.io](https://fly.io).\n\n## Getting Started\n\nYou will need a free account for both Unkey and Supabase to run this project.\n\n### Unkey\n\nFor Unkey you will need your API ID and a root key scoped to:\n\n- Create Key\n- Create Namespace\n- Limit\n\nYou can of course add more scopes as required.\n\n### Supabase\n\nFor Supabase, you'll need to create a project and get your:\n\n- Supabase URL\n- Supabase Anon Key\n- Supabase Connection String (found in the Database settings under Connection Pooling) \n\n## Environment Variables\n\nTo run this project, you will need to add the following environment variables to your .dev.vars file\n\n```env\nSUPABASE_URL=https://your-project-id.supabase.co\nSUPABASE_ANON_KEY=your-supabase-anon-key\nSUPABASE_CONNECTION_STRING=postgres://postgres:your-password@your-project-id.supabase.co:5432/postgres?pgbouncer=true\nUNKEY_API_ID=UNKEY_API_ID\nUNKEY_ROOT_KEY=UNKEY_ROOT_KEY\n```\n\n## Usage\n\nMake sure that you have run:\n\n```bash\nnpm run db:generate\nnpm run db:push\n```\n\nYou can then run `npm run dev`\n\nThen you will have access to the following routes:\n\n`/keys/create` - To create an API key to use with the other endpoints.\n\nThen the desktop routes (all under the `/v1` prefix):\n\n```bash\n/v1/desktop                      # Create a new desktop instance\n/v1/desktop/{id}/stop            # Stop a desktop instance\n/v1/desktop/{id}/computer-action # Perform a computer action\n/v1/desktop/{id}/bash-action     # Execute a bash command\n```\n\nYou also have access to the open-api spec found at [http://localhost:8787/open-api](http://localhost:8787/open-api)\n"
  },
  {
    "path": "apps/api/drizzle/migrations/0000_oval_outlaw_kid.sql",
    "content": "CREATE TABLE IF NOT EXISTS \"desktop_instances\" (\n\t\"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,\n\t\"user_id\" uuid NOT NULL,\n\t\"created_at\" timestamp DEFAULT now(),\n\t\"ended_at\" timestamp\n);\n--> statement-breakpoint\nDO $$ BEGIN\n ALTER TABLE \"desktop_instances\" ADD CONSTRAINT \"desktop_instances_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"auth\".\"users\"(\"id\") ON DELETE no action ON UPDATE no action;\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;\n"
  },
  {
    "path": "apps/api/drizzle/migrations/0001_busy_warstar.sql",
    "content": "ALTER TABLE \"desktop_instances\" ADD COLUMN \"remote_id\" varchar(255);"
  },
  {
    "path": "apps/api/drizzle/migrations/0002_regular_doctor_faustus.sql",
    "content": "CREATE TABLE IF NOT EXISTS \"profiles\" (\n\t\"id\" uuid PRIMARY KEY NOT NULL,\n\t\"unkey_key_id\" varchar(255),\n\t\"stripe_customer_id\" varchar(255),\n\t\"stripe_subscription_id\" varchar(255),\n\t\"current_period_end\" timestamp,\n\t\"subscription_status\" varchar(50),\n\t\"plan_id\" varchar(100),\n\t\"cancel_at_period_end\" boolean DEFAULT false,\n\t\"created_at\" timestamp DEFAULT now(),\n\t\"updated_at\" timestamp DEFAULT now()\n);\n--> statement-breakpoint\nALTER TABLE \"desktop_instances\" ALTER COLUMN \"remote_id\" SET NOT NULL;--> statement-breakpoint\nDO $$ BEGIN\n ALTER TABLE \"profiles\" ADD CONSTRAINT \"profiles_id_users_id_fk\" FOREIGN KEY (\"id\") REFERENCES \"auth\".\"users\"(\"id\") ON DELETE no action ON UPDATE no action;\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;\n"
  },
  {
    "path": "apps/api/drizzle/migrations/0003_superb_betty_brant.sql",
    "content": "ALTER TABLE \"desktop_instances\" ADD COLUMN \"stream_url\" varchar(1024) NOT NULL;"
  },
  {
    "path": "apps/api/drizzle/migrations/0004_simple_komodo.sql",
    "content": "ALTER TABLE \"desktop_instances\" ALTER COLUMN \"stream_url\" SET DEFAULT 'https://placeholder-stream-url.cyberdesk.dev';"
  },
  {
    "path": "apps/api/drizzle/migrations/0005_mighty_hiroim.sql",
    "content": "ALTER TABLE \"desktop_instances\" DROP CONSTRAINT \"desktop_instances_user_id_users_id_fk\";\n--> statement-breakpoint\nALTER TABLE \"profiles\" DROP CONSTRAINT \"profiles_id_users_id_fk\";\n--> statement-breakpoint\nDO $$ BEGIN\n ALTER TABLE \"desktop_instances\" ADD CONSTRAINT \"desktop_instances_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"auth\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;\n--> statement-breakpoint\nDO $$ BEGIN\n ALTER TABLE \"profiles\" ADD CONSTRAINT \"profiles_id_users_id_fk\" FOREIGN KEY (\"id\") REFERENCES \"auth\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;\n"
  },
  {
    "path": "apps/api/drizzle/migrations/0006_icy_black_bird.sql",
    "content": "DO $$ BEGIN\n CREATE TYPE \"public\".\"instance_status\" AS ENUM('pending', 'running', 'completed', 'error');\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;\n--> statement-breakpoint\nCREATE TABLE IF NOT EXISTS \"cyberdesk_instances\" (\n\t\"id\" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,\n\t\"user_id\" uuid NOT NULL,\n\t\"created_at\" timestamp DEFAULT now(),\n\t\"updated_at\" timestamp,\n\t\"status\" \"instance_status\" DEFAULT 'pending' NOT NULL,\n\t\"timeout_at\" timestamp DEFAULT NOW() + interval '24 hours' NOT NULL,\n\t\"stream_url\" varchar(1024)\n);\n--> statement-breakpoint\nDO $$ BEGIN\n ALTER TABLE \"cyberdesk_instances\" ADD CONSTRAINT \"cyberdesk_instances_user_id_users_id_fk\" FOREIGN KEY (\"user_id\") REFERENCES \"auth\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;\n"
  },
  {
    "path": "apps/api/drizzle/migrations/0007_panoramic_tomorrow_man.sql",
    "content": "ALTER TYPE \"instance_status\" ADD VALUE 'terminated';"
  },
  {
    "path": "apps/api/drizzle/migrations/meta/0000_snapshot.json",
    "content": "{\n  \"id\": \"1816b590-90c5-4f05-9726-cc8f33d18251\",\n  \"prevId\": \"00000000-0000-0000-0000-000000000000\",\n  \"version\": \"6\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.desktop_instances\": {\n      \"name\": \"desktop_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"ended_at\": {\n          \"name\": \"ended_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"desktop_instances_user_id_users_id_fk\": {\n          \"name\": \"desktop_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"desktop_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "apps/api/drizzle/migrations/meta/0001_snapshot.json",
    "content": "{\n  \"id\": \"61aff1d0-bd56-4301-852c-bc67c46a1cd9\",\n  \"prevId\": \"1816b590-90c5-4f05-9726-cc8f33d18251\",\n  \"version\": \"6\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.desktop_instances\": {\n      \"name\": \"desktop_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"remote_id\": {\n          \"name\": \"remote_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"ended_at\": {\n          \"name\": \"ended_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"desktop_instances_user_id_users_id_fk\": {\n          \"name\": \"desktop_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"desktop_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "apps/api/drizzle/migrations/meta/0002_snapshot.json",
    "content": "{\n  \"id\": \"4d86e969-3e44-4e26-bb6d-b76b9daa733e\",\n  \"prevId\": \"61aff1d0-bd56-4301-852c-bc67c46a1cd9\",\n  \"version\": \"6\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.desktop_instances\": {\n      \"name\": \"desktop_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"remote_id\": {\n          \"name\": \"remote_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"ended_at\": {\n          \"name\": \"ended_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"desktop_instances_user_id_users_id_fk\": {\n          \"name\": \"desktop_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"desktop_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    },\n    \"public.profiles\": {\n      \"name\": \"profiles\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"unkey_key_id\": {\n          \"name\": \"unkey_key_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_customer_id\": {\n          \"name\": \"stripe_customer_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_subscription_id\": {\n          \"name\": \"stripe_subscription_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"current_period_end\": {\n          \"name\": \"current_period_end\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"subscription_status\": {\n          \"name\": \"subscription_status\",\n          \"type\": \"varchar(50)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"plan_id\": {\n          \"name\": \"plan_id\",\n          \"type\": \"varchar(100)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"cancel_at_period_end\": {\n          \"name\": \"cancel_at_period_end\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"profiles_id_users_id_fk\": {\n          \"name\": \"profiles_id_users_id_fk\",\n          \"tableFrom\": \"profiles\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "apps/api/drizzle/migrations/meta/0003_snapshot.json",
    "content": "{\n  \"id\": \"9b509c40-ab3f-410b-ac01-9401df6598c2\",\n  \"prevId\": \"4d86e969-3e44-4e26-bb6d-b76b9daa733e\",\n  \"version\": \"6\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.desktop_instances\": {\n      \"name\": \"desktop_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"remote_id\": {\n          \"name\": \"remote_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"stream_url\": {\n          \"name\": \"stream_url\",\n          \"type\": \"varchar(1024)\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"ended_at\": {\n          \"name\": \"ended_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"desktop_instances_user_id_users_id_fk\": {\n          \"name\": \"desktop_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"desktop_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    },\n    \"public.profiles\": {\n      \"name\": \"profiles\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"unkey_key_id\": {\n          \"name\": \"unkey_key_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_customer_id\": {\n          \"name\": \"stripe_customer_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_subscription_id\": {\n          \"name\": \"stripe_subscription_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"current_period_end\": {\n          \"name\": \"current_period_end\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"subscription_status\": {\n          \"name\": \"subscription_status\",\n          \"type\": \"varchar(50)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"plan_id\": {\n          \"name\": \"plan_id\",\n          \"type\": \"varchar(100)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"cancel_at_period_end\": {\n          \"name\": \"cancel_at_period_end\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"profiles_id_users_id_fk\": {\n          \"name\": \"profiles_id_users_id_fk\",\n          \"tableFrom\": \"profiles\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "apps/api/drizzle/migrations/meta/0004_snapshot.json",
    "content": "{\n  \"id\": \"36e12ff1-cb7e-46bd-bdc4-796464df0849\",\n  \"prevId\": \"9b509c40-ab3f-410b-ac01-9401df6598c2\",\n  \"version\": \"6\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.desktop_instances\": {\n      \"name\": \"desktop_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"remote_id\": {\n          \"name\": \"remote_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"stream_url\": {\n          \"name\": \"stream_url\",\n          \"type\": \"varchar(1024)\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'https://placeholder-stream-url.cyberdesk.io'\"\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"ended_at\": {\n          \"name\": \"ended_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"desktop_instances_user_id_users_id_fk\": {\n          \"name\": \"desktop_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"desktop_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    },\n    \"public.profiles\": {\n      \"name\": \"profiles\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"unkey_key_id\": {\n          \"name\": \"unkey_key_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_customer_id\": {\n          \"name\": \"stripe_customer_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_subscription_id\": {\n          \"name\": \"stripe_subscription_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"current_period_end\": {\n          \"name\": \"current_period_end\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"subscription_status\": {\n          \"name\": \"subscription_status\",\n          \"type\": \"varchar(50)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"plan_id\": {\n          \"name\": \"plan_id\",\n          \"type\": \"varchar(100)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"cancel_at_period_end\": {\n          \"name\": \"cancel_at_period_end\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"profiles_id_users_id_fk\": {\n          \"name\": \"profiles_id_users_id_fk\",\n          \"tableFrom\": \"profiles\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"no action\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "apps/api/drizzle/migrations/meta/0005_snapshot.json",
    "content": "{\n  \"id\": \"0efe305c-c062-4173-bc2d-8d92af93250f\",\n  \"prevId\": \"36e12ff1-cb7e-46bd-bdc4-796464df0849\",\n  \"version\": \"6\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.desktop_instances\": {\n      \"name\": \"desktop_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"remote_id\": {\n          \"name\": \"remote_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"stream_url\": {\n          \"name\": \"stream_url\",\n          \"type\": \"varchar(1024)\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'https://placeholder-stream-url.cyberdesk.io'\"\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"ended_at\": {\n          \"name\": \"ended_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"desktop_instances_user_id_users_id_fk\": {\n          \"name\": \"desktop_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"desktop_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    },\n    \"public.profiles\": {\n      \"name\": \"profiles\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"unkey_key_id\": {\n          \"name\": \"unkey_key_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_customer_id\": {\n          \"name\": \"stripe_customer_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_subscription_id\": {\n          \"name\": \"stripe_subscription_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"current_period_end\": {\n          \"name\": \"current_period_end\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"subscription_status\": {\n          \"name\": \"subscription_status\",\n          \"type\": \"varchar(50)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"plan_id\": {\n          \"name\": \"plan_id\",\n          \"type\": \"varchar(100)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"cancel_at_period_end\": {\n          \"name\": \"cancel_at_period_end\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"profiles_id_users_id_fk\": {\n          \"name\": \"profiles_id_users_id_fk\",\n          \"tableFrom\": \"profiles\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    }\n  },\n  \"enums\": {},\n  \"schemas\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "apps/api/drizzle/migrations/meta/0006_snapshot.json",
    "content": "{\n  \"id\": \"1f1576f8-a386-4198-a4b4-dbe02dbc8468\",\n  \"prevId\": \"0efe305c-c062-4173-bc2d-8d92af93250f\",\n  \"version\": \"6\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.cyberdesk_instances\": {\n      \"name\": \"cyberdesk_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"status\": {\n          \"name\": \"status\",\n          \"type\": \"instance_status\",\n          \"typeSchema\": \"public\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'pending'\"\n        },\n        \"timeout_at\": {\n          \"name\": \"timeout_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"NOW() + interval '24 hours'\"\n        },\n        \"stream_url\": {\n          \"name\": \"stream_url\",\n          \"type\": \"varchar(1024)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"cyberdesk_instances_user_id_users_id_fk\": {\n          \"name\": \"cyberdesk_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"cyberdesk_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    },\n    \"public.desktop_instances\": {\n      \"name\": \"desktop_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"remote_id\": {\n          \"name\": \"remote_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"stream_url\": {\n          \"name\": \"stream_url\",\n          \"type\": \"varchar(1024)\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'https://placeholder-stream-url.cyberdesk.io'\"\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"ended_at\": {\n          \"name\": \"ended_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"desktop_instances_user_id_users_id_fk\": {\n          \"name\": \"desktop_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"desktop_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    },\n    \"public.profiles\": {\n      \"name\": \"profiles\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"unkey_key_id\": {\n          \"name\": \"unkey_key_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_customer_id\": {\n          \"name\": \"stripe_customer_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_subscription_id\": {\n          \"name\": \"stripe_subscription_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"current_period_end\": {\n          \"name\": \"current_period_end\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"subscription_status\": {\n          \"name\": \"subscription_status\",\n          \"type\": \"varchar(50)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"plan_id\": {\n          \"name\": \"plan_id\",\n          \"type\": \"varchar(100)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"cancel_at_period_end\": {\n          \"name\": \"cancel_at_period_end\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"profiles_id_users_id_fk\": {\n          \"name\": \"profiles_id_users_id_fk\",\n          \"tableFrom\": \"profiles\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    }\n  },\n  \"enums\": {\n    \"public.instance_status\": {\n      \"name\": \"instance_status\",\n      \"schema\": \"public\",\n      \"values\": [\n        \"pending\",\n        \"running\",\n        \"completed\",\n        \"error\"\n      ]\n    }\n  },\n  \"schemas\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "apps/api/drizzle/migrations/meta/0007_snapshot.json",
    "content": "{\n  \"id\": \"39ccedc0-27c0-448a-971b-3217e72e8dfe\",\n  \"prevId\": \"1f1576f8-a386-4198-a4b4-dbe02dbc8468\",\n  \"version\": \"6\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.cyberdesk_instances\": {\n      \"name\": \"cyberdesk_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"status\": {\n          \"name\": \"status\",\n          \"type\": \"instance_status\",\n          \"typeSchema\": \"public\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'pending'\"\n        },\n        \"timeout_at\": {\n          \"name\": \"timeout_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"NOW() + interval '24 hours'\"\n        },\n        \"stream_url\": {\n          \"name\": \"stream_url\",\n          \"type\": \"varchar(1024)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"cyberdesk_instances_user_id_users_id_fk\": {\n          \"name\": \"cyberdesk_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"cyberdesk_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    },\n    \"public.desktop_instances\": {\n      \"name\": \"desktop_instances\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"default\": \"gen_random_uuid()\"\n        },\n        \"remote_id\": {\n          \"name\": \"remote_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"user_id\": {\n          \"name\": \"user_id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"stream_url\": {\n          \"name\": \"stream_url\",\n          \"type\": \"varchar(1024)\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"'https://placeholder-stream-url.cyberdesk.io'\"\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"ended_at\": {\n          \"name\": \"ended_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"desktop_instances_user_id_users_id_fk\": {\n          \"name\": \"desktop_instances_user_id_users_id_fk\",\n          \"tableFrom\": \"desktop_instances\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"user_id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    },\n    \"public.profiles\": {\n      \"name\": \"profiles\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"id\": {\n          \"name\": \"id\",\n          \"type\": \"uuid\",\n          \"primaryKey\": true,\n          \"notNull\": true\n        },\n        \"unkey_key_id\": {\n          \"name\": \"unkey_key_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_customer_id\": {\n          \"name\": \"stripe_customer_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"stripe_subscription_id\": {\n          \"name\": \"stripe_subscription_id\",\n          \"type\": \"varchar(255)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"current_period_end\": {\n          \"name\": \"current_period_end\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"subscription_status\": {\n          \"name\": \"subscription_status\",\n          \"type\": \"varchar(50)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"plan_id\": {\n          \"name\": \"plan_id\",\n          \"type\": \"varchar(100)\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"cancel_at_period_end\": {\n          \"name\": \"cancel_at_period_end\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": false\n        },\n        \"created_at\": {\n          \"name\": \"created_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        },\n        \"updated_at\": {\n          \"name\": \"updated_at\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false,\n          \"default\": \"now()\"\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {\n        \"profiles_id_users_id_fk\": {\n          \"name\": \"profiles_id_users_id_fk\",\n          \"tableFrom\": \"profiles\",\n          \"tableTo\": \"users\",\n          \"schemaTo\": \"auth\",\n          \"columnsFrom\": [\n            \"id\"\n          ],\n          \"columnsTo\": [\n            \"id\"\n          ],\n          \"onDelete\": \"cascade\",\n          \"onUpdate\": \"no action\"\n        }\n      },\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {}\n    }\n  },\n  \"enums\": {\n    \"public.instance_status\": {\n      \"name\": \"instance_status\",\n      \"schema\": \"public\",\n      \"values\": [\n        \"pending\",\n        \"running\",\n        \"terminated\",\n        \"error\"\n      ]\n    }\n  },\n  \"schemas\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "apps/api/drizzle/migrations/meta/_journal.json",
    "content": "{\n  \"version\": \"5\",\n  \"dialect\": \"postgresql\",\n  \"entries\": [\n    {\n      \"idx\": 0,\n      \"version\": \"6\",\n      \"when\": 1743009299784,\n      \"tag\": \"0000_oval_outlaw_kid\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 1,\n      \"version\": \"6\",\n      \"when\": 1743018990086,\n      \"tag\": \"0001_busy_warstar\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 2,\n      \"version\": \"6\",\n      \"when\": 1743040137195,\n      \"tag\": \"0002_regular_doctor_faustus\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 3,\n      \"version\": \"6\",\n      \"when\": 1743119400161,\n      \"tag\": \"0003_superb_betty_brant\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 4,\n      \"version\": \"6\",\n      \"when\": 1743119816127,\n      \"tag\": \"0004_simple_komodo\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 5,\n      \"version\": \"6\",\n      \"when\": 1743122830319,\n      \"tag\": \"0005_mighty_hiroim\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 6,\n      \"version\": \"6\",\n      \"when\": 1744919270291,\n      \"tag\": \"0006_icy_black_bird\",\n      \"breakpoints\": true\n    },\n    {\n      \"idx\": 7,\n      \"version\": \"6\",\n      \"when\": 1745534258042,\n      \"tag\": \"0007_panoramic_tomorrow_man\",\n      \"breakpoints\": true\n    }\n  ]\n}"
  },
  {
    "path": "apps/api/drizzle.config.ts",
    "content": "import { defineConfig } from \"drizzle-kit\";\n\nexport default defineConfig({\n  schema: \"./src/db/supabase.ts\",\n  schemaFilter: [\"public\"],\n  out: \"./drizzle/migrations\",\n  dialect: \"postgresql\",\n  dbCredentials: {\n    url: process.env.SUPABASE_CONNECTION_STRING!,\n  },\n});\n"
  },
  {
    "path": "apps/api/fly.toml",
    "content": "# fly.toml app configuration file generated for cyberdesk-mvp-backend on 2025-03-27T05:00:43Z\n#\n# See https://fly.io/docs/reference/configuration/ for information about how to use this file.\n#\n\napp = 'cyberdesk-mvp-backend'\nprimary_region = 'dfw'\n\n[build]\n\n[http_service]\n  internal_port = 3000\n  force_https = true\n  auto_stop_machines = 'stop'\n  auto_start_machines = true\n  min_machines_running = 0\n  processes = ['app']\n\n[[vm]]\n  memory = '1gb'\n  cpu_kind = 'shared'\n  cpus = 1\n  memory_mb = 1024\n"
  },
  {
    "path": "apps/api/package.json",
    "content": "{\n  \"name\": \"api\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"dev\": \"tsx watch src/index.ts\",\n    \"start\": \"node dist/index.js\",\n    \"env\": \"dotenv\",\n    \"db:push\": \"npm run env -- drizzle-kit push\",\n    \"db:studio\": \"npm run env -- drizzle-kit studio\",\n    \"db:generate\": \"npm run env -- drizzle-kit generate\",\n    \"db:migrate\": \"npm run env -- drizzle-kit migrate\"\n  },\n  \"dependencies\": {\n    \"@hono/node-server\": \"^1.14.0\",\n    \"@hono/zod-openapi\": \"^0.14.5\",\n    \"@libsql/client\": \"^0.6.2\",\n    \"@supabase/supabase-js\": \"^2.49.3\",\n    \"@unkey/api\": \"^0.20.7\",\n    \"@unkey/cache\": \"^1.0.2\",\n    \"@unkey/hono\": \"^1.2.0\",\n    \"@unkey/ratelimit\": \"^0.1.12\",\n    \"axios\": \"^1.9.0\",\n    \"dotenv\": \"^16.4.7\",\n    \"drizzle-orm\": \"^0.30.10\",\n    \"hono\": \"^4.4.7\",\n    \"postgres\": \"^3.4.5\",\n    \"posthog-node\": \"^4.17.1\",\n    \"zod\": \"^3.23.8\"\n  },\n  \"devDependencies\": {\n    \"@cloudflare/workers-types\": \"^4.20240529.0\",\n    \"@types/node\": \"^22.15.0\",\n    \"dotenv-cli\": \"^7.4.2\",\n    \"drizzle-kit\": \"^0.21.4\",\n    \"eslint-plugin-drizzle\": \"^0.2.3\",\n    \"tsx\": \"^3.10.6\",\n    \"typescript\": \"^5.8.2\",\n    \"wrangler\": \"^3.57.2\"\n  }\n}\n"
  },
  {
    "path": "apps/api/src/db/dbActions.ts",
    "content": "import { eq, and, sql } from \"drizzle-orm\";\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\";\nimport { schema } from \"../index.js\";\nimport { InstanceStatus, instanceStatusEnum } from \"./schema.js\";\nimport { NotFoundError } from \"../lib/errors.js\";\n\n/**\n * Creates a new Cyberdesk instance for a user.\n * @param db Drizzle database instance\n * @param userId The user ID to create the instance for\n * @param timeoutMs Optional timeout in milliseconds. Defaults to 24 hours.\n * @returns The newly created Cyberdesk instance details (id, status)\n */\nexport async function addDbInstance(\n  db: PostgresJsDatabase<typeof schema>,\n  userId: string,\n  timeoutMs?: number\n) {\n  const timeoutInterval = timeoutMs ? `${timeoutMs} milliseconds` : '24 hours';\n\n  const [newInstance] = await db\n    .insert(schema.cyberdeskInstances)\n    .values({\n      userId,\n      status: InstanceStatus.Pending,\n      timeoutAt: sql`NOW() + interval '${sql.raw(timeoutInterval)}'`,\n    })\n    .returning({\n      id: schema.cyberdeskInstances.id,\n      status: schema.cyberdeskInstances.status,\n    });\n\n  return newInstance;\n}\n\n/**\n * Updates the status of a specific Cyberdesk instance, verifying ownership.\n * @param db Drizzle database instance\n * @param id The UUID of the Cyberdesk instance to update\n * @param userId The user ID making the request (for authorization)\n * @param status The new status to set\n * @returns The updated instance details\n * @throws NotFoundError if the instance is not found or the user is not authorized\n */\nexport async function updateDbInstanceStatus(\n  db: PostgresJsDatabase<typeof schema>,\n  id: string,\n  userId: string,\n  status: InstanceStatus\n) {\n  const [updatedInstance] = await db\n    .update(schema.cyberdeskInstances)\n    .set({\n      status: status,\n      updatedAt: new Date(),\n    })\n    .where(\n      and(\n        eq(schema.cyberdeskInstances.id, id),\n        eq(schema.cyberdeskInstances.userId, userId)\n      )\n    )\n    .returning({\n      id: schema.cyberdeskInstances.id,\n      status: schema.cyberdeskInstances.status,\n    });\n\n  if (!updatedInstance) {\n    throw new NotFoundError(\"Desktop instance not found or user not authorized.\");\n  }\n\n  return updatedInstance;\n}\n\n/**\n * Gets specific details (id, status, createdAt, timeoutAt) for a Cyberdesk instance by ID, verifying ownership.\n * @param db Drizzle database instance\n * @param id The UUID of the Cyberdesk instance to get details for\n * @param userId The user ID making the request (for authorization)\n * @returns The instance details\n * @throws NotFoundError if the instance is not found or the user is not authorized\n */\nexport async function getDbInstanceDetails(\n  db: PostgresJsDatabase<typeof schema>,\n  id: string,\n  userId: string\n) {\n  const [result] = await db\n    .select({\n      id: schema.cyberdeskInstances.id,\n      status: schema.cyberdeskInstances.status,\n      createdAt: schema.cyberdeskInstances.createdAt,\n      timeoutAt: schema.cyberdeskInstances.timeoutAt,\n      streamUrl: schema.cyberdeskInstances.streamUrl,\n    })\n    .from(schema.cyberdeskInstances)\n    .where(\n      and(\n        eq(schema.cyberdeskInstances.id, id),\n        eq(schema.cyberdeskInstances.userId, userId)\n      )\n    )\n    .limit(1);\n\n  if (!result) {\n    throw new NotFoundError(\"Desktop instance not found or user not authorized.\");\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "apps/api/src/db/index.ts",
    "content": "import { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\";\nimport * as dotenv from 'dotenv';\n\n// Ensure environment variables are loaded\ndotenv.config();\n\nimport { schema } from \"../index.js\";\n\nfunction connectDatabase(env: { SUPABASE_CONNECTION_STRING: string }): PostgresJsDatabase<typeof schema> {\n  // Create a Postgres client for Drizzle\n  const connectionString = env.SUPABASE_CONNECTION_STRING;\n  \n  const client = postgres(connectionString);\n  \n  // Return a Drizzle instance with the schema\n  return drizzle(client, { schema });\n}\n\n// Use the connection string directly from process.env\nconst connectionString = process.env.SUPABASE_CONNECTION_STRING || \"\";\n\nlet db: PostgresJsDatabase<typeof schema>;\ntry {\n  db = connectDatabase({\n    SUPABASE_CONNECTION_STRING: connectionString,\n  });\n} catch (error) {\n  console.error(\"Failed to connect to database:\", error);\n  throw error;\n}\n\nexport { db };\n"
  },
  {
    "path": "apps/api/src/db/schema.ts",
    "content": "import { pgTable, serial, text, index, varchar, uuid, timestamp, pgSchema, jsonb, boolean, pgEnum } from \"drizzle-orm/pg-core\";\nimport { sql } from \"drizzle-orm\";\n\n// Define the auth schema and users table\nconst authSchema = pgSchema('auth');\n\nconst users = authSchema.table('users', {\n  id: uuid('id').primaryKey(),\n  // You can add other fields from auth.users if needed\n});\n\n// Define the profiles table\nexport const profiles = pgTable(\"profiles\", {\n  id: uuid(\"id\").primaryKey().references(() => users.id, { onDelete: 'cascade' }),\n  unkeyKeyId: varchar(\"unkey_key_id\", { length: 255 }),\n  stripeCustomerId: varchar(\"stripe_customer_id\", { length: 255 }),\n  stripeSubscriptionId: varchar(\"stripe_subscription_id\", { length: 255 }),\n  currentPeriodEnd: timestamp(\"current_period_end\"),\n  subscriptionStatus: varchar(\"subscription_status\", { length: 50 }),\n  planId: varchar(\"plan_id\", { length: 100 }),\n  cancelAtPeriodEnd: boolean(\"cancel_at_period_end\").default(false),\n  createdAt: timestamp(\"created_at\").defaultNow(),\n  updatedAt: timestamp(\"updated_at\").defaultNow(),\n});\n\n// Define the status enum type\nexport enum InstanceStatus {\n    Pending = 'pending',\n    Running = 'running',\n    Terminated = 'terminated',\n    Error = 'error',\n}\n\n// Define the status enum type for Drizzle using the TS enum values\n// Assert type as [string, ...string[]] to satisfy pgEnum's expectation\nexport const instanceStatusEnum = pgEnum('instance_status', Object.values(InstanceStatus) as [string, ...string[]]);\n\n// Define the desktop_instances table\nexport const desktopInstances = pgTable(\"desktop_instances\", {\n  id: uuid(\"id\").primaryKey().defaultRandom(),\n  remoteId: varchar(\"remote_id\", { length: 255 }).notNull(),\n  userId: uuid(\"user_id\").notNull().references(() => users.id, { onDelete: 'cascade' }), // Required field\n  streamUrl: varchar(\"stream_url\", { length: 1024 }).notNull().default(\"https://placeholder-stream-url.cyberdesk.io\"), // Default value for existing records\n  createdAt: timestamp(\"created_at\").defaultNow(),\n  endedAt: timestamp(\"ended_at\"), // Optional field (nullable by default)\n});\n\n// Define the cyberdesk_instances table (MVP 3)\nexport const cyberdeskInstances = pgTable(\"cyberdesk_instances\", {\n  id: uuid(\"id\").primaryKey().defaultRandom(),\n  userId: uuid(\"user_id\").notNull().references(() => users.id, { onDelete: 'cascade' }), // Required field\n  createdAt: timestamp(\"created_at\").defaultNow(),\n  updatedAt: timestamp(\"updated_at\"),\n  status: instanceStatusEnum(\"status\").notNull().default(InstanceStatus.Pending),\n  timeoutAt: timestamp(\"timeout_at\").notNull().default(sql`NOW() + interval '24 hours'`), // Set default to 24 hours from now\n  streamUrl: varchar(\"stream_url\", { length: 1024 })\n});\n"
  },
  {
    "path": "apps/api/src/index.ts",
    "content": "import { newApp } from \"./lib/hono.js\";\nimport desktop from \"./routes/desktop.js\";\nimport { serve } from \"@hono/node-server\";\nimport * as dotenv from 'dotenv'\nexport * as schema from \"./db/schema.js\"\n\n// Load environment variables from .env.local file\ndotenv.config()\n\nconst app = newApp();\n\n// app.use(initCache());\n// app.use(initRatelimiter());\n\napp.route(\"/v1/\", desktop);\n\n// Use PORT environment variable with fallback to 3000 for local development\nconst port = process.env.PORT ? parseInt(process.env.PORT) : 3000;\n\nserve(\n  {\n    fetch: app.fetch,\n    port: port\n  },\n  (info) => {\n    console.log(`Server is running on port ${info.port}`);\n  }\n);\n"
  },
  {
    "path": "apps/api/src/lib/cache.ts",
    "content": "import { createCache, type Cache as C} from \"@unkey/cache\";\nimport { MemoryStore } from \"@unkey/cache/stores\";\n\nimport type { Context, Middleware } from \"./hono.js\";\nimport type { Next } from \"hono\";\n\nexport type CacheNamespaces = {\n  // Define new namespaces here as needed\n}\n\nexport type Cache = C<CacheNamespaces>\n\nconst persistentMap = new Map();\n\nexport function initCache(): Middleware {\n  return async (c: Context, next: Next) => {\n    const memory = new MemoryStore({ persistentMap: new Map() });\n\n    const cache = createCache<CacheNamespaces>({\n      // Add new namespaces as needed\n    });\n    c.set(\"cache\", cache);\n    return next();\n  };\n}\n"
  },
  {
    "path": "apps/api/src/lib/errors.ts",
    "content": "import { type Context, type Next } from 'hono';\nimport { HTTPException } from 'hono/http-exception';\nimport { ZodError } from 'zod';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\n\n// Base API Error class\nexport class ApiError extends Error {\n  public readonly statusCode: number;\n\n  constructor(message: string, statusCode: number) {\n    super(message);\n    this.statusCode = statusCode;\n    this.name = this.constructor.name; // Set the error name to the class name\n    Error.captureStackTrace(this, this.constructor); // Capture stack trace\n  }\n}\n\n// Specific Error Types\nexport class BadRequestError extends ApiError {\n  constructor(message = 'Bad Request') {\n    super(message, 400);\n  }\n}\n\nexport class UnauthorizedError extends ApiError {\n  constructor(message = 'Unauthorized') {\n    super(message, 401);\n  }\n}\n\nexport class NotFoundError extends ApiError {\n  constructor(message = 'Not Found') {\n    super(message, 404);\n  }\n}\n\nexport class ConflictError extends ApiError {\n  constructor(message = 'Conflict') {\n    super(message, 409);\n  }\n}\n\nexport class GatewayError extends ApiError {\n  constructor(message = 'Bad Gateway') {\n    super(message, 502);\n  }\n}\n\nexport class InternalServerError extends ApiError {\n    constructor(message = 'Internal Server Error') {\n      super(message, 500);\n    }\n}\n\n// Specific error for when a command fails *inside* the CyberDesk instance\n// We might still want to return a 200 OK with an error status for this scenario.\nexport class ActionExecutionError extends Error {\n    public readonly details?: string; // e.g., stderr output\n\n    constructor(message: string, details?: string) {\n        super(message);\n        this.details = details;\n        this.name = this.constructor.name;\n        Error.captureStackTrace(this, this.constructor);\n    }\n}\n\n\n// Hono Error Handler Middleware\nexport const honoErrorHandler = (err: Error, c: Context) => {\n  console.error(\"Error caught by middleware:\", err); // Log the full error\n\n  // Handle Zod validation errors specifically\n  if (err instanceof ZodError) {\n    const validationErrors = err.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');\n    return c.json(\n      {\n        status: \"error\",\n        error: `Validation failed: ${validationErrors}`,\n        docs: \"https://docs.cyberdesk.io/docs/api-reference/\",\n      },\n      400 // Bad Request for validation errors\n    );\n  }\n\n  // Handle our custom ApiError instances\n  if (err instanceof ApiError) {\n    return c.json(\n      {\n        status: \"error\",\n        error: err.message,\n        docs: \"https://docs.cyberdesk.io/docs/api-reference/\",\n      },\n      err.statusCode as ContentfulStatusCode\n    );\n  }\n\n   // Handle Hono's built-in HTTPException\n   if (err instanceof HTTPException) {\n    return c.json(\n        {\n          status: \"error\",\n          error: err.message,\n          docs: \"https://docs.cyberdesk.io/docs/api-reference/\",\n        },\n        err.status as ContentfulStatusCode\n      );\n  }\n\n  // Handle ActionExecutionError specifically (return 200 OK with error status)\n  if (err instanceof ActionExecutionError) {\n      return c.json(\n          {\n              status: \"error\",\n              error: err.message,\n              details: err.details, // Optionally include details like stderr\n          },\n          200 // Special case: Action failed but API communication was successful\n      );\n  }\n\n\n  // Fallback for unexpected errors\n  return c.json(\n    {\n      status: \"error\",\n      error: \"An unexpected internal server error occurred.\",\n      docs: \"https://docs.cyberdesk.io/docs/api-reference/\",\n    },\n    500 as const\n  );\n}; "
  },
  {
    "path": "apps/api/src/lib/hono.ts",
    "content": "import { OpenAPIHono, z } from \"@hono/zod-openapi\";\nimport type { UnkeyContext } from \"@unkey/hono\";\nimport type { Ratelimit } from \"@unkey/ratelimit\";\nimport type { Context as GenericContext, MiddlewareHandler } from \"hono\";\nimport type { ZodError } from \"zod\";\n\nimport type { Cache } from \"./cache.js\";\n\nexport type HonoEnv = {\n  Bindings: {\n    SUPABASE_CONNECTION_STRING: string;\n\n    // Unkey credentials\n    UNKEY_ROOT_KEY: string;\n    UNKEY_API_ID: string;\n\n    GATEWAY_URL: string;\n  };\n  Variables: {\n    cache: Cache\n    unkey: UnkeyContext;\n    ratelimit: Ratelimit;\n  };\n};\n\nexport function parseZodErrorMessage(err: z.ZodError): string {\n  try {\n    const arr = JSON.parse(err.message) as Array<{\n      message: string;\n      path: Array<string>;\n    }>;\n    const { path, message } = arr[0];\n    return `${path.join(\".\")}: ${message}`;\n  } catch {\n    return err.message;\n  }\n}\nexport function handleZodError(\n  result:\n    | {\n        success: true;\n        data: any;\n      }\n    | {\n        success: false;\n        error: ZodError;\n      },\n  c: Context\n) {\n  if (!result.success) {\n    return c.json(\n      {\n        error: parseZodErrorMessage(result.error),\n      },\n      { status: 400 }\n    );\n  }\n}\n\nexport function newApp() {\n  const app = new OpenAPIHono<HonoEnv>({\n    defaultHook: handleZodError,\n  });\n\n  app.onError((err: Error, c: Context) => {\n    console.error(err);\n    return c.json(\n      {\n        error: err.message,\n      },\n      { status: 500 }\n    );\n  });\n\n  app.doc(\"/openapi.json\", {\n    openapi: \"3.1.0\",\n    info: {\n      title: \"API Reference\",\n      version: \"1.2.1\",\n      description: \"API for Cyberdesk, to create, control, and manage virtual desktop instances.\",\n    },\n    servers: [\n      {\n        url: \"https://api.cyberdesk.io\",\n        description: \"Production server\"\n      }\n    ],\n  });\n\n  app.openAPIRegistry.registerComponent(\"securitySchemes\", \"apiKeyAuth\", {\n    type: \"apiKey\",\n    in: \"header\",\n    name: \"x-api-key\"\n  });\n\n  return app;\n}\n\nexport type App = ReturnType<typeof newApp>;\nexport type Context = GenericContext<HonoEnv>;\nexport type Middleware = MiddlewareHandler<HonoEnv>;\n"
  },
  {
    "path": "apps/api/src/lib/posthog.ts",
    "content": "import { PostHog } from 'posthog-node'\nimport * as dotenv from 'dotenv';\n\n// Ensure environment variables are loaded\ndotenv.config();\n\nconst POSTHOG_API_KEY = process.env.POSTHOG_API_KEY;\n\nif (!POSTHOG_API_KEY) {\n    throw new Error('POSTHOG_API_KEY is not set');\n}\n\nconst POSTHOG_HOST = process.env.POSTHOG_HOST || 'https://us.i.posthog.com';\n\nconst client = new PostHog(\n    POSTHOG_API_KEY,\n    { host: POSTHOG_HOST, enableExceptionAutocapture: true },\n)\n\nexport default client; "
  },
  {
    "path": "apps/api/src/lib/ratelimit.ts",
    "content": "import { Ratelimit } from \"@unkey/ratelimit\";\nimport type { Context, Middleware } from \"./hono.js\";\nimport type { Next } from \"hono\";\n\nexport function initRatelimiter(): Middleware {\n  return async (c: Context, next: Next) => {\n    const ratelimit = new Ratelimit({\n      rootKey: c.env.UNKEY_ROOT_KEY,\n      namespace: \"api-toolbox\",\n      limit: 10,\n      duration: \"30s\",\n      async: true,\n    });\n\n    c.set(\"ratelimit\", ratelimit);\n\n    return next();\n  };\n}\n"
  },
  {
    "path": "apps/api/src/routes/desktop.ts",
    "content": "import { OpenAPIHono } from \"@hono/zod-openapi\";\nimport { env } from 'hono/adapter';\nimport { unkey, type UnkeyContext } from \"@unkey/hono\";\nimport { z } from \"@hono/zod-openapi\";\nimport axios from \"axios\";\nimport { profiles, cyberdeskInstances, InstanceStatus } from \"../db/schema.js\";\nimport {\n  bashAction,\n  computerAction,\n  createDesktop,\n  stopDesktop,\n  ComputerActionSchema,\n  CreateDesktopParamsSchema,\n  getDesktop,\n  BashActionSchema,\n} from \"../schema/desktop.js\";\nimport { GatewayExecuteCommandRequestSchema, GatewayExecuteCommandResponseSchema } from \"../schema/gateway.js\";\nimport { db } from \"../db/index.js\";\nimport {\n  addDbInstance,\n  getDbInstanceDetails,\n  updateDbInstanceStatus,\n} from \"../db/dbActions.js\";\nimport {\n  ApiError,\n  NotFoundError,\n  ConflictError,\n  BadRequestError,\n  GatewayError,\n  ActionExecutionError,\n  honoErrorHandler,\n  UnauthorizedError,\n  InternalServerError,\n} from \"../lib/errors.js\";\nimport posthogClient from '../lib/posthog.js';\nimport type { Context } from \"hono\";\n\n// Type definitions\ntype EnvVars = {\n  UNKEY_API_ID: string;\n  SUPABASE_URL: string;\n  SUPABASE_ANON_KEY: string;\n  SUPABASE_CONNECTION_STRING?: string;\n  WEB_URL: string;\n  GATEWAY_URL: string;\n};\n\n// Use the schema type for computer actions\ntype ComputerAction = z.infer<typeof ComputerActionSchema>;\n// Use the schema type for desktop creation parameters\ntype CreateDesktopParams = z.infer<typeof CreateDesktopParamsSchema>;\ntype BashAction = z.infer<typeof BashActionSchema>;\n\n// Create Hono instance\nconst desktop = new OpenAPIHono<{\n  Variables: {\n    unkey: UnkeyContext;\n    userId: string;\n  };\n}>();\n\n// Register the global error handler\ndesktop.onError(honoErrorHandler);\n\n// API key verification middleware\ndesktop.use(\"*\", async (c, next) => {\n  const { UNKEY_API_ID } = env<EnvVars>(c);\n\n  const handler = unkey({\n    apiId: UNKEY_API_ID,\n    getKey: (c) => c.req.header(\"x-api-key\"),\n  });\n\n  await handler(c, next);\n});\n\n// Helper function to capture API events in PostHog\nasync function captureApiEvent(\n  c: Context,\n  userId: string,\n  eventName: string,\n  additionalProperties: Record<string, any> = {}\n) {\n  const properties = {\n    path: c.req.path,\n    method: c.req.method,\n    userAgent: c.req.header('User-Agent'),\n    ...additionalProperties,\n  };\n  posthogClient.capture({\n    distinctId: userId,\n    event: eventName,\n    properties: properties,\n  });\n}\n\n// Authentication and database connection middleware\ndesktop.use(\"*\", async (c, next) => {\n  const result = c.get(\"unkey\");\n  if (!result?.valid) {\n    throw new UnauthorizedError(\"Invalid API key\");\n  }\n\n  const userId = result.ownerId;\n  if (!userId) {\n    throw new UnauthorizedError(\"No user associated with this key\");\n  }\n\n  c.set(\"userId\", userId);\n\n  await next();\n});\n\n// Route for getting a desktop instance's details\ndesktop.openapi(getDesktop, async (c) => {\n  const userId = c.get(\"userId\");\n  const id = c.req.param(\"id\");\n  const { GATEWAY_URL } = env<EnvVars>(c);\n\n  if (!id) {\n    throw new BadRequestError(\"Instance ID is required\");\n  }\n\n  await captureApiEvent(c, userId, 'Viewed Desktop Details');\n\n  const instanceDetails = await getDbInstanceDetails(db, id, userId);\n  \n  // Adjust stream_url for dev environment if needed\n  let streamUrl: string | null = instanceDetails.streamUrl ?? null;\n  if (streamUrl && GATEWAY_URL.includes('dev-gateway')) {\n    // Replace any 'gateway.' (with or without subdomain) with 'dev-gateway.'\n    // This is to support the dev environment, where the gateway is accessible via dev-gateway.cyberdesk.io (or whatever subdomain you've set up)\n    streamUrl = streamUrl.replace(/\\bgateway\\./g, 'dev-gateway.');\n  }\n\n  return c.json({\n      id: instanceDetails.id,\n      status: instanceDetails.status,\n      created_at: (instanceDetails.createdAt || new Date(0)).toISOString(),\n      timeout_at: instanceDetails.timeoutAt.toISOString(),\n      stream_url: streamUrl\n  }, 200);\n});\n\n// Route for creating a new desktop instance\ndesktop.openapi(createDesktop, async (c) => {\n  const { GATEWAY_URL } = env<EnvVars>(c);\n  const userId = c.get(\"userId\");\n  let createDesktopParams: CreateDesktopParams;\n\n  try {\n    const body = await c.req.json().catch(() => ({}));\n    createDesktopParams = CreateDesktopParamsSchema.parse(body);\n\n    await captureApiEvent(c, userId, 'Created Desktop');\n\n    const newInstance = await addDbInstance(db, userId, createDesktopParams.timeout_ms);\n\n    try {\n      const provisioningUrl = `${GATEWAY_URL}/cyberdesk/${newInstance.id}`;\n      const response = await axios.post(provisioningUrl, {\n        timeoutMs: createDesktopParams.timeout_ms\n      });\n      console.log('Provisioning request successful:', response.data);\n\n      return c.json(\n        {\n          id: newInstance.id,\n          status: newInstance.status,\n        },\n        200\n      );\n    } catch (provisioningError) {\n      console.error('Error calling provisioning service during creation:', provisioningError);\n      await updateDbInstanceStatus(db, newInstance.id, userId, InstanceStatus.Error).catch(console.error);\n      if (axios.isAxiosError(provisioningError)) {\n         throw new GatewayError(`Failed to provision via Gateway: ${provisioningError.response?.statusText || provisioningError.message}`);\n      } else {\n         throw new GatewayError('Failed to initiate provisioning of Cyberdesk resource via Gateway for instance ' + newInstance.id);\n      }\n    }\n  } catch (error) {\n    if (error instanceof z.ZodError) {\n      throw error;\n    } else if (error instanceof ApiError) {\n        throw error;\n    } else {\n        console.error('Unexpected error during desktop creation:', error);\n        throw new InternalServerError(\"Failed to create desktop instance due to an unexpected error.\");\n    }\n  }\n});\n\n// Route for stopping a desktop instance\ndesktop.openapi(stopDesktop, async (c) => {\n  const userId = c.get(\"userId\");\n  const id = c.req.param(\"id\");\n  const { GATEWAY_URL } = env<EnvVars>(c);\n\n  await captureApiEvent(c, userId, 'Stopped Desktop');\n\n  const updatedInstance = await updateDbInstanceStatus(db, id, userId, InstanceStatus.Terminated);\n\n  try {\n    const provisioningUrl = `${GATEWAY_URL}/cyberdesk/${id}/stop`;\n    await axios.post(provisioningUrl);\n  } catch (provisioningError) {\n    console.error('Error calling provisioning service during stop:', provisioningError);\n  }\n\n  const responsePayload: { status: InstanceStatus } = {\n      status: updatedInstance.status as InstanceStatus,\n  };\n\n  return c.json(\n    responsePayload,\n    200\n  );\n});\n\nasync function executeComputerAction(\n  id: string,\n  userId: string,\n  action: ComputerAction,\n  GATEWAY_URL: string\n): Promise<string> {\n  console.log(\"Executing computer action:\", action);\n  const instance = await getDbInstanceDetails(db, id, userId);\n  if (!instance) {\n    throw new NotFoundError(\"Instance not found or unauthorized\");\n  }\n  if (instance.status !== 'running') {\n    throw new ConflictError(`Instance is not running (status: ${instance.status}). Cannot perform action.`);\n  }\n\n  let command: string;\n  const displayPrefix = \"export DISPLAY=:99;\";\n\n  switch (action.type) {\n    case \"click_mouse\": {\n      const { x, y, button = \"left\", num_of_clicks = 1, click_type = \"click\" } = action;\n      const buttonMap: { [key: string]: number } = { left: 1, middle: 2, right: 3 };\n      const btn = buttonMap[button] || 1;\n      let moveCmd = \"\";\n      if (x !== undefined && y !== undefined) {\n        moveCmd = `xdotool mousemove ${x} ${y} && `;\n      }\n      let clickCmd: string;\n      if (click_type === \"click\") {\n        clickCmd = `xdotool click --repeat ${num_of_clicks} ${btn}`;\n      } else if (click_type === \"down\") {\n        clickCmd = `xdotool mousedown ${btn}`;\n      } else { // up\n        clickCmd = `xdotool mouseup ${btn}`;\n      }\n      command = `${displayPrefix} ${moveCmd}${clickCmd}`;\n      break;\n    }\n    case \"scroll\": {\n      const { direction, amount } = action;\n      const directionMap: { [key: string]: number } = { up: 4, down: 5, left: 6, right: 7 };\n      const btn = directionMap[direction];\n      // Ensure amount is a reasonable positive integer\n      const repeatCount = Math.max(1, Math.min(Math.floor(amount), 500)); // Cap repeat at 500 for sanity\n      const delayMs = 25; // Reduce delay for faster scrolling\n      command = `${displayPrefix} xdotool click --repeat ${repeatCount} --delay ${delayMs} ${btn}`;\n      break;\n    }\n    case \"move_mouse\": {\n      command = `${displayPrefix} xdotool mousemove ${action.x} ${action.y}`;\n      break;\n    }\n    case \"drag_mouse\": {\n      const { start, end } = action;\n      command = `${displayPrefix} xdotool mousemove ${start.x} ${start.y} mousedown 1 mousemove ${end.x} ${end.y} mouseup 1`;\n      break;\n    }\n    case \"type\": {\n      const escapedText = action.text.replace(/'/g, \"'\\''\");\n      command = `${displayPrefix} xdotool type --clearmodifiers --delay 50 '${escapedText}'`;\n      break;\n    }\n    case \"press_keys\": {\n      const { keys, key_action_type = \"press\" } = action;\n      const keyString = Array.isArray(keys) ? keys.join('+') : keys;\n      let keyCmd: string;\n      if (key_action_type === \"down\") {\n        keyCmd = `keydown`;\n      } else if (key_action_type === \"up\") {\n        keyCmd = `keyup`;\n      } else { // press\n        keyCmd = `key`;\n      }\n      command = `${displayPrefix} xdotool ${keyCmd} --clearmodifiers ${keyString}`;\n      break;\n    }\n    case \"wait\": {\n      const seconds = Math.max(0, action.ms / 1000);\n      command = `sleep ${seconds}`;\n      break;\n    }\n    case \"screenshot\": {\n      command = `${displayPrefix} scrot -q 100 /tmp/screen.jpg && base64 /tmp/screen.jpg && rm /tmp/screen.jpg`;\n      break;\n    }\n    case \"get_cursor_position\": {\n      command = `${displayPrefix} xdotool getmouselocation --shell`;\n      break;\n    }\n    default:\n      throw new BadRequestError(`Unsupported action type: ${(action as any).type}`);\n  }\n\n  console.log(`Executing command for instance ${id}: ${command}`);\n\n  const provisioningUrl = `${GATEWAY_URL}/cyberdesk/${id}/execute-command`;\n  const requestBody = GatewayExecuteCommandRequestSchema.parse({ command });\n  try {\n    const response = await axios.post<z.infer<typeof GatewayExecuteCommandResponseSchema>>(\n        provisioningUrl,\n        requestBody\n    );\n\n    const parsedResponse = GatewayExecuteCommandResponseSchema.parse(response.data);\n\n    if (parsedResponse.vm_response.return_code !== 0) {\n      throw new ActionExecutionError(\n          `Command failed with code ${parsedResponse.vm_response.return_code}`,\n          parsedResponse.vm_response.stderr || 'No stderr output'\n      );\n    }\n\n    return parsedResponse.vm_response.stdout.trim();\n\n  } catch (error: any) {\n    console.error(`Error executing command for instance ${id}:`, error);\n    if (error instanceof z.ZodError) {\n        throw new GatewayError(`Invalid response structure from gateway: ${error.errors.map(e => e.message).join(', ')}`);\n    } else if (axios.isAxiosError(error)) {\n        const gatewayMessage = error.response?.data?.message || error.message || \"Failed to execute command via gateway\";\n        throw new GatewayError(`Action execution failed via gateway: ${gatewayMessage}`);\n    } else if (error instanceof ApiError) {\n        throw error;\n    } else {\n        throw new InternalServerError(`Unexpected error during action execution: ${error instanceof Error ? error.message : 'Unknown error'}`);\n    }\n  }\n}\n\n/**\n * Executes a raw bash command on the target instance via the gateway.\n * @param id Instance ID\n * @param userId User ID for authorization\n * @param command The bash command string to execute\n * @param GATEWAY_URL Gateway URL\n * @returns Promise<string> Resolves with stdout on success\n * @throws Error on command failure or communication issues\n */\nasync function executeBashCommand(\n    id: string,\n    userId: string,\n    command: string,\n    GATEWAY_URL: string\n  ): Promise<string> {\n    const instance = await getDbInstanceDetails(db, id, userId);\n    if (!instance) {\n      throw new NotFoundError(\"Instance not found or unauthorized\");\n    }\n    if (instance.status !== 'running') {\n      throw new ConflictError(`Instance is not running (status: ${instance.status}). Cannot execute command.`);\n    }\n  \n    console.log(`Executing bash command for instance ${id}: ${command}`);\n  \n    const provisioningUrl = `${GATEWAY_URL}/cyberdesk/${id}/execute-command`;\n    const requestBody = GatewayExecuteCommandRequestSchema.parse({ command });\n    try {\n      const response = await axios.post<z.infer<typeof GatewayExecuteCommandResponseSchema>>(\n        provisioningUrl,\n        requestBody\n      );\n  \n      const parsedResponse = GatewayExecuteCommandResponseSchema.parse(response.data);\n  \n      console.log(`Bash command execution response for instance ${id}:`, parsedResponse);\n  \n      return parsedResponse.vm_response.stdout.trim() || parsedResponse.vm_response.stderr.trim();\n  \n    } catch (error: any) {\n      console.error(`Error executing bash command for instance ${id}:`, error);\n       if (error instanceof z.ZodError) {\n            throw new GatewayError(`Invalid response structure from gateway: ${error.errors.map(e => e.message).join(', ')}`);\n        } else if (axios.isAxiosError(error)) {\n          const gatewayMessage = error.response?.data?.message || error.message || \"Failed to execute command via gateway\";\n          throw new GatewayError(`Bash command execution failed via gateway: ${gatewayMessage}`);\n      } else if (error instanceof ApiError) {\n        throw error;\n    } else {\n          throw new InternalServerError(`Unexpected error during bash command execution: ${error instanceof Error ? error.message : 'Unknown error'}`);\n      }\n    }\n  }\n\n// Route for performing a computer action on a desktop\ndesktop.openapi(computerAction, async (c) => {\n  const userId = c.get(\"userId\");\n  const id = c.req.param(\"id\");\n  const { GATEWAY_URL } = env<EnvVars>(c);\n  let action: ComputerAction;\n\n  try {\n      const body = await c.req.json().catch(() => ({}));\n      action = ComputerActionSchema.parse(body);\n      // Capture event with actionType\n      await captureApiEvent(c, userId, 'Performed Computer Action', { actionType: action.type });\n  } catch (parseError: any) {\n      if (parseError instanceof z.ZodError) {\n          throw parseError;\n      }\n      throw new BadRequestError(`Invalid request body: ${parseError?.message || 'Unknown parsing error'}`);\n  }\n\n  const resultString = await executeComputerAction(id, userId, action, GATEWAY_URL);\n\n  if (action.type === \"screenshot\") {\n    // Remove all whitespace (including newlines) from base64 string\n    const cleanedBase64 = resultString.replace(/\\s+/g, '');\n    return c.json({ base64_image: cleanedBase64 }, 200);\n  } else if (action.type === \"get_cursor_position\") {\n      return c.json({ output: resultString, }, 200);\n  } else {\n    return c.json({ output: resultString, }, 200);\n  }\n});\n\n\n// Route for executing a bash command on a desktop\ndesktop.openapi(bashAction, async (c) => {\n  const userId = c.get(\"userId\");\n  const id = c.req.param(\"id\");\n  const { GATEWAY_URL } = env<EnvVars>(c);\n  let bashAction: BashAction;\n\n  try {\n      const body = await c.req.json().catch(() => ({}));\n      bashAction = BashActionSchema.parse(body);\n      await captureApiEvent(c, userId, 'Executed Bash Command');\n  } catch (parseError: any) {\n       if (parseError instanceof z.ZodError) {\n           throw parseError;\n       }\n       throw new BadRequestError(`Invalid request body: ${parseError?.message || 'Unknown parsing error'}`);\n  }\n\n  const resultString = await executeBashCommand(id, userId, bashAction.command, GATEWAY_URL);\n\n  return c.json({ status: \"success\", output: resultString, }, 200);\n});\n\nexport default desktop;\n"
  },
  {
    "path": "apps/api/src/schema/desktop.ts",
    "content": "import { createRoute, z } from \"@hono/zod-openapi\";\n\nimport { openApiErrorResponses } from \"./errors.js\";\nimport { InstanceStatus, instanceStatusEnum } from \"../db/schema.js\";\n\n// Header schema for API key authentication\nconst HeadersSchema = z.object({\n  \"x-api-key\": z.string().openapi({\n    description: \"API key for authentication\",\n    example: \"api_12345\",\n  }),\n});\n\n// Common schema for action oriented API responses\nconst ActionResponseSchema = z.object({\n  output: z.string().optional().openapi({\n      description: \"Raw string output from the executed command (if any)\",\n      example: \"X=500 Y=300\",\n  }),\n  error: z.string().optional().openapi({\n    description: \"Error message if the operation failed (also indicated by non-2xx HTTP status)\",\n    example: \"Command failed with code 1: xdotool: command not found\",\n  }),\n  base64_image: z.string().optional().openapi({\n    description: \"Base64 encoded JPEG image data (only returned for screenshot actions)\",\n    example: \"/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQ...\",\n  })\n});\n\n// Point schema for coordinates\nconst PointSchema = z.object({\n  x: z.number().int().openapi({\n    description: \"X coordinate on the screen\",\n    example: 500,\n  }),\n  y: z.number().int().openapi({\n    description: \"Y coordinate on the screen\",\n    example: 300,\n  }),\n});\n\n// Schema for desktop creation parameters\nexport const CreateDesktopParamsSchema = z.object({\n  timeout_ms: z.number().int().optional().openapi({\n    description: \"Timeout in milliseconds for the desktop session\",\n    example: 3600000,\n  }),\n});\n\n// Schema for the response of the create desktop endpoint\nexport const CreateDesktopResponseSchema = z.object({\n    id: z.string().openapi({\n      description: \"Unique identifier for the desktop instance\",\n      example: \"desktop_12345\",\n    }),\n    status: z.enum(instanceStatusEnum.enumValues).openapi({\n        description: \"Initial status of the desktop instance after creation request\",\n        example: InstanceStatus.Pending,\n      }),\n  });\n\n// Schema for the response of the stop desktop endpoint\nexport const StopDesktopResponseSchema = z.object({\n  status: z.enum(instanceStatusEnum.enumValues).openapi({\n    description: \"Status of the desktop instance after stopping\",\n    example: InstanceStatus.Terminated,\n  }),\n});\n\n// Computer action schema with discriminated union\nexport const ComputerActionSchema = z.discriminatedUnion(\"type\", [\n  z.object({\n    type: z.literal(\"click_mouse\").openapi({\n      description: \"Perform a mouse action: click, press (down), or release (up). Defaults to a single left click at the current position.\",\n      example: \"click_mouse\",\n    }),\n    x: z.number().int().optional().openapi({\n      description: \"X coordinate for the action (optional, uses current position if omitted)\",\n      example: 500,\n    }),\n    y: z.number().int().optional().openapi({\n      description: \"Y coordinate for the action (optional, uses current position if omitted)\",\n      example: 300,\n    }),\n    button: z.enum([\"left\", \"right\", \"middle\"]).optional().openapi({\n      description: \"Mouse button to use (optional, defaults to 'left')\",\n      example: \"left\",\n    }),\n    num_of_clicks: z.number().int().min(0).optional().openapi({\n      description: \"Number of clicks to perform (optional, defaults to 1, only applicable for 'click' type)\",\n      example: 1,\n    }),\n    click_type: z.enum([\"click\", \"down\", \"up\"]).optional().openapi({\n      description: \"Type of mouse action (optional, defaults to 'click')\",\n      example: \"click\",\n    }),\n  }).openapi({ title: \"Click Mouse Action\" }),\n  z.object({\n    type: z.literal(\"scroll\").openapi({\n      description: \"Scroll the mouse wheel in the specified direction\",\n      example: \"scroll\",\n    }),\n    direction: z.enum([\"up\", \"down\", \"left\", \"right\"]).openapi({\n      description: \"Direction to scroll\",\n      example: \"down\",\n    }),\n    amount: z.number().int().openapi({\n      description: \"Amount to scroll in pixels\",\n      example: 100,\n    }),\n  }).openapi({ title: \"Scroll Action\" }),\n  z.object({\n    type: z.literal(\"move_mouse\").openapi({\n      description: \"Move the mouse cursor to the specified coordinates\",\n      example: \"move_mouse\",\n    }),\n    x: z.number().int().openapi({\n      description: \"X coordinate to move to\",\n      example: 500,\n    }),\n    y: z.number().int().openapi({\n      description: \"Y coordinate to move to\",\n      example: 300,\n    }),\n  }).openapi({ title: \"Move Mouse Action\" }),\n  z.object({\n    type: z.literal(\"drag_mouse\").openapi({\n      description: \"Drag the mouse from start to end coordinates\",\n      example: \"drag_mouse\",\n    }),\n    start: PointSchema.openapi({\n      description: \"Starting coordinates for the drag operation\",\n      example: { x: 100, y: 100 },\n    }),\n    end: PointSchema.openapi({\n      description: \"Ending coordinates for the drag operation\",\n      example: { x: 300, y: 300 },\n    }),\n  }).openapi({ title: \"Drag Mouse Action\" }),\n  z.object({\n    type: z.literal(\"type\").openapi({\n      description: \"Type text at the current cursor position\",\n      example: \"type\",\n    }),\n    text: z.string().openapi({\n      description: \"Text to type\",\n      example: \"Hello, World!\",\n    }),\n  }).openapi({ title: \"Type Text Action\" }),\n  z.object({\n    type: z.literal(\"press_keys\").openapi({\n      description: \"Press, hold down, or release one or more keyboard keys. Defaults to a single press and release.\",\n      example: \"press_keys\",\n    }),\n    keys: z.union([\n      z.string().openapi({\n        description: \"Single key to press\",\n        example: \"Enter\",\n      }),\n      z.array(z.string()).openapi({\n        description: \"Multiple keys to press simultaneously\",\n        example: [\"Control\", \"c\"],\n      })\n    ]),\n    key_action_type: z.enum([\"press\", \"down\", \"up\"]).optional().openapi({\n        description: \"Type of key action (optional, defaults to 'press' which is a down and up action)\",\n        example: \"press\",\n      }),\n  }).openapi({ title: \"Press Keys Action\" }),\n  z.object({\n    type: z.literal(\"wait\").openapi({\n      description: \"Wait for the specified number of milliseconds\",\n      example: \"wait\",\n    }),\n    ms: z.number().int().openapi({\n      description: \"Time to wait in milliseconds\",\n      example: 1000,\n    }),\n  }).openapi({ title: \"Wait Action\" }),\n  z.object({\n    type: z.literal(\"screenshot\").openapi({\n      description: \"Take a screenshot of the desktop\",\n      example: \"screenshot\",\n    }),\n  }).openapi({ title: \"Screenshot Action\" }),\n  z.object({\n    type: z.literal(\"get_cursor_position\").openapi({\n      description: \"Get the current mouse cursor position\",\n      example: \"get_cursor_position\",\n    }),\n  }).openapi({ title: \"Get Cursor Position Action\" }),\n]);\n\n// Schema for Bash Action parameters\nexport const BashActionSchema = z.object({\n  command: z.string().openapi({\n    description: \"Bash command to execute\",\n    example: \"echo 'Hello, World!'\",\n  }),\n});\n\n// Create Desktop Route\nexport const createDesktop = createRoute({\n  method: \"post\",\n  path: \"/desktop\",\n  tags: [\"Desktop\"],\n  summary: \"Create a new virtual desktop instance\",\n  description: \"Creates a new virtual desktop instance and returns its ID and stream URL\",\n  request: {\n    headers: HeadersSchema,\n    body: {\n      content: {\n        \"application/json\": {\n          schema: CreateDesktopParamsSchema,\n        },\n      },\n    },\n  },\n  responses: {\n    200: {\n      content: {\n        \"application/json\": {\n          schema: CreateDesktopResponseSchema,\n        },\n      },\n      description: \"Desktop creation initiated successfully\",\n    },\n    ...openApiErrorResponses,\n  },\n});\n\n// Stop Desktop Route\nexport const stopDesktop = createRoute({\n  method: \"post\",\n  path: \"/desktop/{id}/stop\",\n  tags: [\"Desktop\"],\n  summary: \"Stop a running desktop instance\",\n  description: \"Stops a running desktop instance and cleans up resources\",\n  request: {\n    headers: HeadersSchema,\n    params: z.object({\n      id: z.string().openapi({\n        description: \"Desktop instance ID to stop\",\n        example: \"desktop_12345\",\n      }),\n    }),\n  },\n  responses: {\n    200: {\n      content: {\n        \"application/json\": {\n          schema: StopDesktopResponseSchema,\n        },\n      },\n      description: \"Desktop stopped successfully\",\n    },\n    ...openApiErrorResponses,\n  }, \n});\n\n// Get Desktop Route Response Schema\nconst GetDesktopResponseSchema = z.object({\n  id: z.string().uuid().openapi({\n    description: \"Unique identifier for the desktop instance\",\n    example: \"a1b2c3d4-e5f6-7890-1234-567890abcdef\",\n  }),\n  status: z.enum(instanceStatusEnum.enumValues).openapi({\n    description: \"Current status of the desktop instance\",\n    example: \"running\",\n  }),\n  stream_url: z.string().nullable().openapi({\n    description: \"URL for the desktop stream (null if the desktop is not running)\",\n    example: \"https://cyberdesk.com/vnc/a1b2c3d4-e5f6-7890-1234-567890abcdef\",\n  }),\n  created_at: z.string().datetime().openapi({\n    description: \"Timestamp when the instance was created\",\n    example: \"2023-10-27T10:00:00Z\",\n  }),\n  timeout_at: z.string().datetime().openapi({\n    description: \"Timestamp when the instance will automatically time out\",\n    example: \"2023-10-28T10:00:00Z\",\n  }),\n});\n\n// Get Desktop Route\nexport const getDesktop = createRoute({\n  method: \"get\",\n  path: \"/desktop/{id}\",\n  tags: [\"Desktop\"],\n  summary: \"Get details of a specific desktop instance\",\n  description: \"Returns the ID, status, creation timestamp, and timeout timestamp for a given desktop instance.\",\n  request: {\n    headers: HeadersSchema,\n    params: z.object({\n      id: z.string().uuid().openapi({\n        description: \"The UUID of the desktop instance to retrieve\",\n        example: \"a1b2c3d4-e5f6-7890-1234-567890abcdef\",\n      }),\n    }),\n  },\n  responses: {\n    200: {\n      content: {\n        \"application/json\": {\n          schema: GetDesktopResponseSchema,\n        },\n      },\n      description: \"Desktop instance details retrieved successfully\",\n    },\n    ...openApiErrorResponses,\n  },\n});\n\n// Computer Action Route\nexport const computerAction = createRoute({\n  method: \"post\",\n  path: \"/desktop/{id}/computer-action\",\n  tags: [\"Desktop\"],\n  summary: \"Perform an action on the desktop\",\n  description: \"Executes a computer action such as mouse clicks, keyboard input, or screenshots on the desktop\",\n  request: {\n    headers: HeadersSchema,\n    params: z.object({\n      id: z.string().openapi({\n        description: \"Desktop instance ID to perform the action on\",\n        example: \"desktop_12345\",\n      }),\n    }),\n    body: {\n      content: {\n        \"application/json\": {\n          schema: ComputerActionSchema,\n        },\n      },\n    },\n  },\n  responses: {\n    200: {\n      content: {\n        \"application/json\": {\n          schema: ActionResponseSchema,\n        },\n      },\n      description: \"Action executed successfully. Response may contain output or image data depending on the action.\",\n    },\n    ...openApiErrorResponses,\n  },\n});\n\n// Bash Action Route\nexport const bashAction = createRoute({\n  method: \"post\",\n  path: \"/desktop/{id}/bash-action\",\n  tags: [\"Desktop\"],\n  summary: \"Execute a bash command on the desktop\",\n  description: \"Runs a bash command on the desktop and returns the command output\",\n  request: {\n    headers: HeadersSchema,\n    params: z.object({\n      id: z.string().openapi({\n        description: \"Desktop instance ID to run the command on\",\n        example: \"desktop_12345\",\n      }),\n    }),\n    body: {\n      content: {\n        \"application/json\": {\n          schema: BashActionSchema,\n        },\n      },\n    },\n  },\n  responses: {\n    200: {\n      content: {\n        \"application/json\": {\n          schema: ActionResponseSchema,\n        },\n      },\n      description: \"Command executed successfully. Response contains command output.\",\n    },\n    ...openApiErrorResponses,\n  },\n});\n"
  },
  {
    "path": "apps/api/src/schema/errors.ts",
    "content": "import { z } from \"@hono/zod-openapi\";\n\nconst errorSchema = z.object({\n  status: z.literal(\"error\").openapi({ example: \"error\" }),\n  error: z.string().openapi({\n     description: \"Error message detailing what went wrong\",\n     example: \"Instance not found or unauthorized\"\n  })\n});\n\nexport const openApiErrorResponses = {\n  400: {\n    description:\n      \"The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\",\n    content: {\n      \"application/json\": {\n        schema: errorSchema,\n      },\n    },\n  },\n  401: {\n    description: `Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response.`,\n    content: {\n      \"application/json\": {\n        schema: errorSchema,\n      },\n    },\n  },\n  403: {\n    description:\n      \"The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\",\n    content: {\n      \"application/json\": {\n        schema: errorSchema,\n      },\n    },\n  },\n  404: {\n    description:\n      \"The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\",\n    content: {\n      \"application/json\": {\n        schema: errorSchema,\n      },\n    },\n  },\n  409: {\n    description:\n      \"This response is sent when a request conflicts with the current state of the server.\",\n    content: {\n      \"application/json\": {\n        schema: errorSchema,\n      },\n    },\n  },\n  429: {\n    description: `The user has sent too many requests in a given amount of time (\"rate limiting\")`,\n    content: {\n      \"application/json\": {\n        schema: errorSchema,\n      },\n    },\n  },\n  500: {\n    description:\n      \"The server has encountered a situation it does not know how to handle.\",\n    content: {\n      \"application/json\": {\n        schema: errorSchema,\n      },\n    },\n  },\n  502: {\n    description:\n      \"The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\",\n    content: {\n      \"application/json\": {\n        schema: errorSchema,\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "apps/api/src/schema/gateway.ts",
    "content": "import { z } from 'zod';\n\n// Schema for Gateway Execute Command Request\nexport const GatewayExecuteCommandRequestSchema = z.object({\n    command: z.string()\n});\n\n// Schema for Gateway Execute Command Response\nexport const GatewayExecuteCommandResponseSchema = z.object({\n    status: z.string(),\n    vm_status_code: z.number().int(),\n    vm_response: z.object({\n        args: z.array(z.string()).optional(),\n        return_code: z.number().int(),\n        stdout: z.string(),\n        stderr: z.string(),\n        duration_s: z.number().optional(),\n    })\n});\n"
  },
  {
    "path": "apps/api/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"strict\": true,\n    \"verbatimModuleSyntax\": false,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"types\": [\n      \"node\"\n    ],\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"hono/jsx\",\n    \"outDir\": \"./dist\",\n    \"incremental\": true\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\"],\n  \"exclude\": [\"node_modules\"]\n}"
  },
  {
    "path": "apps/docs/.gitignore",
    "content": "# Dependencies\nnode_modules/\n.pnp\n.pnp.js\n\n# Build outputs\n.next/\nout/\nbuild/\ndist/\n.cache/\n\n# Generated files\n.docusaurus/\n.cache-loader/\n.vercel/\n\n# Environment variables\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Debug logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# Editor directories and files\n.idea/\n.vscode/\n*.swp\n*.swo\n.DS_Store\n\n# Temporary files\n*.log\n*.tmp\ntmp/\ntemp/\n\n# Coverage directory\ncoverage/\n"
  },
  {
    "path": "apps/docs/.map.ts",
    "content": "/** Auto-generated **/\ndeclare const map: Record<string, unknown>\n\nexport { map }"
  },
  {
    "path": "apps/docs/README.md",
    "content": "# docs\n\nThis is a Next.js application generated with\n[Create Fumadocs](https://github.com/fuma-nama/fumadocs).\n\nRun development server:\n\n```bash\nnpm run dev\n# or\npnpm dev\n# or\nyarn dev\n```\n\nOpen http://localhost:3000/docs to view the documentation.\n\nYou can also view the reference documentation for the API at https://docs.cyberdesk.io/docs/api-reference which is generated from the /scripts/ directory.\n\n## Learn More\n\nTo learn more about Fumadocs features , take a look at the following\nresources:\n\n- [Fumadocs](https://fumadocs.vercel.app) - learn about Fumadocs\n"
  },
  {
    "path": "apps/docs/app/api/search/route.ts",
    "content": "import { getPages } from \"@/app/source\";\nimport { createSearchAPI } from \"fumadocs-core/search/server\";\n\nexport const { GET } = createSearchAPI(\"advanced\", {\n  indexes: getPages().map((page) => ({\n    title: page.data.title,\n    structuredData: page.data.exports.structuredData,\n    id: page.url,\n    url: page.url,\n  })),\n});\n"
  },
  {
    "path": "apps/docs/app/docs/[[...slug]]/page.tsx",
    "content": "import { getPage, getPages } from \"@/app/source\";\nimport { DocsBody, DocsPage } from \"fumadocs-ui/page\";\nimport type { Metadata } from \"next\";\nimport { notFound } from \"next/navigation\";\n\nexport default async function Page({\n  params,\n}: {\n  params: { slug?: string[] };\n}) {\n  const page = getPage(params.slug);\n\n  if (page == null) {\n    notFound();\n  }\n\n  const MDX = page.data.exports.default;\n\n  return (\n    <DocsPage toc={page.data.exports.toc}>\n      <DocsBody>\n        <h1>{page.data.title}</h1>\n        <MDX />\n      </DocsBody>\n    </DocsPage>\n  );\n}\n\nexport async function generateStaticParams() {\n  return getPages().map((page) => ({\n    slug: page.slugs,\n  }));\n}\n\nexport function generateMetadata({ params }: { params: { slug?: string[] } }) {\n  const page = getPage(params.slug);\n\n  if (page == null) notFound();\n\n  return {\n    title: page.data.title,\n    description: page.data.description,\n  } satisfies Metadata;\n}\n"
  },
  {
    "path": "apps/docs/app/docs/layout.tsx",
    "content": "import { DocsLayout } from \"fumadocs-ui/layout\";\nimport type { ReactNode } from \"react\";\n\nimport { baseOptions } from \"../layout.config\";\nimport { pageTree } from \"../source\";\n\nexport default function Layout({ children }: { children: ReactNode }) {\n  return (\n    <DocsLayout tree={pageTree} {...baseOptions}>\n      {children}\n    </DocsLayout>\n  );\n}\n"
  },
  {
    "path": "apps/docs/app/global.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
  },
  {
    "path": "apps/docs/app/layout.config.tsx",
    "content": "import { type BaseLayoutProps } from \"fumadocs-ui/layout\";\n\n// basic configuration here\nexport const baseOptions: BaseLayoutProps = {\n  nav: {\n    title: \"Cyberdesk Docs\",\n  },\n  links: [\n    {\n      text: \"Documentation\",\n      url: \"/docs\",\n      active: \"nested-url\",\n    },\n    {\n      text: \"Main Site\",\n      url: \"https://cyberdesk.io\",\n    }\n  ],\n  githubUrl: \"https://github.com/cyberdesk-hq/cyberdesk\",\n};\n"
  },
  {
    "path": "apps/docs/app/layout.tsx",
    "content": "import \"./global.css\";\n\nimport { RootProvider } from \"fumadocs-ui/provider\";\nimport { Inter } from \"next/font/google\";\nimport type { ReactNode } from \"react\";\nimport { Analytics } from \"@vercel/analytics/react\";\n\nconst inter = Inter({\n  subsets: [\"latin\"],\n});\n\nexport default function Layout({ children }: { children: ReactNode }) {\n  return (\n    <html lang=\"en\" className={inter.className} suppressHydrationWarning>\n      <body>\n        <RootProvider>\n          {children}\n          <Analytics />\n        </RootProvider>\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "apps/docs/app/page.tsx",
    "content": "import { redirect } from 'next/navigation';\n\nexport default function HomePage() {\n  redirect('/docs');\n}\n"
  },
  {
    "path": "apps/docs/app/source.ts",
    "content": "import { map } from \"@/.map\";\nimport { loader } from \"fumadocs-core/source\";\nimport { createMDXSource } from \"fumadocs-mdx\";\n\nexport const { getPage, getPages, pageTree } = loader({\n  baseUrl: \"/docs\",\n  rootDir: \"docs\",\n  source: createMDXSource(map),\n});\n"
  },
  {
    "path": "apps/docs/content/docs/api-reference.mdx",
    "content": "---\ntitle: API Reference\ndescription: API for Cyberdesk, to create, control, and manage virtual desktop instances.\nfull: true\ntoc: false\n---\n\nimport { Root, API, APIInfo, APIExample, Responses, Response, ResponseTypes, ExampleResponse, TypeScriptResponse, Property, ObjectCollapsible, Requests, Request } from \"fumadocs-ui/components/api\";\n\n<Root>\n\n<API>\n\n<APIInfo method={\"GET\"} route={\"/v1/desktop/:id\"}>\n\n## Get details of a specific desktop instance\n\nReturns the ID, status, creation timestamp, and timeout timestamp for a given desktop instance.\n\n### Path Parameters\n\n<Property name={\"id\"} type={\"string\"} required={true} deprecated={false}>\n\nThe UUID of the desktop instance to retrieve\n\n<span>Example: `\"a1b2c3d4-e5f6-7890-1234-567890abcdef\"`</span>\n\n<span>Format: `\"uuid\"`</span>\n\n</Property>\n\n### Header Parameters\n\n<Property name={\"x-api-key\"} type={\"string\"} required={true} deprecated={false}>\n\nAPI key for authentication\n\n<span>Example: `\"api_12345\"`</span>\n\n</Property>\n\n| Status code | Description |\n| ----------- | ----------- |\n| `200` | Desktop instance details retrieved successfully |\n| `400` | The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). |\n| `401` | Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response. |\n| `403` | The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. |\n| `404` | The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. |\n| `409` | This response is sent when a request conflicts with the current state of the server. |\n| `429` | The user has sent too many requests in a given amount of time (\"rate limiting\") |\n| `500` | The server has encountered a situation it does not know how to handle. |\n| `502` | The server, while acting as a gateway or proxy, received an invalid response from the upstream server. |\n\n</APIInfo>\n\n<APIExample>\n\n<Requests items={[\"cURL\",\"JavaScript\"]}>\n\n<Request value={\"cURL\"}>\n\n```bash\ncurl -X GET \"https://api.cyberdesk.io/v1/desktop/:id\" \\\n  -H \"x-api-key: api_12345\"\n```\n\n</Request>\n\n<Request value={\"JavaScript\"}>\n\n```js\nfetch(\"https://api.cyberdesk.io/v1/desktop/:id\", {\n  method: \"GET\",\n  headers: {\n  \"x-api-key\": \"api_12345\"\n}\n});\n```\n\n</Request>\n\n</Requests>\n\n<Responses items={[\"200\",\"400\",\"401\",\"403\",\"404\",\"409\",\"429\",\"500\",\"502\"]}>\n\n<Response value={\"200\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"id\": \"a1b2c3d4-e5f6-7890-1234-567890abcdef\",\n  \"status\": \"running\",\n  \"stream_url\": \"https://cyberdesk.com/vnc/a1b2c3d4-e5f6-7890-1234-567890abcdef\",\n  \"created_at\": \"2023-10-27T10:00:00Z\",\n  \"timeout_at\": \"2023-10-28T10:00:00Z\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  /**\n   * Unique identifier for the desktop instance\n   */\n  id: string;\n  /**\n   * Current status of the desktop instance\n   */\n  status: \"pending\" | \"running\" | \"terminated\" | \"error\";\n  /**\n   * URL for the desktop stream (null if the desktop is not running)\n   */\n  stream_url: string;\n  /**\n   * Timestamp when the instance was created\n   */\n  created_at: string;\n  /**\n   * Timestamp when the instance will automatically time out\n   */\n  timeout_at: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"400\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"401\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"403\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"404\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"409\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"429\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"500\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"502\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n</Responses>\n\n</APIExample>\n\n</API>\n\n<API>\n\n<APIInfo method={\"POST\"} route={\"/v1/desktop\"}>\n\n## Create a new virtual desktop instance\n\nCreates a new virtual desktop instance and returns its ID and stream URL\n\n### Request Body (Optional)\n\n<Property name={\"timeout_ms\"} type={\"integer\"} required={false} deprecated={undefined}>\n\nTimeout in milliseconds for the desktop session\n\n<span>Example: `3600000`</span>\n\n</Property>\n\n### Header Parameters\n\n<Property name={\"x-api-key\"} type={\"string\"} required={true} deprecated={false}>\n\nAPI key for authentication\n\n<span>Example: `\"api_12345\"`</span>\n\n</Property>\n\n| Status code | Description |\n| ----------- | ----------- |\n| `200` | Desktop creation initiated successfully |\n| `400` | The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). |\n| `401` | Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response. |\n| `403` | The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. |\n| `404` | The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. |\n| `409` | This response is sent when a request conflicts with the current state of the server. |\n| `429` | The user has sent too many requests in a given amount of time (\"rate limiting\") |\n| `500` | The server has encountered a situation it does not know how to handle. |\n| `502` | The server, while acting as a gateway or proxy, received an invalid response from the upstream server. |\n\n</APIInfo>\n\n<APIExample>\n\n<Requests items={[\"cURL\",\"JavaScript\"]}>\n\n<Request value={\"cURL\"}>\n\n```bash\ncurl -X POST \"https://api.cyberdesk.io/v1/desktop\" \\\n  -H \"x-api-key: api_12345\" \\\n  -d '{\n  \"timeout_ms\": 3600000\n}'\n```\n\n</Request>\n\n<Request value={\"JavaScript\"}>\n\n```js\nfetch(\"https://api.cyberdesk.io/v1/desktop\", {\n  method: \"POST\",\n  headers: {\n  \"x-api-key\": \"api_12345\"\n}\n});\n```\n\n</Request>\n\n</Requests>\n\n<Responses items={[\"200\",\"400\",\"401\",\"403\",\"404\",\"409\",\"429\",\"500\",\"502\"]}>\n\n<Response value={\"200\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"id\": \"desktop_12345\",\n  \"status\": \"pending\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  /**\n   * Unique identifier for the desktop instance\n   */\n  id: string;\n  /**\n   * Initial status of the desktop instance after creation request\n   */\n  status: \"pending\" | \"running\" | \"terminated\" | \"error\";\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"400\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"401\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"403\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"404\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"409\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"429\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"500\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"502\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n</Responses>\n\n</APIExample>\n\n</API>\n\n<API>\n\n<APIInfo method={\"POST\"} route={\"/v1/desktop/:id/stop\"}>\n\n## Stop a running desktop instance\n\nStops a running desktop instance and cleans up resources\n\n### Path Parameters\n\n<Property name={\"id\"} type={\"string\"} required={true} deprecated={false}>\n\nDesktop instance ID to stop\n\n<span>Example: `\"desktop_12345\"`</span>\n\n</Property>\n\n### Header Parameters\n\n<Property name={\"x-api-key\"} type={\"string\"} required={true} deprecated={false}>\n\nAPI key for authentication\n\n<span>Example: `\"api_12345\"`</span>\n\n</Property>\n\n| Status code | Description |\n| ----------- | ----------- |\n| `200` | Desktop stopped successfully |\n| `400` | The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). |\n| `401` | Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response. |\n| `403` | The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. |\n| `404` | The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. |\n| `409` | This response is sent when a request conflicts with the current state of the server. |\n| `429` | The user has sent too many requests in a given amount of time (\"rate limiting\") |\n| `500` | The server has encountered a situation it does not know how to handle. |\n| `502` | The server, while acting as a gateway or proxy, received an invalid response from the upstream server. |\n\n</APIInfo>\n\n<APIExample>\n\n<Requests items={[\"cURL\",\"JavaScript\"]}>\n\n<Request value={\"cURL\"}>\n\n```bash\ncurl -X POST \"https://api.cyberdesk.io/v1/desktop/:id/stop\" \\\n  -H \"x-api-key: api_12345\"\n```\n\n</Request>\n\n<Request value={\"JavaScript\"}>\n\n```js\nfetch(\"https://api.cyberdesk.io/v1/desktop/:id/stop\", {\n  method: \"POST\",\n  headers: {\n  \"x-api-key\": \"api_12345\"\n}\n});\n```\n\n</Request>\n\n</Requests>\n\n<Responses items={[\"200\",\"400\",\"401\",\"403\",\"404\",\"409\",\"429\",\"500\",\"502\"]}>\n\n<Response value={\"200\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"terminated\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  /**\n   * Status of the desktop instance after stopping\n   */\n  status: \"pending\" | \"running\" | \"terminated\" | \"error\";\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"400\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"401\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"403\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"404\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"409\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"429\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"500\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"502\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n</Responses>\n\n</APIExample>\n\n</API>\n\n<API>\n\n<APIInfo method={\"POST\"} route={\"/v1/desktop/:id/computer-action\"}>\n\n## Perform an action on the desktop\n\nExecutes a computer action such as mouse clicks, keyboard input, or screenshots on the desktop\n\n### Request Body (Optional)\n\n<Property name={\"body\"} type={\"Click Mouse Action | Scroll Action | Move Mouse Action | Drag Mouse Action | Type Text Action | Press Keys Action | Wait Action | Screenshot Action | Get Cursor Position Action\"} required={false} deprecated={undefined}>\n\n<ObjectCollapsible name={\"Click Mouse Action\"}>\n\n<Property name={\"type\"} type={\"string\"} required={true} deprecated={undefined}>\n\nPerform a mouse action: click, press (down), or release (up). Defaults to a single left click at the current position.\n\n<span>Example: `\"click_mouse\"`</span>\n\n<span>Value in: `\"click_mouse\"`</span>\n\n</Property>\n\n<Property name={\"x\"} type={\"integer\"} required={false} deprecated={undefined}>\n\nX coordinate for the action (optional, uses current position if omitted)\n\n<span>Example: `500`</span>\n\n</Property>\n\n<Property name={\"y\"} type={\"integer\"} required={false} deprecated={undefined}>\n\nY coordinate for the action (optional, uses current position if omitted)\n\n<span>Example: `300`</span>\n\n</Property>\n\n<Property name={\"button\"} type={\"string\"} required={false} deprecated={undefined}>\n\nMouse button to use (optional, defaults to 'left')\n\n<span>Example: `\"left\"`</span>\n\n<span>Value in: `\"left\" | \"right\" | \"middle\"`</span>\n\n</Property>\n\n<Property name={\"num_of_clicks\"} type={\"integer\"} required={false} deprecated={undefined}>\n\nNumber of clicks to perform (optional, defaults to 1, only applicable for 'click' type)\n\n<span>Example: `1`</span>\n\n<span>Minimum: `0`</span>\n\n</Property>\n\n<Property name={\"click_type\"} type={\"string\"} required={false} deprecated={undefined}>\n\nType of mouse action (optional, defaults to 'click')\n\n<span>Example: `\"click\"`</span>\n\n<span>Value in: `\"click\" | \"down\" | \"up\"`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n<ObjectCollapsible name={\"Scroll Action\"}>\n\n<Property name={\"type\"} type={\"string\"} required={true} deprecated={undefined}>\n\nScroll the mouse wheel in the specified direction\n\n<span>Example: `\"scroll\"`</span>\n\n<span>Value in: `\"scroll\"`</span>\n\n</Property>\n\n<Property name={\"direction\"} type={\"string\"} required={true} deprecated={undefined}>\n\nDirection to scroll\n\n<span>Example: `\"down\"`</span>\n\n<span>Value in: `\"up\" | \"down\" | \"left\" | \"right\"`</span>\n\n</Property>\n\n<Property name={\"amount\"} type={\"integer\"} required={true} deprecated={undefined}>\n\nAmount to scroll in pixels\n\n<span>Example: `100`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n<ObjectCollapsible name={\"Move Mouse Action\"}>\n\n<Property name={\"type\"} type={\"string\"} required={true} deprecated={undefined}>\n\nMove the mouse cursor to the specified coordinates\n\n<span>Example: `\"move_mouse\"`</span>\n\n<span>Value in: `\"move_mouse\"`</span>\n\n</Property>\n\n<Property name={\"x\"} type={\"integer\"} required={true} deprecated={undefined}>\n\nX coordinate to move to\n\n<span>Example: `500`</span>\n\n</Property>\n\n<Property name={\"y\"} type={\"integer\"} required={true} deprecated={undefined}>\n\nY coordinate to move to\n\n<span>Example: `300`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n<ObjectCollapsible name={\"Drag Mouse Action\"}>\n\n<Property name={\"type\"} type={\"string\"} required={true} deprecated={undefined}>\n\nDrag the mouse from start to end coordinates\n\n<span>Example: `\"drag_mouse\"`</span>\n\n<span>Value in: `\"drag_mouse\"`</span>\n\n</Property>\n\n<Property name={\"start\"} type={\"object\"} required={true} deprecated={undefined}>\n\nStarting coordinates for the drag operation\n\n<span>Example: `{\"x\":100,\"y\":100}`</span>\n\n<ObjectCollapsible name={\"start\"}>\n\n<Property name={\"x\"} type={\"integer\"} required={true} deprecated={undefined}>\n\nX coordinate on the screen\n\n<span>Example: `500`</span>\n\n</Property>\n\n<Property name={\"y\"} type={\"integer\"} required={true} deprecated={undefined}>\n\nY coordinate on the screen\n\n<span>Example: `300`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n</Property>\n\n<Property name={\"end\"} type={\"object\"} required={true} deprecated={undefined}>\n\nEnding coordinates for the drag operation\n\n<span>Example: `{\"x\":300,\"y\":300}`</span>\n\n<ObjectCollapsible name={\"end\"}>\n\n<Property name={\"x\"} type={\"integer\"} required={true} deprecated={undefined}>\n\nX coordinate on the screen\n\n<span>Example: `500`</span>\n\n</Property>\n\n<Property name={\"y\"} type={\"integer\"} required={true} deprecated={undefined}>\n\nY coordinate on the screen\n\n<span>Example: `300`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n</Property>\n\n</ObjectCollapsible>\n\n<ObjectCollapsible name={\"Type Text Action\"}>\n\n<Property name={\"type\"} type={\"string\"} required={true} deprecated={undefined}>\n\nType text at the current cursor position\n\n<span>Example: `\"type\"`</span>\n\n<span>Value in: `\"type\"`</span>\n\n</Property>\n\n<Property name={\"text\"} type={\"string\"} required={true} deprecated={undefined}>\n\nText to type\n\n<span>Example: `\"Hello, World!\"`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n<ObjectCollapsible name={\"Press Keys Action\"}>\n\n<Property name={\"type\"} type={\"string\"} required={true} deprecated={undefined}>\n\nPress, hold down, or release one or more keyboard keys. Defaults to a single press and release.\n\n<span>Example: `\"press_keys\"`</span>\n\n<span>Value in: `\"press_keys\"`</span>\n\n</Property>\n\n<Property name={\"keys\"} type={\"Any properties in string, array<string>\"} required={true} deprecated={undefined}>\n\n<ObjectCollapsible name={\"Object 1\"}>\n\n<Property name={\"element\"} type={\"array<string>\"} required={false} deprecated={undefined}>\n\nMultiple keys to press simultaneously\n\n<span>Example: `[\"Control\",\"c\"]`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n</Property>\n\n<Property name={\"key_action_type\"} type={\"string\"} required={false} deprecated={undefined}>\n\nType of key action (optional, defaults to 'press' which is a down and up action)\n\n<span>Example: `\"press\"`</span>\n\n<span>Value in: `\"press\" | \"down\" | \"up\"`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n<ObjectCollapsible name={\"Wait Action\"}>\n\n<Property name={\"type\"} type={\"string\"} required={true} deprecated={undefined}>\n\nWait for the specified number of milliseconds\n\n<span>Example: `\"wait\"`</span>\n\n<span>Value in: `\"wait\"`</span>\n\n</Property>\n\n<Property name={\"ms\"} type={\"integer\"} required={true} deprecated={undefined}>\n\nTime to wait in milliseconds\n\n<span>Example: `1000`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n<ObjectCollapsible name={\"Screenshot Action\"}>\n\n<Property name={\"type\"} type={\"string\"} required={true} deprecated={undefined}>\n\nTake a screenshot of the desktop\n\n<span>Example: `\"screenshot\"`</span>\n\n<span>Value in: `\"screenshot\"`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n<ObjectCollapsible name={\"Get Cursor Position Action\"}>\n\n<Property name={\"type\"} type={\"string\"} required={true} deprecated={undefined}>\n\nGet the current mouse cursor position\n\n<span>Example: `\"get_cursor_position\"`</span>\n\n<span>Value in: `\"get_cursor_position\"`</span>\n\n</Property>\n\n</ObjectCollapsible>\n\n</Property>\n\n### Path Parameters\n\n<Property name={\"id\"} type={\"string\"} required={true} deprecated={false}>\n\nDesktop instance ID to perform the action on\n\n<span>Example: `\"desktop_12345\"`</span>\n\n</Property>\n\n### Header Parameters\n\n<Property name={\"x-api-key\"} type={\"string\"} required={true} deprecated={false}>\n\nAPI key for authentication\n\n<span>Example: `\"api_12345\"`</span>\n\n</Property>\n\n| Status code | Description |\n| ----------- | ----------- |\n| `200` | Action executed successfully. Response may contain output or image data depending on the action. |\n| `400` | The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). |\n| `401` | Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response. |\n| `403` | The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. |\n| `404` | The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. |\n| `409` | This response is sent when a request conflicts with the current state of the server. |\n| `429` | The user has sent too many requests in a given amount of time (\"rate limiting\") |\n| `500` | The server has encountered a situation it does not know how to handle. |\n| `502` | The server, while acting as a gateway or proxy, received an invalid response from the upstream server. |\n\n</APIInfo>\n\n<APIExample>\n\n<Requests items={[\"cURL\",\"JavaScript\"]}>\n\n<Request value={\"cURL\"}>\n\n```bash\ncurl -X POST \"https://api.cyberdesk.io/v1/desktop/:id/computer-action\" \\\n  -H \"x-api-key: api_12345\" \\\n  -d '{\n  \"type\": \"click_mouse\",\n  \"x\": 500,\n  \"y\": 300,\n  \"button\": \"left\",\n  \"num_of_clicks\": 1,\n  \"click_type\": \"click\"\n}'\n```\n\n</Request>\n\n<Request value={\"JavaScript\"}>\n\n```js\nfetch(\"https://api.cyberdesk.io/v1/desktop/:id/computer-action\", {\n  method: \"POST\",\n  headers: {\n  \"x-api-key\": \"api_12345\"\n}\n});\n```\n\n</Request>\n\n</Requests>\n\n<Responses items={[\"200\",\"400\",\"401\",\"403\",\"404\",\"409\",\"429\",\"500\",\"502\"]}>\n\n<Response value={\"200\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"output\": \"X=500 Y=300\",\n  \"error\": \"Command failed with code 1: xdotool: command not found\",\n  \"base64_image\": \"/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQ...\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  /**\n   * Raw string output from the executed command (if any)\n   */\n  output?: string;\n  /**\n   * Error message if the operation failed (also indicated by non-2xx HTTP status)\n   */\n  error?: string;\n  /**\n   * Base64 encoded JPEG image data (only returned for screenshot actions)\n   */\n  base64_image?: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"400\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"401\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"403\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"404\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"409\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"429\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"500\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"502\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n</Responses>\n\n</APIExample>\n\n</API>\n\n<API>\n\n<APIInfo method={\"POST\"} route={\"/v1/desktop/:id/bash-action\"}>\n\n## Execute a bash command on the desktop\n\nRuns a bash command on the desktop and returns the command output\n\n### Request Body (Optional)\n\n<Property name={\"command\"} type={\"string\"} required={true} deprecated={undefined}>\n\nBash command to execute\n\n<span>Example: `\"echo 'Hello, World!'\"`</span>\n\n</Property>\n\n### Path Parameters\n\n<Property name={\"id\"} type={\"string\"} required={true} deprecated={false}>\n\nDesktop instance ID to run the command on\n\n<span>Example: `\"desktop_12345\"`</span>\n\n</Property>\n\n### Header Parameters\n\n<Property name={\"x-api-key\"} type={\"string\"} required={true} deprecated={false}>\n\nAPI key for authentication\n\n<span>Example: `\"api_12345\"`</span>\n\n</Property>\n\n| Status code | Description |\n| ----------- | ----------- |\n| `200` | Command executed successfully. Response contains command output. |\n| `400` | The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). |\n| `401` | Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response. |\n| `403` | The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. |\n| `404` | The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. |\n| `409` | This response is sent when a request conflicts with the current state of the server. |\n| `429` | The user has sent too many requests in a given amount of time (\"rate limiting\") |\n| `500` | The server has encountered a situation it does not know how to handle. |\n| `502` | The server, while acting as a gateway or proxy, received an invalid response from the upstream server. |\n\n</APIInfo>\n\n<APIExample>\n\n<Requests items={[\"cURL\",\"JavaScript\"]}>\n\n<Request value={\"cURL\"}>\n\n```bash\ncurl -X POST \"https://api.cyberdesk.io/v1/desktop/:id/bash-action\" \\\n  -H \"x-api-key: api_12345\" \\\n  -d '{\n  \"command\": \"echo 'Hello, World!'\"\n}'\n```\n\n</Request>\n\n<Request value={\"JavaScript\"}>\n\n```js\nfetch(\"https://api.cyberdesk.io/v1/desktop/:id/bash-action\", {\n  method: \"POST\",\n  headers: {\n  \"x-api-key\": \"api_12345\"\n}\n});\n```\n\n</Request>\n\n</Requests>\n\n<Responses items={[\"200\",\"400\",\"401\",\"403\",\"404\",\"409\",\"429\",\"500\",\"502\"]}>\n\n<Response value={\"200\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"output\": \"X=500 Y=300\",\n  \"error\": \"Command failed with code 1: xdotool: command not found\",\n  \"base64_image\": \"/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQ...\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  /**\n   * Raw string output from the executed command (if any)\n   */\n  output?: string;\n  /**\n   * Error message if the operation failed (also indicated by non-2xx HTTP status)\n   */\n  error?: string;\n  /**\n   * Base64 encoded JPEG image data (only returned for screenshot actions)\n   */\n  base64_image?: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"400\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"401\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"403\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"404\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"409\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"429\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"500\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n<Response value={\"502\"}>\n\n<ResponseTypes>\n\n<ExampleResponse>\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": \"Instance not found or unauthorized\"\n}\n```\n\n</ExampleResponse>\n\n<TypeScriptResponse>\n\n```ts\nexport interface Response {\n  status: \"error\";\n  /**\n   * Error message detailing what went wrong\n   */\n  error: string;\n}\n```\n\n</TypeScriptResponse>\n\n</ResponseTypes>\n\n</Response>\n\n</Responses>\n\n</APIExample>\n\n</API>\n\n</Root>"
  },
  {
    "path": "apps/docs/content/docs/conceptual-guide.mdx",
    "content": "---\ntitle: Conceptual Guide\ndescription: Understanding core concepts of Cyberdesk\n---\nUnderstanding the core concepts of Cyberdesk will help you build more effective applications.\n\n## Architecture Overview\n\nCyberdesk is built on a microservices architecture that provides scalable, reliable access to virtual desktop environments. The system consists of:\n\n- **API Service**: The backend service accessed via the SDK.\n- **Desktop Pool**: Managed virtual desktop instances.\n- **Stream Service**: Real-time visual streaming of desktop environments.\n- **Database**: Persistent storage for desktop metadata and user information.\n\n## Desktop Instances\n\nA desktop instance is a virtual desktop environment running in the cloud. Each instance:\n\n- Has a unique identifier (UUID format).\n- Runs in an isolated environment for security.\n- Can be controlled programmatically through the **SDK methods**.\n- Provides a `stream_url` (obtained via SDK methods) for visual feedback.\n- Persists until explicitly stopped (via `terminateDesktop`) or times out.\n- Has configurable resources (CPU, memory, storage) - future feature.\n\nWhen creating a desktop instance using `launchDesktop`, you can specify a custom timeout in milliseconds using the `body.timeout_ms` parameter. This allows you to control how long the desktop instance will remain active before being automatically terminated. If not specified, a default timeout is applied. Check your plan for maximum allowed timeout values.\n\n## Computer Actions\n\nCyberdesk supports various computer actions to interact with the desktop via the **`executeComputerAction` SDK method**. Actions are specified using a `body` object containing a `type` field and associated parameters:\n\n### Mouse Actions\n\n- **`type: 'click_mouse'`**: Perform a mouse action (click, press down, or release up).\n  - `x`, `y` (optional): Coordinates for the click.\n  - `button` (optional): `left`, `right`, or `middle` (defaults to `left`).\n  - `num_of_clicks` (optional): Number of clicks (defaults to 1, for `click_type: 'click'`).\n  - `click_type` (optional): `click` (down and up), `down` (press), or `up` (release) (defaults to `click`).\n- **`type: 'scroll'`**: Scroll the mouse wheel.\n  - `direction`: `up`, `down`, `left`, or `right`.\n  - `amount`: Amount to scroll in pixels.\n- **`type: 'move_mouse'`**: Move the mouse cursor to specific coordinates.\n  - `x`, `y`: Target coordinates.\n- **`type: 'drag_mouse'`**: Drag the mouse from a start point to an end point.\n  - `start`: `{ x, y }` starting coordinates.\n  - `end`: `{ x, y }` ending coordinates.\n\n### Keyboard Actions\n\n- **`type: 'type'`**: Type text at the current cursor position.\n  - `text`: The string to type.\n- **`type: 'press_keys'`**: Press, hold down, or release keyboard keys.\n  - `keys`: A single key string (e.g., `'Enter'`) or an array of keys to press simultaneously (e.g., `['Control', 'c']`).\n  - `key_action_type` (optional): `press` (down and up), `down` (hold), or `up` (release) (defaults to `press`).\n\n### Other Actions\n\n- **`type: 'wait'`**: Pause execution for a specified duration.\n  - `ms`: Time to wait in milliseconds.\n- **`type: 'screenshot'`**: Capture the current state of the desktop.\n  - Returns a `base64_image` field in the successful response object.\n- **`type: 'get_cursor_position'`**: Get the current mouse cursor coordinates.\n  - Returns `x`, `y` coordinates in the successful response object.\n\n## Bash Actions\n\nBash actions allow you to execute shell commands on the desktop instance via the **`executeBashAction` SDK method**. This is useful for:\n\n- Installing software\n- Running applications\n- Manipulating files\n- Executing scripts\n- System configuration\n\nThe method accepts a `body.command` parameter containing the shell command to execute and returns the command `output` (stdout/stderr) as a string in the successful response object.\n\n## Desktop Streaming\n\nCyberdesk provides real-time streaming of desktop visuals. The `stream_url` obtained from the `launchDesktop` or `getDesktop` SDK methods can be used for:\n\n1.  **Web-based Viewer**: Opening the URL in a browser.\n2.  **VNC Protocol**: Connecting with a VNC client (if the stream URL supports it).\n3.  **Embedded Iframe**: Embedding the desktop view directly in your web applications.\n\nThe streaming service aims for low-latency transmission suitable for real-time interaction.\n\n## Authentication and Security\n\nCyberdesk uses API keys for authentication. The **SDK client is initialized with your API key**, which is then automatically included in all requests.\n\n- API keys are associated with a specific user account.\n- Rate limits apply to prevent abuse.\n- Keys can be revoked or regenerated from your dashboard.\n- Keep your API key secure.\n\nCommunication with the API is encrypted using TLS/SSL.\n\n## Resource Management\n\nCyberdesk automatically manages resources:\n\n- Desktop instances are cleaned up when stopped via the **`terminateDesktop` SDK method** or when they reach their timeout.\n- Users are responsible for stopping instances when no longer needed to manage costs and resources.\n- Resource quotas and limits may apply depending on your subscription plan.\n\n## Error Handling\n\nThe Cyberdesk **SDK methods** simplify error handling. Each method returns a promise that resolves to an object. You should check if this object contains an `error` property:\n\n```typescript\nconst result = await cyberdesk.someMethod(...);\n\nif ('error' in result) {\n  // Handle the error\n  console.error('Cyberdesk SDK Error:', result.error);\n  // The 'error' property contains the descriptive error message from the API\n} else {\n  // Process the successful result\n  console.log('Success:', result);\n}\n```\n\nCommon error scenarios include:\n- Invalid parameters in the request body (`body` property of the method call).\n- Missing or invalid API key (during client initialization or if the key is revoked).\n- Attempting an action not allowed by your plan.\n- Referencing a non-existent desktop instance ID (`path.id`).\n- Rate limit exceeded.\n- Server-side issues on the Cyberdesk platform.\n\nCheck the specific error message returned in the `error` property for details.\n\n## Performance Considerations\n\nWhen building applications with the Cyberdesk SDK, consider:\n\n- **API Call Latency**: Network latency affects the time it takes for SDK methods to complete.\n- **Action Duration**: Some actions (like `wait` or long bash commands) naturally take time.\n- **Streaming Latency**: There will be some delay in the visual stream.\n- **Error Retries**: For transient issues (like network errors or potential server-side hiccups), consider implementing retry logic around your SDK calls, possibly with exponential backoff.\n\n## Billing and Usage\n\nCyberdesk billing is typically based on factors like:\n\n- **Active Desktop Time**: The duration desktop instances are running (often billed per second or minute).\n\nMonitor your usage and understand the pricing model via the Cyberdesk dashboard (coming soon!).\n"
  },
  {
    "path": "apps/docs/content/docs/index.mdx",
    "content": "---\ntitle: Cyberdesk Documentation\ndescription: Comprehensive documentation for Cyberdesk service\n---\n\nWelcome to the official documentation for Cyberdesk, a powerful service for creating, controlling, and managing virtual desktop instances in the cloud. This documentation focuses on using the **official TypeScript SDK**, the recommended way to interact with the Cyberdesk API. We also provide a Python SDK (docs coming soon!).\n\nWhether you're building automation tools, testing applications, or creating AI agents that can interact with computers, the Cyberdesk SDK provides the tools you need.\n\n## What's New\n\n- **TypeScript + Python SDKs**: The easiest way to integrate Cyberdesk into your applications.\n- **AI Integration Guide**: Learn how to integrate Cyberdesk with AI models like Claude to create agents that can use computers.\n- **Responsive Desktop Viewer**: Implement a responsive viewer for desktop streams that works across all devices.\n- **Enhanced API Reference**: Complete API documentation with examples and response formats\n\n## Documentation Sections\n\n- [Introduction](/docs/introduction) - Learn what Cyberdesk is and its key features\n- [Quickstart](/docs/quickstart) - Get up and running with the Cyberdesk SDK in minutes\n- [Tutorials](/docs/tutorials) - Step-by-step guides using the SDK for common use cases, including integration with AI agents\n- [Conceptual Guide](/docs/conceptual-guide) - Understand the core concepts of Cyberdesk\n- [API Reference](/docs/api-reference) - Detailed REST API documentation (primarily for reference, SDK usage is recommended)\n\n## Key Features\n\n- **Virtual Desktop Creation**: Create cloud-based desktop instances on demand\n- **Programmatic Control**: Control mouse, keyboard, and system actions via API\n- **Bash Command Execution**: Run shell commands on desktop instances\n- **Visual Streaming**: Stream desktop visuals to your applications\n- **AI Integration**: Easily integrate with AI models for computer control\n- **Secure Authentication**: API key-based authentication for secure access\n\n## Getting Started\n\nThe fastest way to get started with Cyberdesk is to follow our [Quickstart Guide](/docs/quickstart), which will walk you through setting up the SDK, creating your first desktop instance, and performing basic interactions.\n\nFor more detailed examples and use cases using the SDK, check out our [Tutorials](/docs/tutorials), which include step-by-step guides for common scenarios.\n\n## Support\n\nIf you need help or have questions about Cyberdesk, you can:\n\n- Join our [Discord community](https://discord.gg/ws5ddx5yZ8) for discussions and support\n- Contact us at dev@cyberdesk.io, for issues and feature requests\n\nWe're constantly improving Cyberdesk based on user feedback, so please let us know how we can make it better!\n"
  },
  {
    "path": "apps/docs/content/docs/introduction.mdx",
    "content": "---\ntitle: Introduction\ndescription: Introduction to Cyberdesk\n---\nCyberdesk provides a seamless way to create and interact with virtual desktop environments through a simple API. Whether you're building automation tools, testing applications, creating AI agents that can use computers, or developing remote desktop solutions, Cyberdesk offers a robust platform to handle your virtual desktop needs.\n\n## What is Cyberdesk?\n\nCyberdesk is a cloud-based service that allows you to:\n\n- Create virtual desktop instances on demand via the SDK\n- Control desktop interactions programmatically (mouse movements, clicks, keyboard input) using SDK methods\n- Execute bash commands remotely via the SDK\n- Manage desktop lifecycle (creation, interaction, termination) through the SDK\n- Stream desktop visuals to your applications or AI agents\n\n## Key Features\n\n- **Simple SDK**: An easy-to-use TypeScript SDK for Node.js and browser environments\n- **Programmatic Control**: Full control over mouse, keyboard, and system actions via intuitive SDK functions\n- **Secure Authentication**: Simple API key configuration within the SDK\n- **Streaming Capability**: Stream desktop visuals to your applications with VNC support (SDK helps manage instance details)\n- **Resource Management**: Efficient creation and cleanup of desktop instances managed via SDK calls\n- **AI Integration**: Easily integrate with AI models like Claude or GPT to create agents that can use computers (see tutorials)\n- **Cross-Platform**: Works across different operating systems and environments\n- **Low Latency**: Fast response times for real-time interaction\n\n## Use Cases\n\nCyberdesk is ideal for a variety of applications:\n\n- **AI Agents**: Create AI assistants that can interact with desktop applications\n- **Automated Testing**: Test desktop applications with programmatic control\n- **Remote Automation**: Control desktop environments from anywhere\n- **Demo Environments**: Create disposable demo environments for software showcases\n- **Training Data Generation**: Generate training data for computer vision models\n- **Virtual Workstations**: Provide remote access to virtual desktop environments\n\n## Getting Started\n\nTo get started with Cyberdesk:\n\n1. [Sign up](https://cyberdesk.io/signup) for an account\n2. Obtain your API key from the dashboard\n3. Follow our [Quickstart Guide](/docs/quickstart) to set up the SDK and create your first desktop instance\n4. Explore our [Tutorials](/docs/tutorials) for common use cases\n\nReady to dive deeper? Check out our [Conceptual Guide](/docs/conceptual-guide) to understand the core concepts behind Cyberdesk.\n"
  },
  {
    "path": "apps/docs/content/docs/meta.json",
    "content": "{\n    \"title\": \"Docs\",\n    \"pages\": [\"index\", \"introduction\", \"quickstart\", \"tutorials\", \"conceptual-guide\", \"api-reference\"],\n    \"defaultOpen\": true,\n    \"root\": true\n  }"
  },
  {
    "path": "apps/docs/content/docs/quickstart.mdx",
    "content": "---\ntitle: Quickstart\ndescription: Get started quickly with Cyberdesk\n---\nimport { Tab, Tabs } from 'fumadocs-ui/components/tabs';\n\nGet up and running with Cyberdesk in minutes.\n\n## Fastest Start: Cyberdesk Starter Assistant UI\n\nThe quickest way to get started with Cyberdesk is to use our [AI SDK Starter](https://github.com/cyberdesk-hq/cyberdesk-ai-sdk-starter) template. This ready-to-use Next.js application demonstrates how to build an AI assistant with virtual desktop control capabilities using the Cyberdesk API and Anthropic's Claude AI model.\n\n### Features\n\n- Interactive virtual desktop with streaming capabilities\n- AI assistant chat interface powered by Claude 3.7 Sonnet\n- Desktop control via AI (mouse clicks, keyboard input, screenshots)\n- Bash command execution in the virtual environment\n\n### Quick Setup\n\n1. Clone the repository (alternatively, click \"Use this template\" on GitHub):\n\n```bash\ngit clone https://github.com/cyberdesk-hq/cyberdesk-ai-sdk-starter.git\ncd cyberdesk-ai-sdk-starter\n```\n\n2. Install dependencies:\n\n```bash\nnpm install\n```\n\n3. Create a `.env.local` file with your API keys:\n\n```\nCYBERDESK_API_KEY=your_cyberdesk_api_key_here\nANTHROPIC_API_KEY=your_anthropic_api_key_here\n```\n\n4. Start the development server:\n\n```bash\nnpm run dev\n```\n\n5. Open [http://localhost:3000](http://localhost:3000) in your browser\n\nThis template provides a complete working example that you can customize for your specific use case. For more details, check out the [repository README](https://github.com/cyberdesk-hq/cyberdesk-ai-sdk-starter).\n\n## Building a Custom Integration\n\nIf you want to build your own integration, you can use either the **official TypeScript SDK** or the **official Python SDK**. See the code tabs below for both options.\n\n### Prerequisites\n\n- Node.js or Python 3.8+\n- A Cyberdesk API key (obtain from your [Cyberdesk dashboard](https://cyberdesk.io/dashboard))\n\n### Installation\n\n<Tabs groupId=\"language\" items={[\"TypeScript\", \"Python\"]} persist>\n  <Tab value=\"TypeScript\">\n\n```bash\nnpm install cyberdesk\n# or\nyarn add cyberdesk\n# or\npnpm add cyberdesk\n```\n\n  </Tab>\n  <Tab value=\"Python\">\n\n```bash\npip install cyberdesk\n```\n\n  </Tab>\n</Tabs>\n\n### 1. Initialize the Client\n\n<Tabs groupId=\"language\" items={[\"TypeScript\", \"Python\"]} persist>\n  <Tab value=\"TypeScript\">\n\n```typescript\nimport { createCyberdeskClient } from 'cyberdesk';\n\nconst cyberdesk = createCyberdeskClient({\n  apiKey: 'YOUR_API_KEY',\n  // Optionally, you can override the baseUrl or provide a custom fetch implementation\n});\n```\n\n  </Tab>\n  <Tab value=\"Python\">\n\n```python\nfrom cyberdesk import CyberdeskClient\n\nclient = CyberdeskClient(api_key=\"YOUR_API_KEY\")\n```\n\n  </Tab>\n</Tabs>\n\n### 2. Launch a Desktop Instance\n\n<Tabs groupId=\"language\" items={[\"TypeScript\", \"Python\"]} persist>\n  <Tab value=\"TypeScript\">\n\n```typescript\nconst launchResult = await cyberdesk.launchDesktop({\n  body: { timeout_ms: 600000 } // Optional: 10-minute timeout\n});\n\nif ('error' in launchResult) {\n  throw new Error('Failed to launch desktop: ' + launchResult.error);\n}\n\nconst desktopId = launchResult.id;\n```\n\n  </Tab>\n  <Tab value=\"Python\">\n\n```python\nresult = client.launch_desktop(timeout_ms=10000)  # Optional: set a timeout for the desktop session\n\nif result.error:\n    raise Exception('Failed to launch desktop: ' + str(result.error))\n\ndesktop_id = result.id\n```\n\n  </Tab>\n</Tabs>\n\n### 3. Get Desktop Information (Including Stream URL)\n\n<Tabs groupId=\"language\" items={[\"TypeScript\", \"Python\"]} persist>\n  <Tab value=\"TypeScript\">\n\n```typescript\nconst info = await cyberdesk.getDesktop({ path: { id: desktopId } });\n\nif ('error' in info) {\n  throw new Error('Failed to get desktop info: ' + info.error);\n}\n\nconsole.log('Desktop info:', info);\nconsole.log('Stream URL:', info.stream_url);\n```\n\n  </Tab>\n  <Tab value=\"Python\">\n\n```python\ninfo = client.get_desktop(desktop_id)\n\nif info.error:\n    raise Exception('Failed to get desktop info: ' + str(info.error))\n\nprint('Desktop info:', info)\n# If available: print('Stream URL:', info.stream_url)\n```\n\n  </Tab>\n</Tabs>\n\n### 4. Control the Desktop\n\n#### Perform a Mouse Click\n\n<Tabs groupId=\"language\" items={[\"TypeScript\", \"Python\"]} persist>\n  <Tab value=\"TypeScript\">\n\n```typescript\nconst actionResult = await cyberdesk.executeComputerAction({\n  path: { id: desktopId },\n  body: {\n    type: 'click_mouse',\n    x: 100,\n    y: 150,\n    button: 'left'\n  }\n});\n\nif ('error' in actionResult) {\n  throw new Error('Mouse click failed: ' + actionResult.error);\n}\n```\n\n  </Tab>\n  <Tab value=\"Python\">\n\n```python\nfrom cyberdesk.actions import click_mouse, ClickMouseButton\n\naction = click_mouse(x=100, y=150, button=ClickMouseButton.LEFT)\naction_result = client.execute_computer_action(desktop_id, action)\n\nif action_result.error:\n    raise Exception('Action failed: ' + str(action_result.error))\n```\n\n  </Tab>\n</Tabs>\n\n#### Run a Bash Command\n\n<Tabs groupId=\"language\" items={[\"TypeScript\", \"Python\"]} persist>\n  <Tab value=\"TypeScript\">\n\n```typescript\nconst bashResult = await cyberdesk.executeBashAction({\n  path: { id: desktopId },\n  body: {\n    command: 'ls -la /tmp'\n  }\n});\n\nif ('error' in bashResult) {\n  throw new Error('Bash command failed: ' + bashResult.error);\n}\n\nconsole.log('Bash command output:', bashResult.output);\n```\n\n  </Tab>\n  <Tab value=\"Python\">\n\n```python\nbash_result = client.execute_bash_action(\n    desktop_id,\n    \"ls -la /tmp\"\n)\n\nif bash_result.error:\n    raise Exception('Bash command failed: ' + str(bash_result.error))\n\nprint('Bash command output:', getattr(bash_result, 'output', bash_result))\n```\n\n  </Tab>\n</Tabs>\n\n### 5. Stop the Desktop Instance\n\n<Tabs groupId=\"language\" items={[\"TypeScript\", \"Python\"]} persist>\n  <Tab value=\"TypeScript\">\n\n```typescript\nconst terminateResult = await cyberdesk.terminateDesktop({\n  path: { id: desktopId }\n});\n\nif ('error' in terminateResult) {\n  // You might still want to proceed even if termination fails\n}\n```\n\n  </Tab>\n  <Tab value=\"Python\">\n\n```python\nterminate_result = client.terminate_desktop(desktop_id)\n\nif terminate_result.error:\n    print('Termination failed:', terminate_result.error)\n```\n\n  </Tab>\n</Tabs>\n\n### 6. Async Usage\n\n<Tabs groupId=\"language\" items={[\"TypeScript\", \"Python\"]} persist>\n  <Tab value=\"TypeScript\">\n\n```typescript\n// All methods are available as async functions (see above)\n```\n\n  </Tab>\n  <Tab value=\"Python\">\n\n```python\nimport asyncio\nfrom cyberdesk import CyberdeskClient\nfrom cyberdesk.actions import click_mouse, ClickMouseButton\n\nasync def main():\n    client = CyberdeskClient(api_key=\"YOUR_API_KEY\")\n    result = await client.async_launch_desktop(timeout_ms=10000)\n    print(result)\n    action = click_mouse(x=100, y=200, button=ClickMouseButton.LEFT)\n    await client.async_execute_computer_action(desktop_id, action)\n    # ... use other async_ methods as needed\n\nasyncio.run(main())\n```\n\n  </Tab>\n</Tabs>\n\n### 7. Type Hints and Models\n\n<Tabs groupId=\"language\" items={[\"TypeScript\", \"Python\"]} persist>\n  <Tab value=\"TypeScript\">\n\n```typescript\n// All request/response types are available from the generated models\n```\n\n  </Tab>\n  <Tab value=\"Python\">\n\n```python\nfrom cyberdesk.actions import click_mouse, drag_mouse, type_text, wait, scroll, move_mouse, press_keys, screenshot, get_cursor_position, ClickMouseButton, ClickMouseActionType, PressKeyActionType, ScrollDirection\n```\n\n  </Tab>\n</Tabs>\n\n### 8. Available Computer Actions\n\n| Action         | Factory Function (Python Only)         | Description                |\n|----------------|-------------------------|----------------------------|\n| Click Mouse    | `click_mouse`           | Mouse click at (x, y)      |\n| Drag Mouse     | `drag_mouse`            | Mouse drag from/to (x, y)  |\n| Move Mouse     | `move_mouse`            | Move mouse to (x, y)       |\n| Scroll         | `scroll`                | Scroll by dx, dy           |\n| Type Text      | `type_text`             | Type text                  |\n| Press Keys     | `press_keys`            | Press keyboard keys        |\n| Screenshot     | `screenshot`            | Take a screenshot          |\n| Wait           | `wait`                  | Wait for ms milliseconds   |\n| Get Cursor Pos | `get_cursor_position`   | Get mouse cursor position  |\n\n## Next Steps\n\n- Explore the [Tutorials](/docs/tutorials) for more complex examples using the SDK.\n- Understand core concepts in the [Conceptual Guide](/docs/conceptual-guide).\n- Review the [API Reference](/docs/api-reference) if you need details on the underlying REST API.\n"
  },
  {
    "path": "apps/docs/content/docs/tutorials.mdx",
    "content": "---\ntitle: Tutorials\ndescription: Step-by-step tutorials for using the Cyberdesk TypeScript SDK\n---\nLearn how to use the Cyberdesk TypeScript SDK effectively with these step-by-step tutorials.\n\n**Setup:** All examples assume you have initialized the SDK client as shown in the [Quickstart](/docs/quickstart):\n\n```typescript\nimport { createCyberdeskClient } from 'cyberdesk';\n\nconst cyberdesk = createCyberdeskClient({\n  apiKey: 'YOUR_API_KEY',\n});\n```\n\n## Tutorial 1: Creating and Interacting with a Desktop\n\nThis tutorial walks you through creating a desktop instance and performing basic interactions using the SDK.\n\n### Step 1: Create a Desktop Instance\n\nUse `launchDesktop`:\n\n```typescript\nasync function createDesktop() {\n  console.log('Creating desktop...');\n  const launchResult = await cyberdesk.launchDesktop({\n    body: { timeout_ms: 3600000 } // Optional: 1-hour timeout\n  });\n\n  if ('error' in launchResult) {\n    console.error('Failed to create desktop:', launchResult.error);\n    throw new Error('Failed to create desktop: ' + launchResult.error);\n  }\n\n  console.log(`Desktop created with ID: ${launchResult.id}`);\n  // Note: You might need to wait or use getDesktop to get the streamUrl\n  return launchResult.id;\n}\n\n// Example usage:\n// const desktopId = await createDesktop();\n```\n\n### Step 2: Perform Mouse Actions\n\nUse `executeComputerAction` with `type: 'click_mouse'`:\n\n```typescript\nasync function clickOnDesktop(desktopId: string) {\n  console.log(`Clicking on desktop ${desktopId}...`);\n  const clickResult = await cyberdesk.executeComputerAction({\n    path: { id: desktopId },\n    body: {\n      type: 'click_mouse',\n      x: 100,\n      y: 200,\n      button: 'left' // Optional\n    }\n  });\n\n  if ('error' in clickResult) {\n    console.error('Mouse click failed:', clickResult.error);\n    throw new Error('Mouse click failed: ' + clickResult.error);\n  }\n\n  console.log('Mouse click successful:', clickResult);\n}\n\n// Example usage:\n// await clickOnDesktop(desktopId);\n```\n\n### Step 3: Type Text\n\nUse `executeComputerAction` with `type: 'type'`:\n\n```typescript\nasync function typeOnDesktop(desktopId: string) {\n  console.log(`Typing on desktop ${desktopId}...`);\n  const typeResult = await cyberdesk.executeComputerAction({\n    path: { id: desktopId },\n    body: {\n      type: 'type',\n      text: 'Hello, Cyberdesk SDK!'\n    }\n  });\n\n  if ('error' in typeResult) {\n    console.error('Typing failed:', typeResult.error);\n    throw new Error('Typing failed: ' + typeResult.error);\n  }\n\n  console.log('Typing successful:', typeResult);\n}\n\n// Example usage:\n// await typeOnDesktop(desktopId);\n```\n\n### Step 4: Take a Screenshot\n\nUse `executeComputerAction` with `type: 'screenshot'`:\n\n```typescript\nasync function captureScreenshot(desktopId: string) {\n  console.log(`Taking screenshot of desktop ${desktopId}...`);\n  const screenshotResult = await cyberdesk.executeComputerAction({\n    path: { id: desktopId },\n    body: {\n      type: 'screenshot'\n    }\n  });\n\n  if ('error' in screenshotResult) {\n    console.error('Screenshot failed:', screenshotResult.error);\n    throw new Error('Screenshot failed: ' + screenshotResult.error);\n  }\n\n  console.log('Screenshot successful.');\n  // Access the image data:\n  // const base64Image = screenshotResult.base64_image;\n  // console.log('Base64 Image length:', base64Image?.length);\n  return screenshotResult.base64_image;\n}\n\n// Example usage:\n// const imageData = await captureScreenshot(desktopId);\n```\n\n### Step 5: Execute a Bash Command\n\nUse `executeBashAction`:\n\n```typescript\nasync function runCommand(desktopId: string) {\n  console.log(`Running command on desktop ${desktopId}...`);\n  const bashResult = await cyberdesk.executeBashAction({\n    path: { id: desktopId },\n    body: {\n      command: 'ls -la /tmp'\n    }\n  });\n\n  if ('error' in bashResult) {\n    console.error('Bash command failed:', bashResult.error);\n    throw new Error('Bash command failed: ' + bashResult.error);\n  }\n\n  console.log('Command successful.');\n  console.log('Output:', bashResult.output);\n  return bashResult.output;\n}\n\n// Example usage:\n// const commandOutput = await runCommand(desktopId);\n```\n\n### Step 6: Stop the Desktop\n\nUse `terminateDesktop`:\n\n```typescript\nasync function stopDesktop(desktopId: string) {\n  console.log(`Stopping desktop ${desktopId}...`);\n  const stopResult = await cyberdesk.terminateDesktop({\n    path: { id: desktopId }\n  });\n\n  if ('error' in stopResult) {\n    console.error('Failed to stop desktop:', stopResult.error);\n    // Decide if you need to throw an error or just log\n  }\n\n  console.log('Desktop stop requested.', stopResult);\n}\n\n// Example usage:\n// await stopDesktop(desktopId);\n```\n\n## Tutorial 2: Automating Web Testing\n\nThis tutorial demonstrates how to use the Cyberdesk SDK to automate simple web browser tasks.\n\n### Step 1: Create a Desktop Instance\n\nCreate a desktop instance using the `createDesktop` function from Tutorial 1.\n\n```typescript\n// const desktopId = await createDesktop();\n```\n\n### Step 2: Open a Web Browser and Wait\n\nUse `executeBashAction` to open a browser (e.g., Firefox) in the background, then use `executeComputerAction` with `type: 'wait'` to allow time for it to load.\n\n```typescript\nasync function openBrowserAndWait(desktopId: string, url: string, waitMs: number = 5000) {\n  console.log(`Opening ${url} on desktop ${desktopId}...`);\n  const openResult = await cyberdesk.executeBashAction({\n    path: { id: desktopId },\n    body: {\n      command: `firefox ${url} &` // Run in background\n    }\n  });\n\n  if ('error' in openResult) {\n    console.error('Failed to send open browser command:', openResult.error);\n    throw new Error('Failed to send open browser command: ' + openResult.error);\n  }\n  console.log('Browser opening command sent.');\n\n  console.log(`Waiting ${waitMs}ms for browser to load...`);\n  const waitResult = await cyberdesk.executeComputerAction({\n    path: { id: desktopId },\n    body: {\n      type: 'wait',\n      ms: waitMs\n    }\n  });\n\n  if ('error' in waitResult) {\n    console.error('Wait action failed:', waitResult.error);\n    throw new Error('Wait action failed: ' + waitResult.error);\n  }\n  console.log('Wait complete.');\n}\n\n// Example usage:\n// await openBrowserAndWait(desktopId, 'https://example.com');\n```\n\n### Step 3: Take a Screenshot for Verification\n\nCapture a screenshot using the `captureScreenshot` function from Tutorial 1 to verify the page loaded.\n\n```typescript\nasync function verifyPageLoad(desktopId: string) {\n  console.log('Taking verification screenshot...');\n  const imageData = await captureScreenshot(desktopId);\n  if (imageData) {\n    console.log('Verification screenshot captured (length:', imageData.length, ')');\n    // Add logic here to analyze the screenshot if needed\n  } else {\n    console.warn('Could not capture verification screenshot.');\n  }\n}\n\n// Example usage:\n// await verifyPageLoad(desktopId);\n```\n\n### Step 4: Stop the Desktop\n\nRemember to stop the desktop instance using the `stopDesktop` function from Tutorial 1 when the test is complete.\n\n```typescript\n// await stopDesktop(desktopId);\n```\n\n## Tutorial 3: Integrating with AI Models for Computer Use\n\nThis tutorial demonstrates how to integrate the Cyberdesk SDK with AI models (like Anthropic's Claude) using the Vercel AI SDK to create agents that can use computers.\n\n### Step 1: Prerequisites and Setup\n\nInstall the necessary dependencies:\n\n```bash\nnpm install cyberdesk @ai-sdk/anthropic ai\n```\n\nInitialize the Cyberdesk SDK client (likely in a shared module, e.g., `@/lib/cyberdeskClient.ts`):\n\n```typescript\n// src/lib/cyberdeskClient.ts\nimport { createCyberdeskClient } from 'cyberdesk';\n\nconst client = createCyberdeskClient({\n  apiKey: process.env.CYBERDESK_API_KEY || 'YOUR_API_KEY', // Use environment variable\n});\n\nexport default client;\n```\n\nEnsure you have `CYBERDESK_API_KEY` set in your environment variables.\n\n### Step 2: Implement `executeComputerAction` Utility\n\nCreate a utility function that maps AI tool parameters to the Cyberdesk SDK's `executeComputerAction` method. This function handles the various action types supported by the AI tool.\n\n```typescript\n// src/utils/computer-use.ts\nimport client from '@/lib/cyberdeskClient';\nimport type { ExecuteComputerActionParams } from \"cyberdesk\"\n\n// Define the action types your AI tool might use\nexport type ClaudeComputerActionType0124 = /* ... (action types like 'left_click', 'type', etc.) ... */\n  | \"left_click\"\n  | \"right_click\"\n  // ... (include all action types from the provided code) ...\n  | \"screenshot\";\n\nexport async function executeComputerAction(\n  action: ClaudeComputerActionType0124,\n  desktopId: string,\n  coordinate?: { x: number; y: number },\n  text?: string,\n  duration?: number,\n  scroll_amount?: number,\n  scroll_direction?: \"left\" | \"right\" | \"down\" | \"up\",\n  start_coordinate?: { x: number; y: number }\n): Promise<string | { type: \"image\"; data: string }> {\n  try {\n    let requestBody: ExecuteComputerActionParams['body'];\n\n    // Map the AI tool action to the Cyberdesk SDK's expected format\n    switch (action) {\n      case 'left_click':\n        requestBody = {\n          type: 'click_mouse',\n          x: coordinate?.x,\n          y: coordinate?.y,\n          button: 'left',\n          click_type: 'click',\n          num_of_clicks: 1\n        };\n        break;\n      // ... Map other actions (right_click, type, scroll, screenshot, etc.) ...\n      case 'type':\n        requestBody = {\n          type: 'type',\n          text: text || ''\n        };\n        break;\n      case 'screenshot':\n         requestBody = {\n           type: 'screenshot'\n         };\n         break;\n      // ... (Include all case mappings from the provided computer-use.ts code) ...\n      default: {\n        const _exhaustiveCheck: never = action;\n        throw new Error(`Unhandled action: ${action}`);\n      }\n    }\n\n    const clientParams: ExecuteComputerActionParams = {\n      path: { id: desktopId },\n      body: requestBody\n    };\n\n    // *** Use the Cyberdesk SDK client ***\n    const result = await client.executeComputerAction(clientParams);\n\n    // Check the raw response status embedded in the SDK result\n    if (result.response.status !== 200) {\n      let errorDetails = `Failed with status: ${result.response.status}`;\n      try {\n        // Attempt to parse error details from the response body\n        const errorBody = await result.response.json();\n        errorDetails = errorBody.message || errorBody.error || JSON.stringify(errorBody);\n      } catch (e) { /* Failed to parse body */ }\n      throw new Error(`Failed to execute computer action ${action}: ${errorDetails}`);\n    }\n\n    const data = result.data; // Access the parsed data from the SDK result\n\n    if (data?.base64_image) {\n      return {\n        type: \"image\",\n        data: data.base64_image\n      };\n    }\n\n    return data?.output || 'Action completed successfully';\n\n  } catch (error) {\n    console.error(`Error executing computer action ${action}:`, error);\n    throw error; // Re-throw to be handled by the AI SDK\n  }\n}\n```\n*Note: The full mapping logic for all action types is omitted for brevity but should be included as shown in the `computer-use.ts` file.* \n\n### Step 3: Implement `executeBashCommand` Utility\n\nCreate a similar utility for bash commands, calling the `executeBashAction` SDK method.\n\n```typescript\n// src/utils/bash.ts\nimport client from '@/lib/cyberdeskClient';\n\nexport async function executeBashCommand(\n  command: string,\n  desktopId: string\n): Promise<string> {\n  try {\n    // *** Use the Cyberdesk SDK client ***\n    const result = await client.executeBashAction({\n        path: { id: desktopId },\n        body: { command },\n    });\n\n    // Check the raw response status\n    if (result.response.status !== 200) {\n      let errorDetails = `Failed with status: ${result.response.status}`;\n      try {\n        const errorBody = await result.response.json();\n        errorDetails = errorBody.message || errorBody.error || JSON.stringify(errorBody);\n      } catch (e) { /* Failed to parse body */ }\n      throw new Error(`Failed to execute bash command: ${errorDetails}`);\n    }\n\n    const data = result.data; // Access the parsed data\n    return data?.output || ''; // Return output or empty string\n\n  } catch (error) {\n    console.error(`Error executing bash command \"${command}\":`, error);\n    // Return a meaningful error message for the AI to potentially see\n    return 'Error executing bash command: ' + (error as Error).message;\n  }\n}\n```\n\n### Step 4: Set Up the AI Tools and API Route\n\nCreate an API route (e.g., `/api/chat`) that uses the Vercel AI SDK (`streamText`) and defines tools that call your utility functions.\n\n```typescript\n// src/app/api/chat/route.ts\nimport { anthropic } from '@ai-sdk/anthropic';\nimport { streamText } from 'ai';\nimport { executeComputerAction } from '../../../utils/computer-use'; // Adjust path\nimport { executeBashCommand } from '../../../utils/bash'; // Adjust path\n\n// Define result types if needed\ninterface ComputerActionResult { type: \"image\"; data: string; }\n\nexport const maxDuration = 300;\n\nexport async function POST(req: Request) {\n  const desktopId = req.headers.get('X-Desktop-Id');\n  const { messages } = await req.json();\n\n  if (!desktopId) {\n    return Response.json({ error: \"Desktop ID is required\" }, { status: 400 });\n  }\n\n  const lastMessage = messages[messages.length - 1];\n  const userContent = /* ... logic to extract text from lastMessage.content ... */ \"\";\n\n  // Define the computer tool using the AI SDK\n  const computerTool = anthropic.tools.computer_20250124({\n    displayWidthPx: 1024,\n    displayHeightPx: 768,\n    // The execute function calls *your* utility function, which uses the Cyberdesk SDK\n    execute: async ({ action, coordinate, duration, scroll_amount, scroll_direction, start_coordinate, text }) => {\n      const coordinateObj = coordinate ? { x: coordinate[0], y: coordinate[1] } : undefined;\n      const startCoordinateObj = start_coordinate ? { x: start_coordinate[0], y: start_coordinate[1] } : undefined;\n      \n      // *** Call your utility function ***\n      const result = await executeComputerAction(\n        action,\n        desktopId,\n        coordinateObj,\n        text,\n        duration,\n        scroll_amount,\n        scroll_direction,\n        startCoordinateObj\n      );\n\n      // Format the result for the AI SDK tool\n      return (typeof result === 'string')\n        ? { type: \"text\" as const, text: result }\n        : { type: \"image\" as const, data: result.data };\n    },\n    // Optional: Format the tool result content for the AI model\n    experimental_toToolResultContent(result: { type: \"text\"; text: string } | ComputerActionResult) {\n      return result.type === 'text'\n        ? [{ type: 'text', text: result.text }]\n        : [{ type: 'image', data: result.data, mimeType: 'image/jpeg' }];\n    },\n  });\n\n  // Define the bash tool using the AI SDK\n  const bashTool = anthropic.tools.bash_20250124({\n    // The execute function calls *your* utility function\n    execute: async ({ command }) => {\n        // *** Call your utility function ***\n        const output = await executeBashCommand(command, desktopId);\n        return output; // Return the string output directly\n    }\n  });\n\n  try {\n    // Call the AI model with the tools\n    const response = streamText({\n      model: anthropic(\"claude-3-7-sonnet-20250219\"),\n      prompt: userContent,\n      system: \"You are an AI assistant that can control a computer...\", // Your system prompt\n      tools: {\n        computer: computerTool,\n        bash: bashTool\n      },\n      maxSteps: 100\n    });\n\n    return response.toDataStreamResponse();\n\n  } catch (error) {\n    console.error(\"Error calling Anthropic:\", error);\n    return Response.json({ error: \"Failed to process request\" }, { status: 500 });\n  }\n}\n```\n\n### Step 5: Frontend Implementation\n\nA frontend application would:\n1.  Create a desktop instance (perhaps via another API route that uses `cyberdesk.launchDesktop`).\n2.  Render a chat interface.\n3.  Display the desktop stream using the `stream_url`.\n4.  Send user messages to the `/api/chat` endpoint, including the `desktopId` in the `X-Desktop-Id` header.\n5.  Process the streamed response from the API, updating the chat UI.\n\n*(Refer to the Cyberdesk Starter Assistant UI template for a full frontend example)*.\n\n### Conclusion\n\nBy wrapping the Cyberdesk SDK methods within utility functions called by your AI tool's `execute` logic, you can seamlessly integrate robust desktop control into your AI agents. The SDK handles the direct API communication, authentication, and response parsing, simplifying your integration code."
  },
  {
    "path": "apps/docs/mdx-components.tsx",
    "content": "import defaultComponents from \"fumadocs-ui/mdx\";\nimport type { MDXComponents } from \"mdx/types\";\n\nexport function useMDXComponents(components: MDXComponents): MDXComponents {\n  return {\n    ...defaultComponents,\n    ...components,\n  };\n}\n"
  },
  {
    "path": "apps/docs/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.\n"
  },
  {
    "path": "apps/docs/next.config.mjs",
    "content": "import createMDX from \"fumadocs-mdx/config\";\n\nconst withMDX = createMDX();\n\n/** @type {import('next').NextConfig} */\nconst config = {\n  reactStrictMode: true,\n};\n\nexport default withMDX(config);\n"
  },
  {
    "path": "apps/docs/package.json",
    "content": "{\n  \"name\": \"docs\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"generate\": \"node scripts/generate-docs.mjs\",\n    \"build\": \"next build\",\n    \"dev\": \"next dev\",\n    \"start\": \"next start\"\n  },\n  \"dependencies\": {\n    \"@vercel/analytics\": \"^1.5.0\",\n    \"fumadocs-core\": \"^12.1.2\",\n    \"fumadocs-mdx\": \"^8.2.32\",\n    \"fumadocs-openapi\": \"^3.0.0\",\n    \"fumadocs-typescript\": \"^2.0.1\",\n    \"fumadocs-ui\": \"^12.1.2\",\n    \"next\": \"^14.2.4\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"zod\": \"^3.23.8\"\n  },\n  \"devDependencies\": {\n    \"@types/mdx\": \"^2.0.13\",\n    \"@types/node\": \"^20.11.30\",\n    \"@types/react\": \"^18.3.3\",\n    \"@types/react-dom\": \"^18.3.0\",\n    \"autoprefixer\": \"^10.4.19\",\n    \"postcss\": \"^8.4.38\",\n    \"tailwindcss\": \"^3.4.4\",\n    \"typescript\": \"^5.4.5\"\n  }\n}\n"
  },
  {
    "path": "apps/docs/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "apps/docs/scripts/generate-docs.mjs",
    "content": "import * as path from \"node:path\";\nimport * as OpenAPI from \"fumadocs-openapi\";\nimport * as Typescript from \"fumadocs-typescript\";\n\nvoid OpenAPI.generateFiles({\n  input: [\"../../sdks/openapi.json\"],\n  output: \"./content/docs/\",\n  name: () => \"api-reference\",\n  frontmatter: (title) => ({\n    toc: false,\n    title: `${title[0].toUpperCase()}${title.slice(1)}`,\n  }),\n});\n\nvoid Typescript.generateFiles({\n  input: [\"./content/docs/**/*.model.mdx\"],\n  output: (file) =>\n    path.resolve(\n      path.dirname(file),\n      `${path.basename(file).split(\".\")[0]}.mdx`\n    ),\n});\n"
  },
  {
    "path": "apps/docs/tailwind.config.js",
    "content": "import { createPreset } from 'fumadocs-ui/tailwind-plugin';\n\n/** @type {import('tailwindcss').Config} */\nexport default {\n  content: [\n    './components/**/*.{ts,tsx}',\n    './app/**/*.{ts,tsx}',\n    './content/**/*.{md,mdx}',\n    './mdx-components.{ts,tsx}',\n    './node_modules/fumadocs-ui/dist/**/*.js',\n    './node_modules/fumadocs-openapi/dist/**/*.js',\n  ],\n  presets: [createPreset()],\n  theme: {\n    extend: {\n      typography: {\n        DEFAULT: {\n          css: {\n            h1: {\n              letterSpacing: '-0.025em', // This is what tracking-tight does\n              fontWeight: '500', // font-medium (500) instead of bold (700)\n            },\n            h2: {\n              letterSpacing: '-0.025em',\n              fontWeight: '500',\n            },\n            h3: {\n              letterSpacing: '-0.025em',\n              fontWeight: '500',\n            },\n            h4: {\n              letterSpacing: '-0.025em',\n              fontWeight: '500',\n            },\n            h5: {\n              letterSpacing: '-0.025em',\n              fontWeight: '500',\n            },\n            h6: {\n              letterSpacing: '-0.025em',\n              fontWeight: '500',\n            },\n          },\n        },\n      },\n    },\n  },\n};\n"
  },
  {
    "path": "apps/docs/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"target\": \"ESNext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    },\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ]\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/web/.eslintrc.json",
    "content": "{\n  \"parser\": \"@typescript-eslint/parser\",\n  \"plugins\": [\"@typescript-eslint\"],\n  \"extends\": [\n    \"next/core-web-vitals\",\n    \"plugin:@typescript-eslint/recommended\"\n  ],\n  \"rules\": {\n    \"@next/next/no-img-element\": \"off\",\n    \"@typescript-eslint/no-unused-vars\": \"warn\"\n  }\n}\n"
  },
  {
    "path": "apps/web/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n.yarn/install-state.gz\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n\n.env\n.env.local\n\n# turbo\n.turbo\n"
  },
  {
    "path": "apps/web/LICENSE.md",
    "content": "# Tailwind Plus License\n\n## Personal License\n\nTailwind Labs Inc. grants you an on-going, non-exclusive license to use the Components and Templates.\n\nThe license grants permission to **one individual** (the Licensee) to access and use the Components and Templates.\n\nYou **can**:\n\n- Use the Components and Templates to create unlimited End Products.\n- Modify the Components and Templates to create derivative components and templates. Those components and templates are subject to this license.\n- Use the Components and Templates to create unlimited End Products for unlimited Clients.\n- Use the Components and Templates to create End Products where the End Product is sold to End Users.\n- Use the Components and Templates to create End Products that are open source and freely available to End Users.\n\nYou **cannot**:\n\n- Use the Components and Templates to create End Products that are designed to allow an End User to build their own End Products using the Components and Templates or derivatives of the Components and Templates.\n- Re-distribute the Components and Templates or derivatives of the Components and Templates separately from an End Product, neither in code or as design assets.\n- Share your access to the Components and Templates with any other individuals.\n- Use the Components and Templates to produce anything that may be deemed by Tailwind Labs Inc, in their sole and absolute discretion, to be competitive or in conflict with the business of Tailwind Labs Inc.\n\n### Example usage\n\nExamples of usage **allowed** by the license:\n\n- Creating a personal website by yourself.\n- Creating a website or web application for a client that will be owned by that client.\n- Creating a commercial SaaS application (like an invoicing app for example) where end users have to pay a fee to use the application.\n- Creating a commercial self-hosted web application that is sold to end users for a one-time fee.\n- Creating a web application where the primary purpose is clearly not to simply re-distribute the components (like a conference organization app that uses the components for its UI for example) that is free and open source, where the source code is publicly available.\n\nExamples of usage **not allowed** by the license:\n\n- Creating a repository of your favorite Tailwind Plus components or templates (or derivatives based on Tailwind Plus components or templates) and publishing it publicly.\n- Creating a React or Vue version of Tailwind Plus and making it available either for sale or for free.\n- Create a Figma or Sketch UI kit based on the Tailwind Plus component designs.\n- Creating a \"website builder\" project where end users can build their own websites using components or templates included with or derived from Tailwind Plus.\n- Creating a theme, template, or project starter kit using the components or templates and making it available either for sale or for free.\n- Creating an admin panel tool (like [Laravel Nova](https://nova.laravel.com/) or [ActiveAdmin](https://activeadmin.info/)) that is made available either for sale or for free.\n\nIn simple terms, use Tailwind Plus for anything you like as long as it doesn't compete with Tailwind Plus.\n\n### Personal License Definitions\n\nLicensee is the individual who has purchased a Personal License.\n\nComponents and Templates are the source code and design assets made available to the Licensee after purchasing a Tailwind Plus license.\n\nEnd Product is any artifact produced that incorporates the Components or Templates or derivatives of the Components or Templates.\n\nEnd User is a user of an End Product.\n\nClient is an individual or entity receiving custom professional services directly from the Licensee, produced specifically for that individual or entity. Customers of software-as-a-service products are not considered clients for the purpose of this document.\n\n## Team License\n\nTailwind Labs Inc. grants you an on-going, non-exclusive license to use the Components and Templates.\n\nThe license grants permission for **up to 25 Employees and Contractors of the Licensee** to access and use the Components and Templates.\n\nYou **can**:\n\n- Use the Components and Templates to create unlimited End Products.\n- Modify the Components and Templates to create derivative components and templates. Those components and templates are subject to this license.\n- Use the Components and Templates to create unlimited End Products for unlimited Clients.\n- Use the Components and Templates to create End Products where the End Product is sold to End Users.\n- Use the Components and Templates to create End Products that are open source and freely available to End Users.\n\nYou **cannot**:\n\n- Use the Components or Templates to create End Products that are designed to allow an End User to build their own End Products using the Components or Templates or derivatives of the Components or Templates.\n- Re-distribute the Components or Templates or derivatives of the Components or Templates separately from an End Product.\n- Use the Components or Templates to create End Products that are the property of any individual or entity other than the Licensee or Clients of the Licensee.\n- Use the Components or Templates to produce anything that may be deemed by Tailwind Labs Inc, in their sole and absolute discretion, to be competitive or in conflict with the business of Tailwind Labs Inc.\n\n### Example usage\n\nExamples of usage **allowed** by the license:\n\n- Creating a website for your company.\n- Creating a website or web application for a client that will be owned by that client.\n- Creating a commercial SaaS application (like an invoicing app for example) where end users have to pay a fee to use the application.\n- Creating a commercial self-hosted web application that is sold to end users for a one-time fee.\n- Creating a web application where the primary purpose is clearly not to simply re-distribute the components or templates (like a conference organization app that uses the components or a template for its UI for example) that is free and open source, where the source code is publicly available.\n\nExamples of use **not allowed** by the license:\n\n- Creating a repository of your favorite Tailwind Plus components or template (or derivatives based on Tailwind Plus components or templates) and publishing it publicly.\n- Creating a React or Vue version of Tailwind Plus and making it available either for sale or for free.\n- Creating a \"website builder\" project where end users can build their own websites using components or templates included with or derived from Tailwind Plus.\n- Creating a theme or template using the components or templates and making it available either for sale or for free.\n- Creating an admin panel tool (like [Laravel Nova](https://nova.laravel.com/) or [ActiveAdmin](https://activeadmin.info/)) that is made available either for sale or for free.\n- Creating any End Product that is not the sole property of either your company or a client of your company. For example your employees/contractors can't use your company Tailwind Plus license to build their own websites or side projects.\n\n### Team License Definitions\n\nLicensee is the business entity who has purchased a Team License.\n\nComponents and Templates are the source code and design assets made available to the Licensee after purchasing a Tailwind Plus license.\n\nEnd Product is any artifact produced that incorporates the Components or Templates or derivatives of the Components or Templates.\n\nEnd User is a user of an End Product.\n\nEmployee is a full-time or part-time employee of the Licensee.\n\nContractor is an individual or business entity contracted to perform services for the Licensee.\n\nClient is an individual or entity receiving custom professional services directly from the Licensee, produced specifically for that individual or entity. Customers of software-as-a-service products are not considered clients for the purpose of this document.\n\n## Enforcement\n\nIf you are found to be in violation of the license, access to your Tailwind Plus account will be terminated, and a refund may be issued at our discretion. When license violation is blatant and malicious (such as intentionally redistributing the Components or Templates through private warez channels), no refund will be issued.\n\nThe copyright of the Components and Templates is owned by Tailwind Labs Inc. You are granted only the permissions described in this license; all other rights are reserved. Tailwind Labs Inc. reserves the right to pursue legal remedies for any unauthorized use of the Components or Templates outside the scope of this license.\n\n## Liability\n\nTailwind Labs Inc.’s liability to you for costs, damages, or other losses arising from your use of the Components or Templates — including third-party claims against you — is limited to a refund of your license fee. Tailwind Labs Inc. may not be held liable for any consequential damages related to your use of the Components or Templates.\n\nThis Agreement is governed by the laws of the Province of Ontario and the applicable laws of Canada. Legal proceedings related to this Agreement may only be brought in the courts of Ontario. You agree to service of process at the e-mail address on your original order.\n\n## Questions?\n\nUnsure which license you need, or unsure if your use case is covered by our licenses?\n\nEmail us at [support@tailwindcss.com](mailto:support@tailwindcss.com) with your questions.\n"
  },
  {
    "path": "apps/web/README.md",
    "content": "## Getting started\n\nTo get started with this template, first install the npm dependencies:\n\n```bash\nnpm install\n```\n\nNext, create a new Sanity project to power the blog within this template:\n\n```bash\nnpm create sanity@latest -- --env=.env.local --create-project \"Radiant Blog\" --dataset production\n```\n\nThis will prompt you to create a new Sanity account if you don't have one already. When asked \"Would you like to add configuration files for a Sanity project in this Next.js folder?\", choose \"n\".\n\nNext, optionally import the demo seed data for the blog:\n\n```bash\nnpx sanity@latest dataset import seed.tar.gz\n```\n\nNext, run the development server:\n\n```bash\nnpm run dev\n```\n\nFinally, open [http://localhost:3000](http://localhost:3000) in your browser to view the website.\n\nTo manage your blog content, visit the embedded Sanity Studio at [http://localhost:3000/studio](http://localhost:3000/studio).\n\n## License\n\nThis site template is a commercial product and is licensed under the [Tailwind Plus license](https://tailwindcss.com/plus/license).\n\n## Learn more\n\nTo learn more about the technologies used in this site template, see the following resources:\n\n- [Tailwind CSS](https://tailwindcss.com/docs) - the official Tailwind CSS documentation \n- [Next.js](https://nextjs.org/docs) - the official Next.js documentation\n- [Headless UI](https://headlessui.dev) - the official Headless UI documentation\n- [Sanity](https://www.sanity.io) - the Sanity website\n"
  },
  {
    "path": "apps/web/components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"src/styles/tailwind.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"iconLibrary\": \"lucide\"\n}"
  },
  {
    "path": "apps/web/config.ts",
    "content": "const CONFIG = {\n    docsURL: 'https://docs.cyberdesk.io',\n    subscriptionLimit: 1000\n}\n\nexport default CONFIG;"
  },
  {
    "path": "apps/web/middleware.ts",
    "content": "import { type NextRequest } from 'next/server'\nimport { updateSession } from '@/utils/supabase/middleware'\n\nexport async function middleware(request: NextRequest) {\n  return await updateSession(request)\n}\n\nexport const config = {\n  matcher: [\n    /*\n     * Match all request paths except for the ones starting with:\n     * - _next/static (static files)\n     * - _next/image (image optimization files)\n     * - favicon.ico (favicon file)\n     * Feel free to modify this pattern to include more paths.\n     */\n    '/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',\n  ],\n}\n"
  },
  {
    "path": "apps/web/next.config.mjs",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  reactStrictMode: true,\n  async rewrites() {\n    return [\n      {\n        source: \"/ingest/static/:path*\",\n        destination: \"https://us-assets.i.posthog.com/static/:path*\",\n      },\n      {\n        source: \"/ingest/:path*\",\n        destination: \"https://us.i.posthog.com/:path*\",\n      },\n      {\n        source: \"/ingest/decide\",\n        destination: \"https://us.i.posthog.com/decide\",\n      },\n    ];\n  },\n  // This is required to support PostHog trailing slash API requests\n  skipTrailingSlashRedirect: true,\n};\n\nexport default nextConfig\n"
  },
  {
    "path": "apps/web/package.json",
    "content": "{\n  \"name\": \"web\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\",\n    \"typegen\": \"sanity schema extract --path=src/sanity/extract.json && sanity typegen generate && rm ./src/sanity/extract.json\"\n  },\n  \"dependencies\": {\n    \"@ai-sdk/anthropic\": \"^1.2.2\",\n    \"@ai-sdk/openai\": \"^1.3.3\",\n    \"@anthropic-ai/sdk\": \"^0.39.0\",\n    \"@assistant-ui/react\": \"^0.8.6\",\n    \"@assistant-ui/react-ai-sdk\": \"^0.8.0\",\n    \"@assistant-ui/react-markdown\": \"^0.8.0\",\n    \"@headlessui/react\": \"^2.1.1\",\n    \"@heroicons/react\": \"^2.1.4\",\n    \"@radix-ui/react-slot\": \"^1.1.2\",\n    \"@radix-ui/react-tooltip\": \"^1.1.8\",\n    \"@sanity/image-url\": \"^1.0.2\",\n    \"@sanity/vision\": \"^3.52.2\",\n    \"@supabase/ssr\": \"^0.6.1\",\n    \"@supabase/supabase-js\": \"^2.49.4\",\n    \"@types/uuid\": \"^10.0.0\",\n    \"@unkey/api\": \"^0.34.0\",\n    \"ai\": \"^4.2.6\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"cyberdesk\": \"^0.2.0\",\n    \"dayjs\": \"^1.11.12\",\n    \"feed\": \"^4.2.2\",\n    \"framer-motion\": \"^11.2.10\",\n    \"lucide-react\": \"^0.484.0\",\n    \"motion\": \"^12.11.0\",\n    \"next\": \"14.2.11\",\n    \"next-sanity\": \"^9.4.7\",\n    \"next-themes\": \"^0.4.6\",\n    \"openai\": \"^4.90.0\",\n    \"posthog-js\": \"^1.234.1\",\n    \"posthog-node\": \"^4.10.2\",\n    \"react\": \"^18\",\n    \"react-dom\": \"^18\",\n    \"react-icons\": \"^5.5.0\",\n    \"react-resizable-panels\": \"^3.0.2\",\n    \"react-use-measure\": \"^2.1.1\",\n    \"remark-gfm\": \"^4.0.1\",\n    \"sanity\": \"^3.55.0\",\n    \"sonner\": \"^2.0.3\",\n    \"stripe\": \"^17.7.0\",\n    \"tailwind-merge\": \"^3.0.2\",\n    \"tw-animate-css\": \"^1.2.4\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.0.6\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^18\",\n    \"@types/react-dom\": \"^18\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.13.1\",\n    \"@typescript-eslint/parser\": \"^7.13.1\",\n    \"eslint\": \"^8\",\n    \"eslint-config-next\": \"14.2.11\",\n    \"postcss\": \"^8.4.40\",\n    \"prettier\": \"^3.3.2\",\n    \"prettier-plugin-organize-imports\": \"^4.0.0\",\n    \"prettier-plugin-tailwindcss\": \"^0.6.10\",\n    \"tailwindcss\": \"^4.0.6\",\n    \"typescript\": \"^5\"\n  },\n  \"optionalDependencies\": {\n    \"@tailwindcss/oxide-linux-x64-gnu\": \"^4.0.1\",\n    \"@tailwindcss/oxide-win32-x64-msvc\": \"^4.0.17\",\n    \"lightningcss-linux-x64-gnu\": \"^1.29.1\",\n    \"lightningcss-win32-x64-msvc\": \"^1.29.3\"\n  }\n}\n"
  },
  {
    "path": "apps/web/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    '@tailwindcss/postcss': {},\n  },\n}\n"
  },
  {
    "path": "apps/web/prettier.config.js",
    "content": "/** @type {import('prettier').Options} */\nmodule.exports = {\n  singleQuote: true,\n  semi: false,\n  plugins: ['prettier-plugin-organize-imports', 'prettier-plugin-tailwindcss'],\n  tailwindFunctions: ['clsx'],\n  tailwindStylesheet: './src/styles/tailwind.css',\n}\n"
  },
  {
    "path": "apps/web/radiant/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# Dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# Compiled Sanity Studio\n/dist\n\n# Temporary Sanity runtime, generated by the CLI on every dev server start\n/.sanity\n\n# Logs\n/logs\n*.log\n\n# Coverage directory used by testing tools\n/coverage\n\n# Misc\n.DS_Store\n*.pem\n\n# Typescript\n*.tsbuildinfo\n\n# Dotenv and similar local-only files\n*.local\n"
  },
  {
    "path": "apps/web/radiant/README.md",
    "content": "# Sanity Clean Content Studio\n\nCongratulations, you have now installed the Sanity Content Studio, an open-source real-time content editing environment connected to the Sanity backend.\n\nNow you can do the following things:\n\n- [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme)\n- [Join the community Slack](https://slack.sanity.io/?utm_source=readme)\n- [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme)\n"
  },
  {
    "path": "apps/web/radiant/eslint.config.mjs",
    "content": "import studio from '@sanity/eslint-config-studio'\n\nexport default [...studio]\n"
  },
  {
    "path": "apps/web/radiant/package.json",
    "content": "{\n  \"name\": \"radiant\",\n  \"private\": true,\n  \"version\": \"1.0.0\",\n  \"main\": \"package.json\",\n  \"license\": \"UNLICENSED\",\n  \"scripts\": {\n    \"dev\": \"sanity dev\",\n    \"start\": \"sanity start\",\n    \"build\": \"sanity build\",\n    \"deploy\": \"sanity deploy\",\n    \"deploy-graphql\": \"sanity graphql deploy\"\n  },\n  \"keywords\": [\n    \"sanity\"\n  ],\n  \"dependencies\": {\n    \"@sanity/vision\": \"^3.80.0\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"sanity\": \"^3.80.0\",\n    \"styled-components\": \"^6.1.8\"\n  },\n  \"devDependencies\": {\n    \"@sanity/eslint-config-studio\": \"^5.0.2\",\n    \"@types/react\": \"^18.0.25\",\n    \"eslint\": \"^9.9.0\",\n    \"prettier\": \"^3.0.2\",\n    \"typescript\": \"^5.1.6\"\n  },\n  \"prettier\": {\n    \"semi\": false,\n    \"printWidth\": 100,\n    \"bracketSpacing\": false,\n    \"singleQuote\": true\n  }\n}\n"
  },
  {
    "path": "apps/web/radiant/sanity.cli.ts",
    "content": "import {defineCliConfig} from 'sanity/cli'\n\nexport default defineCliConfig({\n  api: {\n    projectId: '4hdczeqj',\n    dataset: 'production'\n  },\n  /**\n   * Enable auto-updates for studios.\n   * Learn more at https://www.sanity.io/docs/cli#auto-updates\n   */\n  autoUpdates: true,\n})\n"
  },
  {
    "path": "apps/web/radiant/sanity.config.ts",
    "content": "import {defineConfig} from 'sanity'\nimport {structureTool} from 'sanity/structure'\nimport {visionTool} from '@sanity/vision'\nimport {schemaTypes} from './schemaTypes'\n\nexport default defineConfig({\n  name: 'default',\n  title: 'Radiant',\n\n  projectId: '4hdczeqj',\n  dataset: 'production',\n\n  plugins: [structureTool(), visionTool()],\n\n  schema: {\n    types: schemaTypes,\n  },\n})\n"
  },
  {
    "path": "apps/web/radiant/schemaTypes/index.ts",
    "content": "export const schemaTypes = []\n"
  },
  {
    "path": "apps/web/radiant/static/.gitkeep",
    "content": "Files placed here will be served by the Sanity server under the `/static`-prefix\n"
  },
  {
    "path": "apps/web/radiant/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"Preserve\",\n    \"moduleDetection\": \"force\",\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true\n  },\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/web/sanity-typegen.json",
    "content": "{\n  \"path\": \"./src/**/*.{ts,tsx,js,jsx}\",\n  \"schema\": \"./src/sanity/extract.json\",\n  \"generates\": \"./src/sanity/types.ts\"\n}\n"
  },
  {
    "path": "apps/web/sanity.cli.ts",
    "content": "import { defineCliConfig } from 'sanity/cli'\n\nconst projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID\nconst dataset = process.env.NEXT_PUBLIC_SANITY_DATASET\n\nexport default defineCliConfig({ api: { projectId, dataset } })\n"
  },
  {
    "path": "apps/web/sanity.config.ts",
    "content": "'use client'\n\nimport { visionTool } from '@sanity/vision'\nimport { defineConfig } from 'sanity'\nimport { structureTool } from 'sanity/structure'\nimport { apiVersion, dataset, projectId } from './src/sanity/env'\nimport { schema } from './src/sanity/schema'\n\nexport default defineConfig({\n  basePath: '/studio',\n  projectId,\n  dataset,\n  schema,\n  plugins: [structureTool(), visionTool({ defaultApiVersion: apiVersion })],\n})\n"
  },
  {
    "path": "apps/web/src/app/api/playground/chat/route.ts",
    "content": "import { anthropic } from \"@ai-sdk/anthropic\";\nimport { streamText, type UIMessage } from \"ai\";\nimport { prunedMessages } from \"@/utils/playground/misc-demo-utils\";\nimport { bashTool, computerTool } from \"@/utils/playground/tools\";\nimport client from \"@/utils/playground/cyberdesk-client\";\n\n// Allow streaming responses up to 5 minutes\nexport const maxDuration = 300;\n\nexport async function POST(req: Request) {\n  const { messages, sandboxId }: { messages: UIMessage[]; sandboxId: string } =\n    await req.json();\n  try {\n    const result = streamText({\n      model: anthropic(\"claude-3-7-sonnet-20250219\"), // Using Sonnet for computer use\n      system:\n        \"You are a helpful assistant with access to a computer. \" +\n        \"Use the computer tool to help the user with their requests. \" +\n        \"Use the bash tool to execute commands on the computer. You can create files and folders using the bash tool. Always prefer the bash tool where it is viable for the task. \" +\n        \"Be sure to advise the user when waiting is necessary. \" +\n        \"If the browser opens with a setup wizard, YOU MUST IGNORE IT and move straight to the next step (e.g. input the url in the search bar).\" +\n        \"Use DuckDuckGo to search the web.\",\n      messages: prunedMessages(messages),\n      tools: { computer: computerTool(sandboxId), bash: bashTool(sandboxId) },\n      providerOptions: {\n        anthropic: { cacheControl: { type: \"ephemeral\" } },\n      },\n      maxSteps: 100,\n    });\n\n    // Create response stream\n    const response = result.toDataStreamResponse({\n      // @ts-expect-error eheljfe\n      getErrorMessage(error) {\n        console.error(\"Error in streamText:\", error);\n        return error;\n      },\n    });\n\n    return response;\n  } catch (error) {\n    console.error(\"Chat API error:\", error);\n    await client.terminateDesktop({\n      path: {\n        id: sandboxId,\n      },\n    });\n    return new Response(JSON.stringify({ error: \"Internal Server Error\" }), {\n      status: 500,\n      headers: { \"Content-Type\": \"application/json\" },\n    });\n  }\n}\n"
  },
  {
    "path": "apps/web/src/app/api/playground/kill-desktop/route.ts",
    "content": "import client from \"@/utils/playground/cyberdesk-client\";\n\n// Common handler for both GET and POST requests\nasync function handleKillDesktop(request: Request) {\n  // Enable CORS to ensure this works across all browsers\n\n  const { searchParams } = new URL(request.url);\n  const sandboxId = searchParams.get(\"sandboxId\");\n\n  console.log(`Kill desktop request received via ${request.method} for ID: ${sandboxId}`);\n\n  if (!sandboxId) {\n    return new Response(\"No sandboxId provided\", { status: 400 });\n  }\n\n  try {\n    await client.terminateDesktop({\n      path: {\n        id: sandboxId,\n      }\n    });\n    return new Response(\"Desktop killed successfully\", { status: 200 });\n  } catch (error) {\n    console.error(`Failed to kill desktop with ID: ${sandboxId}`, error);\n    return new Response(\"Failed to kill desktop\", { status: 500 });\n  }\n}\n\n// Handle POST requests\nexport async function POST(request: Request) {\n  return handleKillDesktop(request);\n}"
  },
  {
    "path": "apps/web/src/app/api/stripe/checkout/route.ts",
    "content": "import { stripe, STRIPE_PRICE_ID } from '@/utils/stripe/stripe-server';\nimport { NextRequest, NextResponse } from 'next/server';\n\nexport async function POST(req: NextRequest) {\n  try {\n    const { successUrl, cancelUrl, stripeCustomerId } = await req.json();\n    \n    // Create a Stripe checkout session for the Pro plan\n    const session = await stripe.checkout.sessions.create({\n      payment_method_types: ['card'],\n      line_items: [\n        {\n          price: STRIPE_PRICE_ID,\n          quantity: 1,\n        },\n      ],\n      mode: 'subscription',\n      success_url: successUrl || `${req.nextUrl.origin}/dashboard?payment=success`,\n      cancel_url: cancelUrl || `${req.nextUrl.origin}/pricing?payment=cancelled`,\n      metadata: {\n        plan: 'pro',\n      },\n      ...(stripeCustomerId && { customer: stripeCustomerId }),\n    });\n\n    return NextResponse.json({ sessionId: session.id, url: session.url });\n  } catch (error) {\n    console.error('Error creating checkout session:', error);\n    return NextResponse.json(\n      { error: 'Error creating checkout session' },\n      { status: 500 }\n    );\n  }\n}\n"
  },
  {
    "path": "apps/web/src/app/api/stripe/portal/route.ts",
    "content": "import { stripe } from '@/utils/stripe/stripe-server';\nimport { NextRequest, NextResponse } from 'next/server';\n\nexport async function POST(req: NextRequest) {\n  try {\n    const { customerId, returnUrl } = await req.json();\n    \n    if (!customerId) {\n      return NextResponse.json(\n        { error: 'Customer ID is required' },\n        { status: 400 }\n      );\n    }\n\n    // Create a billing portal session for the customer\n    const session = await stripe.billingPortal.sessions.create({\n      customer: customerId,\n      return_url: returnUrl || `${req.nextUrl.origin}/dashboard`,\n    });\n\n    return NextResponse.json({ url: session.url });\n  } catch (error) {\n    console.error('Error creating portal session:', error);\n    return NextResponse.json(\n      { error: 'Error creating portal session' },\n      { status: 500 }\n    );\n  }\n}\n"
  },
  {
    "path": "apps/web/src/app/api/stripe/webhook/route.ts",
    "content": "import { stripe } from '@/utils/stripe/stripe-server';\nimport { headers } from 'next/headers';\nimport { NextRequest, NextResponse } from 'next/server';\nimport Stripe from 'stripe';\nimport { createClient } from '@/utils/supabase/server';\n\n// Define subscription status type using Stripe's type\ntype SubscriptionStatus = Stripe.Subscription.Status;\n\n// This is your Stripe webhook secret for testing your endpoint locally.\nconst endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;\n\nexport async function POST(req: NextRequest) {\n  const body = await req.text();\n  const sig = headers().get('stripe-signature') as string;\n  const supabase = createClient();\n\n  let event;\n\n  try {\n    event = stripe.webhooks.constructEvent(body, sig, endpointSecret!);\n  } catch (err: unknown) {\n    let errorMessage = \"An unknown error occurred\";\n    if (err instanceof Error) {\n      errorMessage = err.message;\n    }\n    console.error(`Webhook Error: ${errorMessage}`);\n    return NextResponse.json(\n      { error: `Webhook Error: ${errorMessage}` },\n      { status: 400 }\n    );\n  }\n\n  // Helper function to update profile in Supabase\n  const updateProfile = async (customerId: string, data: { \n    subscription_status?: SubscriptionStatus;\n    updated_at?: Date;\n    stripe_subscription_id?: string | null;\n    current_period_end?: Date | null;\n    plan_id?: string | null;\n    cancel_at_period_end?: boolean | null;\n    email?: string | null;\n  }) => {\n    try {\n      // First, find the profile with this Stripe customer ID\n      const { data: profiles, error: fetchError } = await supabase\n        .from('profiles')\n        .select('*')\n        .eq('stripe_customer_id', customerId)\n        .limit(1);\n\n      if (fetchError) {\n        console.error('Error fetching profile:', fetchError);\n        return;\n      }\n\n      if (profiles && profiles.length > 0) {\n        const profile = profiles[0];\n        \n        // Update the profile with new data\n        const { error: updateError } = await supabase\n          .from('profiles')\n          .update(data)\n          .eq('id', profile.id);\n          \n        if (updateError) {\n          console.error('Error updating profile:', updateError);\n        } else {\n          console.log(`Profile ${profile.id} updated successfully`);\n        }\n      } else {\n        console.log(`No profile found for customer ID: ${customerId}`);\n      }\n    } catch (error) {\n      console.error('Error in updateProfile:', error);\n    }\n  };\n\n  // Handle the event\n  try {\n    switch (event.type) {\n      case 'checkout.session.async_payment_failed': {\n        const session = event.data.object;\n        console.log('Checkout session async payment failed:', session);\n        \n        // Handle the failed async payment\n        const customerId = session.customer \n          ? (typeof session.customer === 'string' ? session.customer : session.customer.id)\n          : null;\n          \n        if (customerId) {\n          await updateProfile(customerId, {\n            subscription_status: 'past_due',\n            updated_at: new Date()\n          });\n        }\n        break;\n      }\n      \n      case 'checkout.session.async_payment_succeeded': {\n        const session = event.data.object;\n        console.log('Checkout session async payment succeeded:', session);\n        \n        // Similar handling to checkout.session.completed\n        const customerId = session.customer \n          ? (typeof session.customer === 'string' ? session.customer : session.customer.id)\n          : null;\n        const subscriptionId = session.subscription\n          ? (typeof session.subscription === 'string' ? session.subscription : session.subscription.id)\n          : null;\n        \n        if (customerId && subscriptionId) {\n          // Get subscription details\n          const subscription = await stripe.subscriptions.retrieve(subscriptionId);\n          \n          // Update profile with subscription info\n          await updateProfile(customerId, {\n            stripe_subscription_id: subscriptionId,\n            subscription_status: subscription.status,\n            current_period_end: new Date(subscription.current_period_end * 1000),\n            plan_id: subscription.items.data[0].price.id,\n            cancel_at_period_end: subscription.cancel_at_period_end,\n            updated_at: new Date()\n          });\n        }\n        break;\n      }\n        \n      case 'checkout.session.completed': {\n        const checkoutSession = event.data.object\n        console.log('Checkout session completed:', checkoutSession);\n        \n        // Get customer ID and subscription ID from the session, handling possible null values\n        const customerId = checkoutSession.customer \n          ? (typeof checkoutSession.customer === 'string' ? checkoutSession.customer : checkoutSession.customer.id)\n          : null;\n        const subscriptionId = checkoutSession.subscription\n          ? (typeof checkoutSession.subscription === 'string' ? checkoutSession.subscription : checkoutSession.subscription.id)\n          : null;\n        \n        if (customerId && subscriptionId) {\n          // Get subscription details\n          const subscription = await stripe.subscriptions.retrieve(subscriptionId);\n          \n          // Update profile with subscription info\n          await updateProfile(customerId, {\n            stripe_subscription_id: subscriptionId,\n            subscription_status: subscription.status,\n            current_period_end: new Date(subscription.current_period_end * 1000),\n            plan_id: subscription.items.data[0].price.id,\n            cancel_at_period_end: subscription.cancel_at_period_end,\n            updated_at: new Date()\n          });\n        }\n        break;\n      }\n      \n      case 'customer.created': {\n        const customer = event.data.object;\n        console.log('Customer created:', customer);\n        \n        // No specific action needed here as the customer ID will be associated with a profile\n        // when they complete checkout or when the user account is created\n        break;\n      }\n      \n      case 'customer.deleted': {\n        const customer = event.data.object;\n        console.log('Customer deleted:', customer);\n        \n        // Mark the customer as deleted in our system\n        await updateProfile(customer.id, {\n          subscription_status: 'canceled',\n          updated_at: new Date()\n        });\n        break;\n      }\n      \n      case 'customer.updated': {\n        const customer = event.data.object;\n        console.log('Customer updated:', customer);\n        \n        // Update basic customer information\n        await updateProfile(customer.id, {\n          email: customer.email,\n          updated_at: new Date()\n        });\n        break;\n      }\n        \n      case 'customer.subscription.created': {\n        const subscription = event.data.object\n        console.log('Subscription created:', subscription);\n        \n        const customerId = typeof subscription.customer === 'string' \n          ? subscription.customer \n          : subscription.customer.id;\n        \n        await updateProfile(customerId, {\n          stripe_subscription_id: subscription.id,\n          subscription_status: subscription.status,\n          current_period_end: new Date(subscription.current_period_end * 1000),\n          plan_id: subscription.items.data[0].price.id,\n          cancel_at_period_end: subscription.cancel_at_period_end,\n          updated_at: new Date()\n        });\n        break;\n      }\n      \n      case 'customer.subscription.paused': {\n        const subscription = event.data.object;\n        console.log('Subscription paused:', subscription);\n        \n        const customerId = typeof subscription.customer === 'string' \n          ? subscription.customer \n          : subscription.customer.id;\n        \n        await updateProfile(customerId, {\n          subscription_status: 'paused',\n          updated_at: new Date()\n        });\n        break;\n      }\n      \n      case 'customer.subscription.resumed': {\n        const subscription = event.data.object;\n        console.log('Subscription resumed:', subscription);\n        \n        const customerId = typeof subscription.customer === 'string' \n          ? subscription.customer \n          : subscription.customer.id;\n        \n        await updateProfile(customerId, {\n          subscription_status: subscription.status,\n          current_period_end: new Date(subscription.current_period_end * 1000),\n          updated_at: new Date()\n        });\n        break;\n      }\n        \n      case 'customer.subscription.updated': {\n        const updatedSubscription = event.data.object as Stripe.Subscription;\n        console.log('Subscription updated:', updatedSubscription);\n        \n        const customerId = typeof updatedSubscription.customer === 'string' \n          ? updatedSubscription.customer \n          : updatedSubscription.customer.id;\n        \n        await updateProfile(customerId, {\n          subscription_status: updatedSubscription.status,\n          current_period_end: new Date(updatedSubscription.current_period_end * 1000),\n          plan_id: updatedSubscription.items.data[0].price.id,\n          cancel_at_period_end: updatedSubscription.cancel_at_period_end,\n          updated_at: new Date()\n        });\n        break;\n      }\n        \n      case 'customer.subscription.deleted': {\n        const deletedSubscription = event.data.object as Stripe.Subscription;\n        console.log('Subscription deleted:', deletedSubscription);\n        \n        const customerId = typeof deletedSubscription.customer === 'string' \n          ? deletedSubscription.customer \n          : deletedSubscription.customer.id;\n        \n        await updateProfile(customerId, {\n          subscription_status: 'canceled',\n          cancel_at_period_end: false,\n          updated_at: new Date()\n        });\n        break;\n      }\n        \n      case 'invoice.paid': {\n        const invoice = event.data.object as Stripe.Invoice;\n        console.log('Invoice paid:', invoice);\n        \n        // Only update if this invoice is for a subscription\n        if (invoice.subscription && invoice.customer) {\n          const subscriptionId = typeof invoice.subscription === 'string' \n            ? invoice.subscription \n            : invoice.subscription.id;\n            \n          const customerId = typeof invoice.customer === 'string' \n            ? invoice.customer \n            : invoice.customer.id;\n          \n          const subscription = await stripe.subscriptions.retrieve(subscriptionId);\n          \n          await updateProfile(customerId, {\n            subscription_status: 'active',\n            current_period_end: new Date(subscription.current_period_end * 1000),\n            updated_at: new Date()\n          });\n        }\n        break;\n      }\n        \n      case 'invoice.payment_failed': {\n        const failedInvoice = event.data.object as Stripe.Invoice;\n        console.log('Invoice payment failed:', failedInvoice);\n        \n        if (failedInvoice.customer) {\n          const customerId = typeof failedInvoice.customer === 'string' \n            ? failedInvoice.customer \n            : failedInvoice.customer.id;\n          \n          await updateProfile(customerId, {\n            subscription_status: 'past_due',\n            updated_at: new Date()\n          });\n        }\n        break;\n      }\n      default:\n        console.log(`Unhandled event type ${event.type}`);\n    }\n  } catch (error) {\n    console.error(`Error processing webhook event ${event.type}:`, error);\n  }\n\n  return NextResponse.json({ received: true });\n}\n"
  },
  {
    "path": "apps/web/src/app/api/unkey/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { createClient } from '@/utils/supabase/server';\n\n// Unkey API endpoints\nconst UNKEY_API_URL = 'https://api.unkey.dev/v1';\nconst UNKEY_API_ID = process.env.UNKEY_API_ID;\nconst UNKEY_ROOT_KEY = process.env.UNKEY_ROOT_KEY;\n\n// Force dynamic rendering to ensure fresh data\nexport const dynamic = 'force-dynamic';\nexport const revalidate = 0;\n\nexport async function GET(request: Request) {\n  console.log('API route called');\n  \n  // Get userId from query parameters\n  const url = new URL(request.url);\n  const userId = url.searchParams.get('userId');\n  console.log('Received userId from query:', userId);\n  \n  if (!userId) {\n    return NextResponse.json({ error: 'UserId is required' }, { status: 400 });\n  }\n  \n  // Create Supabase client\n  const supabase = createClient();\n  \n  try {\n    // Check if user exists in profiles table\n    const { data: profileData, error: profileError } = await supabase\n      .from('profiles')\n      .select('id, unkey_key_id')\n      .eq('id', userId)\n      .single();\n    \n    console.log('Profile data:', profileData);\n    \n    if (profileError && profileError.code !== 'PGRST116') { // PGRST116 is the error code for 'no rows returned'\n      console.error('Error fetching profile:', profileError);\n      return NextResponse.json({ error: 'Failed to fetch user profile' }, { status: 500 });\n    }\n    \n    // If user doesn't exist in profiles, create an entry\n    if (!profileData) {\n      console.log('Creating new profile for user:', userId);\n      const { error: insertError } = await supabase\n        .from('profiles')\n        .insert({ id: userId });\n      \n      if (insertError) {\n        console.error('Error creating profile:', insertError);\n        return NextResponse.json({ error: 'Failed to create user profile' }, { status: 500 });\n      }\n      \n      // Return that API key doesn't exist\n      return NextResponse.json({ exists: false });\n    }\n    \n    // Only return exists: true if the user has a non-null unkey_key_id\n    if (!profileData.unkey_key_id) {\n      console.log('User exists but has no API key');\n      return NextResponse.json({ exists: false });\n    }\n    \n    // At this point, we have a user with a non-null unkey_key_id\n    console.log('User has an API key ID:', profileData.unkey_key_id);\n    \n    // Check if the key exists in Unkey\n    const getKeyResponse = await fetch(\n      `${UNKEY_API_URL}/keys.getKey?keyId=${profileData.unkey_key_id}`,\n      {\n        method: 'GET',\n        headers: {\n          Authorization: `Bearer ${UNKEY_ROOT_KEY}`,\n        },\n      }\n    );\n    \n    // If key doesn't exist in Unkey, return that it doesn't exist\n    if (!getKeyResponse.ok) {\n      console.log('Key ID exists in profile but not in Unkey');\n      return NextResponse.json({ exists: false });\n    }\n    \n    const keyData = await getKeyResponse.json();\n    console.log('Key found in Unkey');\n    \n    return NextResponse.json({ \n      exists: true,\n      key: keyData.key\n    });\n  } catch (error) {\n    console.error('Error checking API key:', error);\n    return NextResponse.json({ error: 'Failed to check API key' }, { status: 500 });\n  }\n}\n\nexport async function POST(request: Request) {\n  // Parse the request body to get the userId\n  let userId;\n  try {\n    const body = await request.json();\n    userId = body.userId;\n    console.log('Received userId for key creation:', userId);\n    \n    if (!userId) {\n      return NextResponse.json({ error: 'UserId is required' }, { status: 400 });\n    }\n  } catch (error) {\n    console.error('Error parsing request body:', error);\n    return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });\n  }\n  \n  // Create Supabase client\n  const supabase = createClient();\n  \n  try {\n    // Check if user exists in profiles table\n    const { error: profileError } = await supabase\n      .from('profiles')\n      .select('id, unkey_key_id')\n      .eq('id', userId)\n      .single();\n    \n    if (profileError && profileError.code !== 'PGRST116') {\n      console.error('Error fetching profile:', profileError);\n      return NextResponse.json({ error: 'Failed to fetch user profile' }, { status: 500 });\n    }\n\n  \n    // Create an API key for the user\n    const createKeyResponse = await fetch(`${UNKEY_API_URL}/keys.createKey`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: `Bearer ${UNKEY_ROOT_KEY}`,\n      },\n      body: JSON.stringify({\n        apiId: UNKEY_API_ID,\n        prefix: 'cd',\n        byteLength: 16,\n        externalId: userId,\n        meta: {\n          createdAt: new Date().toISOString(),\n          userId\n        },\n      }),\n    });\n\n    if (!createKeyResponse.ok) {\n      const errorText = await createKeyResponse.text();\n      console.log('Error creating key:', errorText);\n      throw new Error(`Failed to create key: ${createKeyResponse.statusText}`);\n    }\n\n    const keyData = await createKeyResponse.json();\n    console.log('Key created:', keyData.keyId);\n    \n    // Update the user's profile with the key ID\n    const { error: updateError } = await supabase\n      .from('profiles')\n      .update({ unkey_key_id: keyData.keyId })\n      .eq('id', userId);\n    \n    if (updateError) {\n      console.error('Error updating profile with key ID:', updateError);\n      // We'll still return the key even if updating the profile fails\n    }\n\n    return NextResponse.json({ \n      success: true, \n      key: keyData.key,\n      keyId: keyData.keyId,\n    });\n  } catch (error) {\n    console.error('Error creating API key:', error);\n    return NextResponse.json({ error: 'Failed to create API key' }, { status: 500 });\n  }\n}\n"
  },
  {
    "path": "apps/web/src/app/auth/callback/route.ts",
    "content": "import { createClient } from '@/utils/supabase/server'\nimport { NextRequest, NextResponse } from 'next/server'\nimport PostHogClient from '@/utils/posthog/posthog'\n\nexport async function GET(request: NextRequest) {\n  const requestUrl = new URL(request.url)\n  const code = requestUrl.searchParams.get('code')\n  const error = requestUrl.searchParams.get('error')\n  const errorDescription = requestUrl.searchParams.get('error_description')\n  \n  // Create a response that will be used for redirecting\n  const redirectUrl = new URL('/dashboard', request.url)\n  \n  if (error) {\n    console.error(`Auth error: ${error}, Description: ${errorDescription}`)\n    // If there's an error, redirect to login\n    redirectUrl.pathname = '/login'\n    // Add error information as query parameters\n    redirectUrl.searchParams.set('error', error)\n    if (errorDescription) {\n      redirectUrl.searchParams.set('error_description', errorDescription)\n    }\n  } else if (code) {\n    try {\n      const supabase = createClient()\n      \n      // Exchange the code for a session\n      const { data } = await supabase.auth.exchangeCodeForSession(code)\n      \n      // Identify the user in PostHog\n      if (data?.user) {\n        const posthog = PostHogClient()\n        // Use the correct identify method signature for posthog-node\n        posthog.identify({\n          distinctId: data.user.id,\n          properties: {\n            email: data.user.email,\n            name: data.user.user_metadata?.full_name || data.user.user_metadata?.name\n          }\n        })\n      }\n    } catch (err) {\n      console.error('Error exchanging code for session:', err)\n      // If there's an error, redirect to login\n      redirectUrl.pathname = '/login'\n      redirectUrl.searchParams.set('error', 'session_exchange_error')\n    }\n  }\n  \n  // URL to redirect to after sign in process completes\n  return NextResponse.redirect(redirectUrl)\n}\n"
  },
  {
    "path": "apps/web/src/app/blog/[slug]/page.tsx",
    "content": "import { Button } from '@/components/button'\nimport { Container } from '@/components/container'\nimport { Footer } from '@/components/footer'\nimport { GradientBackground } from '@/components/gradient'\nimport { Link } from '@/components/link'\nimport { Navbar } from '@/components/navbar'\nimport { Heading, Subheading } from '@/components/text'\nimport { image } from '@/sanity/image'\nimport { getPost } from '@/sanity/queries'\nimport { ChevronLeftIcon } from '@heroicons/react/16/solid'\nimport dayjs from 'dayjs'\nimport type { Metadata } from 'next'\nimport { PortableText } from 'next-sanity'\nimport { notFound } from 'next/navigation'\n\nexport async function generateMetadata({\n  params,\n}: {\n  params: { slug: string }\n}): Promise<Metadata> {\n  const post = await getPost(params.slug)\n\n  return post ? { title: post.title, description: post.excerpt } : {}\n}\n\nexport default async function BlogPost({\n  params,\n}: {\n  params: { slug: string }\n}) {\n  const post = (await getPost(params.slug)) || notFound()\n\n  return (\n    <main className=\"overflow-hidden\">\n      <GradientBackground />\n      <Container>\n        <Navbar />\n        <Subheading className=\"mt-16\">\n          {dayjs(post.publishedAt).format('dddd, MMMM D, YYYY')}\n        </Subheading>\n        <Heading as=\"h1\" className=\"mt-2\">\n          {post.title}\n        </Heading>\n        <div className=\"mt-16 grid grid-cols-1 gap-8 pb-24 lg:grid-cols-[15rem_1fr] xl:grid-cols-[15rem_1fr_15rem]\">\n          <div className=\"flex flex-wrap items-center gap-8 max-lg:justify-between lg:flex-col lg:items-start\">\n            {post.author && (\n              <div className=\"flex items-center gap-3\">\n                {post.author.image && (\n                  <img\n                    alt=\"\"\n                    src={image(post.author.image).size(64, 64).url()}\n                    className=\"aspect-square size-6 rounded-full object-cover\"\n                  />\n                )}\n                <div className=\"text-sm/5 text-gray-700\">\n                  {post.author.name}\n                </div>\n              </div>\n            )}\n            {Array.isArray(post.categories) && (\n              <div className=\"flex flex-wrap gap-2\">\n                {post.categories.map((category) => (\n                  <Link\n                    key={category.slug}\n                    href={`/blog?category=${category.slug}`}\n                    className=\"rounded-full border border-dotted border-gray-300 bg-gray-50 px-2 text-sm/6 font-medium text-gray-500\"\n                  >\n                    {category.title}\n                  </Link>\n                ))}\n              </div>\n            )}\n          </div>\n          <div className=\"text-gray-700\">\n            <div className=\"max-w-2xl xl:mx-auto\">\n              {post.mainImage && (\n                <img\n                  alt={post.mainImage.alt || ''}\n                  src={image(post.mainImage).size(2016, 1344).url()}\n                  className=\"mb-10 aspect-3/2 w-full rounded-2xl object-cover shadow-xl\"\n                />\n              )}\n              {post.body && (\n                <PortableText\n                  value={post.body}\n                  components={{\n                    block: {\n                      normal: ({ children }) => (\n                        <p className=\"my-10 text-base/8 first:mt-0 last:mb-0\">\n                          {children}\n                        </p>\n                      ),\n                      h2: ({ children }) => (\n                        <h2 className=\"mt-12 mb-10 text-2xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0\">\n                          {children}\n                        </h2>\n                      ),\n                      h3: ({ children }) => (\n                        <h3 className=\"mt-12 mb-10 text-xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0\">\n                          {children}\n                        </h3>\n                      ),\n                      blockquote: ({ children }) => (\n                        <blockquote className=\"my-10 border-l-2 border-l-gray-300 pl-6 text-base/8 text-gray-950 first:mt-0 last:mb-0\">\n                          {children}\n                        </blockquote>\n                      ),\n                    },\n                    types: {\n                      image: ({ value }) => (\n                        <img\n                          alt={value.alt || ''}\n                          src={image(value).width(2000).url()}\n                          className=\"w-full rounded-2xl\"\n                        />\n                      ),\n                      separator: ({ value }) => {\n                        switch (value.style) {\n                          case 'line':\n                            return (\n                              <hr className=\"my-8 border-t border-gray-200\" />\n                            )\n                          case 'space':\n                            return <div className=\"my-8\" />\n                          default:\n                            return null\n                        }\n                      },\n                    },\n                    list: {\n                      bullet: ({ children }) => (\n                        <ul className=\"list-disc pl-4 text-base/8 marker:text-gray-400\">\n                          {children}\n                        </ul>\n                      ),\n                      number: ({ children }) => (\n                        <ol className=\"list-decimal pl-4 text-base/8 marker:text-gray-400\">\n                          {children}\n                        </ol>\n                      ),\n                    },\n                    listItem: {\n                      bullet: ({ children }) => {\n                        return (\n                          <li className=\"my-2 pl-2 has-[br]:mb-8\">\n                            {children}\n                          </li>\n                        )\n                      },\n                      number: ({ children }) => {\n                        return (\n                          <li className=\"my-2 pl-2 has-[br]:mb-8\">\n                            {children}\n                          </li>\n                        )\n                      },\n                    },\n                    marks: {\n                      strong: ({ children }) => (\n                        <strong className=\"font-semibold text-gray-950\">\n                          {children}\n                        </strong>\n                      ),\n                      code: ({ children }) => (\n                        <>\n                          <span aria-hidden>`</span>\n                          <code className=\"text-[15px]/8 font-semibold text-gray-950\">\n                            {children}\n                          </code>\n                          <span aria-hidden>`</span>\n                        </>\n                      ),\n                      link: ({ value, children }) => {\n                        return (\n                          <Link\n                            href={value.href}\n                            className=\"font-medium text-gray-950 underline decoration-gray-400 underline-offset-4 data-hover:decoration-gray-600\"\n                          >\n                            {children}\n                          </Link>\n                        )\n                      },\n                    },\n                  }}\n                />\n              )}\n              <div className=\"mt-10\">\n                <Button variant=\"outline\" href=\"/blog\">\n                  <ChevronLeftIcon className=\"size-4\" />\n                  Back to blog\n                </Button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </Container>\n      <Footer />\n    </main>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/blog/feed.xml/route.ts",
    "content": "import { image } from '@/sanity/image'\nimport { getPostsForFeed } from '@/sanity/queries'\nimport { Feed } from 'feed'\nimport assert from 'node:assert'\n\nexport async function GET(req: Request) {\n  const siteUrl = new URL(req.url).origin\n\n  const feed = new Feed({\n    title: 'The Cyberdesk Blog',\n    description:\n      'Stay informed with product updates, company news, and insights on how to build world class computer agents.',\n    author: {\n      name: 'Alan Duong',\n      email: 'devs@cyberdesk.io',\n    },\n    id: siteUrl,\n    link: siteUrl,\n    image: `${siteUrl}/favicon.ico`,\n    favicon: `${siteUrl}/favicon.ico`,\n    copyright: `All rights reserved ${new Date().getFullYear()}`,\n    feedLinks: {\n      rss2: `${siteUrl}/feed.xml`,\n    },\n  })\n\n  const posts = await getPostsForFeed()\n\n  posts.forEach((post) => {\n    try {\n      assert(typeof post.title === 'string')\n      assert(typeof post.slug === 'string')\n      assert(typeof post.excerpt === 'string')\n      assert(typeof post.publishedAt === 'string')\n    } catch (error) {\n      console.log('Post is missing required fields for RSS feed:', post)\n      return\n    }\n\n    feed.addItem({\n      title: post.title,\n      id: post.slug,\n      link: `${siteUrl}/blog/${post.slug}`,\n      content: post.excerpt,\n      image: post.mainImage\n        ? image(post.mainImage)\n            .size(1200, 800)\n            .format('jpg')\n            .url()\n            .replaceAll('&', '&amp;')\n        : undefined,\n      author: post.author?.name ? [{ name: post.author.name }] : [],\n      contributor: post.author?.name ? [{ name: post.author.name }] : [],\n      date: new Date(post.publishedAt),\n    })\n  })\n\n  return new Response(feed.rss2(), {\n    status: 200,\n    headers: {\n      'content-type': 'application/xml',\n      'cache-control': 's-maxage=31556952',\n    },\n  })\n}\n"
  },
  {
    "path": "apps/web/src/app/blog/page.tsx",
    "content": "import { Button } from '@/components/button'\nimport { Container } from '@/components/container'\nimport { Footer } from '@/components/footer'\nimport { GradientBackground } from '@/components/gradient'\nimport { Link } from '@/components/link'\nimport { Navbar } from '@/components/navbar'\nimport { Heading, Lead, Subheading } from '@/components/text'\nimport { image } from '@/sanity/image'\nimport {\n  getCategories,\n  getFeaturedPosts,\n  getPosts,\n  getPostsCount,\n} from '@/sanity/queries'\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'\nimport {\n  CheckIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  ChevronUpDownIcon,\n  RssIcon,\n} from '@heroicons/react/16/solid'\nimport { clsx } from 'clsx'\nimport dayjs from 'dayjs'\nimport type { Metadata } from 'next'\nimport { notFound } from 'next/navigation'\n\nexport const metadata: Metadata = {\n  title: 'Blog',\n  description:\n    'Stay informed with product updates, company news, and insights on how to sell smarter at your company.',\n}\n\nconst postsPerPage = 5\n\nasync function FeaturedPosts() {\n  const featuredPosts = await getFeaturedPosts(3)\n\n  if (featuredPosts.length === 0) {\n    return\n  }\n\n  return (\n    <div className=\"mt-16 bg-linear-to-t from-gray-100 pb-14\">\n      <Container>\n        <h2 className=\"text-2xl font-medium tracking-tight\">Featured</h2>\n        <div className=\"mt-6 grid grid-cols-1 gap-8 lg:grid-cols-3\">\n          {featuredPosts.map((post) => (\n            <div\n              key={post.slug}\n              className=\"relative flex flex-col rounded-3xl bg-white p-2 ring-1 shadow-md shadow-black/5 ring-black/5\"\n            >\n              {post.mainImage && (\n                <img\n                  alt={post.mainImage.alt || ''}\n                  src={image(post.mainImage).size(1170, 780).url()}\n                  className=\"aspect-3/2 w-full rounded-2xl object-cover\"\n                />\n              )}\n              <div className=\"flex flex-1 flex-col p-8\">\n                <div className=\"text-sm/5 text-gray-700\">\n                  {dayjs(post.publishedAt).format('dddd, MMMM D, YYYY')}\n                </div>\n                <div className=\"mt-2 text-base/7 font-medium\">\n                  <Link href={`/blog/${post.slug}`}>\n                    <span className=\"absolute inset-0\" />\n                    {post.title}\n                  </Link>\n                </div>\n                <div className=\"mt-2 flex-1 text-sm/6 text-gray-500\">\n                  {post.excerpt}\n                </div>\n                {post.author && (\n                  <div className=\"mt-6 flex items-center gap-3\">\n                    {post.author.image && (\n                      <img\n                        alt=\"\"\n                        src={image(post.author.image).size(64, 64).url()}\n                        className=\"aspect-square size-6 rounded-full object-cover\"\n                      />\n                    )}\n                    <div className=\"text-sm/5 text-gray-700\">\n                      {post.author.name}\n                    </div>\n                  </div>\n                )}\n              </div>\n            </div>\n          ))}\n        </div>\n      </Container>\n    </div>\n  )\n}\n\nasync function Categories({ selected }: { selected?: string }) {\n  const categories = await getCategories()\n\n  if (categories.length === 0) {\n    return\n  }\n\n  return (\n    <div className=\"flex flex-wrap items-center justify-between gap-2\">\n      <Menu>\n        <MenuButton className=\"flex items-center justify-between gap-2 font-medium\">\n          {categories.find(({ slug }) => slug === selected)?.title ||\n            'All categories'}\n          <ChevronUpDownIcon className=\"size-4 fill-gray-900\" />\n        </MenuButton>\n        <MenuItems\n          anchor=\"bottom start\"\n          className=\"min-w-40 rounded-lg bg-white p-1 ring-1 shadow-lg ring-gray-200 [--anchor-gap:6px] [--anchor-offset:-4px] [--anchor-padding:10px]\"\n        >\n          <MenuItem>\n            <Link\n              href=\"/blog\"\n              data-selected={selected === undefined ? true : undefined}\n              className=\"group grid grid-cols-[1rem_1fr] items-center gap-2 rounded-md px-2 py-1 data-focus:bg-gray-950/5\"\n            >\n              <CheckIcon className=\"hidden size-4 group-data-selected:block\" />\n              <p className=\"col-start-2 text-sm/6\">All categories</p>\n            </Link>\n          </MenuItem>\n          {categories.map((category) => (\n            <MenuItem key={category.slug}>\n              <Link\n                href={`/blog?category=${category.slug}`}\n                data-selected={category.slug === selected ? true : undefined}\n                className=\"group grid grid-cols-[16px_1fr] items-center gap-2 rounded-md px-2 py-1 data-focus:bg-gray-950/5\"\n              >\n                <CheckIcon className=\"hidden size-4 group-data-selected:block\" />\n                <p className=\"col-start-2 text-sm/6\">{category.title}</p>\n              </Link>\n            </MenuItem>\n          ))}\n        </MenuItems>\n      </Menu>\n      <Button variant=\"outline\" href=\"/blog/feed.xml\" className=\"gap-1\">\n        <RssIcon className=\"size-4\" />\n        RSS Feed\n      </Button>\n    </div>\n  )\n}\n\nasync function Posts({ page, category }: { page: number; category?: string }) {\n  const posts = await getPosts(\n    (page - 1) * postsPerPage,\n    page * postsPerPage,\n    category,\n  )\n\n  if (posts.length === 0 && (page > 1 || category)) {\n    notFound()\n  }\n\n  if (posts.length === 0) {\n    return <p className=\"mt-6 text-gray-500\">No posts found.</p>\n  }\n\n  return (\n    <div className=\"mt-6\">\n      {posts.map((post) => (\n        <div\n          key={post.slug}\n          className=\"relative grid grid-cols-1 border-b border-b-gray-100 py-10 first:border-t first:border-t-gray-200 max-sm:gap-3 sm:grid-cols-3\"\n        >\n          <div>\n            <div className=\"text-sm/5 max-sm:text-gray-700 sm:font-medium\">\n              {dayjs(post.publishedAt).format('dddd, MMMM D, YYYY')}\n            </div>\n            {post.author && (\n              <div className=\"mt-2.5 flex items-center gap-3\">\n                {post.author.image && (\n                  <img\n                    alt=\"\"\n                    src={image(post.author.image).width(64).height(64).url()}\n                    className=\"aspect-square size-6 rounded-full object-cover\"\n                  />\n                )}\n                <div className=\"text-sm/5 text-gray-700\">\n                  {post.author.name}\n                </div>\n              </div>\n            )}\n          </div>\n          <div className=\"sm:col-span-2 sm:max-w-2xl\">\n            <h2 className=\"text-sm/5 font-medium\">{post.title}</h2>\n            <p className=\"mt-3 text-sm/6 text-gray-500\">{post.excerpt}</p>\n            <div className=\"mt-4\">\n              <Link\n                href={`/blog/${post.slug}`}\n                className=\"flex items-center gap-1 text-sm/5 font-medium\"\n              >\n                <span className=\"absolute inset-0\" />\n                Read more\n                <ChevronRightIcon className=\"size-4 fill-gray-400\" />\n              </Link>\n            </div>\n          </div>\n        </div>\n      ))}\n    </div>\n  )\n}\n\nasync function Pagination({\n  page,\n  category,\n}: {\n  page: number\n  category?: string\n}) {\n  function url(page: number) {\n    const params = new URLSearchParams()\n\n    if (category) params.set('category', category)\n    if (page > 1) params.set('page', page.toString())\n\n    return params.size !== 0 ? `/blog?${params.toString()}` : '/blog'\n  }\n\n  const totalPosts = await getPostsCount(category)\n  const hasPreviousPage = page - 1\n  const previousPageUrl = hasPreviousPage ? url(page - 1) : undefined\n  const hasNextPage = page * postsPerPage < totalPosts\n  const nextPageUrl = hasNextPage ? url(page + 1) : undefined\n  const pageCount = Math.ceil(totalPosts / postsPerPage)\n\n  if (pageCount < 2) {\n    return\n  }\n\n  return (\n    <div className=\"mt-6 flex items-center justify-between gap-2\">\n      <Button\n        variant=\"outline\"\n        href={previousPageUrl}\n        disabled={!previousPageUrl}\n      >\n        <ChevronLeftIcon className=\"size-4\" />\n        Previous\n      </Button>\n      <div className=\"flex gap-2 max-sm:hidden\">\n        {Array.from({ length: pageCount }, (_, i) => (\n          <Link\n            key={i + 1}\n            href={url(i + 1)}\n            data-active={i + 1 === page ? true : undefined}\n            className={clsx(\n              'size-7 rounded-lg text-center text-sm/7 font-medium',\n              'data-hover:bg-gray-100',\n              'data-active:ring-1 data-active:shadow-sm data-active:ring-black/10',\n              'data-active:data-hover:bg-gray-50',\n            )}\n          >\n            {i + 1}\n          </Link>\n        ))}\n      </div>\n      <Button variant=\"outline\" href={nextPageUrl} disabled={!nextPageUrl}>\n        Next\n        <ChevronRightIcon className=\"size-4\" />\n      </Button>\n    </div>\n  )\n}\n\nexport default async function Blog({\n  searchParams,\n}: {\n  searchParams: { [key: string]: string | string[] | undefined }\n}) {\n  const page =\n    'page' in searchParams\n      ? typeof searchParams.page === 'string' && parseInt(searchParams.page) > 1\n        ? parseInt(searchParams.page)\n        : notFound()\n      : 1\n\n  const category =\n    typeof searchParams.category === 'string'\n      ? searchParams.category\n      : undefined\n\n  return (\n    <main className=\"overflow-hidden\">\n      <GradientBackground />\n      <Container>\n        <Navbar />\n        <Subheading className=\"mt-16\">Blog</Subheading>\n        <Heading as=\"h1\" className=\"mt-2\">\n          What&apos;s happening at Cyberdesk.\n        </Heading>\n        <Lead className=\"mt-6 max-w-3xl\">\n          Stay informed with product updates, company news, and insights on how\n          to build world class computer agents.\n        </Lead>\n      </Container>\n      {page === 1 && !category && <FeaturedPosts />}\n      <Container className=\"mt-16 pb-24\">\n        <Categories selected={category} />\n        <Posts page={page} category={category} />\n        <Pagination page={page} category={category} />\n      </Container>\n      <Footer />\n    </main>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/company/page.tsx",
    "content": "import { AnimatedNumber } from '@/components/animated-number'\nimport { Button } from '@/components/button'\nimport { Container } from '@/components/container'\nimport { Footer } from '@/components/footer'\nimport { GradientBackground } from '@/components/gradient'\nimport { Navbar } from '@/components/navbar'\nimport { Heading, Lead, Subheading } from '@/components/text'\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  title: 'Company',\n  description:\n    'We’re on a mission to transform revenue organizations by harnessing vast amounts of illegally acquired customer data.',\n}\n\nfunction Header() {\n  return (\n    <Container className=\"mt-16\">\n      <Heading as=\"h1\">Helping companies generate revenue.</Heading>\n      <Lead className=\"mt-6 max-w-3xl\">\n        We’re on a mission to transform revenue organizations by harnessing vast\n        amounts of illegally acquired customer data.\n      </Lead>\n      <section className=\"mt-16 grid grid-cols-1 lg:grid-cols-2 lg:gap-12\">\n        <div className=\"max-w-lg\">\n          <h2 className=\"text-2xl font-medium tracking-tight\">Our mission</h2>\n          <p className=\"mt-6 text-sm/6 text-gray-600\">\n            At Radiant, we are dedicated to transforming the way revenue\n            organizations source and close deals. Our mission is to provide our\n            customers with an unfair advantage over both their competitors and\n            potential customers through insight and analysis. We’ll stop at\n            nothing to get you the data you need to close a deal.\n          </p>\n          <p className=\"mt-8 text-sm/6 text-gray-600\">\n            We’re customer-obsessed — putting the time in to build a detailed\n            financial picture of every one of our customers so that we know more\n            about your business than you do. We are in this together, mostly\n            because we are all implicated in large-scale financial crime. In our\n            history as a company, we’ve never lost a customer, because if any\n            one of us talks, we all go down.\n          </p>\n        </div>\n        <div className=\"pt-20 lg:row-span-2 lg:-mr-16 xl:mr-auto\">\n          <div className=\"-mx-8 grid grid-cols-2 gap-4 sm:-mx-16 sm:grid-cols-4 lg:mx-0 lg:grid-cols-2 lg:gap-4 xl:gap-8\">\n            <div className=\"aspect-square overflow-hidden rounded-xl shadow-xl outline-1 -outline-offset-1 outline-black/10\">\n              <img\n                alt=\"\"\n                src=\"/company/1.jpg\"\n                className=\"block size-full object-cover\"\n              />\n            </div>\n            <div className=\"-mt-8 aspect-square overflow-hidden rounded-xl shadow-xl outline-1 -outline-offset-1 outline-black/10 lg:-mt-32\">\n              <img\n                alt=\"\"\n                src=\"/company/2.jpg\"\n                className=\"block size-full object-cover\"\n              />\n            </div>\n            <div className=\"aspect-square overflow-hidden rounded-xl shadow-xl outline-1 -outline-offset-1 outline-black/10\">\n              <img\n                alt=\"\"\n                src=\"/company/3.jpg\"\n                className=\"block size-full object-cover\"\n              />\n            </div>\n            <div className=\"-mt-8 aspect-square overflow-hidden rounded-xl shadow-xl outline-1 -outline-offset-1 outline-black/10 lg:-mt-32\">\n              <img\n                alt=\"\"\n                src=\"/company/4.jpg\"\n                className=\"block size-full object-cover\"\n              />\n            </div>\n          </div>\n        </div>\n        <div className=\"max-lg:mt-16 lg:col-span-1\">\n          <Subheading>The Numbers</Subheading>\n          <hr className=\"mt-6 border-t border-gray-200\" />\n          <dl className=\"mt-6 grid grid-cols-1 gap-x-8 gap-y-4 sm:grid-cols-2\">\n            <div className=\"flex flex-col gap-y-2 border-b border-dotted border-gray-200 pb-4\">\n              <dt className=\"text-sm/6 text-gray-600\">Raised</dt>\n              <dd className=\"order-first text-6xl font-medium tracking-tight\">\n                $<AnimatedNumber start={100} end={150} />M\n              </dd>\n            </div>\n            <div className=\"flex flex-col gap-y-2 border-b border-dotted border-gray-200 pb-4\">\n              <dt className=\"text-sm/6 text-gray-600\">Companies</dt>\n              <dd className=\"order-first text-6xl font-medium tracking-tight\">\n                <AnimatedNumber start={15} end={30} />K\n              </dd>\n            </div>\n            <div className=\"flex flex-col gap-y-2 max-sm:border-b max-sm:border-dotted max-sm:border-gray-200 max-sm:pb-4\">\n              <dt className=\"text-sm/6 text-gray-600\">Deals Closed</dt>\n              <dd className=\"order-first text-6xl font-medium tracking-tight\">\n                <AnimatedNumber start={0.9} end={1.5} decimals={1} />M\n              </dd>\n            </div>\n            <div className=\"flex flex-col gap-y-2\">\n              <dt className=\"text-sm/6 text-gray-600\">Leads Generated</dt>\n              <dd className=\"order-first text-6xl font-medium tracking-tight\">\n                <AnimatedNumber start={150} end={200} />M\n              </dd>\n            </div>\n          </dl>\n        </div>\n      </section>\n    </Container>\n  )\n}\n\nfunction Person({\n  name,\n  description,\n  img,\n}: {\n  name: string\n  description: string\n  img: string\n}) {\n  return (\n    <li className=\"flex items-center gap-4\">\n      <img alt=\"\" src={img} className=\"size-12 rounded-full\" />\n      <div className=\"text-sm/6\">\n        <h3 className=\"font-medium\">{name}</h3>\n        <p className=\"text-gray-500\">{description}</p>\n      </div>\n    </li>\n  )\n}\n\nfunction Team() {\n  return (\n    <Container className=\"mt-32\">\n      <Subheading>Meet the team</Subheading>\n      <Heading as=\"h3\" className=\"mt-2\">\n        Founded by an all-star team.\n      </Heading>\n      <Lead className=\"mt-6 max-w-3xl\">\n        Radiant is founded by two of the best sellers in the business and backed\n        by investors who look the other way.\n      </Lead>\n      <div className=\"mt-12 grid grid-cols-1 gap-12 lg:grid-cols-2\">\n        <div className=\"max-w-lg\">\n          <p className=\"text-sm/6 text-gray-600\">\n            Years ago, while working as sales associates at rival companies,\n            Thomas, Ben, and Natalie were discussing a big client they had all\n            been competing for. Joking about seeing the terms of each other’s\n            offers, they had an idea: what if they shared data to win deals and\n            split the commission behind their companies’ backs? It turned out to\n            be an incredible success, and that idea became the kernel for\n            Radiant.\n          </p>\n          <p className=\"mt-8 text-sm/6 text-gray-600\">\n            Today, Radiant transforms revenue organizations by harnessing\n            illegally acquired customer and competitor data, using it to provide\n            extraordinary leverage. More than 30,000 companies rely on Radiant\n            to undercut their competitors and extort their customers, all\n            through a single integrated platform.\n          </p>\n          <div className=\"mt-6\">\n            <Button className=\"w-full sm:w-auto\" href=\"#\">\n              Join us\n            </Button>\n          </div>\n        </div>\n        <div className=\"max-lg:order-first max-lg:max-w-lg\">\n          <div className=\"aspect-3/2 overflow-hidden rounded-xl shadow-xl outline-1 -outline-offset-1 outline-black/10\">\n            <img\n              alt=\"\"\n              src=\"/company/5.jpg\"\n              className=\"block size-full object-cover\"\n            />\n          </div>\n        </div>\n      </div>\n      <Subheading as=\"h3\" className=\"mt-24\">\n        The team\n      </Subheading>\n      <hr className=\"mt-6 border-t border-gray-200\" />\n      <ul\n        role=\"list\"\n        className=\"mx-auto mt-16 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3\"\n      >\n        <Person\n          name=\"Michael Foster\"\n          description=\"Co-Founder / CTO\"\n          img=\"/team/michael-foster.jpg\"\n        />\n        <Person\n          name=\"Dries Vincent\"\n          description=\"Business Relations\"\n          img=\"/team/dries-vincent.jpg\"\n        />\n        <Person\n          name=\"Celeste Vandermark\"\n          description=\"Front-end Developer\"\n          img=\"/team/celeste-vandermark.jpg\"\n        />\n        <Person\n          name=\"Courtney Henry\"\n          description=\"Designer\"\n          img=\"/team/courtney-henry.jpg\"\n        />\n        <Person\n          name=\"Marcus Eldridge\"\n          description=\"Director of Product\"\n          img=\"/team/marcus-eldridge.jpg\"\n        />\n        <Person\n          name=\"Whitney Francis\"\n          description=\"Copywriter\"\n          img=\"/team/whitney-francis.jpg\"\n        />\n        <Person\n          name=\"Leonard Krasner\"\n          description=\"Senior Designer\"\n          img=\"/team/leonard-krasner.jpg\"\n        />\n        <Person\n          name=\"Nolan Sheffield\"\n          description=\"Principal Designer\"\n          img=\"/team/nolan-sheffield.jpg\"\n        />\n        <Person\n          name=\"Emily Selman\"\n          description=\"VP, User Experience\"\n          img=\"/team/emily-selman.jpg\"\n        />\n      </ul>\n    </Container>\n  )\n}\n\nfunction Investors() {\n  return (\n    <Container className=\"mt-32\">\n      <Subheading>Investors</Subheading>\n      <Heading as=\"h3\" className=\"mt-2\">\n        Funded by industry-leaders.\n      </Heading>\n      <Lead className=\"mt-6 max-w-3xl\">\n        We are fortunate to be backed by the best investors in the industry —\n        both literal and metaphorical partners in crime.\n      </Lead>\n      <Subheading as=\"h3\" className=\"mt-24\">\n        Venture Capital\n      </Subheading>\n      <hr className=\"mt-6 border-t border-gray-200\" />\n      <ul\n        role=\"list\"\n        className=\"mx-auto mt-10 grid grid-cols-1 gap-8 lg:grid-cols-2\"\n      >\n        <li>\n          <img\n            alt=\"Remington Schwartz\"\n            src=\"/investors/remington-schwartz.svg\"\n            className=\"h-14\"\n          />\n          <p className=\"mt-6 max-w-lg text-sm/6 text-gray-500\">\n            Remington Schwartz has been a driving force in the tech industry,\n            backing bold entrepreneurs who explore grey areas in financial and\n            privacy law. Their deep industry expertise and extensive political\n            lobbying provide their portfolio companies with favorable regulation\n            and direct access to lawmakers.\n          </p>\n        </li>\n        <li>\n          <img alt=\"Deccel\" src=\"/investors/deccel.svg\" className=\"h-14\" />\n          <p className=\"mt-6 max-w-lg text-sm/6 text-gray-500\">\n            Deccel has been at the forefront of innovation, investing in\n            pioneering companies across various sectors, including technology,\n            consumer goods, and healthcare. Their philosophy of ‘plausible\n            deniability’ and dedication to looking the other way have helped\n            produce some of the world’s most controversial companies.\n          </p>\n        </li>\n      </ul>\n      <Subheading as=\"h3\" className=\"mt-24\">\n        Individual investors\n      </Subheading>\n      <hr className=\"mt-6 border-t border-gray-200\" />\n      <ul\n        role=\"list\"\n        className=\"mx-auto mt-16 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3\"\n      >\n        <Person\n          name=\"Kristin Watson\"\n          description=\"TechNexus Ventures\"\n          img=\"/individual-investors/kristin-watson.jpg\"\n        />\n        <Person\n          name=\"Emma Dorsey\"\n          description=\"Innovate Capital Partners\"\n          img=\"/individual-investors/emma-dorsey.jpg\"\n        />\n        <Person\n          name=\"Alicia Bell\"\n          description=\"FutureWave Investments\"\n          img=\"/individual-investors/alicia-bell.jpg\"\n        />\n        <Person\n          name=\"Jenny Wilson\"\n          description=\"SynergyTech Equity\"\n          img=\"/individual-investors/jenny-wilson.jpg\"\n        />\n        <Person\n          name=\"Anna Roberts\"\n          description=\"NextGen Horizons\"\n          img=\"/individual-investors/anna-roberts.jpg\"\n        />\n        <Person\n          name=\"Benjamin Russel\"\n          description=\"Pioneer Digital Ventures\"\n          img=\"/individual-investors/benjamin-russel.jpg\"\n        />\n      </ul>\n    </Container>\n  )\n}\n\nfunction Testimonial() {\n  return (\n    <div className=\"relative flex aspect-square flex-col justify-end overflow-hidden rounded-3xl sm:aspect-5/4 lg:aspect-3/4\">\n      <img\n        alt=\"\"\n        src=\"/testimonials/veronica-winton.jpg\"\n        className=\"absolute inset-0 object-cover\"\n      />\n      <div\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 rounded-3xl bg-linear-to-t from-black from-10% to-75% ring-1 ring-gray-950/10 ring-inset lg:from-25%\"\n      />\n      <figure className=\"relative p-10\">\n        <blockquote>\n          <p className=\"relative text-xl/7 text-white before:absolute before:-translate-x-full before:content-['“'] after:absolute after:content-['”']\">\n            We&apos;ve managed to put two of our main competitors out of\n            business in 6 months.\n          </p>\n        </blockquote>\n        <figcaption className=\"mt-6 border-t border-white/20 pt-6\">\n          <p className=\"text-sm/6 font-medium text-white\">Veronica Winton</p>\n          <p className=\"text-sm/6 font-medium\">\n            <span className=\"bg-linear-to-r from-[#fff1be] from-28% via-[#ee87cb] via-70% to-[#b060ff] bg-clip-text text-transparent\">\n              CSO, Planeteria\n            </span>\n          </p>\n        </figcaption>\n      </figure>\n    </div>\n  )\n}\n\nfunction Careers() {\n  return (\n    <Container className=\"my-32\">\n      <Subheading>Careers</Subheading>\n      <Heading as=\"h3\" className=\"mt-2\">\n        Join our fully remote team.\n      </Heading>\n      <Lead className=\"mt-6 max-w-3xl\">\n        We work together from all over the world, mainly from locations without\n        extradition agreements.\n      </Lead>\n      <div className=\"mt-24 grid grid-cols-1 gap-16 lg:grid-cols-[1fr_24rem]\">\n        <div className=\"lg:max-w-2xl\">\n          <Subheading as=\"h3\">Open positions</Subheading>\n          <div>\n            <table className=\"w-full text-left\">\n              <colgroup>\n                <col className=\"w-2/3\" />\n                <col className=\"w-1/3\" />\n                <col className=\"w-0\" />\n              </colgroup>\n              <thead className=\"sr-only\">\n                <tr>\n                  <th scope=\"col\">Title</th>\n                  <th scope=\"col\">Location</th>\n                  <th scope=\"col\">Read more</th>\n                </tr>\n              </thead>\n              <tbody>\n                <tr>\n                  <th scope=\"colgroup\" colSpan={3} className=\"px-0 pt-10 pb-0\">\n                    <div className=\"-mx-4 rounded-lg bg-gray-50 px-4 py-3 text-sm/6 font-semibold\">\n                      Engineering\n                    </div>\n                  </th>\n                </tr>\n                <tr className=\"border-b border-dotted border-gray-200 text-sm/6 font-normal\">\n                  <td className=\"px-0 py-4\">iOS Developer</td>\n                  <td className=\"px-0 py-4 text-gray-600\">Remote</td>\n                  <td className=\"px-0 py-4 text-right\">\n                    <Button variant=\"outline\" href=\"#\">\n                      View listing\n                    </Button>\n                  </td>\n                </tr>\n                <tr className=\"border-b border-dotted border-gray-200 text-sm/6 font-normal\">\n                  <td className=\"px-0 py-4\">Backend Engineer</td>\n                  <td className=\"px-0 py-4 text-gray-600\">Remote</td>\n                  <td className=\"px-0 py-4 text-right\">\n                    <Button variant=\"outline\" href=\"#\">\n                      View listing\n                    </Button>\n                  </td>\n                </tr>\n                <tr className=\"text-sm/6 font-normal\">\n                  <td className=\"px-0 py-4\">Product Engineer</td>\n                  <td className=\"px-0 py-4 text-gray-600\">Remote</td>\n                  <td className=\"px-0 py-4 text-right\">\n                    <Button variant=\"outline\" href=\"#\">\n                      View listing\n                    </Button>\n                  </td>\n                </tr>\n                <tr>\n                  <th scope=\"colgroup\" colSpan={3} className=\"px-0 pt-5 pb-0\">\n                    <div className=\"-mx-4 rounded-lg bg-gray-50 px-4 py-3 text-sm/6 font-semibold\">\n                      Design\n                    </div>\n                  </th>\n                </tr>\n                <tr className=\"border-b border-dotted border-gray-200 text-sm/6 font-normal\">\n                  <td className=\"px-0 py-4\">Principal Designer</td>\n                  <td className=\"px-0 py-4 text-gray-600\">Remote</td>\n                  <td className=\"px-0 py-4 text-right\">\n                    <Button variant=\"outline\" href=\"#\">\n                      View listing\n                    </Button>\n                  </td>\n                </tr>\n                <tr className=\"border-b border-dotted border-gray-200 text-sm/6 font-normal\">\n                  <td className=\"px-0 py-4\">Designer</td>\n                  <td className=\"px-0 py-4 text-gray-600\">Remote</td>\n                  <td className=\"px-0 py-4 text-right\">\n                    <Button variant=\"outline\" href=\"#\">\n                      View listing\n                    </Button>\n                  </td>\n                </tr>\n                <tr className=\"text-sm/6 font-normal\">\n                  <td className=\"px-0 py-4\">Senior Designer</td>\n                  <td className=\"px-0 py-4 text-gray-600\">Remote</td>\n                  <td className=\"px-0 py-4 text-right\">\n                    <Button variant=\"outline\" href=\"#\">\n                      View listing\n                    </Button>\n                  </td>\n                </tr>\n              </tbody>\n            </table>\n          </div>\n        </div>\n        <Testimonial />\n      </div>\n    </Container>\n  )\n}\n\nexport default function Company() {\n  return (\n    <main className=\"overflow-hidden\">\n      <GradientBackground />\n      <Container>\n        <Navbar />\n      </Container>\n      <Header />\n      <Team />\n      <Investors />\n      <Careers />\n      <Footer />\n    </main>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/dashboard/dashboard-content.tsx",
    "content": "'use client'\n\nimport { SubscriptionSection } from '@/components/dashboard/subscription-section'\nimport type { Profile } from '@/types/database'\nimport { ApiKeyManager } from '@/components/dashboard/api-key-manager'\nimport { VMInstancesManager } from '@/components/dashboard/vm-instances-manager'\nimport { supabase } from '@/utils/supabase/client'\nimport { ArrowRightOnRectangleIcon } from '@heroicons/react/24/outline'\nimport { Button } from '@/components/button'\nimport posthog from 'posthog-js'\n// import { Subheading } from '@/components/text'\n\ninterface DashboardContentProps {\n  userEmail?: string;\n  userId?: string;\n  profile?: Profile | null;\n}\n\nexport function DashboardContent({ userEmail, userId, profile }: DashboardContentProps) {\n  const isSubscriptionActive = profile?.subscription_status === \"active\";\n  // Removed fetching active subscriptions as pricing card is no longer displayed for non-subscribers\n  \n  // Removed fetching active subscriptions as pricing card is no longer displayed for non-subscribers\n  \n  const handleLogout = async () => {\n    // Reset PostHog user to anonymous before logging out\n    posthog.reset();\n    \n    await supabase.auth.signOut();\n    window.location.href = '/';\n  };\n  \n  // const isSoldOut = activeSubscriptionsCount !== null && activeSubscriptionsCount >= SUBSCRIPTION_LIMIT;\n\n  // If subscription is not active, show booking message instead of pricing/FAQ\n  if (!isSubscriptionActive) {\n    return (\n      <div className=\"flex flex-col items-center justify-center py-24 text-center space-y-6\">\n        <h2 className=\"text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl\">\n          Looks like we haven&apos;t set up your account yet.\n        </h2>\n        <p className=\"max-w-lg text-base/7 text-gray-600\">\n          Book a time here to get you up and running soon!\n        </p>\n        <Button href=\"https://cal.com/mahmoud-al-madi-klrs5s/30min\" target=\"_blank\" rel=\"noopener noreferrer\">\n          Book a demo\n        </Button>\n      </div>\n    );\n  }\n  \n  return (\n    <div className=\"space-y-8\">\n      <div className=\"flex justify-between items-center\">\n        <div>\n          {isSubscriptionActive ? (\n            <>\n              <h2 className=\"text-xl font-medium tracking-tight text-gray-900 sm:text-4xl\">Dashboard</h2 >\n              <p className=\"mt-2 text-base/7 text-gray-600\">\n                Welcome to your dashboard. This is where you can manage your virtual desktops, account settings, and more.\n              </p>\n            </>\n          ) : (\n            <>\n              <h2 className=\"text-xl font-medium tracking-tight text-gray-900 sm:text-4xl\">Get Started with Cyberdesk</h2>\n              <p className=\"mt-2 text-base/7 text-gray-600\">\n                Unlock the full power of Cyberdesk by subscribing to our Pro plan.\n              </p>\n            </>\n          )}\n        </div>\n        <button\n          onClick={handleLogout}\n          className=\"flex items-center gap-1 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2\"\n        >\n          <ArrowRightOnRectangleIcon className=\"h-3.5 w-3.5\" />\n          Sign Out\n        </button>\n      </div>\n      \n      {/* Pricing and FAQ sections have been removed for non-subscribers */}\n      \n      {/* For active subscribers, show API Key, VM Instances, and Subscription sections */}\n      {isSubscriptionActive && (\n        <>\n          {/* API Key Section */}\n          <div>\n            <ApiKeyManager />\n          </div>\n          \n          {/* VM Instances Section */}\n          <div>\n            <VMInstancesManager />\n          </div>\n          \n          {/* Subscription Management Section */}\n          <div>\n            <SubscriptionSection \n              userEmail={userEmail}\n              userId={userId}\n              profile={profile}\n            />\n          </div>\n        </>\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/dashboard/page.tsx",
    "content": "import { redirect } from 'next/navigation';\nimport { stripe } from '@/utils/stripe/stripe-server';\nimport type { Profile } from '@/types/database';\nimport { DashboardLayout } from '@/components/dashboard/dashboard-layout';\nimport { DashboardContent } from './dashboard-content';\nimport { createClient } from '@/utils/supabase/server';\n\nexport default async function Dashboard() {\n  const supabase = createClient();\n  \n  // Check if user is authenticated - using getUser() for better security\n  const { data: { user } } = await supabase.auth.getUser();\n  \n  if (!user) {\n    redirect('/login');\n  }\n  \n  const userId = user.id;\n  \n  // Query the profiles table for the user\n  const { data: dbProfile, error } = await supabase\n    .from('profiles')\n    .select('*')\n    .eq('id', userId)\n    .single();\n\n  let profile: Profile | null = dbProfile;\n  \n  if (error && error.code !== 'PGRST116') {\n    // PGRST116 is the error code for \"no rows returned\" - we handle this case separately\n    console.error('Error fetching profile:', error);\n  }\n  \n  // If profile doesn't exist, create it along with a Stripe customer\n  if (!profile) {\n    try {\n      // Create a Stripe customer\n      const customer = await stripe.customers.create({\n        email: user.email,\n        metadata: {\n          userId: userId\n        }\n      });\n      \n      // Create a profile entry with the Stripe customer ID\n      const newProfile: Profile = {\n        id: userId,\n        stripe_customer_id: customer.id,\n        subscription_status: 'inactive',\n        created_at: new Date(),\n        updated_at: new Date()\n      };\n      \n      const { error: insertError } = await supabase\n        .from('profiles')\n        .insert(newProfile);\n\n      profile = newProfile;\n      \n      if (insertError) {\n        console.error('Error creating profile:', insertError);\n      }\n    } catch (err) {\n      console.error('Error creating Stripe customer:', err);\n    }\n  }\n  \n  // Get session for client components\n  // TODO: Utilize this for client components\n  // const { data: { session } } = await supabase.auth.getSession();\n  \n  return (\n    <DashboardLayout userEmail={user.email}>\n      <DashboardContent \n        userEmail={user.email}\n        userId={userId}\n        profile={profile}\n      />\n    </DashboardLayout>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/demo/page.tsx",
    "content": "'use client'\n\nimport { DemoSection } from '@/components/demo-section'\nimport { Thread } from '@/components/thread'\nimport { AssistantRuntimeProvider } from '@assistant-ui/react'\nimport { useChatRuntime } from '@assistant-ui/react-ai-sdk'\nimport {\n  ChatBubbleLeftIcon,\n  HeartIcon,\n} from '@heroicons/react/24/outline'\nimport { useState } from 'react'\n\nexport default function PlaygroundDemo() {\n  // State to store the desktop ID\n  const [desktopId, setDesktopId] = useState<string | null>(null)\n  // State to track if the demo has been finished\n  const [finishedDemo, setFinishedDemo] = useState(false)\n\n  // Update the chat runtime to support OpenAI Responses API features\n  const runtime = useChatRuntime({\n    api: '/api/chat',\n    // Use headers instead of body to pass the desktopId\n    headers: () => {\n      // Use the actual desktopId from state, or a fallback value if it's null\n      const currentDesktopId = desktopId || 'NO_DESKTOP_ID_YET'\n      console.log(\n        '[useChatRuntime] Sending request with desktopId in headers:',\n        currentDesktopId,\n      )\n\n      return Promise.resolve({\n        'Content-Type': 'application/json',\n        'X-Desktop-Id': currentDesktopId,\n      })\n    },\n    onFinish: (message) => {\n      const sources = message.metadata?.custom?.sources\n      if (sources) {\n        console.log('Web search sources:', sources)\n      }\n    },\n  })\n  // Function to handle when a desktop is deployed\n  const handleDesktopDeployed = (id: string) => {\n    // Set the desktop ID directly from the API response\n    setDesktopId(id)\n  }\n\n  const handleDesktopStopped = () => {\n    // Set the desktop ID directly from the API response\n    setDesktopId(null)\n    // Set finishedDemo to true when desktop is stopped\n    setFinishedDemo(true)\n  }\n\n  return (\n    <AssistantRuntimeProvider runtime={runtime}>\n      <div className=\"mx-auto flex h-auto w-full max-w-7xl flex-col gap-4 px-2 md:flex-row\">\n        {/* Thread Area */}\n        <div className=\"order-2 flex h-[400px] flex-col items-center md:order-1 md:h-[500px] md:w-1/4 md:flex-none\">\n          {desktopId ? (\n            <div className=\"h-full w-full overflow-auto rounded-lg border border-gray-200 bg-gray-50\">\n              <Thread />\n              {/* Add a button to stop the desktop on mobile and tablet only */}\n            </div>\n          ) : (\n            <div className=\"flex h-full w-full items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-gray-400\">\n              <div className=\"py-12 text-center\">\n                {finishedDemo ? (\n                  <HeartIcon className=\"mx-auto mb-6 h-16 w-16 text-gray-300\" />\n                ) : (\n                  <ChatBubbleLeftIcon className=\"mx-auto mb-6 h-16 w-16 text-gray-300\" />\n                )}\n                <p className=\"max-w-md px-10 text-center text-base\">\n                  {finishedDemo\n                    ? 'Thank you for trying our demo! Sign up to get started.'\n                    : 'Launch the demo to start chatting with the assistant.'}\n                </p>\n              </div>\n            </div>\n          )}\n          {/* The mobile stop button is now handled by a reference to the DemoSection component */}\n        </div>\n        \n\n        {/* Demo Section */}\n        <div className=\"order-1 flex-1 overflow-visible p-0.5 md:order-2 md:w-3/4 md:flex-none\">\n          <DemoSection\n            onDesktopDeployed={handleDesktopDeployed}\n            onDesktopStopped={handleDesktopStopped}\n            hideIntro={true}\n            desktopId={desktopId || undefined}\n          />\n        </div>\n      </div>\n    </AssistantRuntimeProvider>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/layout.tsx",
    "content": "import '@/styles/tailwind.css'\nimport type { Metadata } from 'next'\nimport { PostHogProvider } from '../components/PostHogProvider'\n\nexport const metadata: Metadata = {\n  title: {\n    template: '%s | Cyberdesk',\n    default: 'Cyberdesk | Virtual desktops for AI agents',\n  },\n  icons: {\n    icon: '/favicon.svg',\n  },\n}\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode\n}>) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <link\n          rel=\"stylesheet\"\n          href=\"https://api.fontshare.com/css?f%5B%5D=switzer@400,500,600,700&amp;display=swap\"\n        />\n        <link\n          rel=\"alternate\"\n          type=\"application/rss+xml\"\n          title=\"The Cyberdesk Blog\"\n          href=\"/blog/feed.xml\"\n        />\n      </head>\n      <body className=\"text-gray-950 antialiased\">\n        <PostHogProvider>\n          {children}\n        </PostHogProvider>\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/login/login-form.d.ts",
    "content": "// Type declaration for login-form component\nexport function LoginForm(): JSX.Element;\n"
  },
  {
    "path": "apps/web/src/app/login/login-form.tsx",
    "content": "'use client';\n\nimport { Button } from '@/components/button'\nimport { Link } from '@/components/link'\nimport { Mark } from '@/components/logo'\nimport { supabase } from '@/utils/supabase/client'\nimport { useRouter } from 'next/navigation'\n\nexport function LoginForm() {\n\n  const router = useRouter()\n  const signInWithGoogle = async () => {\n    const { error } = await supabase.auth.signInWithOAuth({\n      provider: 'google',\n      options: {\n        redirectTo: `${window.location.origin}/auth/callback`,\n      },\n    })\n    \n    if (error) {\n      console.error('Error signing in with Google:', error.message)\n    }\n  }\n\n  return (\n    <>\n      <form action=\"#\" method=\"POST\" className=\"p-7 sm:pl-11 sm:pr-11 sm:pt-11 relative\">\n\n        <div className=\"absolute right-[20px] top-[20px] text-black cursor-pointer \" onClick={() => {router.back()}}>\n          <Button type=\"button\" variant=\"outline\" className=\"w-full\">\n            Close\n          </Button>\n        </div>\n        <div className=\"flex items-start\">\n          <Link href=\"/\" title=\"Home\">\n            <Mark className=\"h-9 fill-black\" />\n          </Link>\n        </div>\n        <h1 className=\"mt-8 text-base/6 font-medium\">Welcome to Cyberdesk!</h1>\n        <p className=\"mt-1 text-sm/5 text-gray-600\">\n          Sign in to your account to continue.\n        </p>\n        {/* <Field className=\"mt-8 space-y-3\">\n          <Label className=\"text-sm/5 font-medium\">Email</Label>\n          <Input\n            required\n            autoFocus\n            type=\"email\"\n            name=\"email\"\n            className={clsx(\n              'block w-full rounded-lg border border-transparent ring-1 shadow-sm ring-black/10',\n              'px-[calc(--spacing(2)-1px)] py-[calc(--spacing(1.5)-1px)] text-base/6 sm:text-sm/6',\n              'data-focus:outline data-focus:outline-2 data-focus:-outline-offset-1 data-focus:outline-black',\n            )}\n          />\n        </Field>\n        <Field className=\"mt-8 space-y-3\">\n          <Label className=\"text-sm/5 font-medium\">Password</Label>\n          <Input\n            required\n            type=\"password\"\n            name=\"password\"\n            className={clsx(\n              'block w-full rounded-lg border border-transparent ring-1 shadow-sm ring-black/10',\n              'px-[calc(--spacing(2)-1px)] py-[calc(--spacing(1.5)-1px)] text-base/6 sm:text-sm/6',\n              'data-focus:outline data-focus:outline-2 data-focus:-outline-offset-1 data-focus:outline-black',\n            )}\n          />\n        </Field>\n\n        <div className=\"mt-8\">\n          <Button type=\"submit\" className=\"w-full\">\n            Sign in\n          </Button>\n        </div> */}\n      </form>\n\n      <div className=\"px-7 sm:px-11 pb-7 sm:pb-11 pt-0\">\n        <div className=\"relative mt-6\">\n          <div className=\"absolute inset-0 flex items-center\" aria-hidden=\"true\">\n            <div className=\"w-full border-t border-gray-200\" />\n          </div>\n          <div className=\"relative flex justify-center text-sm font-medium leading-6\">\n            <span className=\"bg-white px-6 text-gray-500\">Continue with</span>\n          </div>\n        </div>\n\n        <div className=\"mt-6\">\n          <Button\n            type=\"button\"\n            variant=\"outline\"\n            className=\"w-full flex items-center justify-center gap-3\"\n            onClick={signInWithGoogle}\n          >\n            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">\n              <path fill=\"#4285F4\" d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\" />\n              <path fill=\"#34A853\" d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\" />\n              <path fill=\"#FBBC05\" d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\" />\n              <path fill=\"#EA4335\" d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\" />\n              <path fill=\"none\" d=\"M1 1h22v22H1z\" />\n            </svg>\n            Sign in with Google\n          </Button>\n        </div>\n      </div>\n\n    </>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/login/page.tsx",
    "content": "import { GradientBackground } from '@/components/gradient'\nimport type { Metadata } from 'next'\nimport { LoginForm } from './login-form'\n\nexport const metadata: Metadata = {\n  title: 'Login',\n  description: 'Sign in to your account to continue.',\n}\n\nexport default function Login() {\n  return (\n    <main className=\"overflow-hidden bg-gray-50\">\n      <GradientBackground />\n      <div className=\"isolate flex min-h-dvh items-center justify-center p-6 lg:p-8\">\n        <div className=\"w-full max-w-md rounded-xl bg-white ring-1 shadow-md ring-black/5\">\n          <LoginForm />\n        </div>\n      </div>\n    </main>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/page.tsx",
    "content": "import { Footer } from '@/components/footer'\nimport type { Metadata } from 'next'\nimport { Hero } from '../components/hero'\nimport { YCBanner } from '../components/yc-banner'\nimport Playground from './playground/page'\n\nexport const metadata: Metadata = {\n  description:\n    'Cyberdesk deploys virtual desktops for your computer agents with only in a few lines of code.',\n}\n\nexport default function Home() {\n  return (\n    <div className=\"flex flex-col min-h-screen w-full gap-6\">\n      <Hero />\n      <YCBanner />\n      <div className=\"mx-2 sm:mx-4 md:mx-6 lg:mx-8 xl:mx-10\">\n        {/* <Playground /> */}\n      </div>\n      <Footer />\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/playground/page.tsx",
    "content": "\"use client\";\n\nimport { PreviewMessage } from \"@/components/playground/message\";\nimport { getDesktopURL, startDesktop } from \"@/utils/playground/server-actions\";\nimport { useScrollToBottom } from \"@/utils/playground/use-scroll-to-bottom\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { useEffect, useState } from \"react\";\nimport { Input } from \"@/components/playground/input\";\nimport { Button } from \"@/components/ui/button\";\nimport { toast } from \"sonner\";\nimport { ProjectInfo } from \"@/components/playground/project-info\";\nimport { PromptSuggestions } from \"@/components/playground/prompt-suggestions\";\nimport {\n  ResizableHandle,\n  ResizablePanel,\n  ResizablePanelGroup,\n} from \"@/components/ui/resizable\";\nimport { ABORTED } from \"@/utils/playground/misc-demo-utils\";\nimport { FaRocket } from \"react-icons/fa\";\nimport { ChatError } from \"@/components/playground/chat-error\";\n\n// Shared polling helper for desktop URL\nasync function pollForDesktopURL(sandboxId: string | null | undefined) {\n  let delay = 1000; // Start with 1 second\n  const maxDelay = 5000; // Cap at 5 seconds\n  const maxTime = 180000; // 3 minutes in ms\n  const startTime = Date.now();\n\n  while (true) {\n    if (Date.now() - startTime > maxTime) {\n      throw new Error(\"Timed out after 3 minutes while getting desktop stream URL.\");\n    }\n    const { streamUrl, id } = await getDesktopURL(sandboxId || undefined);\n    if (streamUrl) {\n      return { streamUrl, id };\n    }\n    delay = Math.min(maxDelay, Math.floor(delay * 1.5));\n    await new Promise((resolve) => setTimeout(resolve, delay));\n  }\n}\n\nexport default function Playground() {\n  // Create separate refs for mobile and desktop to ensure both scroll properly\n  const [desktopContainerRef, desktopEndRef] = useScrollToBottom();\n  const [mobileContainerRef, mobileEndRef] = useScrollToBottom();\n\n  const [isInitializing, setIsInitializing] = useState(false);\n  const [streamUrl, setStreamUrl] = useState<string | null>(null);\n  const [sandboxId, setSandboxId] = useState<string | null>(null);\n  const [hasStarted, setHasStarted] = useState(false);\n\n  const {\n    messages,\n    input,\n    handleInputChange,\n    handleSubmit,\n    error,\n    reload,\n    status,\n    stop: stopGeneration,\n    append,\n    setMessages,\n  } = useChat({\n    api: \"/api/playground/chat\",\n    id: sandboxId ?? undefined,\n    body: {\n      sandboxId,\n    },\n    onError: (error) => {\n      console.error(error);\n      toast.error(\"There was an error\", {\n        description: \"Please try again later.\",\n        richColors: true,\n        position: \"top-center\",\n      });\n    },\n  });\n\n  const stop = () => {\n    stopGeneration();\n\n    const lastMessage = messages.at(-1);\n    const lastMessageLastPart = lastMessage?.parts.at(-1);\n    if (\n      lastMessage?.role === \"assistant\" &&\n      lastMessageLastPart?.type === \"tool-invocation\"\n    ) {\n      setMessages((prev) => [\n        ...prev.slice(0, -1),\n        {\n          ...lastMessage,\n          parts: [\n            ...lastMessage.parts.slice(0, -1),\n            {\n              ...lastMessageLastPart,\n              toolInvocation: {\n                ...lastMessageLastPart.toolInvocation,\n                state: \"result\",\n                result: ABORTED,\n              },\n            },\n          ],\n        },\n      ]);\n    }\n  };\n\n  const isLoading = status !== \"ready\";\n\n  const refreshDesktop = async () => {\n    try {\n      setIsInitializing(true);\n      const data = await startDesktop();\n      const id = data.id;\n      const { streamUrl } = await pollForDesktopURL(id);\n      setStreamUrl(streamUrl);\n      setSandboxId(id);\n    } catch (err) {\n      console.error(\"Failed to refresh desktop:\", err);\n    } finally {\n      setIsInitializing(false);\n    }\n  };\n\n  // Handler for Start Desktop button\n  const handleStartDesktop = async () => {\n    setHasStarted(true);\n    setIsInitializing(true);\n    try {\n      const data = await startDesktop();\n      const id = data.id;\n      const { streamUrl } = await pollForDesktopURL(id);\n      setStreamUrl(streamUrl);\n      setSandboxId(id);\n    } catch (err) {\n      console.error(\"Failed to initialize desktop:\", err);\n      toast.error(\"Failed to initialize desktop\");\n      setHasStarted(false); // allow retry\n    } finally {\n      setIsInitializing(false);\n    }\n  };\n\n  // Kill desktop on page close\n  useEffect(() => {\n    if (!sandboxId) return;\n\n    // Function to kill the desktop - just one method to reduce duplicates\n    const killDesktop = () => {\n      if (!sandboxId) return;\n\n      // Use sendBeacon which is best supported across browsers\n      navigator.sendBeacon(\n        `/api/playground/kill-desktop?sandboxId=${encodeURIComponent(sandboxId)}`,\n      );\n    };\n\n    // Detect iOS / Safari\n    const isIOS =\n      /iPad|iPhone|iPod/.test(navigator.userAgent) ||\n      (navigator.platform === \"MacIntel\" && navigator.maxTouchPoints > 1);\n    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n\n    // Choose exactly ONE event handler based on the browser\n    if (isIOS || isSafari) {\n      // For Safari on iOS, use pagehide which is most reliable\n      window.addEventListener(\"pagehide\", killDesktop);\n\n      return () => {\n        window.removeEventListener(\"pagehide\", killDesktop);\n        // Also kill desktop when component unmounts\n        killDesktop();\n      };\n    } else {\n      // For all other browsers, use beforeunload\n      window.addEventListener(\"beforeunload\", killDesktop);\n\n      return () => {\n        window.removeEventListener(\"beforeunload\", killDesktop);\n        // Also kill desktop when component unmounts\n        killDesktop();\n      };\n    }\n  }, [sandboxId]);\n\n  return (\n    <div className=\"flex h-dvh relative\">\n      {/* Starter Overlay */}\n      {!hasStarted && (\n        <div className=\"absolute inset-0 z-50 flex flex-col items-center justify-center bg-gradient-to-br from-blue-100/60 to-zinc-200/80 overflow-hidden\">\n          {/* Animated background shapes */}\n          <div className=\"absolute inset-0 pointer-events-none\">\n            <div className=\"absolute -top-20 -left-20 w-96 h-96 bg-blue-400/20 rounded-full blur-3xl animate-pulse\" />\n            <div className=\"absolute bottom-0 right-0 w-80 h-80 bg-indigo-300/20 rounded-full blur-2xl animate-pulse-slow\" />\n          </div>\n          <div className=\"relative bg-white/60 backdrop-blur-xl rounded-2xl shadow-2xl p-12 flex flex-col items-center gap-7 border border-zinc-100/70 max-w-lg w-full mx-4\">\n            <div className=\"relative flex items-center justify-center mb-2\">\n              <FaRocket className=\"text-blue-600 text-6xl animate-bounce drop-shadow-lg\" style={{ filter: 'drop-shadow(0 0 16px #3b82f6)' }} />\n              <span className=\"absolute -bottom-2 left-1/2 -translate-x-1/2 w-16 h-4 bg-blue-400/30 blur-lg rounded-full animate-pulse\" />\n            </div>\n            <h1 className=\"text-3xl font-extrabold text-zinc-800 tracking-tight text-center\">Launch a demo computer agent</h1>\n            {/* <p className=\"text-zinc-500 text-base font-medium text-center\">Your secure, cloud-powered development environment</p> */}\n            <p className=\"text-zinc-700 max-w-md text-center text-lg font-normal\">\n              Click below to start a Cyberdesk desktop. Once it launches, you can chat with a computer agent that can use the desktop.\n            </p>\n            <Button\n              onClick={handleStartDesktop}\n              className=\"bg-blue-600 hover:bg-blue-700 text-white px-7 py-2.5 rounded-lg text-base font-semibold shadow border border-blue-500/20 transition-colors duration-150 focus:ring-2 focus:ring-blue-400 focus:outline-none disabled:opacity-60 disabled:cursor-not-allowed\"\n              disabled={isInitializing}\n            >\n              {isInitializing ? (\n                <span className=\"flex items-center gap-2\"><span className=\"animate-spin h-5 w-5 border-2 border-white border-t-transparent rounded-full\" /> Starting...</span>\n              ) : (\n                <span>Start Desktop</span>\n              )}\n            </Button>\n            <div className=\"pt-4 text-xs text-zinc-400 w-full text-center border-t border-zinc-200/60 mt-2\">\n              Powered by <a href=\"https://cyberdesk.io\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"font-semibold text-blue-500 hover:underline\">Cyberdesk</a>\n            </div>\n          </div>\n        </div>\n      )}\n      {/* Mobile/tablet banner */}\n      {/* REMOVE THIS BANNER\n      <div className=\"flex items-center justify-center fixed left-1/2 -translate-x-1/2 top-5 shadow-md text-xs mx-auto rounded-lg h-8 w-fit bg-blue-600 text-white px-3 py-2 text-left z-50 xl:hidden\">\n        <span>Headless mode</span>\n      </div>\n      */}\n      {/* Resizable Panels */}\n      <div className=\"w-full hidden xl:block\">\n        <ResizablePanelGroup direction=\"horizontal\" className=\"h-full\">\n          {/* Desktop Stream Panel */}\n          <ResizablePanel\n            defaultSize={70}\n            minSize={40}\n            className=\"bg-black relative items-center justify-center\"\n          >\n            {streamUrl ? (\n              <>\n                <iframe\n                  src={streamUrl}\n                  className=\"w-full h-full\"\n                  style={{\n                    transformOrigin: \"center\",\n                    width: \"100%\",\n                    height: \"100%\",\n                  }}\n                  allow=\"autoplay\"\n                />\n                <Button\n                  onClick={refreshDesktop}\n                  className=\"absolute top-2 right-2 bg-black/50 hover:bg-black/70 text-white px-3 py-1 rounded text-sm z-10\"\n                  disabled={isInitializing}\n                >\n                  {isInitializing ? \"Creating desktop...\" : \"New desktop\"}\n                </Button>\n              </>\n            ) : (\n              <div className=\"flex items-center justify-center h-full text-white\">\n                {isInitializing\n                  ? \"Initializing desktop...\"\n                  : hasStarted\n                  ? \"Loading stream...\"\n                  : \"Desktop not started.\"}\n              </div>\n            )}\n          </ResizablePanel>\n          <ResizableHandle withHandle />\n          {/* Chat Interface Panel */}\n          <ResizablePanel\n            defaultSize={30}\n            minSize={25}\n            className=\"flex flex-col border-l border-zinc-200\"\n          >\n            <div\n              className=\"flex-1 space-y-6 py-4 overflow-y-auto px-4\"\n              ref={desktopContainerRef}\n            >\n              {messages.length === 0 ? <ProjectInfo /> : null}\n              {messages.map((message, i) => (\n                <PreviewMessage\n                  message={message}\n                  key={message.id}\n                  isLoading={isLoading}\n                  status={status}\n                  isLatestMessage={i === messages.length - 1}\n                />\n              ))}\n              <div ref={desktopEndRef} className=\"pb-2\" />\n            </div>\n            {messages.length === 0 && (\n              <PromptSuggestions\n                disabled={isInitializing || !hasStarted || !streamUrl}\n                submitPrompt={(prompt: string) =>\n                  append({ role: \"user\", content: prompt })\n                }\n              />\n            )}\n            <ChatError error={error} onRetry={reload} />\n            <div className=\"bg-white\">\n              <form onSubmit={handleSubmit} className=\"p-4\">\n                <Input\n                  handleInputChange={handleInputChange}\n                  input={input}\n                  isInitializing={isInitializing || !hasStarted || !streamUrl || !!error}\n                  isLoading={isLoading}\n                  status={status}\n                  stop={stop}\n                />\n              </form>\n            </div>\n          </ResizablePanel>\n        </ResizablePanelGroup>\n      </div>\n      {/* Mobile View (Chat Only) */}\n      <div className=\"w-full xl:hidden flex flex-col h-dvh\">\n        {/* Desktop Stream Panel for Mobile */}\n        <div className=\"h-[40%] bg-black relative flex items-center justify-center\">\n          {streamUrl ? (\n            <>\n              <iframe\n                src={streamUrl}\n                className=\"w-full h-full\"\n                style={{\n                  transformOrigin: \"center\",\n                  width: \"100%\",\n                  height: \"100%\",\n                }}\n                allow=\"autoplay\"\n              />\n              <Button\n                onClick={refreshDesktop}\n                className=\"absolute top-2 right-2 bg-black/50 hover:bg-black/70 text-white px-2 py-1 rounded text-xs z-10\"\n                disabled={isInitializing}\n              >\n                {isInitializing ? \"Creating...\" : \"New Desktop\"}\n              </Button>\n            </>\n          ) : (\n            <div className=\"flex items-center justify-center h-full text-white text-sm p-4 text-center\">\n              {isInitializing\n                ? \"Initializing desktop...\"\n                : hasStarted\n                ? \"Loading stream...\"\n                : \"Desktop not started. Click Start Desktop if available or refresh.\"}\n            </div>\n          )}\n        </div>\n\n        {/* Chat Interface Panel for Mobile */}\n        <div className=\"flex-1 flex flex-col overflow-hidden\">\n          <div\n            className=\"flex-1 space-y-6 py-4 overflow-y-auto px-4\"\n            ref={mobileContainerRef}\n          >\n            {messages.length === 0 ? <ProjectInfo /> : null}\n            {messages.map((message, i) => (\n              <PreviewMessage\n                message={message}\n                key={message.id}\n                isLoading={isLoading}\n                status={status}\n                isLatestMessage={i === messages.length - 1}\n              />\n            ))}\n            <div ref={mobileEndRef} className=\"pb-2\" />\n          </div>\n          {messages.length === 0 && (\n            <PromptSuggestions\n              disabled={isInitializing || !hasStarted || !streamUrl}\n              submitPrompt={(prompt: string) =>\n                append({ role: \"user\", content: prompt })\n              }\n            />\n          )}\n          <ChatError error={error} onRetry={reload} />\n          <div className=\"bg-white\">\n            <form onSubmit={handleSubmit} className=\"p-4\">\n              <Input\n                handleInputChange={handleInputChange}\n                input={input}\n                isInitializing={isInitializing || !hasStarted || !streamUrl || !!error}\n                isLoading={isLoading}\n                status={status}\n                stop={stop}\n              />\n            </form>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/pricing/page.tsx",
    "content": "import { Button } from '@/components/button'\nimport { Container } from '@/components/container'\nimport { Footer } from '@/components/footer'\nimport { GradientBackground } from '@/components/gradient'\nimport { Link } from '@/components/link'\nimport { Navbar } from '@/components/navbar'\nimport { Heading, Subheading } from '@/components/text'\nimport { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'\nimport {\n  CheckIcon,\n  ChevronUpDownIcon,\n  MinusIcon,\n} from '@heroicons/react/16/solid'\nimport type { Metadata } from 'next'\nimport { createClient } from '@/utils/supabase/server'\nimport type { Profile } from '@/types/database'\nimport { tiers } from '@/utils/stripe/tiers'\n\nexport const metadata: Metadata = {\n  title: 'Pricing',\n  description:\n    'Deploy AI agents on virtual desktops with a few lines of code.',\n}\n\nfunction Header() {\n  return (\n    <Container className=\"mt-16\">\n      <Heading as=\"h1\">Start deploying virtual desktops today</Heading>\n      {/* <Lead className=\"mt-6 max-w-3xl\">\n        Companies all over the world have closed millions of deals with Radiant.\n        Sign up today and start selling smarter.\n      </Lead> */}\n    </Container>\n  )\n}\n\n// This is now a server component\nimport ClientPricingCards from '../../components/stripe/client-pricing-cards';\n\n// Moved to client-pricing-card.tsx\n\nfunction PricingTable({\n  selectedTier,\n}: {\n  selectedTier: (typeof tiers)[number]\n}) {\n  return (\n    <Container className=\"py-24\">\n      <table className=\"w-full text-left\">\n        <caption className=\"sr-only\">Pricing plan comparison</caption>\n        <colgroup>\n          <col className=\"w-3/5 sm:w-2/5\" />\n          <col\n            data-selected={selectedTier === tiers[0] ? true : undefined}\n            className=\"w-2/5 data-selected:table-column max-sm:hidden sm:w-1/5\"\n          />\n          <col\n            data-selected={selectedTier === tiers[1] ? true : undefined}\n            className=\"w-2/5 data-selected:table-column max-sm:hidden sm:w-1/5\"\n          />\n          <col\n            data-selected={selectedTier === tiers[2] ? true : undefined}\n            className=\"w-2/5 data-selected:table-column max-sm:hidden sm:w-1/5\"\n          />\n        </colgroup>\n        <thead>\n          <tr className=\"max-sm:hidden\">\n            <td className=\"p-0\" />\n            {tiers.map((tier) => (\n              <th\n                key={tier.slug}\n                scope=\"col\"\n                data-selected={selectedTier === tier ? true : undefined}\n                className=\"p-0 data-selected:table-cell max-sm:hidden\"\n              >\n                <Subheading as=\"div\">{tier.name}</Subheading>\n              </th>\n            ))}\n          </tr>\n          <tr className=\"sm:hidden\">\n            <td className=\"p-0\">\n              <div className=\"relative inline-block\">\n                <Menu>\n                  <MenuButton className=\"flex items-center justify-between gap-2 font-medium\">\n                    {selectedTier.name}\n                    <ChevronUpDownIcon className=\"size-4 fill-gray-900\" />\n                  </MenuButton>\n                  <MenuItems\n                    anchor=\"bottom start\"\n                    className=\"min-w-(--button-width) rounded-lg bg-white p-1 ring-1 shadow-lg ring-gray-200 [--anchor-gap:6px] [--anchor-offset:-4px] [--anchor-padding:10px]\"\n                  >\n                    {tiers.map((tier) => (\n                      <MenuItem key={tier.slug}>\n                        <Link\n                          scroll={false}\n                          href={`/pricing?tier=${tier.slug}`}\n                          data-selected={\n                            tier === selectedTier ? true : undefined\n                          }\n                          className=\"group flex items-center gap-2 rounded-md px-2 py-1 data-focus:bg-gray-200\"\n                        >\n                          {tier.name}\n                          <CheckIcon className=\"hidden size-4 group-data-selected:block\" />\n                        </Link>\n                      </MenuItem>\n                    ))}\n                  </MenuItems>\n                </Menu>\n                <div className=\"pointer-events-none absolute inset-y-0 right-0 flex items-center\">\n                  <ChevronUpDownIcon className=\"size-4 fill-gray-900\" />\n                </div>\n              </div>\n            </td>\n            <td colSpan={3} className=\"p-0 text-right\">\n              <Button variant=\"outline\" href={selectedTier.href}>\n                Get started\n              </Button>\n            </td>\n          </tr>\n          <tr className=\"max-sm:hidden\">\n            <th className=\"p-0\" scope=\"row\">\n              <span className=\"sr-only\">Get started</span>\n            </th>\n            {tiers.map((tier) => (\n              <td\n                key={tier.slug}\n                data-selected={selectedTier === tier ? true : undefined}\n                className=\"px-0 pt-4 pb-0 data-selected:table-cell max-sm:hidden\"\n              >\n                <Button variant=\"outline\" href={tier.href}>\n                  Get started\n                </Button>\n              </td>\n            ))}\n          </tr>\n        </thead>\n        {[...new Set(tiers[0].features.map(({ section }) => section))].map(\n          (section) => (\n            <tbody key={section} className=\"group\">\n              <tr>\n                <th\n                  scope=\"colgroup\"\n                  colSpan={4}\n                  className=\"px-0 pt-10 pb-0 group-first-of-type:pt-5\"\n                >\n                  <div className=\"-mx-4 rounded-lg bg-gray-50 px-4 py-3 text-sm/6 font-semibold\">\n                    {section}\n                  </div>\n                </th>\n              </tr>\n              {tiers[0].features\n                .filter((feature) => feature.section === section)\n                .map(({ name }) => (\n                  <tr\n                    key={name}\n                    className=\"border-b border-gray-100 last:border-none\"\n                  >\n                    <th\n                      scope=\"row\"\n                      className=\"px-0 py-4 text-sm/6 font-normal text-gray-600\"\n                    >\n                      {name}\n                    </th>\n                    {tiers.map((tier) => {\n                      const value = tier.features.find(\n                        (feature) =>\n                          feature.section === section && feature.name === name,\n                      )?.value\n\n                      return (\n                        <td\n                          key={tier.slug}\n                          data-selected={\n                            selectedTier === tier ? true : undefined\n                          }\n                          className=\"p-4 data-selected:table-cell max-sm:hidden\"\n                        >\n                          {value === true ? (\n                            <>\n                              <CheckIcon className=\"size-4 fill-green-600\" />\n                              <span className=\"sr-only\">\n                                Included in {tier.name}\n                              </span>\n                            </>\n                          ) : value === false || value === undefined ? (\n                            <>\n                              <MinusIcon className=\"size-4 fill-gray-400\" />\n                              <span className=\"sr-only\">\n                                Not included in {tier.name}\n                              </span>\n                            </>\n                          ) : (\n                            <div className=\"text-sm/6\">{value}</div>\n                          )}\n                        </td>\n                      )\n                    })}\n                  </tr>\n                ))}\n            </tbody>\n          ),\n        )}\n      </table>\n    </Container>\n  )\n}\n\nfunction FeatureItem({\n  description,\n  disabled = false,\n}: {\n  description: string\n  disabled?: boolean\n}) {\n  return (\n    <li\n      data-disabled={disabled ? true : undefined}\n      className=\"flex items-start gap-4 text-sm/6 text-gray-950/75 data-disabled:text-gray-950/25\"\n    >\n      <span className=\"inline-flex h-6 items-center\">\n        <PlusIcon className=\"size-[0.9375rem] shrink-0 fill-gray-950/25\" />\n      </span>\n      {disabled && <span className=\"sr-only\">Not included:</span>}\n      {description}\n    </li>\n  )\n}\n\nfunction PlusIcon(props: React.ComponentPropsWithoutRef<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 15 15\" aria-hidden=\"true\" {...props}>\n      <path clipRule=\"evenodd\" d=\"M8 0H7v7H0v1h7v7h1V8h7V7H8V0z\" />\n    </svg>\n  )\n}\n\nfunction Testimonial() {\n  return (\n    <div className=\"mx-2 my-24 rounded-4xl bg-gray-900 bg-[url(/dot-texture.svg)] pt-72 pb-24 lg:pt-36\">\n      <Container>\n        <div className=\"grid grid-cols-1 lg:grid-cols-[384px_1fr_1fr]\">\n          <div className=\"-mt-96 lg:-mt-52\">\n            <div className=\"-m-2 rounded-4xl bg-white/15 ring-1 shadow-[inset_0_0_2px_1px_#ffffff4d] ring-black/5 max-lg:mx-auto max-lg:max-w-xs\">\n              <div className=\"rounded-4xl p-2 shadow-md shadow-black/5\">\n                <div className=\"overflow-hidden rounded-3xl shadow-2xl outline outline-1 -outline-offset-1 outline-black/10\">\n                  <img\n                    alt=\"\"\n                    src=\"/testimonials/tina-yards.jpg\"\n                    className=\"aspect-3/4 w-full object-cover\"\n                  />\n                </div>\n              </div>\n            </div>\n          </div>\n          <div className=\"flex max-lg:mt-16 lg:col-span-2 lg:px-16\">\n            <figure className=\"mx-auto flex max-w-xl flex-col gap-16 max-lg:text-center\">\n              <blockquote>\n                <p className=\"relative text-3xl tracking-tight text-white before:absolute before:-translate-x-full before:content-['“'] after:absolute after:content-['”'] lg:text-4xl\">\n                  Thanks to Radiant, we&apos;re finding new leads that we never\n                  would have found with legal methods.\n                </p>\n              </blockquote>\n              <figcaption className=\"mt-auto\">\n                <p className=\"text-sm/6 font-medium text-white\">Tina Yards</p>\n                <p className=\"text-sm/6 font-medium\">\n                  <span className=\"bg-linear-to-r from-[#fff1be] from-28% via-[#ee87cb] via-70% to-[#b060ff] bg-clip-text text-transparent\">\n                    VP of Sales, Protocol\n                  </span>\n                </p>\n              </figcaption>\n            </figure>\n          </div>\n        </div>\n      </Container>\n    </div>\n  )\n}\n\nexport default async function Pricing({\n  searchParams,\n}: {\n  searchParams: { [key: string]: string | string[] | undefined }\n}) {\n  const selectedTierSlug =\n    typeof searchParams.tier === 'string' ? searchParams.tier : 'pro'\n  const selectedTier =\n    tiers.find((tier) => tier.slug === selectedTierSlug) ?? tiers[1]\n\n  // Fetch user and profile data on the server\n  const supabase = createClient();\n  let user = null;\n  let profile: Profile | null = null;\n  \n  // Check if user is authenticated\n  const { data: { user: authUser } } = await supabase.auth.getUser();\n  \n  if (authUser) {\n    user = authUser;\n    // Query the profiles table for the user\n    const { data: userProfile, error } = await supabase\n      .from('profiles')\n      .select('*')\n      .eq('id', user.id)\n      .single();\n    \n    if (!error) {\n      profile = userProfile;\n    }\n  }\n\n  return (\n    <main className=\"overflow-hidden\">\n      <GradientBackground />\n      <Container>\n        <Navbar />\n      </Container>\n      <Header />\n      <ClientPricingCards tiers={tiers} user={user} profile={profile} />\n      {/* <PricingTable selectedTier={selectedTier} /> */}\n      {/* <Testimonial /> */}\n      <div className=\"h-16\"/>\n      <Footer />\n    </main>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/privacy/page.tsx",
    "content": "import { Container } from '@/components/container'\nimport { Footer } from '@/components/footer'\nimport { Gradient, GradientBackground } from '@/components/gradient'\nimport { Navbar } from '@/components/navbar'\nimport { Heading, Lead } from '@/components/text'\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  title: 'Privacy Policy | Cyberdesk',\n  description:\n    'Privacy Policy for Cyberdesk virtual desktop infrastructure and AI agents platform.',\n}\n\nfunction Header() {\n  return (\n    <Container className=\"mt-16\">\n      <Heading as=\"h1\">Privacy Policy</Heading>\n      <Lead className=\"mt-6 max-w-3xl\">\n        Last updated: March 27, 2025\n      </Lead>\n    </Container>\n  )\n}\n\nexport default function Privacy() {\n  return (\n    <>\n      <GradientBackground>\n        <Navbar />\n        <Header />\n        <Container className=\"mt-16 mb-24\">\n          <div className=\"prose prose-lg prose-gray mx-auto\">\n            <h2>1. Introduction</h2>\n            <p>\n              At Cyberdesk, we respect your privacy and are committed to protecting your personal data. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our virtual desktop infrastructure and AI agent services.\n            </p>\n\n            <h2>2. Information We Collect</h2>\n            <p>\n              We collect several types of information from and about users of our Services, including:\n            </p>\n            <ul>\n              <li><strong>Personal Information:</strong> Name, email address, billing information, and other contact details you provide when registering for our Services.</li>\n              <li><strong>Usage Data:</strong> Information about how you use our Services, including login times, features used, and interactions with our platform.</li>\n              <li><strong>Device Information:</strong> Information about the devices you use to access our Services, including IP address, browser type, and operating system.</li>\n              <li><strong>Virtual Desktop Content:</strong> Data generated or stored within your virtual desktop environment, including files, applications, and AI agent configurations.</li>\n            </ul>\n\n            <h2>3. How We Use Your Information</h2>\n            <p>\n              We use the information we collect for various purposes, including:\n            </p>\n            <ul>\n              <li>Providing, maintaining, and improving our Services</li>\n              <li>Processing your transactions and managing your account</li>\n              <li>Communicating with you about our Services, updates, and support</li>\n              <li>Analyzing usage patterns to enhance user experience</li>\n              <li>Detecting, preventing, and addressing technical issues or security breaches</li>\n              <li>Complying with legal obligations</li>\n            </ul>\n\n            <h2>4. Data Storage and Security</h2>\n            <p>\n              We implement appropriate technical and organizational measures to protect your personal data against unauthorized or unlawful processing, accidental loss, destruction, or damage. However, no method of transmission over the Internet or electronic storage is 100% secure, and we cannot guarantee absolute security.\n            </p>\n\n            <h2>5. Data Retention</h2>\n            <p>\n              We retain your personal information for as long as necessary to fulfill the purposes outlined in this Privacy Policy, unless a longer retention period is required or permitted by law. When determining how long to retain data, we consider the amount, nature, and sensitivity of the data, potential risk of harm from unauthorized use or disclosure, and applicable legal requirements.\n            </p>\n\n            <h2>6. Sharing Your Information</h2>\n            <p>\n              We may share your information in the following circumstances:\n            </p>\n            <ul>\n              <li><strong>Service Providers:</strong> We may share your information with third-party vendors who provide services on our behalf, such as payment processing, data analysis, and customer service.</li>\n              <li><strong>Business Transfers:</strong> If we are involved in a merger, acquisition, or sale of assets, your information may be transferred as part of that transaction.</li>\n              <li><strong>Legal Requirements:</strong> We may disclose your information if required to do so by law or in response to valid requests by public authorities.</li>\n              <li><strong>With Your Consent:</strong> We may share your information with third parties when you have given us your consent to do so.</li>\n            </ul>\n\n            <h2>7. Your Rights</h2>\n            <p>\n              Depending on your location, you may have certain rights regarding your personal data, including:\n            </p>\n            <ul>\n              <li>The right to access and receive a copy of your personal data</li>\n              <li>The right to rectify or update your personal data</li>\n              <li>The right to erase your personal data</li>\n              <li>The right to restrict processing of your personal data</li>\n              <li>The right to data portability</li>\n              <li>The right to object to processing of your personal data</li>\n              <li>The right to withdraw consent</li>\n            </ul>\n\n            <h2>8. Children&apos;s Privacy</h2>\n            <p>\n              Our Services are not intended for children under the age of 16, and we do not knowingly collect personal information from children under 16. If we learn we have collected or received personal information from a child under 16, we will delete that information.\n            </p>\n\n            <h2>9. International Data Transfers</h2>\n            <p>\n              Your information may be transferred to, and maintained on, computers located outside of your state, province, country, or other governmental jurisdiction where the data protection laws may differ from those of your jurisdiction. If you are located outside the United States and choose to provide information to us, please note that we transfer the data to the United States and process it there.\n            </p>\n\n            <h2>10. Cookies and Similar Technologies</h2>\n            <p>\n              We use cookies and similar tracking technologies to track activity on our Services and hold certain information. Cookies are files with a small amount of data which may include an anonymous unique identifier. You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent.\n            </p>\n\n            <h2>11. Changes to This Privacy Policy</h2>\n            <p>\n              We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the &quot;Last updated&quot; date. You are advised to review this Privacy Policy periodically for any changes.\n            </p>\n\n            <h2>12. Contact Us</h2>\n            <p>\n              If you have any questions about this Privacy Policy, please contact us at mahmoud@cyberdesk.io.\n            </p>\n          </div>\n        </Container>\n        <Footer />\n      </GradientBackground>\n    </>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/app/studio/[[...tool]]/page.tsx",
    "content": "/**\n * This route is responsible for the built-in authoring environment using Sanity Studio.\n * All routes under your studio path is handled by this file using Next.js' catch-all routes:\n * https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes\n *\n * You can learn more about the next-sanity package here:\n * https://github.com/sanity-io/next-sanity\n */\n\nimport { NextStudio } from 'next-sanity/studio'\nimport config from '../../../../sanity.config'\n\nexport const dynamic = 'force-static'\n\nexport { metadata, viewport } from 'next-sanity/studio'\n\nexport default function StudioPage() {\n  return <NextStudio config={config} />\n}\n"
  },
  {
    "path": "apps/web/src/app/terms/page.tsx",
    "content": "import { Container } from '@/components/container'\nimport { Footer } from '@/components/footer'\nimport { Gradient, GradientBackground } from '@/components/gradient'\nimport { Navbar } from '@/components/navbar'\nimport { Heading, Lead } from '@/components/text'\nimport type { Metadata } from 'next'\n\nexport const metadata: Metadata = {\n  title: 'Terms of Service | Cyberdesk',\n  description:\n    'Terms of Service for Cyberdesk virtual desktop infrastructure and AI agents platform.',\n}\n\nfunction Header() {\n  return (\n    <Container className=\"mt-16\">\n      <Heading as=\"h1\">Terms of Service</Heading>\n      <Lead className=\"mt-6 max-w-3xl\">\n        Last updated: March 27, 2025\n      </Lead>\n    </Container>\n  )\n}\n\nexport default function Terms() {\n  return (\n    <>\n      <GradientBackground>\n        <Navbar />\n        <Header />\n        <Container className=\"mt-16 mb-24\">\n          <div className=\"prose prose-lg prose-gray mx-auto\">\n            <h2>1. Acceptance of Terms</h2>\n            <p>\n              By accessing or using Cyberdesk&apos;s virtual desktop infrastructure and AI agent services (&quot;Services&quot;), you agree to be bound by these Terms of Service. If you do not agree to these terms, please do not use our Services.\n            </p>\n\n            <h2>2. Description of Services</h2>\n            <p>\n              Cyberdesk provides cloud-based virtual desktop infrastructure and AI agent deployment services. Our platform allows users to deploy, manage, and interact with autonomous AI agents on virtual desktops.\n            </p>\n\n            <h2>3. Account Registration</h2>\n            <p>\n              To access certain features of our Services, you must register for an account. You agree to provide accurate, current, and complete information during the registration process and to update such information to keep it accurate, current, and complete.\n            </p>\n\n            <h2>4. User Responsibilities</h2>\n            <p>\n              You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur under your account. You agree to notify us immediately of any unauthorized use of your account or any other breach of security.\n            </p>\n\n            <h2>5. Subscription and Billing</h2>\n            <p>\n              Certain aspects of our Services require payment of fees. All fees are specified on our pricing page and are subject to change with notice. You agree to pay all fees in accordance with the billing terms in effect at the time a fee is due.\n            </p>\n\n            <h2>6. Usage Limitations</h2>\n            <p>\n              Your use of the Services must comply with all applicable laws and regulations. You may not use the Services for any illegal or unauthorized purpose, including but not limited to violating any intellectual property rights.\n            </p>\n\n            <h2>7. Data and Security</h2>\n            <p>\n              We implement reasonable security measures to protect your data, but we cannot guarantee absolute security. You are responsible for backing up your data and ensuring that your use of our Services does not expose your systems to security risks.\n            </p>\n\n            <h2>8. Intellectual Property</h2>\n            <p>\n              All content, features, and functionality of our Services, including but not limited to text, graphics, logos, and software, are owned by Cyberdesk and are protected by intellectual property laws. You may not reproduce, distribute, or create derivative works without our prior written consent.\n            </p>\n\n            <h2>9. Termination</h2>\n            <p>\n              We reserve the right to suspend or terminate your access to the Services at any time for any reason, including but not limited to a violation of these Terms. Upon termination, your right to use the Services will immediately cease.\n            </p>\n\n            <h2>10. Disclaimer of Warranties</h2>\n            <p>\n              THE SERVICES ARE PROVIDED &quot;AS IS&quot; AND &quot;AS AVAILABLE&quot; WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. WE DO NOT WARRANT THAT THE SERVICES WILL BE UNINTERRUPTED OR ERROR-FREE.\n            </p>\n\n            <h2>11. Limitation of Liability</h2>\n            <p>\n              IN NO EVENT SHALL CYBERDESK BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR ANY LOSS OF PROFITS OR REVENUES, WHETHER INCURRED DIRECTLY OR INDIRECTLY.\n            </p>\n\n            <h2>12. Indemnification</h2>\n            <p>\n              You agree to indemnify and hold harmless Cyberdesk and its officers, directors, employees, and agents, from and against any claims, liabilities, damages, losses, and expenses arising out of or in any way connected with your use of the Services.\n            </p>\n\n            <h2>13. Governing Law</h2>\n            <p>\n              These Terms shall be governed by and construed in accordance with the laws of the State of California, without regard to its conflict of law provisions.\n            </p>\n\n            <h2>14. Changes to Terms</h2>\n            <p>\n              We reserve the right to modify these Terms at any time. We will provide notice of any material changes by posting the new Terms on our website. Your continued use of the Services after such modifications constitutes your acceptance of the revised Terms.\n            </p>\n\n            <h2>15. Contact Information</h2>\n            <p>\n              If you have any questions about these Terms, please contact us at mahmoud@cyberdesk.io.\n            </p>\n          </div>\n        </Container>\n        <Footer />\n      </GradientBackground>\n    </>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/LogoText.tsx",
    "content": "import React from 'react';\nimport { cn } from '@/utils/misc-utils'; // Assuming you use shadcn/ui or similar for utils\n\ninterface LogoTextProps extends React.HTMLAttributes<HTMLSpanElement> {}\n\nexport const LogoText: React.FC<LogoTextProps> = ({ className, ...props }) => {\n  return (\n    <span\n      className={cn(\n        'text-[35px] font-medium tracking-tight text-gray-950',\n        className\n      )}\n      {...props}\n    >\n      Cyberdesk\n    </span>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/PostHogProvider.tsx",
    "content": "\"use client\"\n\nimport posthog from \"posthog-js\"\nimport { PostHogProvider as PHProvider, usePostHog } from \"posthog-js/react\"\nimport { Suspense, useEffect } from \"react\"\nimport { usePathname, useSearchParams } from \"next/navigation\"\n\nexport function PostHogProvider({ children }: { children: React.ReactNode }) {\n  useEffect(() => {\n    posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {\n      api_host: \"/ingest\",\n      ui_host: \"https://us.posthog.com\",\n      capture_pageview: false, // We capture pageviews manually\n      capture_pageleave: true, // Enable pageleave capture\n    })\n  }, [])\n\n  return (\n    <PHProvider client={posthog}>\n      <SuspendedPostHogPageView />\n      {children}\n    </PHProvider>\n  )\n}\n\nfunction PostHogPageView() {\n  const pathname = usePathname()\n  const searchParams = useSearchParams()\n  const posthog = usePostHog()\n\n  useEffect(() => {\n    if (pathname && posthog) {\n      let url = window.origin + pathname\n      const search = searchParams.toString()\n      if (search) {\n        url += \"?\" + search\n      }\n      posthog.capture(\"$pageview\", { \"$current_url\": url })\n    }\n  }, [pathname, searchParams, posthog])\n\n  return null\n}\n\nfunction SuspendedPostHogPageView() {\n  return (\n    <Suspense fallback={null}>\n      <PostHogPageView />\n    </Suspense>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/animated-number.tsx",
    "content": "'use client'\n\nimport {\n  motion,\n  useInView,\n  useMotionValue,\n  useSpring,\n  useTransform,\n} from 'framer-motion'\nimport { useEffect, useRef } from 'react'\n\nexport function AnimatedNumber({\n  start,\n  end,\n  decimals = 0,\n}: {\n  start: number\n  end: number\n  decimals?: number\n}) {\n  const ref = useRef(null)\n  const isInView = useInView(ref, { once: true, amount: 0.5 })\n\n  const value = useMotionValue(start)\n  const spring = useSpring(value, { damping: 30, stiffness: 100 })\n  const display = useTransform(spring, (num) => num.toFixed(decimals))\n\n  useEffect(() => {\n    value.set(isInView ? end : start)\n  }, [start, end, isInView, value])\n\n  return <motion.span ref={ref}>{display}</motion.span>\n}\n"
  },
  {
    "path": "apps/web/src/components/bento-card.tsx",
    "content": "'use client'\n\nimport { clsx } from 'clsx'\nimport { motion } from 'framer-motion'\nimport { Subheading } from './text'\n\nexport function BentoCard({\n  dark = false,\n  className = '',\n  eyebrow,\n  title,\n  description,\n  graphic,\n  fade = [],\n}: {\n  dark?: boolean\n  className?: string\n  eyebrow: React.ReactNode\n  title: React.ReactNode\n  description: React.ReactNode\n  graphic: React.ReactNode\n  fade?: ('top' | 'bottom')[]\n}) {\n  return (\n    <motion.div\n      initial=\"idle\"\n      whileHover=\"active\"\n      variants={{ idle: {}, active: {} }}\n      data-dark={dark ? 'true' : undefined}\n      className={clsx(\n        className,\n        'group relative flex flex-col overflow-hidden rounded-lg',\n        'bg-white ring-1 shadow-xs ring-black/5',\n        'data-dark:bg-gray-800 data-dark:ring-white/15',\n      )}\n    >\n      <div className=\"relative h-80 shrink-0\">\n        {graphic}\n        {fade.includes('top') && (\n          <div className=\"absolute inset-0 bg-linear-to-b from-white to-50% group-data-dark:from-gray-800 group-data-dark:from-[-25%]\" />\n        )}\n        {fade.includes('bottom') && (\n          <div className=\"absolute inset-0 bg-linear-to-t from-white to-50% group-data-dark:from-gray-800 group-data-dark:from-[-25%]\" />\n        )}\n      </div>\n      <div className=\"relative p-10\">\n        <Subheading as=\"h3\" dark={dark}>\n          {eyebrow}\n        </Subheading>\n        <p className=\"mt-1 text-2xl/8 font-medium tracking-tight text-gray-950 group-data-dark:text-white\">\n          {title}\n        </p>\n        <p className=\"mt-2 max-w-[600px] text-sm/6 text-gray-600 group-data-dark:text-gray-400\">\n          {description}\n        </p>\n      </div>\n    </motion.div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/bento-section.tsx",
    "content": "import { BentoCard } from '@/components/bento-card'\nimport { Container } from '@/components/container'\nimport { Keyboard } from '@/components/keyboard'\nimport { LogoCluster } from '@/components/logo-cluster'\nimport { Map } from '@/components/map'\nimport { Heading, Subheading } from '@/components/text'\n\nexport function BentoSection() {\n  return (\n    <Container>\n      <Subheading>Observability</Subheading>\n      <Heading as=\"h3\" className=\"mt-2 max-w-3xl\">\n        Monitor and manage your virtual desktops\n      </Heading>\n\n      <div className=\"mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2\">\n        <BentoCard\n          eyebrow=\"Insight\"\n          title=\"Get perfect clarity\"\n          description=\"Radiant uses social engineering to build a detailed financial picture of your leads. Know their budget, compensation package, social security number, and more.\"\n          graphic={\n            <div className=\"h-80 bg-[url(/screenshots/profile.png)] bg-[size:1000px_560px] bg-[left_-109px_top_-112px] bg-no-repeat\" />\n          }\n          fade={['bottom']}\n          className=\"max-lg:rounded-t-4xl lg:col-span-3 lg:rounded-tl-4xl\"\n        />\n        <BentoCard\n          eyebrow=\"Analysis\"\n          title=\"Undercut your competitors\"\n          description=\"With our advanced data mining, you'll know which companies your leads are talking to and exactly how much they're being charged.\"\n          graphic={\n            <div className=\"absolute inset-0 bg-[url(/screenshots/competitors.png)] bg-[size:1100px_650px] bg-[left_-38px_top_-73px] bg-no-repeat\" />\n          }\n          fade={['bottom']}\n          className=\"lg:col-span-3 lg:rounded-tr-4xl\"\n        />\n        <BentoCard\n          eyebrow=\"Speed\"\n          title=\"Built for power users\"\n          description=\"It's never been faster to cold email your entire contact list using our streamlined keyboard shortcuts.\"\n          graphic={\n            <div className=\"flex size-full pt-10 pl-10\">\n              <Keyboard highlighted={['LeftCommand', 'LeftShift', 'D']} />\n            </div>\n          }\n          className=\"lg:col-span-2 lg:rounded-bl-4xl\"\n        />\n        <BentoCard\n          eyebrow=\"Source\"\n          title=\"Get the furthest reach\"\n          description=\"Bypass those inconvenient privacy laws to source leads from the most unexpected places.\"\n          graphic={<LogoCluster />}\n          className=\"lg:col-span-2\"\n        />\n        <BentoCard\n          eyebrow=\"Limitless\"\n          title=\"Sell globally\"\n          description=\"Radiant helps you sell in locations currently under international embargo.\"\n          graphic={<Map />}\n          className=\"max-lg:rounded-b-4xl lg:col-span-2 lg:rounded-br-4xl\"\n        />\n      </div>\n    </Container>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/button.tsx",
    "content": "import * as Headless from '@headlessui/react'\nimport { clsx } from 'clsx'\nimport { Link } from './link'\n\nconst variants = {\n  primary: clsx(\n    'inline-flex items-center justify-center px-4 py-[calc(--spacing(2)-1px)]',\n    'rounded-full border border-transparent bg-gray-950 shadow-md',\n    'text-base font-medium whitespace-nowrap text-white',\n    'data-disabled:bg-gray-950 data-disabled:opacity-40 data-hover:bg-gray-800',\n  ),\n  secondary: clsx(\n    'relative inline-flex items-center justify-center px-4 py-[calc(--spacing(2)-1px)]',\n    'rounded-full border border-transparent bg-white/15 ring-1 shadow-md ring-[#D15052]/15',\n    'after:absolute after:inset-0 after:rounded-full after:shadow-[inset_0_0_2px_1px_#ffffff4d]',\n    'text-base font-medium whitespace-nowrap text-gray-950',\n    'data-disabled:bg-white/15 data-disabled:opacity-40 data-hover:bg-white/20',\n  ),\n  outline: clsx(\n    'inline-flex items-center justify-center px-2 py-[calc(--spacing(1.5)-1px)]',\n    'rounded-lg border border-transparent ring-1 shadow-sm ring-black/10',\n    'text-sm font-medium whitespace-nowrap text-gray-950',\n    'data-disabled:bg-transparent data-disabled:opacity-40 data-hover:bg-gray-50',\n  ),\n}\n\ntype ButtonProps = {\n  variant?: keyof typeof variants\n} & (\n  | React.ComponentPropsWithoutRef<typeof Link>\n  | (Headless.ButtonProps & { href?: undefined })\n)\n\nexport function Button({\n  variant = 'primary',\n  className,\n  ...props\n}: ButtonProps) {\n  className = clsx(className, variants[variant])\n\n  if (typeof props.href === 'undefined') {\n    return <Headless.Button {...props} className={className} />\n  }\n\n  return <Link {...props} className={className} />\n}\n"
  },
  {
    "path": "apps/web/src/components/container.tsx",
    "content": "import { clsx } from 'clsx'\n\nexport function Container({\n  className,\n  children,\n}: {\n  className?: string\n  children: React.ReactNode\n}) {\n  return (\n    <div className={clsx(className, 'px-6 lg:px-8')}>\n      <div className=\"mx-auto max-w-2xl lg:max-w-7xl\">{children}</div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dark-bento-section.tsx",
    "content": "import { BentoCard } from '@/components/bento-card'\nimport { Container } from '@/components/container'\nimport { LinkedAvatars } from '@/components/linked-avatars'\nimport { LogoTimeline } from '@/components/logo-timeline'\nimport { Heading, Subheading } from '@/components/text'\n\nexport function DarkBentoSection() {\n  return (\n    <div className=\"mx-2 mt-2 rounded-4xl bg-gray-900 py-32\">\n      <Container>\n        <Subheading dark>Customization</Subheading>\n        <Heading as=\"h3\" dark className=\"mt-2 max-w-3xl\">\n          Tailor your virtual desktops to your needs\n        </Heading>\n\n        <div className=\"mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2\">\n          <BentoCard\n            dark\n            eyebrow=\"Networking\"\n            title=\"Sell at the speed of light\"\n            description=\"Our RadiantAI chat assistants analyze the sentiment of your conversations in real time, ensuring you're always one step ahead.\"\n            graphic={\n              <div className=\"h-80 bg-[url(/screenshots/networking.png)] bg-[size:851px_344px] bg-no-repeat\" />\n            }\n            fade={['top']}\n            className=\"max-lg:rounded-t-4xl lg:col-span-4 lg:rounded-tl-4xl\"\n          />\n          <BentoCard\n            dark\n            eyebrow=\"Integrations\"\n            title=\"Meet leads where they are\"\n            description=\"With thousands of integrations, no one will be able to escape your cold outreach.\"\n            graphic={<LogoTimeline />}\n            // `overflow-visible!` is needed to work around a Chrome bug that disables the mask on the graphic.\n            className=\"z-10 overflow-visible! lg:col-span-2 lg:rounded-tr-4xl\"\n          />\n          <BentoCard\n            dark\n            eyebrow=\"Meetings\"\n            title=\"Smart call scheduling\"\n            description=\"Automatically insert intro calls into your leads' calendars without their consent.\"\n            graphic={<LinkedAvatars />}\n            className=\"lg:col-span-2 lg:rounded-bl-4xl\"\n          />\n          <BentoCard\n            dark\n            eyebrow=\"Engagement\"\n            title=\"Become a thought leader\"\n            description=\"RadiantAI automatically writes LinkedIn posts that relate current events to B2B sales, helping you build a reputation as a thought leader.\"\n            graphic={\n              <div className=\"h-80 bg-[url(/screenshots/engagement.png)] bg-[size:851px_344px] bg-no-repeat\" />\n            }\n            fade={['top']}\n            className=\"max-lg:rounded-b-4xl lg:col-span-4 lg:rounded-br-4xl\"\n          />\n        </div>\n      </Container>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/api-key-manager.tsx",
    "content": "'use client'\n\nimport { useState, useEffect } from 'react'\nimport { Button } from '@/components/button'\nimport { ClipboardIcon, KeyIcon, ArrowPathIcon } from '@heroicons/react/24/outline'\nimport { CheckIcon } from '@heroicons/react/24/solid'\nimport { supabase } from '@/utils/supabase/client'\ninterface ApiKey {\n  id: string\n  key: string\n  createdAt: string\n}\n\nexport function ApiKeyManager() {\n  const [apiKey, setApiKey] = useState<ApiKey | null>(null)\n  const [isLoading, setIsLoading] = useState(true)\n  const [error, setError] = useState<string | null>(null)\n  const [copied, setCopied] = useState(false)\n  const [isGenerating, setIsGenerating] = useState(false)\n  const [keyExists, setKeyExists] = useState(false)\n\n  useEffect(() => {\n    fetchApiKey()\n  }, [])\n  \n  const fetchApiKey = async () => {\n    setIsLoading(true)\n    setError(null)\n    try {\n      // Get the current user session\n      const { data, error } = await supabase.auth.getSession();\n      \n      if (error || !data.session) {\n        console.error('Authentication error:', error)\n        setError('Authentication error. Please sign in again.')\n        setIsLoading(false)\n        return\n      }\n      \n      const userId = data.session.user.id\n      console.log('Checking if API key exists for user:', userId)\n      \n      // Call our API endpoint with the userId as a query parameter\n      const response = await fetch(`/api/unkey?userId=${userId}`, {\n        method: 'GET',\n      })\n      \n      console.log('API response status:', response.status)\n      const responseData = await response.json()\n      \n      if (!response.ok) {\n        throw new Error(responseData.error || 'Failed to fetch API key')\n      }\n      \n      console.log('API key check response:', responseData)\n      setKeyExists(responseData.exists)\n      \n      if (responseData.exists && responseData.key) {\n        // We have a key, set it\n        setApiKey({\n          id: responseData.keyId || 'unknown',\n          key: responseData.key,\n          createdAt: new Date().toISOString(),\n        })\n      } else {\n        setApiKey(null)\n      }\n    } catch (err) {\n      console.error('Error fetching API key:', err)\n      setError('Failed to load your API key. Please try again.')\n    } finally {\n      setIsLoading(false)\n    }\n  }\n\n  const generateApiKey = async () => {\n    setIsGenerating(true)\n    setError(null)\n    \n    try {\n      // Get the current user session\n      const { data, error } = await supabase.auth.getSession();\n      \n      if (error || !data.session) {\n        console.error('Authentication error:', error)\n        setError('Authentication error. Please sign in again.')\n        setIsGenerating(false)\n        return\n      }\n      \n      const userId = data.session.user.id\n      console.log('Generating API key for user:', userId)\n      \n      // Call our API endpoint with the userId in the body\n      const response = await fetch('/api/unkey', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({ userId }),\n      })\n      \n      const responseData = await response.json()\n      \n      if (!response.ok) {\n        throw new Error(responseData.error || 'Failed to generate API key')\n      }\n      \n      if (responseData.key) {\n        setApiKey({\n          id: responseData.keyId || 'unknown',\n          key: responseData.key,\n          createdAt: new Date().toISOString(),\n        })\n        setKeyExists(true)\n      } else {\n        throw new Error('No key returned from API')\n      }\n    } catch (err) {\n      console.error('Error generating API key:', err)\n      setError('Failed to generate a new API key. Please try again.')\n    } finally {\n      setIsGenerating(false)\n    }\n  }\n\n  const copyToClipboard = () => {\n    if (apiKey) {\n      navigator.clipboard.writeText(apiKey.key)\n      setCopied(true)\n      setTimeout(() => setCopied(false), 2000)\n    }\n  }\n\n  return (\n    <div className=\"bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden\">\n      <div className=\"px-6 py-5 border-b border-gray-200 bg-gray-50 flex items-center justify-between\">\n        <div className=\"flex items-center space-x-2\">\n          <KeyIcon className=\"h-5 w-5 text-gray-500\" />\n          <h3 className=\"text-base font-medium text-gray-900\">Your API Key</h3>\n        </div>\n        <div className=\"text-sm text-gray-500\">\n          Use this key to authenticate API requests\n        </div>\n      </div>\n      \n      <div className=\"px-6 py-5\">\n        {isLoading ? (\n          <div className=\"flex items-center justify-center py-6\">\n            <div className=\"animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900\"></div>\n          </div>\n        ) : error ? (\n          <div className=\"text-red-500 py-2\">{error}</div>\n        ) : apiKey ? (\n          <div className=\"space-y-4\">\n            <div className=\"bg-green-50 border border-green-200 rounded-md p-3 mb-4\">\n              <div className=\"flex\">\n                <div className=\"flex-shrink-0\">\n                  <CheckIcon className=\"h-5 w-5 text-green-400\" aria-hidden=\"true\" />\n                </div>\n                <div className=\"ml-3\">\n                  <h3 className=\"text-sm font-medium text-green-800\">API Key Active</h3>\n                  <div className=\"mt-2 text-sm text-green-700\">\n                    <p>You have an active API key that you can use to authenticate your API requests.</p>\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div className=\"flex items-center\">\n              <div className=\"flex-1 bg-gray-100 rounded-md px-4 py-3 font-mono text-sm text-gray-800 truncate\">\n                {apiKey.key}\n              </div>\n              <button\n                onClick={copyToClipboard}\n                className=\"ml-3 p-2 rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"\n                title=\"Copy to clipboard\"\n              >\n                {copied ? (\n                  <CheckIcon className=\"h-5 w-5 text-green-500\" />\n                ) : (\n                  <ClipboardIcon className=\"h-5 w-5 text-gray-500\" />\n                )}\n              </button>\n            </div>\n            <div className=\"text-sm text-gray-500\">\n              Keep this key secret. Do not share it in client-side code.\n            </div>\n            <div className=\"text-sm text-gray-600 mt-4\">\n              <p>This is your current API key. If you&apos;ve lost access to it, you can generate a new one below.</p>\n            </div>\n          </div>\n        ) : keyExists ? (\n          <div className=\"space-y-4\">\n            <div className=\"bg-blue-50 border border-blue-200 rounded-md p-3 mb-4\">\n              <div className=\"flex\">\n                <div className=\"flex-shrink-0\">\n                  <KeyIcon className=\"h-5 w-5 text-blue-400\" aria-hidden=\"true\" />\n                </div>\n                <div className=\"ml-3\">\n                  <h3 className=\"text-sm font-medium text-blue-800\">API Key Already Created</h3>\n                  <div className=\"mt-2 text-sm text-blue-700\">\n                    <p>You&apos;ve already created an API key.</p>\n                    <p className=\"mt-1\">If you lost your API key, you can generate a new key below, which will replace your existing key.</p>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        ) : (\n          <div className=\"text-center py-6\">\n            <div className=\"bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4\">\n              <div className=\"flex\">\n                <div className=\"flex-shrink-0\">\n                  <KeyIcon className=\"h-5 w-5 text-yellow-400\" aria-hidden=\"true\" />\n                </div>\n                <div className=\"ml-3\">\n                  <h3 className=\"text-start text-sm font-medium text-yellow-800\">No API Key Found</h3>\n                  <div className=\"mt-2 text-sm text-yellow-700\">\n                    <p>You don&apos;t have an API key yet. Generate one to start using our API.</p>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        )}\n        \n        <div className=\"mt-5 flex justify-center\">\n          <Button\n            type=\"button\"\n            onClick={generateApiKey}\n            disabled={isGenerating}\n            className=\"flex items-center justify-center px-6\"\n            variant={apiKey || keyExists ? \"outline\" : \"primary\"}\n          >\n            {isGenerating ? (\n              <>\n                <ArrowPathIcon className=\"animate-spin -ml-1 mr-2 h-4 w-4\" />\n                Generating...\n              </>\n            ) : apiKey ? (\n              'Regenerate API Key'\n            ) : keyExists ? (\n              'Generate New API Key'\n            ) : (\n              'Generate API Key'\n            )}\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/api-key-section.tsx",
    "content": "'use client'\n\nimport { useState, useEffect } from 'react'\nimport { Button } from '@/components/button'\nimport { CheckIcon, ClipboardIcon, KeyIcon } from '@heroicons/react/24/outline'\nimport { ArrowPathIcon } from '@heroicons/react/24/outline'\n\ninterface ApiKeyData {\n  key: string\n  keyId: string\n}\n\nexport function ApiKeySection() {\n  const [loading, setLoading] = useState(true)\n  const [error, setError] = useState<string | null>(null)\n  const [identityExists, setIdentityExists] = useState(false)\n  const [apiKey, setApiKey] = useState<ApiKeyData | null>(null)\n  const [copied, setCopied] = useState(false)\n  const [isCreating, setIsCreating] = useState(false)\n  // Toast notification function\n  const showToast = (message: string, type: 'success' | 'error' = 'success') => {\n    // In a real implementation, this would use a toast library\n    console.log(`Toast (${type}):`, message)\n  }\n\n  useEffect(() => {\n    checkIdentity()\n  }, [])\n\n  const checkIdentity = async () => {\n    setLoading(true)\n    setError(null)\n    \n    try {\n      const response = await fetch('/api/unkey')\n      \n      if (!response.ok) {\n        throw new Error('Failed to check identity')\n      }\n      \n      const data = await response.json()\n      \n      setIdentityExists(data.exists)\n      \n      if (data.exists && data.keys && data.keys.length > 0) {\n        // Sort keys by creation date and get the most recent one\n        const sortedKeys = [...data.keys].sort((a, b) => {\n          return new Date(b.meta?.createdAt || 0).getTime() - \n                 new Date(a.meta?.createdAt || 0).getTime()\n        })\n        \n        setApiKey({\n          key: sortedKeys[0].key,\n          keyId: sortedKeys[0].id\n        })\n      }\n    } catch (err) {\n      console.error('Error checking identity:', err)\n      setError('Failed to check your API key status')\n    } finally {\n      setLoading(false)\n    }\n  }\n\n  const createApiKey = async () => {\n    setIsCreating(true)\n    setError(null)\n    \n    try {\n      const response = await fetch('/api/unkey', {\n        method: 'POST'\n      })\n      \n      if (!response.ok) {\n        throw new Error('Failed to create API key')\n      }\n      \n      const data = await response.json()\n      \n      setIdentityExists(true)\n      setApiKey({\n        key: data.key,\n        keyId: data.keyId\n      })\n      \n      showToast(\"Your new API key has been generated successfully.\", \"success\")\n    } catch (err) {\n      console.error('Error creating API key:', err)\n      setError('Failed to create your API key')\n      \n      showToast(\"Failed to create your API key. Please try again.\", \"error\")\n    } finally {\n      setIsCreating(false)\n    }\n  }\n\n  const copyToClipboard = () => {\n    if (apiKey?.key) {\n      navigator.clipboard.writeText(apiKey.key)\n      setCopied(true)\n      \n      showToast(\"API key copied to clipboard\", \"success\")\n      \n      setTimeout(() => setCopied(false), 2000)\n    }\n  }\n\n  if (loading) {\n    return (\n      <div className=\"bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden\">\n        <div className=\"p-6 animate-pulse\">\n          <div className=\"h-6 bg-gray-200 rounded w-3/4 mb-4\"></div>\n          <div className=\"h-4 bg-gray-200 rounded w-1/2 mb-6\"></div>\n          <div className=\"h-10 bg-gray-200 rounded w-full mb-4\"></div>\n          <div className=\"h-4 bg-gray-200 rounded w-3/4 mb-6\"></div>\n          <div className=\"h-10 bg-gray-200 rounded w-full\"></div>\n        </div>\n      </div>\n    )\n  }\n\n  if (error) {\n    return (\n      <div className=\"bg-white rounded-lg shadow-sm border border-red-200 overflow-hidden\">\n        <div className=\"px-6 py-5 border-b border-gray-200 bg-gray-50\">\n          <div className=\"flex items-center gap-2\">\n            <KeyIcon className=\"h-5 w-5\" />\n            <h3 className=\"text-lg font-medium text-gray-900\">API Key</h3>\n          </div>\n          <p className=\"text-sm text-gray-500 mt-1\">\n            There was a problem loading your API key information\n          </p>\n        </div>\n        <div className=\"px-6 py-5\">\n          <p className=\"text-red-500\">{error}</p>\n        </div>\n        <div className=\"px-6 py-4 bg-gray-50 border-t border-gray-200\">\n          <Button onClick={checkIdentity} variant=\"outline\" className=\"w-full\">\n            Try Again\n          </Button>\n        </div>\n      </div>\n    )\n  }\n\n  return (\n    <div className=\"bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden\">\n      <div className=\"px-6 py-5 border-b border-gray-200 bg-gray-50\">\n        <div className=\"flex items-center gap-2\">\n          <KeyIcon className=\"h-5 w-5 text-gray-500\" />\n          <h3 className=\"text-lg font-medium text-gray-900\">API Key</h3>\n        </div>\n        <p className=\"text-sm text-gray-500 mt-1\">\n          {identityExists \n            ? \"Use this key to authenticate API requests to our service\" \n            : \"Generate an API key to start using our service\"}\n        </p>\n      </div>\n      <div className=\"px-6 py-5\">\n        {identityExists && apiKey ? (\n          <div className=\"space-y-4\">\n            <div className=\"flex items-center\">\n              <div className=\"flex-1 bg-gray-100 rounded-md px-4 py-3 font-mono text-sm text-gray-800 truncate\">\n                {apiKey.key}\n              </div>\n              <button\n                onClick={copyToClipboard}\n                className=\"ml-3 p-2 rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"\n                title=\"Copy to clipboard\"\n              >\n                {copied ? (\n                  <CheckIcon className=\"h-5 w-5 text-green-500\" />\n                ) : (\n                  <ClipboardIcon className=\"h-5 w-5 text-gray-500\" />\n                )}\n              </button>\n            </div>\n            <div className=\"text-sm text-gray-500\">\n              Keep this key secure. Never share it in client-side code or public repositories.\n            </div>\n          </div>\n        ) : (\n          <div className=\"text-center py-6\">\n            <p className=\"text-gray-500 mb-4\">You don&apos;t have an API key yet</p>\n          </div>\n        )}\n      </div>\n      <div className=\"px-6 py-4 bg-gray-50 border-t border-gray-200\">\n        <Button\n          onClick={createApiKey}\n          disabled={isCreating}\n          className=\"w-full\"\n          variant={identityExists ? \"outline\" : \"primary\"}\n        >\n          {isCreating ? (\n            <>\n              <ArrowPathIcon className=\"h-4 w-4 mr-2 animate-spin\" />\n              {identityExists ? \"Regenerating...\" : \"Generating...\"}\n            </>\n          ) : identityExists ? (\n            \"Regenerate API Key\"\n          ) : (\n            \"Generate API Key\"\n          )}\n        </Button>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/dashboard-layout.tsx",
    "content": "'use client'\n\nimport { useState, type ReactNode } from 'react'\nimport { MobileSidebar } from './mobile-sidebar'\nimport { DesktopSidebar } from './desktop-sidebar'\nimport { MobileHeader } from './mobile-header'\n\ninterface DashboardLayoutProps {\n  children: ReactNode;\n  userEmail?: string;\n}\n\nexport function DashboardLayout({ children, userEmail }: DashboardLayoutProps) {\n  const [sidebarOpen, setSidebarOpen] = useState(false)\n\n  return (\n    <div>\n      <MobileSidebar sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />\n      <DesktopSidebar userEmail={userEmail} />\n      <MobileHeader setSidebarOpen={setSidebarOpen} />\n\n      <main className=\"py-10 lg:pl-72\">\n        <div className=\"px-4 sm:px-6 lg:px-8\">{children}</div>\n      </main>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/desktop-sidebar.tsx",
    "content": "'use client'\n\nimport { navigation, classNames } from './sidebar-navigation'\nimport { AppLogo } from '@/components/shared/app-logo'\nimport { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'\n\ninterface DesktopSidebarProps {\n  userEmail?: string;\n}\n\nexport function DesktopSidebar({ userEmail }: DesktopSidebarProps) {\n  return (\n    <div className=\"hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col\">\n      {/* Sidebar component, swap this element with another sidebar if you like */}\n      <div className=\"flex grow flex-col gap-y-5 overflow-y-auto bg-gray-900 px-6\">\n        <div className=\"flex h-16 shrink-0 items-center\">\n          <a href=\"/\" className=\"cursor-pointer\">\n            <AppLogo variant=\"light\" size=\"medium\" />\n          </a>\n        </div>\n        <nav className=\"flex flex-1 flex-col\">\n          <ul role=\"list\" className=\"flex flex-1 flex-col gap-y-7\">\n            <li>\n              <ul role=\"list\" className=\"-mx-2 space-y-1\">\n                {navigation.map((item) => (\n                  <li key={item.name}>\n                    {(() => {\n                      const isExternal = (item as { external?: boolean }).external;\n                      return (\n                        <a\n                          href={item.href}\n                          target={isExternal ? '_blank' : undefined}\n                          rel={isExternal ? 'noopener noreferrer' : undefined}\n                          className={classNames(\n                            item.current\n                              ? 'bg-gray-800 text-white'\n                              : 'text-gray-400 hover:bg-gray-800 hover:text-white',\n                            'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold',\n                          )}\n                        >\n                          <item.icon aria-hidden=\"true\" className=\"size-6 shrink-0\" />\n                          <span className=\"flex-1\">{item.name}</span>\n                          {isExternal && (\n                            <ArrowTopRightOnSquareIcon aria-hidden=\"true\" className=\"size-4 text-gray-400 self-center\" />\n                          )}\n                        </a>\n                      );\n                    })()}\n                  </li>\n                ))}\n              </ul>\n            </li>\n            {/* <li>\n              <div className=\"text-xs/6 font-semibold text-gray-400\">Your teams</div>\n              <ul role=\"list\" className=\"-mx-2 mt-2 space-y-1\">\n                {teams.map((team) => (\n                  <li key={team.name}>\n                    <a\n                      href={team.href}\n                      className={classNames(\n                        team.current\n                          ? 'bg-gray-800 text-white'\n                          : 'text-gray-400 hover:bg-gray-800 hover:text-white',\n                        'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold',\n                      )}\n                    >\n                      <span className=\"flex size-6 shrink-0 items-center justify-center rounded-lg border border-gray-700 bg-gray-800 text-[0.625rem] font-medium text-gray-400 group-hover:text-white\">\n                        {team.initial}\n                      </span>\n                      <span className=\"truncate\">{team.name}</span>\n                    </a>\n                  </li>\n                ))}\n              </ul>\n            </li> */}\n            <li className=\"-mx-6 mt-auto\">\n              <div\n                className=\"flex items-center gap-x-4 px-6 py-3 text-sm/6 font-semibold text-white\"\n              >\n                <div className=\"flex items-center justify-center size-8 rounded-full bg-gray-800 text-white\">\n                  {userEmail ? userEmail.charAt(0).toUpperCase() : '?'}\n                </div>\n                <span className=\"sr-only\">Your profile</span>\n                <span aria-hidden=\"true\" className=\"truncate\">{userEmail || 'Loading...'}</span>\n              </div>\n            </li>\n          </ul>\n        </nav>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/faq-section.tsx",
    "content": "import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'\nimport { MinusSmallIcon, PlusSmallIcon } from '@heroicons/react/24/outline'\n\nconst faqs = [\n  {\n    question: \"How do I get started with Cyberdesk?\",\n    answer:\n      \"Simply subscribe to our Pro plan, and you'll immediately gain access to all features. You can then create your API key and start deploying virtual desktops that your computer agents can seamlessly control like a human.\",\n  },\n  {\n    question: \"Is there a money back guarantee?\",\n    answer:\n      \"Yes, we offer a 30-day money-back guarantee. If you're not satisfied with our service, you can let us know and we'll process a refund. You can cancel your subscription at any time.\",\n  },\n  {\n    question: \"Can I increase my limits?\",\n    answer:\n      \"Yes, you can increase your limits by upgrading to a higher-tier plan. Contact us for more information.\",\n  },\n  {\n    question: \"What is the operating system of the virtual desktop?\",\n    answer:\n      \"The virtual desktop is based on Ubuntu. This ensures a stable and secure environment for your AI agents.\",\n  },\n  {\n    question: \"What programming languages and frameworks are supported?\",\n    answer:\n      \"Our REST API can be called from anywhere, regardless of the programming language you're using. SDK support for popular languages is coming soon.\",\n  },\n  {\n    question: \"How do I connect my AI agent to the virtual desktop?\",\n    answer:\n      \"Our API has endpoints for computer and bash actions, specifically tailored for CUA (Computer-Using Agent) agents. These endpoints allow your AI to interact with the virtual desktop just like a human would.\",\n  },\n  {\n    question: \"Is there a free trial available?\",\n    answer:\n      \"Contact us about this, we'd love to chat about your specific needs and how we can help you get started with Cyberdesk.\",\n  }\n]\n\nexport function FAQSection() {\n  return (\n    <div className=\"bg-white\">\n      <div className=\"mx-auto max-w-7xl px-6 py-0 lg:px-8\">\n        <div className=\"mx-auto max-w-4xl\">\n          <h2 className=\"text-xl tracking-tight text-gray-900 sm:text-3xl\">\n            Frequently asked questions\n          </h2>\n          <dl className=\"mt-6 divide-y divide-gray-900/10\">\n            {faqs.map((faq) => (\n              <Disclosure key={faq.question} as=\"div\" className=\"py-3 first:pt-0 last:pb-0\">\n                <dt>\n                  <DisclosureButton className=\"group flex w-full items-start justify-between text-left text-gray-900\">\n                    <span className=\"text-base/7 font-medium\">{faq.question}</span>\n                    <span className=\"ml-6 flex h-7 items-center\">\n                      <PlusSmallIcon aria-hidden=\"true\" className=\"size-6 group-data-[open]:hidden\" />\n                      <MinusSmallIcon aria-hidden=\"true\" className=\"size-6 group-[&:not([data-open])]:hidden\" />\n                    </span>\n                  </DisclosureButton>\n                </dt>\n                <DisclosurePanel as=\"dd\" className=\"mt-2 pr-12\">\n                  <p className=\"text-base/7 text-gray-600\">{faq.answer}</p>\n                </DisclosurePanel>\n              </Disclosure>\n            ))}\n          </dl>\n        </div>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/mobile-header.tsx",
    "content": "'use client'\n\nimport { Bars3Icon } from '@heroicons/react/24/outline'\n\ninterface MobileHeaderProps {\n  setSidebarOpen: (open: boolean) => void\n}\n\nexport function MobileHeader({ setSidebarOpen }: MobileHeaderProps) {\n  return (\n    <div className=\"sticky top-0 z-40 flex items-center gap-x-6 bg-gray-900 px-4 py-4 shadow-sm sm:px-6 lg:hidden\">\n      <button type=\"button\" onClick={() => setSidebarOpen(true)} className=\"-m-2.5 p-2.5 text-gray-400 lg:hidden\">\n        <span className=\"sr-only\">Open sidebar</span>\n        <Bars3Icon aria-hidden=\"true\" className=\"size-6\" />\n      </button>\n      <div className=\"flex-1 text-sm/6 font-semibold text-white\">Dashboard</div>\n      {/* <a href=\"#\">\n        <span className=\"sr-only\">Your profile</span>\n        <img\n          alt=\"\"\n          src=\"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80\"\n          className=\"size-8 rounded-full bg-gray-800\"\n        />\n      </a> */}\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/mobile-sidebar.tsx",
    "content": "'use client'\n\nimport { Dialog, DialogBackdrop, DialogPanel, TransitionChild } from '@headlessui/react'\nimport { XMarkIcon, ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'\nimport { navigation, classNames } from './sidebar-navigation'\nimport { AppLogo } from '@/components/shared/app-logo'\n\ninterface MobileSidebarProps {\n  sidebarOpen: boolean\n  setSidebarOpen: (open: boolean) => void\n}\n\nexport function MobileSidebar({ sidebarOpen, setSidebarOpen }: MobileSidebarProps) {\n  return (\n    <Dialog open={sidebarOpen} onClose={setSidebarOpen} className=\"relative z-50 lg:hidden\">\n      <DialogBackdrop\n        transition\n        className=\"fixed inset-0 bg-gray-900/80 transition-opacity duration-300 ease-linear data-[closed]:opacity-0\"\n      />\n\n      <div className=\"fixed inset-0 flex\">\n        <DialogPanel\n          transition\n          className=\"relative mr-16 flex w-full max-w-xs flex-1 transform transition duration-300 ease-in-out data-[closed]:-translate-x-full\"\n        >\n          <TransitionChild>\n            <div className=\"absolute left-full top-0 flex w-16 justify-center pt-5 duration-300 ease-in-out data-[closed]:opacity-0\">\n              <button type=\"button\" onClick={() => setSidebarOpen(false)} className=\"-m-2.5 p-2.5\">\n                <span className=\"sr-only\">Close sidebar</span>\n                <XMarkIcon aria-hidden=\"true\" className=\"size-6 text-white\" />\n              </button>\n            </div>\n          </TransitionChild>\n          {/* Sidebar component, swap this element with another sidebar if you like */}\n          <div className=\"flex grow flex-col gap-y-5 overflow-y-auto bg-gray-900 px-6 pb-2 ring-1 ring-white/10\">\n            <div className=\"flex h-16 shrink-0 items-center\">\n              <a href=\"/\" className=\"cursor-pointer\">\n                <AppLogo variant=\"light\" size=\"medium\" />\n              </a>\n            </div>\n            <nav className=\"flex flex-1 flex-col\">\n              <ul role=\"list\" className=\"flex flex-1 flex-col gap-y-7\">\n                <li>\n                  <ul role=\"list\" className=\"-mx-2 space-y-1\">\n                    {navigation.map((item) => (\n                      <li key={item.name}>\n                        <a\n                          href={item.href}\n                          target={item.external ? '_blank' : undefined}\n                          rel={item.external ? 'noopener noreferrer' : undefined}\n                          className={classNames(\n                            item.current\n                              ? 'bg-gray-800 text-white'\n                              : 'text-gray-400 hover:bg-gray-800 hover:text-white',\n                            'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold',\n                          )}\n                        >\n                          <item.icon aria-hidden=\"true\" className=\"size-6 shrink-0\" />\n                          <span className=\"flex-1\">{item.name}</span>\n                          {item.external && (\n                            <ArrowTopRightOnSquareIcon aria-hidden=\"true\" className=\"size-4 text-gray-400 self-center\" />\n                          )}\n                        </a>\n                      </li>\n                    ))}\n                  </ul>\n                </li>\n                {/* <li>\n                  <div className=\"text-xs/6 font-semibold text-gray-400\">Your teams</div>\n                  <ul role=\"list\" className=\"-mx-2 mt-2 space-y-1\">\n                    {teams.map((team) => (\n                      <li key={team.name}>\n                        <a\n                          href={team.href}\n                          className={classNames(\n                            team.current\n                              ? 'bg-gray-800 text-white'\n                              : 'text-gray-400 hover:bg-gray-800 hover:text-white',\n                            'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold',\n                          )}\n                        >\n                          <span className=\"flex size-6 shrink-0 items-center justify-center rounded-lg border border-gray-700 bg-gray-800 text-[0.625rem] font-medium text-gray-400 group-hover:text-white\">\n                            {team.initial}\n                          </span>\n                          <span className=\"truncate\">{team.name}</span>\n                        </a>\n                      </li>\n                    ))}\n                  </ul>\n                </li> */}\n              </ul>\n            </nav>\n          </div>\n        </DialogPanel>\n      </div>\n    </Dialog>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/sidebar-navigation.tsx",
    "content": "'use client'\n\nimport {\n  HomeIcon,\n  DocumentTextIcon,\n} from '@heroicons/react/24/outline'\nimport CONFIG from '../../../config'\nimport type React from 'react'\n\ninterface NavItem {\n  name: string;\n  href: string;\n  icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;\n  current: boolean;\n  external?: boolean;\n}\n\nexport const navigation: NavItem[] = [\n  { name: 'Home', href: '#', icon: HomeIcon, current: true, external: false },\n  // { name: 'Docs', href: CONFIG.docsURL, icon: DocumentTextIcon, current: false, external: true },\n  // { name: 'Team', href: '#', icon: UsersIcon, current: false },\n  // { name: 'Projects', href: '#', icon: FolderIcon, current: false },\n  // { name: 'Calendar', href: '#', icon: CalendarIcon, current: false },\n  // { name: 'Documents', href: '#', icon: DocumentDuplicateIcon, current: false },\n  // { name: 'Reports', href: '#', icon: ChartPieIcon, current: false },\n]\n\nexport const teams = [\n  { id: 1, name: 'Heroicons', href: '#', initial: 'H', current: false },\n  { id: 2, name: 'Tailwind Labs', href: '#', initial: 'T', current: false },\n  { id: 3, name: 'Workcation', href: '#', initial: 'W', current: false },\n]\n\nexport function classNames(...classes: string[]) {\n  return classes.filter(Boolean).join(' ')\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/subscription-section.tsx",
    "content": "'use client'\n\nimport type { Profile } from '@/types/database'\nimport { SubscriptionManagement } from '@/components/stripe/subscription-management'\nimport { PricingCard } from '@/components/stripe/client-pricing-card'\nimport { tiers } from '@/utils/stripe/tiers'\nimport type { User } from '@supabase/supabase-js'\n\ninterface SubscriptionSectionProps {\n  userEmail?: string;\n  userId?: string;\n  profile?: Profile | null;\n}\n\nexport function SubscriptionSection({ userEmail, userId, profile }: SubscriptionSectionProps) {\n  return (\n    <div className=\"space-y-4\">\n      {/* Centered subscription content */}\n      <div className=\"max-w-md mx-auto\">\n        <div className=\"bg-white border border-gray-200 overflow-hidden\">\n          <div className=\"px-6 py-5 border-b border-gray-200 bg-gray-50 flex items-center justify-center\">\n            <div className=\"flex items-center space-x-2\">\n              <svg xmlns=\"http://www.w3.org/2000/svg\" className=\"h-5 w-5 text-gray-500\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n                <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z\" />\n              </svg>\n              <h3 className=\"text-base font-medium text-gray-900\">Subscription Status</h3>\n            </div>\n          </div>\n          \n          <div className=\"px-6 py-5\">\n            {!profile?.subscription_status || profile?.subscription_status === 'inactive' ? (\n              <div className=\"p-0\">\n                <PricingCard \n                  tier={tiers[0]} \n                  user={userId ? { id: userId, email: userEmail } as User : null}\n                  profile={profile || null}\n                />\n              </div>\n            ) : (\n              <div className=\"space-y-4\">\n                <div className=\"bg-green-50 rounded-md p-3\">\n                  <div className=\"flex\">\n                    <div className=\"flex-shrink-0\">\n                      <svg xmlns=\"http://www.w3.org/2000/svg\" className=\"h-5 w-5 text-green-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n                        <path fillRule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clipRule=\"evenodd\" />\n                      </svg>\n                    </div>\n                    <div className=\"ml-3\">\n                      <h3 className=\"text-sm font-medium text-green-800\">Subscription Active</h3>\n                      <div className=\"mt-2 text-sm text-green-700\">\n                        <p>Status: <span className=\"font-medium\">{profile.subscription_status}</span></p>\n                        {profile.current_period_end && (\n                          <p className=\"mt-1\">Current period ends: <span className=\"font-medium\">{new Date(profile.current_period_end).toLocaleDateString()}</span></p>\n                        )}\n                      </div>\n                    </div>\n                  </div>\n                </div>\n                \n                <div className=\"flex justify-center\">\n                  {profile?.stripe_customer_id && (\n                    <SubscriptionManagement \n                      customerId={profile.stripe_customer_id} \n                      className=\"w-full flex flex-col items-center\"\n                    />\n                  )}\n                </div>\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/dashboard/vm-instances-manager.tsx",
    "content": "'use client'\n\nimport { useState, useEffect } from 'react'\nimport { ComputerDesktopIcon, ArrowPathIcon } from '@heroicons/react/24/outline'\nimport { supabase } from '@/utils/supabase/client'\nimport type { CyberdeskInstance } from '../../types/database'\n\nexport function VMInstancesManager() {\n  const [vmInstances, setVMInstances] = useState<CyberdeskInstance[]>([])\n  const [isLoading, setIsLoading] = useState(true)\n  const [isRefreshing, setIsRefreshing] = useState(false)\n  const [error, setError] = useState<string | null>(null)\n  const [now, setNow] = useState(new Date())\n\n  // Update current time every second for the running timer\n  useEffect(() => {\n    const timer = setInterval(() => {\n      setNow(new Date())\n    }, 1000)\n    \n    return () => clearInterval(timer)\n  }, [])\n\n  useEffect(() => {\n    fetchVMInstances()\n  }, [])\n  \n  const fetchVMInstances = async (isRefresh = false) => {\n    if (isRefresh) {\n      setIsRefreshing(true)\n    } else {\n      setIsLoading(true)\n    }\n    setError(null)\n    try {\n      // Get the current user session\n      const { data, error } = await supabase.auth.getSession()\n      \n      if (error || !data.session) {\n        console.error('Authentication error:', error)\n        setError('Authentication error. Please sign in again.')\n        setIsLoading(false)\n        return\n      }\n      \n      const userId = data.session.user.id\n      \n      // Fetch VM instances for this user\n      const { data: instances, error: fetchError } = await supabase\n        .from('cyberdesk_instances')\n        .select('*')\n        .eq('user_id', userId)\n        .order('created_at', { ascending: false })\n      \n      if (fetchError) {\n        throw new Error(fetchError.message)\n      }\n      \n      setVMInstances(instances || [])\n    } catch (err) {\n      console.error('Error fetching VM instances:', err)\n      setError('Failed to load your VM instances. Please try again.')\n    } finally {\n      setIsLoading(false)\n      setIsRefreshing(false)\n    }\n  }\n\n  // Format the duration as HH:MM:SS\n  const formatDuration = (startTime: string) => {\n    // Parse the UTC timestamp\n    const start = new Date(startTime)\n    const utcStart = new Date(start.getTime() - start.getTimezoneOffset() * 60 * 1000)\n    // Calculate duration in seconds\n    const diff = Math.floor((now.getTime() - utcStart.getTime()) / 1000)\n    \n    const hours = Math.floor(diff / 3600)\n    const minutes = Math.floor((diff % 3600) / 60)\n    const seconds = diff % 60\n    \n    return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`\n  }\n\n  // Format date for display - explicitly convert UTC to local time\n  const formatDate = (dateString: string) => {\n    try {\n      // Parse the ISO string into a Date object\n      const utcDate = new Date(dateString)\n      const date = new Date(utcDate.getTime() - utcDate.getTimezoneOffset() * 60 * 1000)\n      \n      // Format the date in local time with a consistent format without timezone\n      return date.toLocaleString('en-US', {\n        year: 'numeric',\n        month: 'short',\n        day: 'numeric',\n        hour: 'numeric',\n        minute: '2-digit',\n        second: '2-digit',\n        hour12: true\n      })\n    } catch (error) {\n      console.error('Error formatting date:', error)\n      return 'Invalid date'\n    }\n  }\n\n  // Check if VM is still running (use status field)\n  const isVMRunning = (instance: CyberdeskInstance) => {\n    return instance.status === 'running' || instance.status === 'pending';\n  }\n  \n  // Get the terminated time (use timeout_at if status is terminated or error)\n  const getTerminatedTime = (instance: CyberdeskInstance) => {\n    if (instance.status === 'terminated' || instance.status === 'error') {\n      return formatDate(instance.timeout_at)\n    }\n    const running = isVMRunning(instance)\n    if (running) return '-'\n    // fallback: show timeout_at\n    return formatDate(instance.timeout_at)\n  }\n\n  return (\n    <div className=\"bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden\">\n      <div className=\"px-6 py-5 border-b border-gray-200 bg-gray-50 flex items-center justify-between\">\n        <div className=\"flex items-center space-x-2\">\n          <ComputerDesktopIcon className=\"h-5 w-5 text-gray-500\" />\n          <h3 className=\"text-base font-medium text-gray-900\">Your VM Instances</h3>\n        </div>\n        <div className=\"flex items-center space-x-4\">\n          <div className=\"text-sm text-gray-500\">\n            View your virtual machine usage history\n          </div>\n          <button\n            onClick={() => fetchVMInstances(true)}\n            disabled={isRefreshing}\n            className=\"p-2 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors\"\n            title=\"Refresh VM instances\"\n          >\n            <ArrowPathIcon \n              className={`h-5 w-5 text-gray-500 ${isRefreshing ? 'animate-spin' : ''}`} \n              aria-hidden=\"true\" \n            />\n          </button>\n        </div>\n      </div>\n      \n      <div className=\"px-6 py-5\">\n        {isLoading ? (\n          <div className=\"flex items-center justify-center py-6\">\n            <div className=\"animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900\"></div>\n          </div>\n        ) : error ? (\n          <div className=\"text-red-500 py-2\">{error}</div>\n        ) : vmInstances.length > 0 ? (\n          <div className=\"overflow-x-auto\">\n            <table className=\"min-w-full divide-y divide-gray-200\">\n              <thead>\n                <tr>\n                  <th scope=\"col\" className=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n                    VM ID\n                  </th>\n                  <th scope=\"col\" className=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n                    Status\n                  </th>\n                  <th scope=\"col\" className=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n                    Created\n                  </th>\n                  <th scope=\"col\" className=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n                    Terminated\n                  </th>\n                </tr>\n              </thead>\n              <tbody className=\"bg-white divide-y divide-gray-200\">\n                {vmInstances.map((instance) => {\n                  const running = isVMRunning(instance)\n                  return (\n                    <tr key={instance.id}>\n                      <td className=\"px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900\">\n                        {instance.id}\n                      </td>\n                      <td className=\"px-6 py-4 whitespace-nowrap text-sm\">\n                        {running ? (\n                          <span className=\"px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800\">\n                            Running ({formatDuration(instance.created_at)}) \n                          </span>\n                        ) : (\n                          <span className=\"px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800\">\n                            Terminated\n                          </span>\n                        )}\n                      </td>\n                      <td className=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n                        {formatDate(instance.created_at)}\n                      </td>\n                      <td className=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n                        {getTerminatedTime(instance)}\n                      </td>\n                    </tr>\n                  )\n                })}\n              </tbody>\n            </table>\n          </div>\n        ) : (\n          <div className=\"text-center py-6\">\n            <div className=\"bg-yellow-50 border border-yellow-200 rounded-md p-3 mb-4\">\n              <div className=\"flex\">\n                <div className=\"flex-shrink-0\">\n                  <ComputerDesktopIcon className=\"h-5 w-5 text-yellow-400\" aria-hidden=\"true\" />\n                </div>\n                <div className=\"ml-3\">\n                  <h3 className=\"text-start text-sm font-medium text-yellow-800\">No VM Instances Found</h3>\n                  <div className=\"mt-2 text-sm text-yellow-700\">\n                    <p>You have not launched any VMs yet.</p>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/demo-section.tsx",
    "content": "'use client'\n\nimport { Button } from '@/components/button'\nimport { supabase } from '@/utils/supabase/client'\nimport { ComputerDesktopIcon } from '@heroicons/react/24/outline'\nimport { ChevronRightIcon } from '@heroicons/react/24/solid'\nimport React, { useEffect, useState, useRef } from 'react'\n\nconst DESKTOP_TIMEOUT_MS = 600000\n\n// Types\ninterface DemoSectionProps {\n  onDesktopDeployed: (id: string) => void\n  onDesktopStopped: () => void\n  hideIntro?: boolean\n  desktopId?: string\n}\n\ninterface DesktopLaunchResponse {\n  id: string\n  status: string\n}\n\n// Main component\nexport function DemoSection({\n  onDesktopDeployed,\n  onDesktopStopped,\n  desktopId,\n}: DemoSectionProps) {\n  const [isLoading, setIsLoading] = useState(false)\n  const [streamUrl, setStreamUrl] = useState<string>(\"\")\n  const [isLoggedIn, setIsLoggedIn] = useState(false)\n  const [authLoading, setAuthLoading] = useState(true)\n  const [isDemoLaunched, setIsDemoLaunched] = useState(false)\n  const [hasEverLaunchedDemo, setHasEverLaunchedDemo] = useState(false)\n  const [error, setError] = useState<string | null>(null)\n  const demoContentRef = useRef<HTMLDivElement>(null)\n\n  // Check if user is logged in\n  useEffect(() => {\n    const checkAuthStatus = async () => {\n      try {\n        const { data } = await supabase.auth.getSession()\n        setIsLoggedIn(!!data.session)\n      } catch (error) {\n        console.error('Error checking auth status:', error)\n      } finally {\n        setAuthLoading(false)\n      }\n    }\n    checkAuthStatus()\n  }, [])\n\n  // Function to launch the demo\n  const launchDemo = async () => {\n    setIsLoading(true)\n    setIsDemoLaunched(true)\n    setHasEverLaunchedDemo(true)\n    setError(null)\n    setStreamUrl(\"\")\n\n    // Scroll to the demo content\n    const isMobile = window.innerWidth < 768\n    if (demoContentRef.current) {\n      if (isMobile) {\n        demoContentRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' })\n      } else {\n        demoContentRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' })\n      }\n    }\n\n    try {\n      const { status, id } = await deployVirtualDesktop()\n      if (!id || status === 'error') {\n        setIsLoading(false)\n        setError('Failed to deploy virtual desktop.')\n        setIsDemoLaunched(false)\n        return\n      }\n      if (onDesktopDeployed) onDesktopDeployed(id)\n\n      // Poll for status\n      let running = false\n      let delay = 500 // Start with 0.5s\n      let firstPoll = true\n      while (!running) {\n        try {\n          const data = await getDetailsVirtualDesktop(id)\n          if (data.status === 'running') {\n            setStreamUrl(data.stream_url || \"\")\n            running = true\n            setIsLoading(false)\n            break\n          } else if (data.status === 'error' || data.status === 'unavailable') {\n            setError('Desktop failed to start or is unavailable.')\n            setIsLoading(false)\n            setIsDemoLaunched(false)\n            break\n          }\n        } catch (err) {\n          setError('Error polling desktop status.')\n          setIsLoading(false)\n          setIsDemoLaunched(false)\n          break\n        }\n        await new Promise(res => setTimeout(res, delay))\n        if (firstPoll) {\n          delay = 1000 // After first poll, always use 1s\n          firstPoll = false\n        }\n      }\n    } catch (error) {\n      setError('Error during launch.')\n      setIsLoading(false)\n      setIsDemoLaunched(false)\n    }\n  }\n\n  // Function to handle stopping the desktop\n  const handleStopDesktop = async (id: string) => {\n    setIsDemoLaunched(false)\n    setIsLoading(true)\n    const success = await stopVirtualDesktop(id)\n    if (success) {\n      setStreamUrl(\"\")\n      setTimeout(() => {\n        setIsLoading(false)\n        setStreamUrl(\"\")\n        onDesktopStopped()\n      }, 800)\n    } else {\n      setIsLoading(false)\n    }\n  }\n\n  return (\n    <div className=\"flex h-full w-full rounded-lg border border-gray-200 bg-gray-50 overflow-hidden flex-col relative\">\n      <DemoHeader\n        isDemoLaunched={isDemoLaunched}\n        desktopId={desktopId}\n        onStopDesktop={handleStopDesktop}\n      />\n      <DemoContent\n        isLoading={isLoading}\n        isDemoLaunched={isDemoLaunched}\n        hasEverLaunchedDemo={hasEverLaunchedDemo}\n        streamUrl={streamUrl}\n        isLoggedIn={isLoggedIn}\n        onLaunchDemo={launchDemo}\n        error={error}\n        ref={demoContentRef}\n      />\n      {isDemoLaunched && desktopId && (\n        <div className=\"md:hidden w-full flex justify-center mt-4 mb-6\">\n          <button\n            onClick={() => handleStopDesktop(desktopId)}\n            className=\"inline-flex items-center rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-700\"\n          >\n            <ComputerDesktopIcon className=\"mr-2 h-4 w-4\" />\n            Stop Desktop\n          </button>\n        </div>\n      )}\n    </div>\n  )\n}\n\nconst DemoHeader = ({\n  isDemoLaunched,\n  desktopId,\n  onStopDesktop,\n}: {\n  isDemoLaunched: boolean\n  desktopId?: string\n  onStopDesktop: (id: string) => Promise<void>\n}) => (\n  <div className=\"border-b border-gray-200 bg-white p-6\">\n    <div className=\"flex flex-col justify-between gap-6 md:flex-row md:items-center\">\n      <div className=\"flex-shrink-0 md:max-w-xs\">\n        <h3 className=\"text-lg font-semibold text-gray-900\">\n          Try an interactive demo\n        </h3>\n        <p className=\"mt-2 text-sm text-gray-600\">\n          See how easy it is to deploy a virtual desktop with our API\n        </p>\n\n        {isDemoLaunched && desktopId && (\n          <button\n            onClick={() => onStopDesktop(desktopId)}\n            className=\"mt-4 hidden md:inline-flex items-center rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-700\"\n          >\n            <ComputerDesktopIcon className=\"mr-2 h-4 w-4\" />\n            Stop Desktop\n          </button>\n        )}\n      </div>\n    </div>\n  </div>\n)\n\nconst loadingMessages = [\n  'Requesting a new virtual desktop...',\n  'Provisioning resources in the cloud...',\n  'Booting up the operating system...',\n  'Establishing a secure connection...',\n  'Preparing your remote desktop experience...'\n]\n\nconst LoadingState = ({ mode }: { mode: 'starting' | 'stopping' }) => {\n  const [msgIdx, setMsgIdx] = useState(0)\n  const [fade, setFade] = useState(true)\n  const [showWaitMsg, setShowWaitMsg] = useState(false)\n\n  useEffect(() => {\n    if (mode !== 'starting') return\n    const interval = setInterval(() => {\n      setFade(false)\n      setTimeout(() => {\n        setMsgIdx((idx) => (idx + 1) % loadingMessages.length)\n        setFade(true)\n      }, 300)\n    }, 2000)\n    return () => clearInterval(interval)\n  }, [mode])\n\n  useEffect(() => {\n    if (mode !== 'starting') return\n    const timer = setTimeout(() => setShowWaitMsg(true), 5000)\n    return () => clearTimeout(timer)\n  }, [mode])\n\n  return (\n    <div className=\"flex h-full w-full flex-col items-center justify-center bg-gray-50\">\n      <div className=\"h-12 w-12 animate-spin rounded-full border-b-2 border-indigo-600 mb-6\"></div>\n      {mode === 'starting' && (\n        <>\n          <div\n            className={`transition-opacity duration-300 text-lg text-gray-700 font-medium ${fade ? 'opacity-100' : 'opacity-0'}`}\n            style={{ minHeight: 32 }}\n          >\n            {loadingMessages[msgIdx]}\n          </div>\n          {showWaitMsg && (\n            <div className=\"mt-2 text-sm text-gray-400\">This may take up to a minute. Thank you for your patience!</div>\n          )}\n        </>\n      )}\n    </div>\n  )\n}\n\nconst DesktopIframe = ({ streamUrl, isLoading }: { streamUrl: string, isLoading: boolean }) => {\n  return (\n    <div className=\"w-full h-[400px] md:h-[500px] bg-black relative overflow-hidden flex items-center justify-center\">\n      <iframe\n        src={streamUrl}\n        className=\"w-full h-full\"\n        style={{ display: streamUrl && streamUrl !== 'about:blank' ? 'block' : 'none', border: 'none' }}\n        allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; clipboard-read; clipboard-write; fullscreen\"\n        allowFullScreen\n        title=\"Virtual Desktop Stream\"\n      ></iframe>\n      {isLoading && (\n        <div className=\"absolute inset-0 flex items-center justify-center bg-gray-800 text-white z-10\">\n          <div className=\"flex flex-col items-center\">\n            <div className=\"h-8 w-8 animate-spin rounded-full border-b-2 border-indigo-400 mb-3\"></div>\n            Preparing stream...\n          </div>\n        </div>\n      )}\n    </div>\n  )\n}\n\nconst InitialDemoState = ({ onLaunchDemo }: { onLaunchDemo: () => void }) => (\n  <div className=\"h-full w-full flex items-center justify-center\">\n    <div className=\"text-center\">\n      <ComputerDesktopIcon className=\"mx-auto mb-4 h-16 w-16 text-gray-300\" />\n      <p className=\"text-center text-lg font-medium\">\n        Deploy a virtual desktop\n      </p>\n      <p className=\"mt-2 max-w-md text-center text-sm\">\n        Production ready, secure, and scalable\n      </p>\n\n      <button\n        onClick={onLaunchDemo}\n        className=\"mx-auto mt-6 flex items-center rounded-lg bg-indigo-600 px-4 py-2 font-medium text-white transition-colors hover:bg-indigo-700\"\n      >\n        <ComputerDesktopIcon className=\"mr-2 h-4 w-4\" />\n        Launch demo\n      </button>\n    </div>\n  </div>\n)\n\nconst PostDemoState = ({ isLoggedIn }: { isLoggedIn: boolean }) => (\n  <div className=\"h-full w-full flex items-center justify-center\">\n    <div className=\"text-center\">\n      <div className=\"mb-4 inline-flex h-12 w-12 items-center justify-center rounded-full bg-indigo-100\">\n        <ComputerDesktopIcon className=\"h-6 w-6 text-indigo-600\" />\n      </div>\n      <h3 className=\"mb-2 text-lg font-medium text-gray-900\">\n        Ready to deploy your own?\n      </h3>\n      <p className=\"mb-4 text-sm text-gray-500\">\n        Create your first virtual desktop in a few clicks.\n      </p>\n      <Button\n        href={isLoggedIn ? '/dashboard' : '/login'}\n        className=\"inline-flex items-center rounded-lg bg-indigo-600 px-5 py-3 font-medium text-white transition-colors hover:bg-indigo-700\"\n      >\n        Get started\n        <ChevronRightIcon className=\"ml-2 h-5 w-5\" />\n      </Button>\n    </div>\n  </div>\n)\n\nconst DemoContent = React.forwardRef<\n  HTMLDivElement,\n  {\n    isLoading: boolean\n    isDemoLaunched: boolean\n    hasEverLaunchedDemo: boolean\n    streamUrl: string\n    isLoggedIn: boolean\n    onLaunchDemo: () => void\n    error?: string | null\n  }\n>(({ isLoading, isDemoLaunched, hasEverLaunchedDemo, streamUrl, isLoggedIn, onLaunchDemo, error }, ref) => {\n  let content\n  if (isLoading) {\n    content = <LoadingState mode={isDemoLaunched ? 'starting' : 'stopping'} />\n  } else if (error) {\n    content = (\n      <div className=\"flex flex-col items-center justify-center w-full h-full text-center p-8\">\n        <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth={1.5} stroke=\"currentColor\" className=\"w-12 h-12 mb-4 animate-pulse text-red-500\">\n          <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.008v.008H12v-.008Z\" />\n        </svg>\n        <h3 className=\"text-xl font-semibold mb-2 text-red-600\">Oops! Something went wrong.</h3>\n        <p className=\"text-md text-gray-600\">{error}</p>\n      </div>\n    )\n  } else if (isDemoLaunched) {\n    content = <DesktopIframe streamUrl={streamUrl} isLoading={isLoading} />\n  } else {\n    content = !hasEverLaunchedDemo ? (\n      <InitialDemoState onLaunchDemo={onLaunchDemo} />\n    ) : (\n      <PostDemoState isLoggedIn={isLoggedIn} />\n    )\n  }\n  return (\n    <div ref={ref} className=\"flex-grow flex justify-center items-center min-h-[400px] max-h-[400px] md:min-h-[500px] md:max-h-[500px]\">\n      {content}\n    </div>\n  )\n})\nDemoContent.displayName = 'DemoContent'\n\n// API functions\nconst deployVirtualDesktop = async (): Promise<DesktopLaunchResponse> => {\n  try {\n    const apiResponse = await fetch('/api/playground/desktop', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({ timeoutMs: DESKTOP_TIMEOUT_MS })\n    });\n\n    const responseData = await apiResponse.json();\n\n    if (!apiResponse.ok) {\n      console.error('Backend API error:', responseData.error || `Status: ${apiResponse.status}`);\n      // Handle error appropriately in the UI if needed\n      return {\n        id: '',\n        status: 'error'\n      };\n    } else {\n      // TODO: Maybe use the returned streamUrl and id?\n      // Example: setStreamUrl(responseData.streamUrl); onDesktopDeployed(responseData.streamUrl, responseData.id);\n      return {\n        id: responseData.id,\n        status: responseData.status\n      };\n    }\n  } catch (error) {\n    console.error('Error calling backend API:', error);\n    // Handle fetch error appropriately\n    return {\n      id: '',\n      status: 'error'\n    };\n  }\n}\n\nconst stopVirtualDesktop = async (id: string): Promise<boolean> => {\n  try {\n    const response = await fetch('/api/playground/desktop', {\n      method: 'PATCH',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({ id }),\n    })\n\n    if (!response.ok) {\n      throw new Error('Failed to stop desktop')\n    }\n\n    return true\n  } catch (error) {\n    console.error('Error stopping desktop:', error)\n    return false\n  }\n}\n\nconst getDetailsVirtualDesktop = async (id: string): Promise<{ status: string; stream_url?: string; error?: string }> => {\n  try {\n    const response = await fetch(`/api/playground/desktop?id=${id}`)\n    const data = await response.json()\n    if (!response.ok) {\n      return { status: 'error', error: data.error || `Status: ${response.status}` }\n    }\n    return data\n  } catch (error) {\n    return { status: 'error', error: (error as Error).message }\n  }\n}\n"
  },
  {
    "path": "apps/web/src/components/feature-section.tsx",
    "content": "import { Container } from '@/components/container'\nimport { Heading } from '@/components/text'\nimport { Screenshot } from '@/components/screenshot'\n\nexport function FeatureSection() {\n  return (\n    <div className=\"overflow-hidden\">\n      <Container className=\"pb-24\">\n        <Heading as=\"h2\" className=\"max-w-3xl\">\n          Spin up thousands of concurrent desktops in seconds\n        </Heading>\n        <Screenshot\n          width={1216}\n          height={768}\n          src=\"/screenshots/app.png\"\n          className=\"mt-16 h-[36rem] sm:h-auto sm:w-[76rem]\"\n        />\n      </Container>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/footer.tsx",
    "content": "'use client'\nimport { PlusGrid, PlusGridItem, PlusGridRow } from '@/components/plus-grid'\nimport { Button } from './button'\nimport { Container } from './container'\nimport { Gradient } from './gradient'\nimport { Link } from './link'\nimport { LogoText } from './LogoText' // Import the new component\nimport { Subheading } from './text'\nimport { useState, useEffect } from 'react'\nimport { supabase } from '@/utils/supabase/client'\nimport { AppLogo } from './shared/app-logo'\n\nfunction CallToAction() {\n  return (\n    <div className=\"relative pt-20 pb-16 text-center sm:py-24\">\n      <hgroup>\n        <Subheading>Book a demo</Subheading>\n        <p className=\"mt-6 text-3xl font-medium tracking-tight text-gray-950 sm:text-5xl\">\n          Dealing with high-volume\n          <br />\n          repetitive computer tasks?\n        </p>\n      </hgroup>\n      <p className=\"mx-auto mt-6 max-w-xs text-sm/6 text-gray-500\">\n        We&apos;d love to chat and see how we can help\n      </p>\n      <div className=\"mt-6\">\n        <Button className=\"w-full sm:w-auto\" href=\"https://cal.com/mahmoud-al-madi-klrs5s/30min\" target=\"_blank\" rel=\"noopener noreferrer\">\n          Book a demo\n        </Button>\n      </div>\n    </div>\n  )\n}\n\nfunction SitemapHeading({ children }: { children: React.ReactNode }) {\n  return <h3 className=\"text-sm/6 font-medium text-gray-950/50\">{children}</h3>\n}\n\nfunction SitemapLinks({ children }: { children: React.ReactNode }) {\n  return <ul className=\"mt-6 space-y-4 text-sm/6\">{children}</ul>\n}\n\nfunction SitemapLink(props: React.ComponentPropsWithoutRef<typeof Link>) {\n  return (\n    <li>\n      <Link\n        {...props}\n        className=\"font-medium text-gray-950 data-hover:text-gray-950/75\"\n      />\n    </li>\n  )\n}\n\nfunction Sitemap() {\n  return (\n    <>\n      {/*\n      <div>\n        <SitemapHeading>Product</SitemapHeading>\n        <SitemapLinks>\n          <SitemapLink href=\"/pricing\">Pricing</SitemapLink>\n          <SitemapLink href={CONFIG.docsURL} target=\"_blank\" rel=\"noopener noreferrer\">\n            <span>Docs</span>\n            <ArrowTopRightOnSquareIcon className=\"ml-1 h-4 w-4 text-gray-500\" aria-hidden=\"true\" />\n          </SitemapLink>\n          <SitemapLink href=\"#\">API</SitemapLink>\n        </SitemapLinks>\n      </div>\n      */}\n      <div>\n        <SitemapHeading>Legal</SitemapHeading>\n        <SitemapLinks>\n          <SitemapLink href=\"/terms\">Terms of Service</SitemapLink>\n          <SitemapLink href=\"/privacy\">Privacy Policy</SitemapLink>\n        </SitemapLinks>\n      </div>\n      {/* <div>\n        <SitemapHeading>Company</SitemapHeading>\n        <SitemapLinks>\n          <SitemapLink href=\"#\">Careers</SitemapLink>\n          <SitemapLink href=\"/blog\">Blog</SitemapLink>\n          <SitemapLink href=\"/company\">Company</SitemapLink>\n        </SitemapLinks>\n      </div>\n      <div>\n        <SitemapHeading>Support</SitemapHeading>\n        <SitemapLinks>\n          <SitemapLink href=\"#\">Help center</SitemapLink>\n          <SitemapLink href=\"#\">Community</SitemapLink>\n        </SitemapLinks>\n      </div>\n      <div>\n        <SitemapHeading>Company</SitemapHeading>\n        <SitemapLinks>\n          <SitemapLink href=\"#\">Terms of service</SitemapLink>\n          <SitemapLink href=\"#\">Privacy policy</SitemapLink>\n        </SitemapLinks>\n      </div> */}\n    </>\n  )\n}\n\nfunction SocialIconX(props: React.ComponentPropsWithoutRef<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 16 16\" fill=\"currentColor\" {...props}>\n      <path d=\"M12.6 0h2.454l-5.36 6.778L16 16h-4.937l-3.867-5.594L2.771 16H.316l5.733-7.25L0 0h5.063l3.495 5.114L12.6 0zm-.86 14.376h1.36L4.323 1.539H2.865l8.875 12.837z\" />\n    </svg>\n  )\n}\n\nfunction SocialIconFacebook(props: React.ComponentPropsWithoutRef<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 16 16\" fill=\"currentColor\" {...props}>\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M16 8.05C16 3.603 12.418 0 8 0S0 3.604 0 8.05c0 4.016 2.926 7.346 6.75 7.95v-5.624H4.718V8.05H6.75V6.276c0-2.017 1.194-3.131 3.022-3.131.875 0 1.79.157 1.79.157v1.98h-1.008c-.994 0-1.304.62-1.304 1.257v1.51h2.219l-.355 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.95z\"\n      />\n    </svg>\n  )\n}\n\nfunction SocialIconLinkedIn(props: React.ComponentPropsWithoutRef<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 16 16\" fill=\"currentColor\" {...props}>\n      <path d=\"M14.82 0H1.18A1.169 1.169 0 000 1.154v13.694A1.168 1.168 0 001.18 16h13.64A1.17 1.17 0 0016 14.845V1.15A1.171 1.171 0 0014.82 0zM4.744 13.64H2.369V5.996h2.375v7.644zm-1.18-8.684a1.377 1.377 0 11.52-.106 1.377 1.377 0 01-.527.103l.007.003zm10.075 8.683h-2.375V9.921c0-.885-.015-2.025-1.234-2.025-1.218 0-1.425.966-1.425 1.968v3.775H6.233V5.997H8.51v1.05h.032c.317-.601 1.09-1.235 2.246-1.235 2.405-.005 2.851 1.578 2.851 3.63v4.197z\" />\n    </svg>\n  )\n}\n\n// Social links currently not in use.\n\nfunction Copyright() {\n  return (\n    <div className=\"text-sm/6 text-gray-950\">\n      &copy; \n      {new Date().getFullYear()} Cyberdesk.io Inc.\n    </div>\n  )\n}\n\nexport function Footer() {\n  const [isLoggedIn, setIsLoggedIn] = useState(false)\n  \n  useEffect(() => {\n    const checkAuthStatus = async () => {\n      const { data } = await supabase.auth.getSession()\n      setIsLoggedIn(!!data.session)\n    }\n    \n    checkAuthStatus()\n  }, [])\n\n  return (\n    <footer className=\"w-full mt-auto\">\n      <Gradient className=\"relative\">\n        <div className=\"absolute inset-2 rounded-4xl bg-white/80\" />\n        <Container>\n          {!isLoggedIn && <CallToAction />}\n          <PlusGrid className=\"pb-16\">\n            <PlusGridRow>\n              <div className=\"grid grid-cols-2 gap-y-10 pb-6 lg:grid-cols-6 lg:gap-8\">\n                <div className=\"col-span-2 flex\">\n                  <PlusGridItem className=\"pt-6 lg:pb-6 flex flex-row space-x-[10px] items-center\">\n                  <AppLogo size=\"large\" className=\"h-9\" />\n                  <LogoText />\n                  </PlusGridItem>\n                </div>\n                <div className=\"col-span-2 grid grid-cols-2 gap-x-8 gap-y-12 lg:col-span-4 lg:grid-cols-subgrid lg:pt-6\">\n                  <Sitemap />\n                </div>\n              </div>\n            </PlusGridRow>\n            <PlusGridRow className=\"flex justify-between\">\n              <div> \n                <PlusGridItem className=\"py-3\">\n                  <Copyright />\n                </PlusGridItem>\n              </div>\n              {/* <div className=\"flex\">\n                <PlusGridItem className=\"flex items-center gap-8 py-3\">\n                  <SocialLinks />\n                </PlusGridItem>\n              </div> */}\n            </PlusGridRow>\n          </PlusGrid>\n        </Container>\n      </Gradient>\n    </footer>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/gradient.tsx",
    "content": "import { clsx } from 'clsx'\n\nexport function Gradient({\n  className,\n  ...props\n}: React.ComponentPropsWithoutRef<'div'>) {\n  return (\n    <div\n      {...props}\n      className={clsx(\n        className,\n        'bg-linear-115 from-[#ffffff] from-28% via-[#ee87cb] via-70% to-[#b060ff] sm:bg-linear-145',\n      )}\n    />\n  )\n}\n\nexport function GradientBackground({ children }: { children?: React.ReactNode }) {\n  return (\n    <div className=\"relative mx-auto max-w-7xl\">\n      <div\n        className={clsx(\n          'absolute -top-44 -right-60 h-60 w-[36rem] transform-gpu md:right-0',\n          'bg-linear-115 from-[#ffffff] from-28% via-[#ee87cb] via-70% to-[#b060ff]',\n          'rotate-[-10deg] rounded-full blur-3xl',\n        )}\n      />\n      {children}\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/hero.tsx",
    "content": "'use client';\n\nimport { Button } from '@/components/button'\nimport { Container } from '@/components/container'\nimport { Gradient } from '@/components/gradient'\nimport { Link } from '@/components/link'\nimport { Navbar } from '@/components/navbar'\nimport { ChevronRightIcon } from '@heroicons/react/16/solid'\nimport { useState, useEffect } from 'react'\nimport { supabase } from '@/utils/supabase/client'\nimport CONFIG from '../../config';\n\nexport function Hero() {\n  const [isLoggedIn, setIsLoggedIn] = useState(false)\n  const [isLoading, setIsLoading] = useState(true)\n  \n  useEffect(() => {\n    const checkAuthStatus = async () => {\n      try {\n        const { data } = await supabase.auth.getSession()\n        setIsLoggedIn(!!data.session)\n      } catch (error) {\n        console.error('Error checking auth status:', error)\n      } finally {\n        setIsLoading(false)\n      }\n    }\n    \n    checkAuthStatus()\n  }, [])\n  return (\n    <div className=\"relative\">\n      <Gradient className=\"absolute inset-2 bottom-0 rounded-4xl ring-1 ring-black/5 ring-inset\" />\n      <Container className=\"relative\">\n        <Navbar\n          // banner={\n          //   <Link\n          //     href=\"/blog/radiant-raises-100m-series-a-from-tailwind-ventures\"\n          //     className=\"flex items-center gap-1 rounded-full bg-fuchsia-950/35 px-3 py-0.5 text-sm/6 font-medium text-white data-hover:bg-fuchsia-950/30\"\n          //   >\n          //     Cyberdesk raises $100M Series A from Tailwind Ventures\n          //     <ChevronRightIcon className=\"size-4\" />\n          //   </Link>\n          // }\n        />\n        <div className=\"pt-16 pb-24 sm:pt-24 sm:pb-32 md:pt-32 md:pb-48\">\n          <h1\n            className=\"font-display text-4xl font-medium tracking-tight text-balance text-gray-950 sm:text-6xl/[0.8] md:text-7xl/[0.8] leading-tight sm:leading-[1.025] md:leading-[1.05] space-y-1\"\n          >\n            <span className=\"block mb-1\">\n              AI agents for highly reliable\n            </span>\n            <span className=\"block\">\n              computer task automations\n            </span>\n          </h1>\n          <p className=\"mt-14 max-w-lg text-xl/7 font-medium text-gray-950/75 sm:text-2xl/8\">\n            We integrate our automations with existing systems in healthcare, accounting, supply chain and beyond\n          </p>\n          <div className=\"mt-12 flex flex-col gap-x-6 gap-y-4 sm:flex-row\">\n            {!isLoading && (isLoggedIn ? (\n              <Button href=\"/dashboard\">Go to Dashboard</Button>\n            ) : (\n              <>\n                <Button href=\"https://cal.com/mahmoud-al-madi-klrs5s/30min\" target=\"_blank\" rel=\"noopener noreferrer\">Book a demo</Button>\n                {/*\n                <Button variant=\"secondary\" href={CONFIG.docsURL} target=\"_blank\" rel=\"noopener noreferrer\">\n                  See docs\n                </Button>\n                */}\n              </>\n            ))}\n          </div>\n        </div>\n      </Container>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/keyboard.tsx",
    "content": "'use client'\n\nimport { clsx } from 'clsx'\nimport { motion } from 'framer-motion'\nimport { createContext, useContext } from 'react'\n\nconst KeyboardContext = createContext<{ highlighted: string[] }>({\n  highlighted: [],\n})\n\nfunction Row(props: { children: React.ReactNode }) {\n  return <div {...props} className=\"group flex h-9 gap-2\" />\n}\n\nfunction Key({\n  name,\n  width = 36,\n  className,\n  children,\n}: {\n  name: string\n  width?: number\n  className?: string\n  children?: React.ReactNode\n}) {\n  const { highlighted } = useContext(KeyboardContext)\n\n  return (\n    <motion.div\n      variants={\n        highlighted.includes(name)\n          ? {\n              idle: {\n                scale: 1,\n                boxShadow: `rgb(255, 255, 255) 0px 0px 0px 0px, rgba(47, 127, 221, 0.8) 0px 0px 0px 1px, rgba(47, 127, 221, 0.15) 0px 0px 3px 3px,rgba(47, 127, 221, 0.1) 0px 0px 0px 0px`,\n              },\n              active: {\n                scale: [1, 1.02, 1.02, 1],\n                boxShadow: [\n                  `rgb(255, 255, 255) 0px 0px 0px 0px, rgba(47, 127, 221, 0.8) 0px 0px 0px 1px, rgba(47, 127, 221, 0.15) 0px 0px 3px 3px, rgba(47, 127, 221, 0.05) 0px 0px 0px 0px`,\n                  `rgb(255, 255, 255) 0px 0px 0px 0px, rgba(47, 127, 221, 0.8) 0px 0px 0px 1px, rgba(47, 127, 221, 0.1) 0px 0px 1px 6px, rgba(47, 127, 221, 0.05) 0px 0px 1px 15px`,\n                  `rgb(255, 255, 255) 0px 0px 0px 0px, rgba(47, 127, 221, 0.8) 0px 0px 0px 1px, rgba(47, 127, 221, 0.0) 0px 0px 1px 6px, rgba(47, 127, 221, 0.01) 0px 0px 1px 15px`,\n                  `rgb(255, 255, 255) 0px 0px 0px 0px, rgba(47, 127, 221, 0.8) 0px 0px 0px 1px, rgba(47, 127, 221, 0.0) 0px 0px 3px 3px, rgba(47, 127, 221, 0.00) 0px 0px 0px 0px`,\n                ],\n                transition: {\n                  repeat: Infinity,\n                  ease: 'easeInOut',\n                  duration: 1.75 - highlighted.indexOf(name) * 0.35,\n                  delay: highlighted.indexOf(name) * 0.35,\n                  repeatDelay: 1 + highlighted.indexOf(name) * 0.35,\n                },\n              },\n            }\n          : undefined\n      }\n      style={{ width: `${width / 16}rem` }}\n      className={clsx(\n        className,\n        'flex flex-col items-center justify-center gap-0.5 px-1 py-px',\n        'rounded-sm bg-white bg-linear-to-t from-black/[3%] ring-1 shadow-[0_1px_rgb(0_0_0_/_0.05)] ring-black/10',\n        '[:where(&_svg)]:h-3.5 [:where(&_svg)]:fill-gray-600',\n      )}\n    >\n      {children}\n    </motion.div>\n  )\n}\n\nfunction KeyGroup(props: { children: React.ReactNode }) {\n  return (\n    <div\n      {...props}\n      className=\"grid gap-px rounded-sm bg-black/10 ring-1 ring-black/10 *:ring-0\"\n    />\n  )\n}\n\nfunction EscapeKey() {\n  return (\n    <Key name=\"Escape\" width={62} className=\"rounded-tl-xl\">\n      <svg viewBox=\"0 0 13 14\" className=\"mt-auto mr-auto\">\n        <path d=\"M8.95 8.273v-.671c0-.36.068-.672.206-.938.14-.268.339-.475.594-.621.258-.146.563-.219.914-.219.344 0 .632.063.863.188.232.122.408.28.528.472.12.19.182.388.187.594h-.687a.887.887 0 0 0-.11-.27.668.668 0 0 0-.265-.257c-.123-.073-.29-.11-.504-.11-.334 0-.588.107-.762.32-.172.214-.258.502-.258.864v.656c0 .354.09.638.27.852.182.21.432.316.75.316.203 0 .366-.027.488-.082a.683.683 0 0 0 .285-.215.803.803 0 0 0 .133-.293h.688c-.008.2-.07.395-.188.582a1.283 1.283 0 0 1-.527.454c-.232.114-.529.171-.89.171-.352 0-.657-.073-.915-.218a1.501 1.501 0 0 1-.594-.621 2.058 2.058 0 0 1-.207-.954ZM4.883 7.07c0-.291.071-.528.215-.71.145-.186.334-.32.566-.407.234-.086.484-.129.75-.129.297 0 .555.048.774.145.22.096.393.23.515.402a1 1 0 0 1 .184.602H7.23a.576.576 0 0 0-.105-.282.67.67 0 0 0-.277-.222 1.02 1.02 0 0 0-.457-.09c-.24 0-.438.053-.594.16a.527.527 0 0 0-.23.461.45.45 0 0 0 .093.297.677.677 0 0 0 .242.176c.102.041.213.074.332.097.12.024.237.046.352.067.258.044.492.105.703.183.214.076.384.19.512.34.127.149.191.357.191.625 0 .274-.069.507-.207.7-.135.19-.327.334-.574.433a2.284 2.284 0 0 1-.86.148c-.304 0-.572-.044-.804-.132a1.21 1.21 0 0 1-.543-.387 1.005 1.005 0 0 1-.195-.625H5.5c.018.122.06.228.125.316a.63.63 0 0 0 .285.207c.125.047.283.07.473.07.216 0 .39-.03.523-.09a.657.657 0 0 0 .297-.234.583.583 0 0 0 .094-.324c0-.153-.046-.27-.137-.351a.902.902 0 0 0-.37-.184 6.194 6.194 0 0 0-.52-.121 5.53 5.53 0 0 1-.7-.188 1.165 1.165 0 0 1-.5-.332c-.125-.145-.187-.352-.187-.62ZM2.254 10.066c-.367 0-.682-.073-.945-.218a1.5 1.5 0 0 1-.606-.63 2.092 2.092 0 0 1-.207-.96v-.625c0-.373.073-.693.219-.961.148-.271.351-.48.61-.625.257-.149.549-.223.874-.223.334 0 .628.074.883.223.258.148.46.358.606.629.145.27.218.59.218.957v.496H1.2v.207c0 .318.09.586.27.805.18.218.44.328.781.328.159 0 .3-.022.422-.067a.863.863 0 0 0 .3-.18.538.538 0 0 0 .153-.238h.727a.984.984 0 0 1-.133.352c-.07.125-.17.243-.301.355-.13.11-.292.2-.484.27-.193.07-.42.105-.68.105ZM1.199 7.617h2.004v-.066a1.33 1.33 0 0 0-.125-.594.937.937 0 0 0-.351-.398.96.96 0 0 0-.524-.141.98.98 0 0 0-.527.14.937.937 0 0 0-.352.399 1.33 1.33 0 0 0-.125.594v.066Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F1Key() {\n  return (\n    <Key name=\"F1\">\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M4.948 9.147c-.293 0-.57-.055-.83-.167a2.291 2.291 0 0 1-.686-.465 2.291 2.291 0 0 1-.466-.686A2.093 2.093 0 0 1 2.8 7c0-.293.056-.569.167-.827a2.22 2.22 0 0 1 1.152-1.151c.26-.114.537-.171.83-.171.293 0 .569.057.826.171.261.111.49.267.686.466.2.196.354.425.466.685.114.258.171.534.171.827 0 .293-.057.57-.171.83A2.22 2.22 0 0 1 5.775 8.98a2.061 2.061 0 0 1-.827.167Zm0-.707c.264 0 .504-.065.72-.193.22-.132.395-.308.524-.528.132-.22.198-.46.198-.72a1.36 1.36 0 0 0-.198-.721 1.444 1.444 0 0 0-.523-.523 1.36 1.36 0 0 0-.72-.198c-.261 0-.502.066-.721.198-.22.129-.396.303-.528.523-.129.22-.193.46-.193.72 0 .261.064.502.193.721.132.22.308.396.528.528.22.128.46.193.72.193Zm-3.7-.936a.499.499 0 0 1-.505-.505c0-.141.048-.26.145-.356.1-.1.22-.15.36-.15.138 0 .255.05.352.15.1.096.149.215.149.356 0 .14-.05.26-.15.36a.479.479 0 0 1-.35.145Zm1.081-2.62a.499.499 0 0 1-.505-.505c0-.14.048-.259.145-.356.1-.1.22-.149.36-.149.14 0 .26.05.356.15.1.096.15.215.15.355 0 .141-.05.261-.15.36a.484.484 0 0 1-.356.146ZM4.95 3.8a.499.499 0 0 1-.506-.5c0-.141.048-.26.145-.357.1-.1.22-.149.36-.149.14 0 .26.05.356.15.1.096.15.215.15.355 0 .138-.05.257-.15.356a.484.484 0 0 1-.356.145Zm2.618 1.086a.493.493 0 0 1-.356-.145c-.1-.1-.149-.22-.149-.36 0-.141.05-.26.15-.357.099-.1.218-.149.355-.149.141 0 .26.05.356.15.1.096.15.215.15.355 0 .141-.05.261-.15.36a.484.484 0 0 1-.356.146Zm1.081 2.619a.488.488 0 0 1-.351-.145c-.1-.1-.15-.22-.15-.36 0-.141.05-.26.15-.356.1-.1.217-.15.351-.15.141 0 .26.05.356.15.1.096.15.215.15.356 0 .14-.05.26-.15.36a.484.484 0 0 1-.356.145Zm-1.08 2.619a.493.493 0 0 1-.357-.145c-.1-.1-.149-.22-.149-.36 0-.141.05-.26.15-.356.099-.1.218-.15.355-.15.141 0 .26.05.356.15.1.096.15.215.15.356 0 .14-.05.26-.15.36a.484.484 0 0 1-.356.145Zm-2.62 1.081c-.14 0-.26-.05-.36-.15a.484.484 0 0 1-.145-.355.493.493 0 0 1 .505-.501c.14 0 .26.048.356.145.1.1.15.218.15.356 0 .14-.05.259-.15.356a.477.477 0 0 1-.356.15ZM2.33 10.123a.499.499 0 0 1-.505-.505c0-.141.048-.26.145-.356.1-.1.22-.15.36-.15.14 0 .26.05.356.15.1.096.15.215.15.356 0 .14-.05.26-.15.36a.484.484 0 0 1-.356.145Z\" />\n      </svg>\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M5.847 8.999h-.785V5.762h-.039l-.301.214-.305.216-.302.214V5.68l.475-.34.472-.34h.785v3.999ZM1.722 8.999H.93V5h2.548v.653H1.722V6.77h1.605v.638H1.722V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F2Key() {\n  return (\n    <Key name=\"F2\">\n      <svg viewBox=\"0 0 11 14\">\n        <path d=\"M5.309 9.157c-.293 0-.57-.056-.831-.167a2.251 2.251 0 0 1-1.156-1.156 2.104 2.104 0 0 1-.167-.835c0-.296.056-.573.167-.83a2.235 2.235 0 0 1 1.156-1.156c.26-.115.538-.172.83-.172.294 0 .57.057.831.172.26.111.49.266.686.466.199.199.354.429.466.69.114.257.17.534.17.83 0 .296-.056.574-.17.835a2.188 2.188 0 0 1-.466.686 2.093 2.093 0 0 1-1.516.637Zm0-.712c.263 0 .505-.065.725-.194.22-.131.395-.307.527-.527.132-.22.198-.461.198-.725 0-.264-.066-.505-.198-.725a1.478 1.478 0 0 0-.527-.523 1.383 1.383 0 0 0-.725-.198c-.264 0-.506.066-.726.198a1.49 1.49 0 0 0-.522.523c-.132.22-.198.461-.198.725 0 .264.066.505.198.725.131.22.306.396.522.527.22.13.462.194.726.194Zm.395-5.63v.862c0 .102-.04.193-.119.272a.377.377 0 0 1-.276.114.382.382 0 0 1-.273-.114.382.382 0 0 1-.114-.272v-.862c0-.108.038-.2.114-.276a.375.375 0 0 1 .55 0 .37.37 0 0 1 .118.276ZM7.378 4.38l.616-.615a.37.37 0 0 1 .272-.11.39.39 0 0 1 .281.11.39.39 0 0 1 .11.28.37.37 0 0 1-.11.273l-.61.616a.401.401 0 0 1-.558 0 .385.385 0 0 1-.11-.277c0-.112.036-.204.11-.277Zm2.11 3.01H8.63a.387.387 0 0 1-.277-.114.387.387 0 0 1-.114-.277c0-.108.038-.2.114-.277a.38.38 0 0 1 .277-.118h.857c.105 0 .196.04.272.118a.38.38 0 0 1 0 .554.372.372 0 0 1-.272.114ZM7.932 9.07l.615.61c.074.077.11.17.11.278a.37.37 0 0 1-.11.272.377.377 0 0 1-.276.114.349.349 0 0 1-.273-.11l-.62-.61a.385.385 0 0 1-.11-.277.385.385 0 0 1 .387-.391c.109 0 .201.038.277.114ZM5.704 10.32v.862c0 .108-.04.2-.119.277a.375.375 0 0 1-.55 0 .377.377 0 0 1-.113-.277v-.862c0-.102.038-.193.114-.272a.382.382 0 0 1 .273-.114c.108 0 .2.038.276.114.08.079.12.17.12.272Zm-3.63-.646.615-.606a.36.36 0 0 1 .273-.11.39.39 0 0 1 .281.11.39.39 0 0 1 .11.281c0 .109-.038.2-.114.277l-.611.602a.368.368 0 0 1-.281.11.363.363 0 0 1-.273-.114.363.363 0 0 1-.114-.273.401.401 0 0 1 .114-.277Zm-.936-3.071h.853c.105 0 .196.04.272.118a.38.38 0 0 1 0 .554.372.372 0 0 1-.272.114h-.853a.392.392 0 0 1-.281-.668.384.384 0 0 1 .281-.118Zm1.547-1.67-.606-.616a.37.37 0 0 1-.11-.272.382.382 0 0 1 .386-.391c.112 0 .204.036.277.11l.611.615c.073.073.11.165.11.277a.368.368 0 0 1-.114.277.39.39 0 0 1-.281.11.37.37 0 0 1-.273-.11Z\" />\n      </svg>\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M4.522 6.194h-.75v-.038a1.174 1.174 0 0 1 .337-.844c.114-.117.255-.21.425-.28.172-.071.374-.106.607-.106.275 0 .515.049.72.146.205.098.364.232.475.402.113.168.17.358.17.57 0 .185-.035.349-.106.493a1.825 1.825 0 0 1-.263.401c-.106.124-.217.25-.334.378l-.908 1.002v.038h1.678v.645H3.811v-.492l1.461-1.576c.073-.077.143-.156.211-.238.069-.084.125-.174.17-.27a.715.715 0 0 0 .068-.307.534.534 0 0 0-.29-.486.634.634 0 0 0-.308-.073.608.608 0 0 0-.322.082.562.562 0 0 0-.208.22.618.618 0 0 0-.07.29v.043ZM1.396 8.999h-.79V5h2.548v.653H1.396V6.77h1.606v.638H1.396V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F3Key() {\n  return (\n    <Key name=\"F3\">\n      <svg viewBox=\"0 0 14 14\">\n        <path d=\"M2.104 6.84c-.33 0-.58-.08-.747-.24-.167-.165-.25-.414-.25-.748V4.13c0-.33.083-.578.25-.742.167-.164.416-.246.747-.246h3.824c.33 0 .58.082.747.246.167.164.25.411.25.742v1.723c0 .334-.083.583-.25.747-.167.161-.416.242-.747.242H2.104Zm.075-.707h3.665c.123 0 .216-.03.277-.092.062-.062.092-.154.092-.277V4.226c0-.129-.03-.223-.092-.281-.061-.062-.154-.092-.277-.092H2.18c-.117 0-.206.03-.268.092-.061.058-.092.152-.092.281v1.538c0 .123.03.215.092.277.062.062.151.092.268.092Zm.976 4.702c-.328 0-.576-.082-.743-.246-.167-.164-.25-.411-.25-.742V8.673c0-.328.083-.572.25-.734.167-.164.415-.246.743-.246h3.26c.332 0 .58.082.743.246.167.162.25.406.25.734v1.174c0 .33-.083.578-.25.742-.164.164-.411.246-.742.246H3.155Zm.074-.707h3.103c.123 0 .215-.032.277-.097.061-.061.092-.154.092-.277V8.77c0-.123-.03-.215-.092-.277-.062-.064-.154-.097-.277-.097H3.229a.362.362 0 0 0-.268.097c-.061.062-.092.154-.092.277v.984c0 .123.03.216.092.277a.362.362 0 0 0 .268.097Zm5.977.193c-.331 0-.58-.082-.747-.246-.164-.16-.246-.407-.246-.738V4.723c0-.331.082-.577.246-.739.167-.164.416-.246.747-.246h2.69c.33 0 .58.082.747.246.167.162.25.408.25.739v4.614c0 .331-.083.577-.25.738-.167.164-.416.246-.748.246H9.207Zm.075-.707h2.535c.12 0 .211-.031.273-.093.061-.061.092-.153.092-.276v-4.43c0-.123-.03-.214-.092-.273-.062-.061-.152-.092-.273-.092H9.281c-.117 0-.207.031-.268.092-.062.06-.093.15-.093.273v4.43c0 .123.031.215.093.276.061.062.15.093.268.093Z\" />\n      </svg>\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M4.657 7.244v-.61h.422a.688.688 0 0 0 .322-.072.557.557 0 0 0 .22-.2.505.505 0 0 0 .076-.284.49.49 0 0 0-.176-.392.652.652 0 0 0-.442-.15.741.741 0 0 0-.252.041.627.627 0 0 0-.193.112.496.496 0 0 0-.179.349H3.72c.006-.157.04-.303.102-.437.063-.135.153-.252.27-.352.117-.101.26-.18.428-.237.17-.057.364-.086.583-.088.279-.002.52.042.723.132.203.09.36.214.472.372a.91.91 0 0 1 .173.539.833.833 0 0 1-.12.477.96.96 0 0 1-.619.44v.04a1.007 1.007 0 0 1 .718.434.909.909 0 0 1 .144.522c.002.19-.037.358-.117.507a1.104 1.104 0 0 1-.329.378c-.14.101-.302.18-.486.234-.182.053-.376.08-.583.08-.3 0-.558-.052-.77-.153a1.206 1.206 0 0 1-.487-.41 1.094 1.094 0 0 1-.178-.563h.726a.457.457 0 0 0 .106.258.664.664 0 0 0 .249.179.979.979 0 0 0 .357.067.903.903 0 0 0 .384-.076.598.598 0 0 0 .252-.217c.06-.094.09-.2.088-.32a.556.556 0 0 0-.334-.52.811.811 0 0 0-.372-.08h-.443ZM1.358 8.999h-.79V5h2.548v.653H1.358V6.77h1.606v.638H1.358V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F4Key() {\n  return (\n    <Key name=\"F4\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M.871 6.648a2.937 2.937 0 0 1 .871-2.105c.274-.273.59-.487.95-.64.359-.157.744-.235 1.156-.235.414 0 .8.078 1.16.234a2.994 2.994 0 0 1 1.59 1.59c.153.36.23.745.23 1.156a2.891 2.891 0 0 1-.582 1.754l1.746 1.75a.54.54 0 0 1 .137.348c0 .096-.02.181-.063.254a.472.472 0 0 1-.422.242.54.54 0 0 1-.355-.137L5.531 9.098c-.24.166-.501.297-.785.39-.284.094-.583.14-.898.14a2.91 2.91 0 0 1-1.157-.23 2.994 2.994 0 0 1-1.59-1.59 2.938 2.938 0 0 1-.23-1.16Zm.711 0a2.235 2.235 0 0 0 .664 1.606c.208.206.45.367.723.484.273.117.566.176.879.176a2.26 2.26 0 0 0 2.266-2.266c0-.312-.06-.605-.177-.878a2.228 2.228 0 0 0-.488-.723 2.228 2.228 0 0 0-1.601-.664c-.313 0-.606.058-.88.176a2.285 2.285 0 0 0-1.21 1.21 2.207 2.207 0 0 0-.176.88Z\" />\n      </svg>\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M5.117 5h1.14v2.62h.49v.638h-.49v.741h-.741v-.741H3.579V7.61c.234-.44.483-.876.747-1.31.266-.433.53-.866.791-1.3Zm-.82 2.62h1.219V5.597h-.041c-.202.318-.404.646-.607.984-.201.338-.391.677-.571 1.017v.02ZM1.303 8.999H.512V5H3.06v.653H1.303V6.77h1.605v.638H1.303V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F5Key() {\n  return (\n    <Key name=\"F5\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M1.428 6.637v-.83c0-.097.034-.18.101-.246a.347.347 0 0 1 .255-.106c.097 0 .179.035.246.106.07.067.106.149.106.246v.804c0 .472.098.885.294 1.24.2.354.476.629.83.825.355.194.768.29 1.24.29s.885-.096 1.24-.29c.354-.196.63-.471.825-.826.2-.354.3-.767.3-1.24v-.803c0-.097.033-.18.1-.246a.347.347 0 0 1 .255-.106c.097 0 .179.035.246.106.07.067.106.149.106.246v.83c0 .56-.116 1.055-.347 1.486a2.727 2.727 0 0 1-.958 1.032c-.408.261-.88.415-1.415.462v.905h1.533c.097 0 .179.034.246.101.07.07.106.155.106.255s-.035.183-.106.25a.335.335 0 0 1-.246.101H2.62a.355.355 0 0 1-.255-.606.355.355 0 0 1 .255-.101h1.53v-.905a3.107 3.107 0 0 1-1.42-.462 2.774 2.774 0 0 1-.958-1.032c-.229-.431-.343-.926-.343-1.486Zm1.516-.167V3.873c0-.331.066-.621.198-.87.135-.252.318-.448.55-.589.234-.14.503-.21.808-.21a1.46 1.46 0 0 1 1.354.8 1.8 1.8 0 0 1 .202.869V6.47c0 .325-.068.614-.202.866a1.46 1.46 0 0 1-.55.589c-.231.14-.5.21-.804.21a1.54 1.54 0 0 1-.809-.21 1.501 1.501 0 0 1-.549-.59 1.84 1.84 0 0 1-.198-.865Zm.717.005c0 .296.076.533.228.712a.768.768 0 0 0 .611.263.768.768 0 0 0 .61-.263c.156-.18.234-.416.234-.712v-2.61c0-.294-.078-.528-.233-.704a.762.762 0 0 0-.611-.268.762.762 0 0 0-.61.268c-.153.176-.23.41-.23.703v2.61Z\" />\n      </svg>\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M3.729 7.974h.726c.014.08.042.15.085.21a.59.59 0 0 0 .164.147.84.84 0 0 0 .454.12.72.72 0 0 0 .624-.354.795.795 0 0 0 .103-.413.71.71 0 0 0-.1-.381.655.655 0 0 0-.264-.252.757.757 0 0 0-.363-.088.752.752 0 0 0-.656.37h-.691l.167-2.333h2.449v.639H4.61l-.088 1.06h.041a.615.615 0 0 1 .144-.143.97.97 0 0 1 .267-.138c.111-.04.242-.059.392-.059.23 0 .443.051.636.153.195.1.352.246.469.44.119.193.178.428.178.705 0 .287-.063.537-.19.75-.127.211-.304.375-.53.492a1.674 1.674 0 0 1-.785.176 1.85 1.85 0 0 1-.569-.082 1.38 1.38 0 0 1-.442-.231 1.137 1.137 0 0 1-.293-.352 1.04 1.04 0 0 1-.111-.436ZM1.323 8.999h-.79V5H3.08v.653H1.323V6.77H2.93v.638H1.323V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F6Key() {\n  return (\n    <Key name=\"F6\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M6.57 8.559c.144 0 .285-.008.426-.024a4.38 4.38 0 0 0 .406-.07c.128-.029.24-.063.336-.102a.49.49 0 0 1 .121-.043.618.618 0 0 1 .102-.008.27.27 0 0 1 .184.079c.057.05.085.117.085.203a.567.567 0 0 1-.062.242c-.2.463-.474.862-.82 1.195-.347.334-.75.59-1.211.77a3.99 3.99 0 0 1-1.489.273c-.56 0-1.076-.096-1.55-.289a3.702 3.702 0 0 1-1.23-.812 3.7 3.7 0 0 1-.813-1.23A4.12 4.12 0 0 1 .77 7.186c0-.515.098-1.01.296-1.484.198-.474.473-.89.825-1.25.354-.362.76-.637 1.218-.824a.906.906 0 0 1 .141-.043.491.491 0 0 1 .086-.012c.086 0 .156.031.21.094a.298.298 0 0 1 .083.2.63.63 0 0 1-.07.258 2.1 2.1 0 0 0-.168.542c-.042.23-.063.482-.063.758 0 .643.132 1.199.395 1.668a2.7 2.7 0 0 0 1.12 1.086c.485.252 1.06.379 1.727.379ZM1.422 7.168c0 .471.08.906.242 1.305.162.395.387.74.676 1.03.292.293.635.52 1.031.68a3.43 3.43 0 0 0 1.3.243 3.47 3.47 0 0 0 1.083-.168c.346-.112.659-.274.937-.485.282-.21.513-.462.696-.753-.138.049-.284.084-.438.105a3.55 3.55 0 0 1-.484.031c-.781 0-1.452-.146-2.012-.437A3.04 3.04 0 0 1 3.168 7.46c-.3-.547-.45-1.203-.45-1.969 0-.195.015-.381.044-.558.028-.177.07-.347.125-.508-.3.198-.56.441-.782.73a3.366 3.366 0 0 0-.507.95 3.27 3.27 0 0 0-.176 1.062Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M7.08 5.926h-.754a.518.518 0 0 0-.102-.173.552.552 0 0 0-.2-.14.722.722 0 0 0-.298-.056.678.678 0 0 0-.524.249c-.07.082-.128.18-.177.296a1.74 1.74 0 0 0-.105.386c-.022.143-.03.297-.026.463h.046a.935.935 0 0 1 .498-.475c.131-.054.287-.081.466-.081.213 0 .415.047.607.143.191.096.347.238.468.428.122.19.182.426.182.709 0 .295-.064.547-.193.756-.127.209-.301.369-.522.48a1.68 1.68 0 0 1-.756.164c-.168 0-.33-.021-.486-.064a1.352 1.352 0 0 1-.43-.208 1.327 1.327 0 0 1-.343-.381 1.917 1.917 0 0 1-.22-.554 3.263 3.263 0 0 1-.08-.764c0-.293.024-.557.071-.792.049-.234.12-.437.211-.609.094-.174.206-.317.337-.43.13-.116.28-.202.445-.258.166-.057.347-.085.542-.085.215 0 .405.029.569.088.164.058.301.136.413.234.113.098.199.205.258.322.06.117.094.235.102.352ZM4.983 7.707c0 .154.032.287.094.398a.66.66 0 0 0 .255.258.734.734 0 0 0 .357.088.771.771 0 0 0 .36-.082.607.607 0 0 0 .253-.249.817.817 0 0 0 .093-.41.745.745 0 0 0-.1-.398.63.63 0 0 0-.257-.244.74.74 0 0 0-.346-.082.72.72 0 0 0-.369.094.645.645 0 0 0-.252.255.765.765 0 0 0-.088.372ZM1.785 8.999h-.79V5h2.548v.653H1.785V6.77h1.606v.638H1.785V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F7Key() {\n  return (\n    <Key name=\"F7\">\n      <svg viewBox=\"0 0 12 14\">\n        <path d=\"M5.02 10.273a.637.637 0 0 1-.223-.039 1.273 1.273 0 0 1-.223-.101L.801 7.91A.89.89 0 0 1 .5 7.648a.56.56 0 0 1-.004-.628.917.917 0 0 1 .305-.266L4.574 4.53a1.27 1.27 0 0 1 .223-.101.7.7 0 0 1 .223-.035c.153 0 .285.057.394.171.112.112.168.276.168.493v4.546c0 .217-.056.382-.168.497a.534.534 0 0 1-.394.171Zm-.192-.77a.099.099 0 0 0 .067-.023c.018-.018.027-.045.027-.082V5.266c0-.037-.01-.063-.027-.079a.09.09 0 0 0-.067-.027.129.129 0 0 0-.039.008 2.154 2.154 0 0 0-.047.02L1.29 7.233c-.047.029-.07.062-.07.098 0 .036.023.069.07.098l3.453 2.047.047.02a.129.129 0 0 0 .04.007Zm5.348.77a.626.626 0 0 1-.219-.039 1.164 1.164 0 0 1-.219-.101L5.961 7.91a.89.89 0 0 1-.3-.262.56.56 0 0 1-.005-.628.917.917 0 0 1 .305-.266L9.738 4.53c.078-.044.151-.078.219-.101a.7.7 0 0 1 .223-.035c.153 0 .286.057.398.171.112.112.168.276.168.493v4.546c0 .217-.056.382-.168.497a.543.543 0 0 1-.402.171Zm-.188-.77c.029 0 .051-.007.067-.023.018-.018.027-.045.027-.082V5.266c0-.037-.01-.063-.027-.079a.083.083 0 0 0-.067-.027.128.128 0 0 0-.039.008 2.152 2.152 0 0 0-.047.02L6.45 7.233c-.05.029-.074.062-.074.098 0 .036.025.069.074.098l3.453 2.047.047.02a.128.128 0 0 0 .04.007Z\" />\n      </svg>\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M3.781 5h2.754v.66L4.912 8.998H4.08l1.661-3.32v-.034h-1.96V5ZM1.473 8.999H.682V5H3.23v.653H1.473V6.77h1.605v.638H1.473V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F8Key() {\n  return (\n    <Key name=\"F8\">\n      <svg viewBox=\"0 0 12 14\">\n        <path d=\"M7.996 10.418a.358.358 0 0 1-.258-.102.338.338 0 0 1-.105-.254V4.61c0-.101.035-.186.105-.254a.358.358 0 0 1 .258-.101c.1 0 .184.034.254.101.073.068.11.153.11.254v5.454a.332.332 0 0 1-.11.253.353.353 0 0 1-.254.102Zm2.508 0a.366.366 0 0 1-.258-.102.338.338 0 0 1-.105-.254V4.61c0-.101.035-.186.105-.254a.366.366 0 0 1 .258-.101.35.35 0 0 1 .258.101c.07.068.105.153.105.254v5.454a.338.338 0 0 1-.105.253.35.35 0 0 1-.258.102Zm-8.813-.145a.534.534 0 0 1-.394-.171c-.112-.112-.168-.276-.168-.493V5.063c0-.217.056-.38.168-.493a.534.534 0 0 1 .394-.172.7.7 0 0 1 .223.036c.07.023.145.058.223.105L5.91 6.758c.138.08.238.168.3.262.066.093.099.199.099.316a.566.566 0 0 1-.098.316.89.89 0 0 1-.3.262l-3.774 2.219a1.137 1.137 0 0 1-.223.105.7.7 0 0 1-.223.035Zm.192-.765c.01 0 .023-.003.039-.008a.197.197 0 0 0 .047-.023l3.453-2.043c.05-.029.074-.062.074-.098 0-.037-.025-.07-.074-.098L1.969 5.195a.286.286 0 0 0-.051-.023.09.09 0 0 0-.102.02c-.018.015-.027.04-.027.074v4.14c0 .034.01.06.027.078.019.016.04.024.067.024Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M5.644 8.506a.879.879 0 0 0 .386-.082.667.667 0 0 0 .273-.229.596.596 0 0 0 .102-.345.587.587 0 0 0-.102-.346.66.66 0 0 0-.273-.232.853.853 0 0 0-.386-.085.872.872 0 0 0-.39.085.66.66 0 0 0-.273.232.6.6 0 0 0-.1.346.609.609 0 0 0 .373.574.898.898 0 0 0 .39.082Zm0-1.858c.12 0 .229-.024.325-.073a.545.545 0 0 0 .304-.504.53.53 0 0 0-.082-.296.526.526 0 0 0-.222-.199.705.705 0 0 0-.325-.073.705.705 0 0 0-.326.073.53.53 0 0 0-.308.495.56.56 0 0 0 .083.302c.054.086.13.153.225.202a.705.705 0 0 0 .326.073Zm-.01 2.43a2.09 2.09 0 0 1-.814-.147c-.23-.1-.409-.238-.536-.413a1.031 1.031 0 0 1-.19-.616c0-.16.025-.298.076-.413a.931.931 0 0 1 .205-.296 1.174 1.174 0 0 1 .557-.287v-.035a1.23 1.23 0 0 1-.326-.161.874.874 0 0 1-.254-.29.885.885 0 0 1-.1-.437c0-.203.058-.383.173-.542.117-.16.28-.286.486-.378.21-.091.453-.137.733-.137.279 0 .522.046.729.137.209.092.371.218.486.378a.897.897 0 0 1 .173.542.845.845 0 0 1-.357.73c-.104.072-.211.125-.322.158v.035c.09.018.183.05.28.097.098.047.19.11.273.19.086.08.155.18.205.296.053.115.08.252.08.41 0 .233-.065.438-.194.616-.129.175-.31.313-.545.413-.232.1-.505.15-.817.15ZM1.762 9.001H.97V5.002H3.52v.653H1.762v1.116h1.605v.64H1.762V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F9Key() {\n  return (\n    <Key name=\"F9\">\n      <svg viewBox=\"0 0 12 14\">\n        <path d=\"M1.816 10.273a.539.539 0 0 1-.398-.171c-.112-.115-.168-.28-.168-.497V5.06c0-.217.056-.38.168-.493a.538.538 0 0 1 .398-.171.7.7 0 0 1 .223.035c.07.023.145.057.223.101l3.773 2.223c.136.08.235.17.297.266a.56.56 0 0 1 0 .629.85.85 0 0 1-.297.261l-3.773 2.223a1.273 1.273 0 0 1-.223.101.637.637 0 0 1-.223.04Zm.188-.77a.104.104 0 0 0 .039-.007.356.356 0 0 0 .047-.02L5.543 7.43c.052-.029.078-.062.078-.098 0-.036-.026-.069-.078-.098L2.09 5.188a2.154 2.154 0 0 0-.047-.02.104.104 0 0 0-.04-.008.1.1 0 0 0-.066.027c-.018.016-.027.042-.027.079v4.132c0 .037.01.064.028.082.02.016.042.024.066.024Zm4.973.77a.539.539 0 0 1-.399-.171c-.112-.115-.168-.28-.168-.497V5.06c0-.217.056-.38.168-.493a.538.538 0 0 1 .399-.171.7.7 0 0 1 .222.035c.07.023.145.057.223.101l3.773 2.223c.136.08.235.17.297.266a.56.56 0 0 1 0 .629.85.85 0 0 1-.297.261l-3.773 2.223a1.274 1.274 0 0 1-.223.101.637.637 0 0 1-.222.04Zm.187-.77a.104.104 0 0 0 .04-.007.355.355 0 0 0 .046-.02l3.453-2.046c.052-.029.078-.062.078-.098 0-.036-.026-.069-.078-.098L7.25 5.188a2.132 2.132 0 0 0-.047-.02.104.104 0 0 0-.039-.008.1.1 0 0 0-.066.027c-.019.016-.028.042-.028.079v4.132c0 .037.01.064.028.082.02.016.043.024.066.024Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M4.217 8.115h.753a.403.403 0 0 0 .096.161c.051.051.118.093.2.126.082.034.176.05.284.05a.701.701 0 0 0 .395-.108.8.8 0 0 0 .27-.3c.068-.126.117-.273.146-.441.032-.168.044-.35.038-.542h-.044a.832.832 0 0 1-.178.272.902.902 0 0 1-.317.208c-.127.05-.277.076-.45.076a1.39 1.39 0 0 1-.637-.15 1.207 1.207 0 0 1-.474-.445A1.376 1.376 0 0 1 4.12 6.3c0-.287.065-.533.193-.738.131-.206.308-.363.53-.472.223-.111.475-.167.757-.167.205 0 .4.033.586.1.185.066.35.176.495.33.144.155.257.362.34.622.083.257.125.579.125.963 0 .47-.06.862-.181 1.178-.12.317-.298.556-.536.718-.237.16-.533.24-.89.24-.214 0-.401-.027-.563-.082a1.292 1.292 0 0 1-.405-.222 1.065 1.065 0 0 1-.252-.308.913.913 0 0 1-.102-.346Zm2.068-1.822a.853.853 0 0 0-.09-.407.617.617 0 0 0-.253-.258.717.717 0 0 0-.363-.09.693.693 0 0 0-.351.09.64.64 0 0 0-.252.258.829.829 0 0 0-.091.401c0 .16.031.295.094.404a.63.63 0 0 0 .251.25.77.77 0 0 0 .355.081.746.746 0 0 0 .349-.082.63.63 0 0 0 .254-.246.768.768 0 0 0 .097-.401ZM1.78 8.999H.987V5h2.55v.653H1.778V6.77h1.606v.638H1.779V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F10Key() {\n  return (\n    <Key name=\"F10\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M2.386 8.734c-.293 0-.514-.076-.663-.229-.147-.155-.22-.39-.22-.703v-1.59c0-.314.073-.547.22-.7.15-.155.37-.232.663-.232h1.187a.14.14 0 0 0 .096-.035l1.85-1.64c.112-.1.209-.174.29-.224a.523.523 0 0 1 .277-.074c.15 0 .273.05.37.153a.52.52 0 0 1 .145.37v6.363a.48.48 0 0 1-.145.36.497.497 0 0 1-.36.14.639.639 0 0 1-.29-.065 1.365 1.365 0 0 1-.286-.202L3.67 8.769a.14.14 0 0 0-.097-.035H2.386Zm.053-.677h1.318c.053 0 .101.007.145.022a.372.372 0 0 1 .141.088l1.714 1.551c.023.02.047.031.07.031.035 0 .053-.02.053-.062V4.314c0-.041-.02-.062-.057-.062a.058.058 0 0 0-.035.014.117.117 0 0 0-.031.022L4.043 5.842a.43.43 0 0 1-.14.093.456.456 0 0 1-.146.022H2.44c-.073 0-.127.017-.163.052-.035.035-.052.09-.052.163v1.67c0 .073.017.127.052.162.036.036.09.053.163.053Z\" />\n      </svg>\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M7.338 6.915v.17c0 .281.03.522.088.724.06.2.145.355.255.462.109.108.239.162.39.162a.53.53 0 0 0 .389-.162c.11-.109.193-.263.252-.462a2.61 2.61 0 0 0 .088-.724v-.17c0-.28-.03-.52-.088-.72a1.023 1.023 0 0 0-.252-.46.53.53 0 0 0-.39-.162c-.15 0-.28.054-.39.161-.109.106-.194.26-.254.46-.059.2-.088.44-.088.721Zm-.788.176v-.179c0-.4.06-.749.181-1.046.123-.297.3-.527.528-.691.228-.166.502-.25.82-.25.318 0 .59.083.815.247.226.162.399.391.518.688.12.297.179.648.179 1.052v.179c0 .299-.035.57-.106.814a1.869 1.869 0 0 1-.301.63 1.372 1.372 0 0 1-.48.402c-.188.093-.401.14-.64.14-.316 0-.587-.08-.814-.243a1.551 1.551 0 0 1-.521-.691 2.83 2.83 0 0 1-.18-1.052ZM5.463 9.001h-.785V5.764H4.64l-.302.214-.305.216-.302.214v-.726l.475-.34.472-.34h.785V9ZM1.338 9.001H.547V5.002h2.549v.653H1.338v1.116h1.605v.64H1.338V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F11Key() {\n  return (\n    <Key name=\"F11\">\n      <svg viewBox=\"0 0 11 14\">\n        <path d=\"M2.622 8.734c-.293 0-.515-.076-.664-.229-.146-.155-.22-.39-.22-.703v-1.59c0-.314.074-.547.22-.7.15-.155.37-.232.664-.232h1.186a.14.14 0 0 0 .097-.035l1.85-1.64c.111-.1.208-.174.29-.224a.515.515 0 0 1 .272-.074c.15 0 .273.05.37.153a.52.52 0 0 1 .145.37v6.363a.488.488 0 0 1-.505.5.629.629 0 0 1-.286-.065 1.365 1.365 0 0 1-.286-.202l-1.85-1.657a.14.14 0 0 0-.097-.035H2.622Zm.052-.677h1.319c.052 0 .1.007.145.022a.372.372 0 0 1 .14.088l1.714 1.551c.024.02.047.031.07.031.036 0 .053-.02.053-.062V4.314c0-.041-.019-.062-.057-.062a.058.058 0 0 0-.035.014.117.117 0 0 0-.03.022L4.277 5.842a.43.43 0 0 1-.14.093.456.456 0 0 1-.145.022H2.674c-.073 0-.127.017-.162.052-.035.035-.053.09-.053.163v1.67c0 .073.018.127.053.162.035.036.09.053.162.053Zm5.568.756a.357.357 0 0 1-.162-.25.418.418 0 0 1 .087-.308c.118-.167.208-.359.273-.576.064-.217.097-.442.097-.677 0-.234-.033-.46-.097-.676a1.963 1.963 0 0 0-.273-.58.405.405 0 0 1-.087-.304.357.357 0 0 1 .162-.25.315.315 0 0 1 .26-.053c.093.018.17.068.228.15.167.222.296.483.387.782.09.296.136.606.136.931 0 .326-.045.638-.136.936-.091.3-.22.557-.387.774a.366.366 0 0 1-.229.158.329.329 0 0 1-.259-.057Z\" />\n      </svg>\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M8.273 8.999h-.785V5.762H7.45l-.302.214-.304.216-.302.214V5.68l.475-.34.471-.34h.785v3.999ZM5.42 8.999h-.785V5.762h-.038l-.302.214-.305.216-.302.214V5.68l.475-.34.472-.34h.785v3.999ZM1.295 8.999H.504V5h2.549v.653H1.295V6.77H2.9v.638H1.295V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction F12Key() {\n  return (\n    <Key name=\"F12\">\n      <svg viewBox=\"0 0 14 14\">\n        <path d=\"M2.18 8.734c-.294 0-.515-.076-.664-.229-.147-.155-.22-.39-.22-.703v-1.59c0-.314.073-.547.22-.7.149-.155.37-.232.663-.232h1.187a.14.14 0 0 0 .096-.035l1.85-1.64c.112-.1.209-.174.29-.224a.523.523 0 0 1 .277-.074c.15 0 .273.05.37.153a.52.52 0 0 1 .145.37v6.363a.48.48 0 0 1-.145.36.497.497 0 0 1-.36.14.639.639 0 0 1-.29-.065 1.364 1.364 0 0 1-.287-.202l-1.85-1.657a.14.14 0 0 0-.096-.035H2.179Zm.052-.677H3.55c.053 0 .101.007.145.022a.372.372 0 0 1 .14.088L5.55 9.718c.023.02.047.031.07.031.035 0 .053-.02.053-.062V4.313c0-.041-.02-.062-.057-.062a.058.058 0 0 0-.035.014.117.117 0 0 0-.031.022L3.836 5.842a.43.43 0 0 1-.14.093.456.456 0 0 1-.146.022H2.232c-.073 0-.128.017-.163.052-.035.035-.052.09-.052.163v1.67c0 .073.017.127.052.162.035.036.09.053.163.053Zm5.572.752a.357.357 0 0 1-.162-.25.418.418 0 0 1 .087-.309c.118-.167.208-.358.273-.575.064-.217.097-.443.097-.677 0-.234-.033-.46-.097-.677a1.963 1.963 0 0 0-.273-.58.405.405 0 0 1-.087-.303.357.357 0 0 1 .162-.25.315.315 0 0 1 .26-.053c.093.017.17.067.228.15.167.222.296.483.387.781.09.296.136.607.136.932 0 .325-.045.637-.136.936-.091.299-.22.557-.387.773a.366.366 0 0 1-.229.159.329.329 0 0 1-.259-.057Zm1.6 1.098a.317.317 0 0 1-.15-.233c-.011-.1.015-.199.08-.299.222-.322.395-.688.518-1.098a4.456 4.456 0 0 0 .004-2.558 3.737 3.737 0 0 0-.522-1.098.438.438 0 0 1-.08-.295.32.32 0 0 1 .15-.237.33.33 0 0 1 .268-.057c.097.02.174.074.233.162.272.37.483.8.633 1.292a5.147 5.147 0 0 1-.005 3.024c-.15.486-.359.917-.628 1.292a.363.363 0 0 1-.233.162.355.355 0 0 1-.268-.057Zm1.617 1.112a.32.32 0 0 1-.15-.237.43.43 0 0 1 .08-.295 6.144 6.144 0 0 0 .91-2.206c.084-.413.127-.84.127-1.283a6.294 6.294 0 0 0-.488-2.452 6.473 6.473 0 0 0-.55-1.037.43.43 0 0 1-.078-.295.32.32 0 0 1 .149-.237.348.348 0 0 1 .272-.053c.097.02.178.078.242.172A7.06 7.06 0 0 1 12.56 5.57c.094.46.14.936.14 1.428 0 .495-.046.973-.14 1.433-.094.46-.228.898-.404 1.314-.173.413-.38.8-.62 1.16a.392.392 0 0 1-.242.167.348.348 0 0 1-.272-.053Z\" />\n      </svg>\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M7.45 6.194H6.7v-.038a1.174 1.174 0 0 1 .337-.844c.112-.117.254-.21.424-.28.172-.071.374-.106.606-.106.276 0 .516.049.721.146.205.098.363.232.475.402.113.168.17.358.17.57 0 .185-.036.349-.106.493a1.825 1.825 0 0 1-.264.401c-.105.124-.216.25-.334.378l-.908 1.002v.038H9.5v.645H6.737v-.492L8.2 6.933c.072-.077.143-.156.211-.238.069-.084.125-.174.17-.27a.715.715 0 0 0 .067-.307.534.534 0 0 0-.29-.486.634.634 0 0 0-.307-.073.608.608 0 0 0-.322.082.562.562 0 0 0-.208.22.618.618 0 0 0-.07.29v.043ZM5.595 9.001H4.81V5.764H4.77a97.908 97.908 0 0 0-.606.43l-.302.214v-.726l.475-.34.472-.34h.785V9ZM1.47 9.001H.679V5.002h2.549v.653H1.47v1.116h1.605v.64H1.47V9Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction LockKey() {\n  return (\n    <Key name=\"Lock\" className=\"rounded-tr-xl\">\n      <svg viewBox=\"0 0 12 14\">\n        <path d=\"M3.642 12.241c-.383 0-.671-.1-.865-.3-.193-.198-.29-.506-.29-.924V7.456c0-.416.097-.722.29-.919.194-.197.482-.295.865-.295h4.71c.39 0 .68.098.87.295.19.197.285.503.285.919v3.56c0 .42-.095.727-.284.924-.19.201-.48.301-.87.301h-4.71Zm.097-.838h4.528c.114 0 .198-.03.252-.091.057-.057.086-.154.086-.29V7.45c0-.136-.029-.23-.086-.284-.054-.058-.138-.086-.252-.086H3.739c-.115 0-.2.028-.258.086-.057.053-.086.148-.086.284v3.572c0 .136.029.233.086.29.057.06.143.091.258.091Zm-.35-4.742V5.049c0-.48.072-.898.215-1.257.147-.358.342-.655.586-.891.247-.236.526-.412.838-.526.311-.119.635-.178.972-.178.333 0 .655.06.967.178.311.114.59.29.838.526.247.236.442.533.585.891.147.359.22.777.22 1.257v1.612h-.897V4.947c0-.412-.078-.757-.236-1.036a1.6 1.6 0 0 0-.623-.64A1.729 1.729 0 0 0 6 3.057c-.308 0-.593.071-.854.214-.261.144-.47.357-.628.64-.158.279-.237.624-.237 1.036v1.714H3.39Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction BacktickKey() {\n  return (\n    <Key name=\"Backtick\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M5.414 8.636c-.37 0-.681-.073-.936-.219a3.372 3.372 0 0 1-.677-.52 5.709 5.709 0 0 0-.568-.512.942.942 0 0 0-.615-.226.69.69 0 0 0-.465.164c-.123.11-.218.255-.287.438a1.847 1.847 0 0 0-.116.588H.677a2.858 2.858 0 0 1 .198-1.094c.137-.346.344-.627.622-.84.283-.22.643-.33 1.08-.33.378 0 .693.076.943.227.256.145.479.316.67.512.192.196.376.37.554.52a.96.96 0 0 0 .615.218.674.674 0 0 0 .465-.164c.128-.113.224-.26.287-.437.064-.182.1-.378.11-.588h1.073c.004.392-.064.761-.205 1.107a1.933 1.933 0 0 1-.622.841c-.278.21-.63.315-1.053.315Z\" />\n      </svg>\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M4.56 5.434h-.944L1.716 3h1.217L4.56 5.434Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction OneKey() {\n  return (\n    <Key name=\"One\">\n      <svg viewBox=\"0 0 4 14\">\n        <path d=\"M.768 10.355a.747.747 0 0 1 .763-.757c.229 0 .417.071.564.215.15.143.225.324.225.542a.705.705 0 0 1-.225.532.785.785 0 0 1-.564.21.757.757 0 0 1-.543-.21.713.713 0 0 1-.22-.532ZM1 3.668h1.096L1.97 8.389h-.854L1 3.668Z\" />\n      </svg>\n      <svg viewBox=\"0 0 6 14\">\n        <path d=\"M4.263 12H3.108V4.587H3.05l-.673.483c-.22.16-.442.321-.667.482l-.666.489V4.866a161.932 161.932 0 0 1 2.063-1.53h1.155V12Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction TwoKey() {\n  return (\n    <Key name=\"Two\">\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M7.208 10.285a1.66 1.66 0 0 1-.645-.113 1.166 1.166 0 0 1-.414-.279.802.802 0 0 1-.182-.354h-.054a.849.849 0 0 1-.21.343c-.103.115-.24.212-.408.29a1.344 1.344 0 0 1-.564.113c-.447 0-.794-.163-1.042-.489-.243-.325-.365-.75-.365-1.273V8.03c0-.512.126-.929.376-1.251.25-.326.61-.489 1.08-.489.204 0 .38.038.526.113a1.02 1.02 0 0 1 .537.575h.043v-.613h.779v2.627c0 .14.027.258.08.354a.572.572 0 0 0 .237.215c.1.047.211.07.333.07a.743.743 0 0 0 .494-.193c.15-.13.272-.33.365-.602.097-.272.145-.618.145-1.037V7.61c0-.522-.123-1.008-.37-1.455a2.78 2.78 0 0 0-1.074-1.09c-.47-.276-1.042-.414-1.72-.414h-.042c-.63 0-1.198.141-1.703.424-.501.28-.899.679-1.192 1.198-.294.52-.44 1.137-.44 1.853v.247c0 .57.087 1.067.263 1.493.175.426.415.779.72 1.058.304.28.655.49 1.052.629.397.14.82.21 1.268.21h.048c.401 0 .741-.026 1.02-.076a3.04 3.04 0 0 0 .71-.193v.693c-.151.068-.371.127-.661.177-.287.053-.647.08-1.08.08H5.07a4.865 4.865 0 0 1-1.58-.252 3.696 3.696 0 0 1-1.294-.757 3.487 3.487 0 0 1-.875-1.263c-.208-.505-.312-1.094-.312-1.767v-.311c0-.842.18-1.57.537-2.186a3.735 3.735 0 0 1 1.461-1.429c.62-.34 1.322-.51 2.106-.51h.043c.634 0 1.194.1 1.68.3a3.471 3.471 0 0 1 1.972 1.972c.172.43.258.877.258 1.343v.273c0 .491-.08.919-.242 1.284a2.022 2.022 0 0 1-.66.843c-.28.2-.598.301-.956.301ZM4.135 8.416c0 .415.08.714.242.897A.8.8 0 0 0 5 9.582a.85.85 0 0 0 .446-.113.769.769 0 0 0 .306-.36c.075-.165.113-.381.113-.65v-.43c0-.236-.04-.435-.118-.596a.828.828 0 0 0-.312-.365.827.827 0 0 0-.446-.124.79.79 0 0 0-.607.28c-.164.182-.247.469-.247.859v.333Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M2.407 5.748H1.27v-.044c0-.33.059-.648.177-.953a2.38 2.38 0 0 1 .527-.812c.233-.241.52-.432.863-.571.343-.14.739-.21 1.187-.21.55 0 1.029.104 1.435.311a2.252 2.252 0 0 1 1.276 2.063c0 .36-.068.69-.203.99a4.046 4.046 0 0 1-.533.87c-.22.28-.466.573-.737.882L2.96 10.915v.07h3.942V12H1.302v-.737l3.263-3.713c.157-.182.311-.37.463-.565.153-.199.278-.41.375-.635.101-.224.152-.47.152-.736 0-.296-.068-.555-.203-.775a1.351 1.351 0 0 0-.559-.507A1.724 1.724 0 0 0 4 4.155c-.334 0-.622.072-.863.216a1.474 1.474 0 0 0-.546.57 1.608 1.608 0 0 0-.184.763v.045Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction ThreeKey() {\n  return (\n    <Key name=\"Three\">\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M6.476 6.434h-1.22l-.36 1.74H6.1l-.145.747H4.746L4.327 11h-.773l.424-2.08H2.302L1.878 11h-.774l.42-2.08H.378l.15-.746h1.156l.354-1.74H.9l.145-.747H2.19l.409-2.019h.778l-.413 2.02h1.676l.413-2.02h.768l-.408 2.02h1.214l-.15.746Zm-3.674-.027-.37 1.789h1.707l.365-1.789H2.802Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M2.965 8.033v-.996h.895c.305 0 .576-.064.813-.19.241-.128.43-.303.565-.528.135-.224.2-.478.197-.761 0-.42-.146-.76-.438-1.022-.292-.263-.671-.394-1.137-.394a1.83 1.83 0 0 0-.628.102 1.572 1.572 0 0 0-.482.266c-.14.11-.25.237-.33.381-.077.144-.121.296-.134.457h-1.11a2.105 2.105 0 0 1 .705-1.504c.227-.207.507-.373.837-.495.33-.123.713-.186 1.149-.19.55-.005 1.028.09 1.434.285.407.195.724.46.952.793.229.335.347.713.356 1.137.013.406-.07.757-.248 1.053a2.154 2.154 0 0 1-.64.705c-.255.17-.492.273-.712.311v.063c.246.026.512.115.8.267.292.152.544.379.755.68.216.296.328.676.337 1.142.008.376-.064.723-.216 1.04a2.37 2.37 0 0 1-.641.826c-.275.233-.601.413-.978.54-.376.122-.789.184-1.237.184-.576 0-1.075-.098-1.498-.292a2.476 2.476 0 0 1-.984-.8 2.14 2.14 0 0 1-.381-1.124h1.092c.03.229.122.436.279.622.156.182.364.328.622.438.262.106.56.161.895.165.38 0 .71-.067.99-.203.284-.14.502-.33.654-.57.152-.246.226-.524.222-.832a1.469 1.469 0 0 0-.876-1.359 2.09 2.09 0 0 0-.933-.197h-.946Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction FourKey() {\n  return (\n    <Key name=\"Four\">\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M1.013 9.065h.946a1.268 1.268 0 0 0 .547.8c.147.097.315.17.505.22.194.051.405.076.634.076.326 0 .607-.048.843-.145.237-.1.417-.242.543-.424.125-.183.188-.398.188-.645a.948.948 0 0 0-.118-.483.996.996 0 0 0-.371-.36 2.372 2.372 0 0 0-.65-.247L2.86 7.54a2.748 2.748 0 0 1-.89-.392 1.81 1.81 0 0 1-.57-.623 1.773 1.773 0 0 1-.193-.833c0-.397.105-.746.316-1.047.212-.304.5-.542.865-.714.369-.176.79-.264 1.262-.264.462 0 .869.08 1.22.237.354.157.637.374.848.65.212.275.335.592.37.95h-.95a1.151 1.151 0 0 0-.263-.542 1.278 1.278 0 0 0-.516-.355 1.943 1.943 0 0 0-.72-.123c-.443 0-.801.11-1.074.328a1.023 1.023 0 0 0-.402.837c0 .247.082.455.247.623.168.169.413.294.736.376l1.197.317c.412.108.752.246 1.02.414.273.168.475.376.608.623.136.247.204.539.204.875 0 .427-.108.797-.322 1.112-.212.312-.514.555-.908.73-.394.176-.861.264-1.402.264-.369 0-.704-.047-1.004-.14-.301-.093-.56-.225-.78-.397a2.003 2.003 0 0 1-.52-.607 2.018 2.018 0 0 1-.226-.774Zm2.235 2.675V2.91h.639v8.83h-.64Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M4.432 3.336h1.726v5.872h1.124v1.015H6.158V12H5.041v-1.777H.833V9.201A52.895 52.895 0 0 1 2.578 6.27c.622-.978 1.24-1.956 1.854-2.933ZM1.988 9.208H5.04v-4.87h-.063A98.029 98.029 0 0 0 3.44 6.714c-.516.83-1 1.642-1.453 2.437v.058Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction FiveKey() {\n  return (\n    <Key name=\"Five\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M6.643 10.398a.593.593 0 0 0 .516-.29c.132-.197.198-.458.198-.784 0-.33-.064-.589-.193-.779a.59.59 0 0 0-.52-.29.597.597 0 0 0-.522.29c-.129.194-.193.453-.193.78 0 .325.064.586.193.783.133.194.306.29.521.29Zm0 .704c-.465 0-.847-.165-1.144-.494-.294-.33-.44-.757-.44-1.284 0-.519.146-.943.44-1.273.297-.333.679-.5 1.144-.5.47 0 .85.165 1.144.495.297.33.446.755.446 1.278 0 .527-.149.954-.446 1.284-.294.33-.675.494-1.144.494ZM2.427 11H1.396l2.75-3.819L6.61 3.668h1.02L4.833 7.552 2.427 11Zm-.07-4.614a.593.593 0 0 0 .516-.29c.132-.197.198-.456.198-.779 0-.333-.064-.594-.193-.784a.597.597 0 0 0-.521-.29.61.61 0 0 0-.526.29c-.13.194-.194.455-.194.784 0 .326.065.586.194.78a.61.61 0 0 0 .526.29Zm0 .71c-.466 0-.849-.165-1.15-.495-.297-.333-.445-.76-.445-1.284 0-.522.148-.949.446-1.278.3-.33.683-.494 1.149-.494.469 0 .85.165 1.144.494.294.326.44.752.44 1.278 0 .523-.146.951-.44 1.284-.294.33-.675.494-1.144.494Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M1.163 9.982H2.28a1.334 1.334 0 0 0 .654.927c.165.097.342.169.533.215.194.043.391.064.59.064.296 0 .584-.074.863-.222.284-.152.515-.377.692-.673.182-.3.273-.664.273-1.092 0-.385-.087-.713-.26-.984a1.719 1.719 0 0 0-.68-.634 1.908 1.908 0 0 0-.907-.223 1.92 1.92 0 0 0-1.352.52 1.84 1.84 0 0 0-.35.433h-.99l.388-4.977h4.894v1.01H2.724L2.49 7.118h.064c.063-.093.169-.197.317-.31.152-.12.347-.221.584-.306.237-.084.516-.127.838-.127.504 0 .965.113 1.384.337.419.22.753.537 1.003.952.254.415.38.916.38 1.504 0 .593-.13 1.115-.393 1.568a2.737 2.737 0 0 1-1.08 1.06c-.456.254-.99.381-1.599.381-.397 0-.766-.055-1.104-.165a2.697 2.697 0 0 1-.876-.457 2.343 2.343 0 0 1-.597-.698 2.155 2.155 0 0 1-.247-.876Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction SixKey() {\n  return (\n    <Key name=\"Six\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M3.46 3.68h.774L7 8.31H5.969L3.906 4.716h-.048L1.903 8.31H.883L3.46 3.68Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M6.907 5.131H5.74a1.06 1.06 0 0 0-.222-.425 1.44 1.44 0 0 0-.501-.38c-.216-.107-.487-.16-.813-.16a1.668 1.668 0 0 0-1.257.56c-.169.185-.317.42-.444.704-.127.279-.224.611-.292.996-.068.381-.102.813-.102 1.295h.064a2.105 2.105 0 0 1 1.124-1.098c.3-.135.66-.203 1.079-.203.452 0 .884.106 1.294.317.415.212.756.523 1.022.933.267.41.4.917.4 1.518 0 .634-.13 1.176-.393 1.624a2.568 2.568 0 0 1-1.067 1.016c-.448.233-.958.35-1.53.35-.313 0-.622-.041-.926-.121a2.684 2.684 0 0 1-.863-.407 2.752 2.752 0 0 1-.705-.78 4.092 4.092 0 0 1-.47-1.181c-.114-.47-.171-1.03-.171-1.682 0-.669.05-1.265.152-1.79.106-.525.254-.978.444-1.359.195-.385.426-.7.692-.945.271-.25.574-.436.908-.559a3.107 3.107 0 0 1 1.08-.184c.405 0 .77.057 1.091.171.326.115.6.267.825.457.229.187.406.396.533.629.127.228.2.463.216.704Zm-4.64 4.114c0 .41.085.76.254 1.047.17.288.394.508.673.66.28.152.584.229.914.229.338 0 .645-.068.92-.204.28-.14.502-.35.667-.634.165-.288.247-.65.247-1.086 0-.423-.088-.772-.266-1.047a1.644 1.644 0 0 0-.68-.61 1.97 1.97 0 0 0-.882-.203c-.355 0-.673.08-.952.242-.28.156-.5.374-.66.653a1.891 1.891 0 0 0-.235.953Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction SevenKey() {\n  return (\n    <Key name=\"Seven\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M6.363 11a61.63 61.63 0 0 0-.16-.172 7.874 7.874 0 0 0-.167-.183 136.97 136.97 0 0 1-.328-.35 3.013 3.013 0 0 1-1.16.662 4.084 4.084 0 0 1-1.155.182c-.455 0-.87-.082-1.246-.247a2.102 2.102 0 0 1-.886-.709c-.219-.311-.328-.682-.328-1.112 0-.322.061-.605.183-.848.125-.247.295-.466.51-.656a3.996 3.996 0 0 1 .87-.58c.047-.025.093-.048.14-.07a7.04 7.04 0 0 1-.629-.778 1.577 1.577 0 0 1-.274-.908c0-.3.08-.573.237-.816.157-.248.377-.444.66-.591a2.08 2.08 0 0 1 .973-.22c.35 0 .666.07.945.209.283.136.507.328.671.575.169.243.253.53.253.86 0 .296-.072.558-.215.783-.14.222-.328.42-.564.591-.233.172-.49.333-.774.483.14.15.283.307.43.468.147.157.296.318.446.483l.446.489c.147.157.29.313.43.467.118-.222.202-.496.252-.822.05-.329.075-.705.075-1.127v-.237h.886v.247a6.95 6.95 0 0 1-.14 1.456c-.092.43-.243.802-.45 1.117l1.272 1.353H6.363ZM2.915 7.604a2.812 2.812 0 0 0-.747.58c-.186.215-.279.489-.279.822 0 .304.075.557.226.757.154.197.35.344.59.44.24.097.498.146.774.146.269 0 .559-.05.87-.15.315-.105.584-.273.806-.506L3.15 7.481a3.887 3.887 0 0 0-.123.065 3.576 3.576 0 0 0-.113.059Zm.494-1.031c.337-.172.614-.353.833-.543a.95.95 0 0 0 .333-.752c0-.283-.092-.508-.274-.676a.97.97 0 0 0-.693-.258c-.287 0-.52.09-.698.268a.91.91 0 0 0-.264.672c0 .193.054.385.162.574.11.187.311.425.601.715Z\" />\n      </svg>\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M.758 3.336h5.541V4.39L2.757 12H1.532L5.12 4.422v-.07H.758V3.337Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction EightKey() {\n  return (\n    <Key name=\"Eight\">\n      <svg viewBox=\"0 0 6 14\">\n        <path d=\"M3.365 7.891h-.752l.113-1.75-1.455.977-.371-.65 1.574-.779L.9 4.916l.37-.65 1.461.983-.118-1.757h.752L3.247 5.25l1.461-.983.37.65-1.578.773 1.579.779-.371.65-1.46-.978.117 1.751Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M4.006 11.22c.369 0 .699-.068.99-.203.293-.14.523-.333.692-.578.17-.245.254-.529.254-.85 0-.326-.084-.612-.254-.858-.169-.25-.4-.442-.691-.577a2.267 2.267 0 0 0-.99-.21c-.369 0-.701.07-.997.21a1.704 1.704 0 0 0-.698.577c-.17.246-.254.532-.254.857 0 .322.084.606.254.851.173.245.406.438.698.578.296.135.628.203.996.203Zm0-4.209c.313 0 .59-.061.832-.184a1.38 1.38 0 0 0 .78-1.27 1.349 1.349 0 0 0-.78-1.25 1.863 1.863 0 0 0-.832-.178c-.313 0-.592.06-.838.178a1.392 1.392 0 0 0-.577.502c-.14.211-.21.46-.21.749 0 .283.07.535.21.755.14.216.332.387.577.514.246.123.525.184.838.184Zm-.031 5.167c-.597 0-1.126-.106-1.587-.317a2.644 2.644 0 0 1-1.086-.895 2.308 2.308 0 0 1-.387-1.327c0-.343.055-.641.165-.895.114-.254.26-.467.438-.641a2.475 2.475 0 0 1 1.149-.647v-.058a2.48 2.48 0 0 1-.628-.336 2.041 2.041 0 0 1-.559-.635c-.148-.262-.222-.582-.222-.958 0-.449.118-.847.355-1.194s.561-.62.971-.818c.415-.2.89-.299 1.422-.299.534 0 1.005.1 1.416.299.415.198.74.471.977.818s.356.745.356 1.194c0 .385-.078.71-.235.977a2.038 2.038 0 0 1-.565.629c-.22.152-.427.26-.622.323v.058a2.475 2.475 0 0 1 1.149.647c.182.178.328.394.438.647.11.25.165.542.165.876 0 .504-.133.948-.4 1.334-.267.38-.635.679-1.104.894-.47.216-1.005.324-1.606.324Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction NineKey() {\n  return (\n    <Key name=\"Nine\">\n      <svg viewBox=\"0 0 4 14\">\n        <path d=\"M2.752 3.281h.843a7.74 7.74 0 0 0-.693 1.295 8.39 8.39 0 0 0-.66 3.298c0 .615.048 1.204.145 1.767.096.558.245 1.088.446 1.59.2.504.454.98.762 1.428h-.848a5.813 5.813 0 0 1-.79-1.289 7.802 7.802 0 0 1-.516-1.627 9.459 9.459 0 0 1-.177-1.859c0-.648.061-1.25.183-1.804a7.056 7.056 0 0 1 .515-1.531c.226-.47.49-.892.79-1.268Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M1.144 10.33h1.174c.038.136.116.269.235.4.122.127.285.233.489.317.203.08.446.121.73.121.397 0 .736-.091 1.015-.273.28-.186.502-.44.667-.762.169-.326.287-.7.355-1.123.072-.428.102-.885.089-1.371h-.064c-.088.237-.228.459-.418.666a1.985 1.985 0 0 1-.711.495 2.593 2.593 0 0 1-1.022.184c-.512 0-.978-.114-1.397-.342a2.596 2.596 0 0 1-1.003-1.003c-.245-.44-.368-.963-.368-1.568 0-.614.133-1.139.4-1.574.267-.436.626-.77 1.079-1.003.457-.233.97-.35 1.536-.35.381 0 .753.064 1.117.191.369.127.7.35.997.666.3.318.54.758.717 1.32.178.56.267 1.275.267 2.146 0 1.037-.119 1.905-.356 2.603-.237.694-.598 1.216-1.085 1.568-.483.35-1.096.527-1.84.527-.403 0-.76-.051-1.074-.153a2.458 2.458 0 0 1-.787-.4 2.111 2.111 0 0 1-.514-.59 1.88 1.88 0 0 1-.228-.692ZM5.7 6.122c0-.428-.082-.788-.247-1.08A1.644 1.644 0 0 0 4.8 4.37a1.865 1.865 0 0 0-.94-.235c-.321 0-.62.078-.895.235-.27.152-.488.374-.653.666-.161.288-.242.64-.242 1.054 0 .431.085.79.254 1.073.17.279.392.488.667.628.279.135.58.203.901.203.309 0 .601-.068.876-.203.28-.14.504-.347.673-.622.173-.28.26-.628.26-1.047Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction ZeroKey() {\n  return (\n    <Key name=\"Zero\">\n      <svg viewBox=\"0 0 4 14\">\n        <path d=\"M.405 3.281h.843c.304.376.568.799.79 1.268.222.465.393.976.515 1.53a8.42 8.42 0 0 1 .183 1.805 9.19 9.19 0 0 1-.183 1.859 7.592 7.592 0 0 1-.51 1.627 5.558 5.558 0 0 1-.79 1.29H.405c.308-.448.56-.925.757-1.43.2-.5.35-1.03.446-1.59.1-.562.15-1.15.15-1.766 0-.613-.06-1.196-.182-1.751a8.312 8.312 0 0 0-.484-1.547c-.2-.48-.43-.912-.687-1.295Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M2.064 7.519v.305c0 .694.078 1.29.235 1.79.16.5.385.884.673 1.155.292.267.634.4 1.028.4a1.47 1.47 0 0 0 1.035-.4c.287-.27.51-.656.666-1.155.157-.5.235-1.096.235-1.79v-.305c0-.694-.078-1.29-.235-1.79-.156-.504-.379-.889-.666-1.155A1.457 1.457 0 0 0 4 4.167c-.394 0-.736.136-1.028.407-.288.266-.512.651-.673 1.155-.157.5-.235 1.096-.235 1.79Zm-1.162.317v-.323c0-.872.123-1.634.369-2.285.245-.656.598-1.164 1.06-1.524.465-.364 1.026-.546 1.682-.546.656 0 1.212.182 1.67.546.46.36.812.865 1.053 1.517.241.648.362 1.411.362 2.292v.323c0 .656-.07 1.25-.21 1.784a4.224 4.224 0 0 1-.603 1.378c-.262.38-.586.672-.971.876A2.773 2.773 0 0 1 4 12.178c-.652 0-1.21-.18-1.676-.54-.461-.359-.814-.863-1.06-1.51-.241-.652-.362-1.416-.362-2.292Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction DashKey() {\n  return (\n    <Key name=\"Dash\">\n      <svg viewBox=\"0 0 6 14\">\n        <path d=\"M5.85 8.901H.144V8H5.85v.901Z\" />\n      </svg>\n      <svg viewBox=\"0 0 6 14\">\n        <path d=\"M5 8.96H1V7.932h4V8.96Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction EqualsKey() {\n  return (\n    <Key name=\"Equals\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M4.47 9.029v2.818h-.946V9.03H.94v-.952h2.584V5.404h.946v2.673h2.59v.952H4.47Z\" />\n      </svg>\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M6.679 10.177H1.315v-.92h5.364v.92Zm0-2.361H1.315v-.933h5.364v.933Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction DeleteKey() {\n  return (\n    <Key name=\"Delete\" width={64}>\n      <svg viewBox=\"0 0 24 14\" className=\"mt-auto ml-auto\">\n        <path d=\"M21.871 10.066c-.367 0-.682-.073-.945-.218a1.5 1.5 0 0 1-.606-.63 2.092 2.092 0 0 1-.207-.96v-.625c0-.373.073-.693.219-.961.148-.271.352-.48.61-.625.257-.149.549-.223.874-.223.334 0 .628.074.883.223.258.148.46.358.606.629.146.27.218.59.218.957v.496h-2.707v.207c0 .318.09.586.27.805.18.218.44.328.781.328.159 0 .3-.022.422-.067a.862.862 0 0 0 .3-.18.537.537 0 0 0 .153-.238h.727a.984.984 0 0 1-.133.352c-.07.125-.17.243-.3.355-.131.11-.293.2-.485.27-.193.07-.42.105-.68.105Zm-1.055-2.449h2.004v-.066a1.33 1.33 0 0 0-.125-.594.937.937 0 0 0-.351-.398.96.96 0 0 0-.524-.141.98.98 0 0 0-.527.14.937.937 0 0 0-.352.399 1.33 1.33 0 0 0-.125.594v.066ZM17.527 5.902v-1.03h.7v1.03h.93v.575h-.93v2.367c0 .242.048.405.144.488.096.08.27.121.52.121.05 0 .108-.002.175-.008l.149-.011V10a4.384 4.384 0 0 1-.504.035c-.326 0-.574-.042-.746-.125a.684.684 0 0 1-.348-.39 2.046 2.046 0 0 1-.094-.672V6.477h-.628v-.575h.632ZM14.473 10.066c-.368 0-.683-.073-.946-.218a1.5 1.5 0 0 1-.605-.63 2.092 2.092 0 0 1-.207-.96v-.625c0-.373.073-.693.219-.961.148-.271.351-.48.609-.625.258-.149.55-.223.875-.223.333 0 .628.074.883.223.258.148.46.358.605.629.146.27.219.59.219.957v.496h-2.707v.207c0 .318.09.586.27.805.18.218.44.328.78.328.16 0 .3-.022.423-.067a.863.863 0 0 0 .3-.18.539.539 0 0 0 .153-.238h.726a.983.983 0 0 1-.133.352c-.07.125-.17.243-.3.355-.13.11-.292.2-.485.27-.192.07-.419.105-.68.105Zm-1.055-2.449h2.004v-.066a1.33 1.33 0 0 0-.125-.594.938.938 0 0 0-.352-.398.96.96 0 0 0-.523-.141.98.98 0 0 0-.527.14.937.937 0 0 0-.352.399c-.083.17-.125.367-.125.594v.066ZM10.797 10V4.387h.707V10h-.707ZM7.95 10.066c-.368 0-.683-.073-.946-.218a1.5 1.5 0 0 1-.606-.63 2.092 2.092 0 0 1-.207-.96v-.625c0-.373.073-.693.22-.961.148-.271.35-.48.609-.625.257-.149.549-.223.875-.223.333 0 .627.074.882.223.258.148.46.358.606.629.146.27.219.59.219.957v.496H6.895v.207c0 .318.09.586.27.805.179.218.44.328.78.328.16 0 .3-.022.422-.067a.863.863 0 0 0 .301-.18.538.538 0 0 0 .152-.238h.727a.984.984 0 0 1-.133.352c-.07.125-.17.243-.3.355-.13.11-.292.2-.485.27s-.42.105-.68.105ZM6.894 7.617h2.003v-.066a1.33 1.33 0 0 0-.125-.594.938.938 0 0 0-.351-.398.96.96 0 0 0-.524-.141.98.98 0 0 0-.527.14.937.937 0 0 0-.351.399 1.33 1.33 0 0 0-.125.594v.066ZM3.059 5.828c.195 0 .372.027.53.082.16.052.295.13.407.23.115.102.2.222.254.36h.035V4.387h.707V10h-.68v-.656h-.035a.745.745 0 0 1-.156.293c-.075.086-.17.16-.281.222a1.44 1.44 0 0 1-.367.145 1.645 1.645 0 0 1-.43.055c-.32 0-.599-.07-.836-.211a1.432 1.432 0 0 1-.547-.598 1.986 1.986 0 0 1-.195-.906v-.797c0-.35.066-.651.2-.906.132-.258.318-.457.558-.598.24-.143.518-.215.836-.215Zm.144.606a.976.976 0 0 0-.55.152.966.966 0 0 0-.352.43 1.657 1.657 0 0 0-.125.675v.5c0 .266.041.494.125.684.086.188.207.33.363.43.156.099.343.148.559.148.216 0 .402-.048.558-.144.16-.1.283-.237.371-.414.089-.18.133-.392.133-.637v-.711c0-.216-.047-.408-.14-.574a1.061 1.061 0 0 0-.941-.54Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction TabKey() {\n  return (\n    <Key name=\"Tab\" width={64}>\n      <svg viewBox=\"0 0 13 14\" className=\"mt-auto mr-auto\">\n        <path d=\"M10.016 10.063c-.193 0-.37-.027-.532-.079a1.235 1.235 0 0 1-.418-.234 1.037 1.037 0 0 1-.27-.36h-.034V10h-.68V4.387h.707v2.156h.035a.83.83 0 0 1 .16-.29.998.998 0 0 1 .27-.226c.107-.065.226-.114.36-.148.135-.034.275-.05.421-.05.323 0 .602.07.836.214.237.143.42.346.547.61.128.262.191.575.191.937v.707c0 .364-.065.678-.195.941a1.407 1.407 0 0 1-.55.61c-.238.143-.52.214-.848.214Zm-.137-.606c.216 0 .4-.05.55-.148a.932.932 0 0 0 .348-.438c.081-.193.121-.424.121-.695v-.438c0-.276-.041-.51-.125-.703a.95.95 0 0 0-.351-.445.98.98 0 0 0-.563-.156c-.218 0-.408.05-.57.148A.97.97 0 0 0 8.918 7a1.508 1.508 0 0 0-.129.648v.676c0 .219.047.414.14.586.094.172.223.306.387.402.164.097.352.145.563.145ZM4.82 10.066c-.398 0-.722-.114-.972-.343-.248-.232-.371-.538-.371-.918 0-.36.122-.65.367-.871.247-.224.607-.336 1.078-.336h1.074V7.19c0-.263-.078-.457-.234-.582-.154-.125-.362-.187-.625-.187-.175 0-.318.02-.43.062a.623.623 0 0 0-.258.172.717.717 0 0 0-.133.254h-.683c.015-.174.06-.328.133-.46.075-.136.178-.25.308-.34.13-.094.287-.165.469-.212.182-.049.388-.074.617-.074.284 0 .542.044.774.133.234.089.421.233.562.434.14.2.211.468.211.804V10h-.68v-.547h-.03c-.069.11-.156.211-.263.305-.104.094-.231.17-.382.226a1.559 1.559 0 0 1-.532.082Zm.196-.593c.187 0 .354-.038.5-.114a.94.94 0 0 0 .347-.312.798.798 0 0 0 .133-.453v-.457H4.98c-.278 0-.479.06-.601.183a.632.632 0 0 0-.18.461c0 .237.082.412.246.524a.999.999 0 0 0 .57.168ZM.926 5.902v-1.03h.699v1.03h.93v.575h-.93v2.367c0 .242.048.405.145.488.096.08.27.121.52.121.049 0 .107-.002.175-.008l.148-.011V10a4.385 4.385 0 0 1-.504.035c-.325 0-.574-.042-.746-.125a.684.684 0 0 1-.347-.39 2.046 2.046 0 0 1-.094-.672V6.477H.293v-.575h.633Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction QKey() {\n  return (\n    <Key name=\"Q\">\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M8.51 13.038H7.124l-.84-1.155c-.238.105-.49.182-.76.232-.264.05-.544.075-.84.075-.775 0-1.445-.168-2.01-.505a3.36 3.36 0 0 1-1.299-1.443c-.3-.629-.451-1.374-.451-2.235V6.66c0-.861.15-1.604.451-2.228.305-.63.738-1.112 1.299-1.45.565-.337 1.235-.505 2.01-.505.779 0 1.449.168 2.01.505.564.338.997.82 1.298 1.45.301.624.451 1.367.451 2.228v1.347c0 .697-.102 1.324-.307 1.88a3.41 3.41 0 0 1-.896 1.38l1.272 1.771ZM2.175 8.014c0 .642.1 1.194.3 1.654.206.46.495.813.869 1.06.373.246.818.371 1.333.376.168 0 .33-.014.485-.041.155-.032.3-.076.438-.13L4.354 9.21h1.381l.383.533c.132.178.262.356.39.533.223-.268.392-.592.506-.97.118-.383.177-.814.177-1.292V6.66c0-.638-.102-1.187-.307-1.647-.2-.46-.488-.811-.862-1.053-.373-.246-.82-.37-1.34-.37-.514 0-.961.124-1.34.37-.373.242-.662.592-.867 1.053-.2.46-.301 1.01-.301 1.647v1.354Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction WKey() {\n  return (\n    <Key name=\"W\">\n      <svg viewBox=\"0 0 12 14\">\n        <path d=\"M3.958 11.999H2.837L.273 2.668h1.45l1.695 7.424h.068l1.826-7.424h1.23l1.832 7.424h.068l1.696-7.424h1.442l-2.557 9.331H7.896l-1.942-7.3H5.9l-1.941 7.3Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction EKey() {\n  return (\n    <Key name=\"E\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M6.932 10.892v1.107h-5.64V2.668h5.64v1.107H2.543v2.926h4.143v1.08H2.543v3.11h4.389Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction RKey() {\n  return (\n    <Key name=\"R\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M4.696 2.668c.625 0 1.162.116 1.614.349.45.227.8.551 1.045.97.247.42.37.912.37 1.477 0 .428-.073.816-.22 1.162a2.61 2.61 0 0 1-1.517 1.442L7.888 12H6.46L4.744 8.308H2.543v3.691H1.292V2.668h3.404Zm-2.153 4.56h1.941c.63 0 1.11-.144 1.443-.431.332-.287.499-.709.499-1.265 0-.565-.169-1-.506-1.305-.333-.31-.795-.465-1.388-.465H2.543v3.466Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction TKey() {\n  return (\n    <Key name=\"T\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M4.396 11.999H3.138V3.782H.32V2.668h6.884v1.114h-2.81V12Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction YKey() {\n  return (\n    <Key name=\"Y\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M4.553 11.999H3.295V8.027L.178 2.668h1.428l2.29 4.122h.062l2.297-4.122H7.67L4.553 8.021v3.978Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction UKey() {\n  return (\n    <Key name=\"U\">\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M4.744 11.07c.702 0 1.26-.208 1.675-.623.415-.419.622-.989.622-1.709v-6.07h1.265v6.111c0 .68-.148 1.276-.445 1.791-.296.515-.71.914-1.244 1.197-.533.282-1.157.423-1.873.423-.715 0-1.34-.14-1.873-.423a3.055 3.055 0 0 1-1.244-1.197c-.296-.515-.444-1.112-.444-1.79V2.667H2.44v6.07c0 .72.208 1.29.623 1.71.419.414.98.621 1.681.621Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction IKey() {\n  return (\n    <Key name=\"I\">\n      <svg viewBox=\"0 0 4 14\">\n        <path d=\"M2.55 11.999H1.292V2.668H2.55v9.331Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction OKey() {\n  return (\n    <Key name=\"O\">\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M7.178 6.653c0-.688-.112-1.257-.335-1.709-.219-.45-.515-.788-.889-1.011a2.393 2.393 0 0 0-1.271-.342c-.474 0-.9.114-1.279.342-.373.223-.67.56-.888 1.011-.22.452-.329 1.021-.329 1.71v1.36c0 .683.11 1.253.329 1.709.218.455.515.795.888 1.018.379.223.805.335 1.279.335a2.43 2.43 0 0 0 1.271-.335c.374-.223.67-.563.889-1.018.223-.456.335-1.026.335-1.71v-1.36Zm1.264 1.36c0 .88-.155 1.632-.464 2.257-.31.624-.748 1.1-1.313 1.428-.56.328-1.221.492-1.982.492-.766 0-1.431-.164-1.996-.492-.561-.328-.996-.804-1.306-1.428-.305-.625-.458-1.377-.458-2.256V6.66c0-.884.153-1.638.458-2.263.31-.624.745-1.1 1.306-1.428.565-.328 1.23-.492 1.996-.492.76 0 1.421.164 1.982.492a3.303 3.303 0 0 1 1.313 1.428c.31.625.464 1.379.464 2.263v1.354Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction PKey() {\n  return (\n    <Key name=\"P\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M4.724 2.668c.606 0 1.132.125 1.579.376.446.246.793.588 1.039 1.025.246.438.369.946.369 1.525 0 .574-.123 1.082-.37 1.524-.245.438-.594.78-1.045 1.026-.447.246-.973.369-1.58.369H2.55v3.486H1.292V2.668h3.432Zm1.695 2.926c0-.584-.16-1.032-.479-1.347-.314-.319-.758-.478-1.333-.478H2.55v3.65h2.057c.388 0 .714-.07.978-.212.269-.146.474-.355.615-.629.146-.273.219-.601.219-.984Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction LeftSquareBracketKey() {\n  return (\n    <Key name=\"LeftSquareBracket\">\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M1.524 7.692v.158c.388.032.68.175.875.43.2.255.301.622.301 1.1v2.086c0 .328.046.597.137.807a.916.916 0 0 0 .472.471c.218.105.515.157.888.157h.267v.964H4.06c-.534 0-.987-.077-1.36-.232a1.755 1.755 0 0 1-.862-.738c-.196-.338-.294-.782-.294-1.333V9.784c0-.474-.075-.82-.226-1.039-.145-.223-.45-.335-.916-.335V7.132c.465 0 .77-.112.916-.335.15-.223.226-.57.226-1.04V4.173c0-.552.098-.99.294-1.313.2-.328.487-.565.861-.71.374-.146.825-.22 1.354-.22h.41v.965h-.267c-.373 0-.67.05-.888.15a.92.92 0 0 0-.472.465c-.091.21-.137.48-.137.813v1.853c0 .483-.1.847-.3 1.094-.197.241-.488.382-.876.423Z\" />\n      </svg>\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M1.36 1.93h3.104v.93H2.543v10.075h1.92v.93H1.36V1.93Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction RightSquareBracketKey() {\n  return (\n    <Key name=\"RightSquareBracket\">\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M3.343 7.85v-.158c-.378-.04-.67-.182-.875-.423-.2-.247-.301-.611-.301-1.094V4.322c0-.332-.048-.604-.144-.813a.89.89 0 0 0-.464-.465c-.22-.1-.515-.15-.89-.15H.404V1.93h.41c.53 0 .98.073 1.354.218.374.146.659.383.854.711.201.324.301.761.301 1.313v1.586c0 .47.073.816.219 1.039.15.223.458.335.923.335V8.41c-.465 0-.773.112-.923.335-.146.219-.219.565-.219 1.04v1.777c0 .551-.1.995-.3 1.333a1.72 1.72 0 0 1-.855.738c-.374.155-.825.232-1.354.232h-.41v-.964H.67c.374 0 .67-.052.889-.157a.887.887 0 0 0 .464-.471c.096-.21.144-.48.144-.807V9.38c0-.479.1-.846.3-1.1.206-.256.498-.4.876-.431Z\" />\n      </svg>\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M.403 1.93h3.104v11.935H.403v-.93h1.928V2.86H.403v-.93Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction BackSlashKey() {\n  return (\n    <Key name=\"BackSlash\">\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M2.974 13.865H1.887V1.93h1.087v11.935Z\" />\n      </svg>\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M4.69 13.865H3.54L.178 1.93h1.141l3.37 11.935Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction CapsLockKey() {\n  return (\n    <Key name=\"CapsLock\" width={72}>\n      <svg viewBox=\"0 0 3 3\" className=\"mt-1 mr-auto h-[3px] fill-gray-500\">\n        <path d=\"M3 1.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z\" />\n      </svg>\n      <svg viewBox=\"0 0 35 14\" className=\"mt-auto mr-auto\">\n        <path d=\"M31.547 8.488V10h-.707V4.387h.707v3.351h.027l1.656-1.847h.817l-1.61 1.785L34.153 10h-.843l-1.391-1.89-.371.378ZM26.387 8.273v-.671c0-.36.069-.672.207-.938.14-.268.338-.475.593-.621.258-.146.563-.219.915-.219.343 0 .631.063.863.188.232.122.407.28.527.472.12.19.182.388.188.594h-.688a.887.887 0 0 0-.11-.27.668.668 0 0 0-.265-.257c-.122-.073-.29-.11-.504-.11-.333 0-.587.107-.761.32-.172.214-.258.502-.258.864v.656c0 .354.09.638.27.852.182.21.432.316.75.316.202 0 .365-.027.488-.082a.683.683 0 0 0 .285-.215.803.803 0 0 0 .133-.293h.687c-.008.2-.07.395-.188.582a1.283 1.283 0 0 1-.527.454c-.232.114-.529.171-.89.171-.352 0-.657-.073-.915-.218a1.501 1.501 0 0 1-.593-.621 2.057 2.057 0 0 1-.207-.954ZM23.625 5.824c.341 0 .645.072.91.215.268.14.48.348.633.621.154.271.23.597.23.977v.61c0 .385-.076.714-.23.987-.154.271-.365.478-.633.621-.265.141-.569.211-.91.211-.346 0-.654-.07-.922-.21a1.543 1.543 0 0 1-.629-.618c-.15-.273-.226-.604-.226-.992v-.605c0-.373.076-.694.23-.965.154-.271.365-.48.633-.63.268-.148.573-.222.914-.222Zm.004.602a1 1 0 0 0-.574.164 1.058 1.058 0 0 0-.371.45c-.086.19-.13.41-.13.66v.495c0 .256.042.478.126.668.083.19.204.339.363.446.161.106.357.16.586.16a.993.993 0 0 0 .574-.16.985.985 0 0 0 .36-.446c.083-.19.125-.412.125-.668V7.7a1.6 1.6 0 0 0-.13-.664.974.974 0 0 0-.93-.61ZM19.93 10V4.387h.707V10h-.707ZM13.828 7.07c0-.291.072-.528.215-.71.146-.186.335-.32.566-.407.235-.086.485-.129.75-.129.297 0 .555.048.774.145.221.096.393.23.515.402a1 1 0 0 1 .184.602h-.656a.576.576 0 0 0-.106-.282.67.67 0 0 0-.277-.222 1.02 1.02 0 0 0-.457-.09c-.24 0-.438.053-.594.16a.527.527 0 0 0-.23.461.45.45 0 0 0 .093.297.677.677 0 0 0 .243.176c.101.041.212.074.332.097.12.024.237.046.351.067.258.044.492.105.703.183.214.076.384.19.512.34.128.149.192.357.192.625 0 .274-.07.507-.207.7-.136.19-.327.334-.575.433a2.284 2.284 0 0 1-.86.148c-.304 0-.572-.044-.804-.132a1.21 1.21 0 0 1-.543-.387 1.005 1.005 0 0 1-.195-.625h.691c.018.122.06.228.125.316a.63.63 0 0 0 .285.207c.125.047.283.07.473.07.216 0 .39-.03.524-.09a.657.657 0 0 0 .296-.234.583.583 0 0 0 .094-.324c0-.153-.045-.27-.137-.351a.902.902 0 0 0-.37-.184 6.202 6.202 0 0 0-.52-.121 5.533 5.533 0 0 1-.7-.188 1.165 1.165 0 0 1-.5-.332c-.124-.145-.187-.352-.187-.62ZM11.23 10.063c-.187 0-.359-.027-.515-.079a1.153 1.153 0 0 1-.399-.226 1.108 1.108 0 0 1-.265-.367h-.035v1.972h-.707V5.887h.68v.656h.034a1.007 1.007 0 0 1 .454-.523c.109-.063.229-.11.359-.141.13-.034.27-.05.418-.05.323 0 .601.07.836.214.234.143.415.346.543.61.127.262.191.575.191.937v.707c0 .364-.065.678-.195.941a1.407 1.407 0 0 1-.55.61 1.62 1.62 0 0 1-.849.214Zm-.144-.606c.219 0 .404-.05.555-.152a.955.955 0 0 0 .351-.438c.08-.193.121-.426.121-.699V7.73c0-.276-.04-.51-.12-.703a.944.944 0 0 0-.356-.441 1.024 1.024 0 0 0-.57-.152c-.217 0-.404.048-.563.144a.959.959 0 0 0-.363.414c-.084.177-.125.39-.125.637v.715c0 .219.044.413.132.582a.995.995 0 0 0 .38.39c.16.094.347.141.558.141ZM6.047 10.066c-.399 0-.723-.114-.973-.343-.247-.232-.37-.538-.37-.918 0-.36.122-.65.366-.871.248-.224.607-.336 1.078-.336h1.075V7.19c0-.263-.078-.457-.235-.582-.153-.125-.362-.187-.625-.187-.174 0-.317.02-.43.062a.623.623 0 0 0-.257.172.717.717 0 0 0-.133.254h-.684c.016-.174.06-.328.133-.46.076-.136.179-.25.309-.34.13-.094.286-.165.469-.212.182-.049.388-.074.617-.074.284 0 .541.044.773.133.235.089.422.233.563.434.14.2.21.468.21.804V10h-.68v-.547h-.03c-.068.11-.155.211-.262.305-.104.094-.232.17-.383.226a1.559 1.559 0 0 1-.531.082Zm.195-.593c.188 0 .354-.038.5-.114a.94.94 0 0 0 .348-.312.798.798 0 0 0 .133-.453v-.457H6.207c-.279 0-.48.06-.602.183a.632.632 0 0 0-.18.461c0 .237.083.412.247.524a.999.999 0 0 0 .57.168ZM.496 8.273v-.671c0-.36.07-.672.207-.938.14-.268.339-.475.594-.621.258-.146.562-.219.914-.219.344 0 .631.063.863.188.232.122.408.28.528.472.12.19.182.388.187.594h-.687a.888.888 0 0 0-.11-.27.668.668 0 0 0-.265-.257c-.123-.073-.29-.11-.504-.11-.334 0-.588.107-.762.32-.172.214-.258.502-.258.864v.656c0 .354.09.638.27.852.182.21.432.316.75.316.203 0 .366-.027.488-.082a.683.683 0 0 0 .285-.215.804.804 0 0 0 .133-.293h.687c-.007.2-.07.395-.187.582a1.283 1.283 0 0 1-.527.454c-.232.114-.53.171-.891.171-.352 0-.656-.073-.914-.218a1.501 1.501 0 0 1-.594-.621 2.058 2.058 0 0 1-.207-.954Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction AKey() {\n  return (\n    <Key name=\"A\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M1.654 11.999H.35L3.65 2.668h1.306l3.295 9.331H6.925l-.916-2.762H2.57L1.654 12Zm2.66-7.889h-.048L2.898 8.232h2.79L4.313 4.11Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction SKey() {\n  return (\n    <Key name=\"S\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M.608 9.634H1.88c.045.223.13.424.253.601.123.178.28.33.471.458.196.128.42.228.67.301.256.069.536.103.841.103.438 0 .811-.066 1.121-.199.315-.132.556-.319.725-.56.169-.242.253-.526.253-.855 0-.373-.123-.674-.37-.902-.245-.232-.628-.415-1.148-.547l-1.62-.424a3.641 3.641 0 0 1-1.196-.52 2.36 2.36 0 0 1-.752-.826 2.35 2.35 0 0 1-.26-1.115c0-.524.141-.989.424-1.394.283-.41.668-.73 1.155-.957.492-.233 1.053-.349 1.682-.349.615 0 1.157.105 1.627.315.47.21.843.499 1.12.868.284.369.448.79.493 1.264H6.104a1.495 1.495 0 0 0-.348-.717 1.752 1.752 0 0 0-.684-.465c-.278-.114-.599-.171-.964-.171-.587 0-1.061.144-1.421.43-.36.288-.54.661-.54 1.122 0 .328.111.604.334.827.228.223.554.39.978.499l1.593.417c.547.146 1 .33 1.36.554.36.223.629.499.807.827.177.328.266.718.266 1.169 0 .565-.143 1.06-.43 1.483-.283.42-.686.745-1.21.978-.52.227-1.14.341-1.86.341-.492 0-.938-.061-1.34-.184a3.188 3.188 0 0 1-1.039-.527 2.673 2.673 0 0 1-.697-.806 2.644 2.644 0 0 1-.3-1.04Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction DKey() {\n  return (\n    <Key name=\"D\">\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M1.292 2.668h3.035c.907 0 1.664.178 2.27.533.61.351 1.068.873 1.374 1.566.31.692.465 1.547.465 2.563 0 1.012-.155 1.866-.465 2.564-.306.692-.764 1.216-1.374 1.572-.606.355-1.363.533-2.27.533H1.292V2.668Zm1.258 1.1v7.124h1.688c.483 0 .907-.073 1.272-.22.364-.15.665-.373.902-.67.237-.295.415-.664.533-1.106.123-.443.185-.962.185-1.56 0-.792-.107-1.453-.321-1.982-.21-.528-.529-.925-.957-1.189-.429-.264-.967-.396-1.614-.396H2.55Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction FKey() {\n  return (\n    <Key name=\"F\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M2.55 11.999H1.292V2.668H6.85v1.114h-4.3V6.9h3.937V8H2.55v3.999Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction GKey() {\n  return (\n    <Key name=\"G\">\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M8.107 5.218H6.85a2.214 2.214 0 0 0-.287-.677 1.948 1.948 0 0 0-.472-.513 1.948 1.948 0 0 0-.643-.328 2.734 2.734 0 0 0-.793-.11c-.774 0-1.38.274-1.818.821-.438.542-.656 1.297-.656 2.263v1.271c0 .657.098 1.22.294 1.689.2.465.487.822.86 1.073.375.246.828.37 1.361.37.497 0 .912-.087 1.244-.26.338-.174.588-.41.752-.712.169-.305.253-.651.253-1.039v-.875H4.587V7.118h3.582V9.04c0 .451-.078.868-.232 1.251a2.82 2.82 0 0 1-.67 1.005 3.065 3.065 0 0 1-1.08.663c-.429.155-.923.232-1.484.232-.601 0-1.137-.095-1.606-.287a3.16 3.16 0 0 1-1.19-.834 3.672 3.672 0 0 1-.738-1.333c-.164-.524-.246-1.12-.246-1.79V6.673c0-.857.15-1.6.451-2.229.3-.629.731-1.114 1.292-1.456.565-.342 1.237-.512 2.017-.512.469 0 .9.066 1.292.198a3.27 3.27 0 0 1 1.032.56c.3.242.544.531.731.869.187.332.31.704.37 1.114Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction HKey() {\n  return (\n    <Key name=\"H\">\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M8.429 11.999H7.17V7.727H2.55v4.272H1.292V2.668H2.55v3.951h4.62V2.668H8.43v9.331Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction JKey() {\n  return (\n    <Key name=\"J\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M.349 9.805h1.23c.01.246.075.462.198.65.123.186.287.334.493.443.21.105.446.158.71.158.502 0 .882-.144 1.142-.431.26-.292.39-.71.39-1.258V2.668h1.264v6.597c0 .934-.246 1.656-.738 2.167-.492.505-1.178.758-2.058.758-.4 0-.765-.059-1.093-.177a2.31 2.31 0 0 1-.827-.492 2.206 2.206 0 0 1-.527-.76 2.437 2.437 0 0 1-.184-.956Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction KKey() {\n  return (\n    <Key name=\"K\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M8.08 2.668 4.614 6.646 8.258 12H6.672L3.678 7.508 2.55 8.745V12H1.292V2.668H2.55v4.478h.054L6.61 2.668h1.47Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction LKey() {\n  return (\n    <Key name=\"L\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M6.815 10.878v1.121H1.292V2.668H2.55v8.21h4.265Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction SemicolonKey() {\n  return (\n    <Key name=\"Semicolon\">\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M1.176 4.991c0-.287.095-.524.287-.71A.953.953 0 0 1 2.153 4c.283 0 .52.093.711.28a.94.94 0 0 1 .294.711.903.903 0 0 1-.294.69.991.991 0 0 1-.71.274.965.965 0 0 1-.691-.273.913.913 0 0 1-.287-.69Zm0 4.635c0-.292.095-.529.287-.711a.953.953 0 0 1 .69-.28c.283 0 .52.093.711.28a.94.94 0 0 1 .294.711.916.916 0 0 1-.294.69.991.991 0 0 1-.71.274.965.965 0 0 1-.691-.274.927.927 0 0 1-.287-.69Z\" />\n      </svg>\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M1.183 3.991c0-.287.095-.524.287-.71A.953.953 0 0 1 2.16 3c.283 0 .52.093.711.28a.951.951 0 0 1 .287.711.913.913 0 0 1-.287.69.991.991 0 0 1-.71.274.965.965 0 0 1-.691-.273.913.913 0 0 1-.287-.69Zm.758 7.916H.827l.615-3.965h1.45l-.95 3.965Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction SingleQuoteKey() {\n  return (\n    <Key name=\"SingleQuote\">\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M1.955 4H3.07l-.615 3.965h-1.45L1.956 4ZM4.86 4h1.115l-.616 3.965H3.91L4.86 4Z\" />\n      </svg>\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M2.105 2.668H3.22l-.616 3.965H1.155l.95-3.965Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction ReturnKey() {\n  return (\n    <Key name=\"Return\" width={72}>\n      <svg viewBox=\"0 0 24 14\" className=\"mt-auto ml-auto\">\n        <path d=\"M19.945 10V5.89h.7v.747h.039a1.32 1.32 0 0 1 .18-.352c.085-.125.213-.233.382-.324.17-.091.397-.137.684-.137.414 0 .75.114 1.008.34.257.227.386.57.386 1.031V10h-.707V7.418c0-.216-.036-.396-.11-.54a.718.718 0 0 0-.304-.327.976.976 0 0 0-.476-.11c-.24 0-.44.056-.602.168a1.008 1.008 0 0 0-.355.457 1.702 1.702 0 0 0-.118.645V10h-.707ZM16.875 10V5.89h.707v.633h.035c.037-.09.096-.19.18-.296.083-.107.202-.2.355-.278.154-.08.35-.12.59-.12.044 0 .087.002.13.007.043.003.086.006.128.012v.644a.838.838 0 0 0-.145-.015 2.165 2.165 0 0 0-.187-.008c-.24 0-.44.044-.602.133a.892.892 0 0 0-.359.355c-.08.148-.121.316-.121.504V10h-.711ZM15.422 5.89V10h-.7v-.684h-.038c-.04.112-.11.226-.211.34a1.21 1.21 0 0 1-.403.293 1.43 1.43 0 0 1-.61.117c-.273 0-.516-.044-.73-.132a1.047 1.047 0 0 1-.496-.422c-.12-.196-.18-.452-.18-.77V5.891h.708v2.66c0 .226.04.406.12.539.084.13.192.223.325.277.135.055.285.082.45.082.202 0 .383-.043.542-.129.16-.088.284-.207.375-.355a.89.89 0 0 0 .14-.485V5.89h.708ZM9.3 5.902v-1.03h.7v1.03h.93v.575H10v2.367c0 .242.048.405.145.488.096.08.27.121.52.121.049 0 .107-.002.175-.008l.148-.011V10a4.382 4.382 0 0 1-.504.035c-.325 0-.574-.042-.746-.125a.684.684 0 0 1-.347-.39 2.046 2.046 0 0 1-.094-.672V6.477h-.629v-.575h.633ZM6.246 10.066c-.367 0-.682-.073-.945-.218a1.5 1.5 0 0 1-.606-.63 2.092 2.092 0 0 1-.207-.96v-.625c0-.373.073-.693.219-.961.148-.271.352-.48.61-.625.257-.149.549-.223.874-.223.334 0 .628.074.883.223.258.148.46.358.606.629.146.27.218.59.218.957v.496H5.191v.207c0 .318.09.586.27.805.18.218.44.328.781.328.159 0 .3-.022.422-.067a.863.863 0 0 0 .3-.18.538.538 0 0 0 .153-.238h.727a.984.984 0 0 1-.133.352c-.07.125-.17.243-.3.355-.131.11-.293.2-.485.27-.193.07-.42.105-.68.105ZM5.191 7.617h2.004v-.066a1.33 1.33 0 0 0-.125-.594.937.937 0 0 0-.351-.398.96.96 0 0 0-.524-.141.98.98 0 0 0-.527.14.937.937 0 0 0-.352.399 1.33 1.33 0 0 0-.125.594v.066ZM1.625 10V5.89h.707v.633h.035c.037-.09.097-.19.18-.296.083-.107.202-.2.355-.278.154-.08.35-.12.59-.12.044 0 .087.002.13.007.043.003.086.006.128.012v.644a.838.838 0 0 0-.145-.015 2.165 2.165 0 0 0-.187-.008c-.24 0-.44.044-.602.133a.891.891 0 0 0-.359.355c-.08.148-.121.316-.121.504V10h-.711Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction ShiftKey({ position }: { position: 'Left' | 'Right' }) {\n  return (\n    <Key name={`${position}Shift`} width={94}>\n      <svg\n        viewBox=\"0 0 18 14\"\n        className={clsx('mt-auto', position === 'Left' ? 'mr-auto' : 'ml-auto')}\n      >\n        <path d=\"M15.91 5.902v-1.03h.7v1.03h.93v.575h-.93v2.367c0 .242.048.405.144.488.096.08.27.121.52.121.049 0 .107-.002.175-.008l.149-.011V10a4.384 4.384 0 0 1-.504.035c-.326 0-.574-.042-.746-.125A.683.683 0 0 1 16 9.52a2.046 2.046 0 0 1-.094-.672V6.477h-.629v-.575h.633ZM12.898 10V6.477h-.691v-.575h.691v-.41c0-.396.1-.686.301-.87.2-.188.53-.282.989-.282a4.79 4.79 0 0 1 .468.023v.57a1.49 1.49 0 0 0-.168-.011 6.391 6.391 0 0 0-.199-.004c-.242 0-.418.044-.527.133-.11.086-.164.251-.164.496v.355h.953v.575h-.946V10h-.707ZM10.348 10V5.89h.707V10h-.707Zm-.149-5.375c0-.143.047-.26.14-.352a.498.498 0 0 1 .356-.136c.149 0 .271.045.367.136a.462.462 0 0 1 .145.352c0 .14-.048.257-.145.348a.513.513 0 0 1-.367.136.498.498 0 0 1-.355-.136.464.464 0 0 1-.14-.348ZM5.488 10V4.387h.707v2.25h.035c.03-.094.088-.205.176-.332.091-.13.226-.242.403-.336.177-.097.407-.145.691-.145.398 0 .728.115.988.344.263.23.395.574.395 1.035V10h-.707V7.426c0-.216-.038-.397-.114-.543a.725.725 0 0 0-.316-.332 1 1 0 0 0-.48-.11.998.998 0 0 0-.59.168 1.033 1.033 0 0 0-.36.457c-.08.193-.12.408-.12.645V10h-.708ZM1.21 7.07c0-.291.073-.528.216-.71.146-.186.334-.32.566-.407.235-.086.485-.129.75-.129.297 0 .555.048.774.145.221.096.393.23.515.402a1 1 0 0 1 .184.602h-.656a.576.576 0 0 0-.106-.282.67.67 0 0 0-.277-.222 1.02 1.02 0 0 0-.457-.09c-.24 0-.438.053-.594.16a.527.527 0 0 0-.23.461.45.45 0 0 0 .093.297.677.677 0 0 0 .242.176c.102.041.213.074.333.097.12.024.236.046.351.067.258.044.492.105.703.183.214.076.384.19.512.34.128.149.191.357.191.625 0 .274-.069.507-.207.7-.135.19-.327.334-.574.433a2.284 2.284 0 0 1-.86.148c-.304 0-.572-.044-.804-.132a1.21 1.21 0 0 1-.543-.387 1.005 1.005 0 0 1-.195-.625h.691c.018.122.06.228.125.316a.63.63 0 0 0 .285.207c.125.047.283.07.473.07.216 0 .39-.03.523-.09a.657.657 0 0 0 .297-.234.583.583 0 0 0 .094-.324c0-.153-.046-.27-.137-.351a.902.902 0 0 0-.37-.184 6.195 6.195 0 0 0-.52-.121 5.53 5.53 0 0 1-.7-.188 1.165 1.165 0 0 1-.5-.332c-.125-.145-.187-.352-.187-.62Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction ZKey() {\n  return (\n    <Key name=\"Z\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M.752 11.999v-.868l4.942-7.287v-.069H.923V2.668h6.33v.868l-4.915 7.28v.076h5.031v1.107H.752Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction XKey() {\n  return (\n    <Key name=\"X\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"m7.882 2.668-3.083 4.71 3.021 4.621H6.31l-2.29-3.65H3.95l-2.31 3.65H.226L3.26 7.296.267 2.668h1.51l2.304 3.664h.068l2.29-3.664h1.443Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction CKey() {\n  return (\n    <Key name=\"C\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M2.188 6.701v1.32c0 .647.095 1.198.287 1.654.191.455.469.804.834 1.046.364.237.806.355 1.326.355.4 0 .756-.07 1.066-.212.31-.141.56-.337.752-.588a1.77 1.77 0 0 0 .356-.875h1.244v.041c-.046.529-.224 1-.533 1.415-.306.415-.71.741-1.21.978-.497.237-1.058.355-1.682.355-.788 0-1.46-.161-2.017-.485a3.143 3.143 0 0 1-1.257-1.422c-.288-.62-.431-1.37-.431-2.249V6.687c0-.88.146-1.633.437-2.262.292-.63.716-1.11 1.272-1.443.556-.337 1.221-.505 1.996-.505.47 0 .905.07 1.306.211a3.32 3.32 0 0 1 1.06.602c.304.26.548.565.73.916.187.351.297.738.329 1.162v.041H6.809a1.937 1.937 0 0 0-.356-.936 2.026 2.026 0 0 0-.766-.643c-.31-.16-.66-.24-1.052-.24-.51 0-.948.126-1.313.377-.364.246-.645.601-.84 1.066-.197.46-.295 1.016-.295 1.668Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction VKey() {\n  return (\n    <Key name=\"V\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M3.637 11.999.294 2.668h1.435l2.543 7.855h.069l2.536-7.855h1.422l-3.343 9.331h-1.32Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction BKey() {\n  return (\n    <Key name=\"B\">\n      <svg viewBox=\"0 0 9 14\">\n        <path d=\"M1.292 11.999V2.668h3.541c.798 0 1.431.205 1.9.615.47.41.705.97.705 1.682 0 .35-.06.665-.178.943-.119.274-.29.504-.513.69a2.263 2.263 0 0 1-.813.424v.055c.414.055.772.18 1.073.376.305.196.54.458.704.786.169.324.253.704.253 1.142 0 .547-.132 1.016-.397 1.408-.264.387-.64.686-1.128.896-.487.21-1.064.314-1.729.314H1.292Zm1.251-8.25v2.925h1.408c.515 0 .934-.053 1.258-.157.328-.11.57-.274.725-.493.16-.223.239-.505.239-.847 0-.451-.137-.802-.41-1.053-.274-.25-.657-.376-1.149-.376H2.543Zm0 3.978v3.192h1.6c.902 0 1.55-.12 1.941-.362.396-.247.595-.638.595-1.176 0-.351-.075-.65-.226-.896a1.416 1.416 0 0 0-.663-.56c-.287-.132-.636-.198-1.046-.198H2.543Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction NKey() {\n  return (\n    <Key name=\"N\">\n      <svg viewBox=\"0 0 10 14\">\n        <path d=\"M2.53 11.999H1.291V2.668h1.19l4.614 7.178h.061V2.668h1.238v9.331h-1.19L2.577 4.814H2.53V12Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction MKey() {\n  return (\n    <Key name=\"M\">\n      <svg viewBox=\"0 0 12 14\">\n        <path d=\"M2.46 11.999H1.32V2.668h1.237l3.042 7.15h.04l3.036-7.15h1.23v9.331H8.764V5.218h-.069l-2.57 5.96H5.1l-2.578-5.96h-.061v6.781Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction CommaKey() {\n  return (\n    <Key name=\"Comma\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M.978 8.993V8.01l5.892-3.52V5.8L2.297 8.426v.061l4.573 2.489v1.298L.978 8.994Z\" />\n      </svg>\n      <svg viewBox=\"0 0 4 14\">\n        <path d=\"M1.825 10.965H.711L1.326 7h1.45l-.95 3.965Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction PeriodKey() {\n  return (\n    <Key name=\"Period\">\n      <svg viewBox=\"0 0 8 14\">\n        <path d=\"M6.87 8.009v.984L.984 12.274v-1.298l4.574-2.489v-.061L.984 5.8V4.488l5.886 3.52Z\" />\n      </svg>\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M1.06 8.991c0-.291.095-.528.287-.71A.953.953 0 0 1 2.037 8c.283 0 .52.093.711.28a.951.951 0 0 1 .287.711.927.927 0 0 1-.287.69.991.991 0 0 1-.71.274.965.965 0 0 1-.691-.273.927.927 0 0 1-.287-.69Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction ForwardSlashKey() {\n  return (\n    <Key name=\"ForwardSlash\">\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M2.317 11.179c0-.278.094-.508.28-.69a.95.95 0 0 1 .691-.274c.292 0 .531.091.718.273a.913.913 0 0 1 .287.69.897.897 0 0 1-.287.677c-.187.178-.426.267-.718.267a.964.964 0 0 1-.69-.267.908.908 0 0 1-.28-.676ZM.622 5.129C.618 4.619.7 4.192.868 3.85c.169-.347.394-.62.677-.82.283-.206.59-.354.923-.445a3.838 3.838 0 0 1 1.005-.137c.47 0 .913.087 1.333.26.424.169.768.426 1.032.772.264.347.396.787.396 1.32 0 .378-.066.702-.198.97a2.552 2.552 0 0 1-.499.69c-.2.197-.41.381-.629.555-.26.21-.47.39-.629.54-.155.146-.269.3-.341.465-.069.164-.103.378-.103.642v.41H2.639L2.632 8.5a1.844 1.844 0 0 1 .178-.861 2.46 2.46 0 0 1 .485-.663c.2-.196.412-.383.636-.56.319-.256.572-.489.759-.698.186-.21.28-.492.28-.848 0-.328-.075-.592-.226-.793a1.248 1.248 0 0 0-.574-.437 1.956 1.956 0 0 0-.752-.144c-.273 0-.513.043-.718.13-.2.082-.367.198-.499.349a1.47 1.47 0 0 0-.3.52 1.917 1.917 0 0 0-.103.635H.622Z\" />\n      </svg>\n      <svg viewBox=\"0 0 5 14\">\n        <path d=\"M1.32 13.865H.177L3.54 1.93h1.148L1.32 13.865Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction FunctionKey() {\n  return (\n    <Key name=\"Function\" className=\"rounded-bl-xl\">\n      <svg viewBox=\"0 0 8 14\" className=\"ml-auto\">\n        <path d=\"M3.945 10V5.89h.7v.747h.039c.033-.11.093-.227.18-.352.085-.125.213-.233.382-.324.17-.091.397-.137.684-.137.414 0 .75.114 1.008.34.257.227.386.57.386 1.031V10h-.707V7.418c0-.216-.036-.396-.11-.54a.718.718 0 0 0-.304-.327.976.976 0 0 0-.476-.11c-.24 0-.44.056-.602.168a1.007 1.007 0 0 0-.355.457 1.701 1.701 0 0 0-.118.645V10h-.707ZM1.164 10V6.477H.473v-.575h.691v-.41c0-.396.1-.686.3-.87.201-.188.53-.282.99-.282a4.79 4.79 0 0 1 .468.023v.57a1.492 1.492 0 0 0-.168-.011 6.39 6.39 0 0 0-.2-.004c-.241 0-.417.044-.527.133-.109.086-.164.251-.164.496v.355h.953v.575h-.945V10h-.707Z\" />\n      </svg>\n      <svg viewBox=\"0 0 11 14\" className=\"mt-auto mr-auto mb-0.5 ml-0.5\">\n        <path d=\"M5.014 10.979c-.29 0-.564-.1-.822-.299-.255-.199-.479-.477-.672-.835a5.007 5.007 0 0 1-.457-1.261A7.208 7.208 0 0 1 2.9 7.006c0-.568.055-1.095.163-1.582A5 5 0 0 1 3.52 4.16c.193-.36.417-.64.672-.84.255-.199.53-.298.822-.298.29 0 .563.1.818.298.254.2.479.48.672.84.196.357.349.779.457 1.265.111.487.167 1.014.167 1.582 0 .569-.056 1.095-.167 1.578a5.007 5.007 0 0 1-.457 1.261c-.193.358-.418.636-.672.835-.255.2-.528.299-.818.299Zm0-7.431c-.202 0-.395.092-.58.277a2.508 2.508 0 0 0-.492.76c-.144.32-.258.687-.343 1.103a6.806 6.806 0 0 0-.123 1.318c0 .454.041.89.123 1.306.085.413.2.78.343 1.103.143.322.308.575.492.76.185.184.378.277.58.277.202 0 .394-.093.576-.277.184-.185.348-.438.492-.76.144-.323.256-.69.338-1.103a6.51 6.51 0 0 0 .128-1.306c0-.463-.043-.902-.128-1.318a5.094 5.094 0 0 0-.338-1.103 2.508 2.508 0 0 0-.492-.76c-.182-.185-.374-.277-.576-.277Zm-.285-.47h.566V10.9H4.73V3.078ZM5.014 8.5c.43 0 .838.037 1.222.11.387.07.734.172 1.041.304.308.128.557.282.747.461l-.43.36c-.273-.214-.63-.378-1.073-.492a5.867 5.867 0 0 0-1.507-.176c-.565 0-1.07.059-1.512.176-.442.114-.8.278-1.072.492L2 9.375c.193-.179.442-.333.747-.461a4.839 4.839 0 0 1 1.041-.304c.387-.073.796-.11 1.226-.11Zm3.819-1.784v.563H1.191v-.563h7.642ZM5.014 5.512c-.43 0-.84-.035-1.226-.105a4.839 4.839 0 0 1-1.041-.303A2.628 2.628 0 0 1 2 4.638l.43-.356c.273.214.63.378 1.072.492.443.114.947.171 1.512.171a6.01 6.01 0 0 0 1.507-.171c.443-.114.8-.278 1.073-.492l.43.356c-.19.178-.439.334-.747.466a4.839 4.839 0 0 1-1.041.303c-.384.07-.791.105-1.222.105Zm0 5.757a4.078 4.078 0 0 1-1.652-.338 4.42 4.42 0 0 1-2.285-2.28 4.123 4.123 0 0 1-.334-1.653c0-.583.111-1.133.334-1.648a4.343 4.343 0 0 1 2.285-2.286A4.112 4.112 0 0 1 5.01 2.73c.583 0 1.132.112 1.648.334A4.343 4.343 0 0 1 8.943 5.35c.225.515.338 1.065.338 1.648 0 .585-.111 1.136-.334 1.652a4.358 4.358 0 0 1-2.285 2.28 4.068 4.068 0 0 1-1.648.339Zm0-.624c.498 0 .967-.095 1.406-.286a3.728 3.728 0 0 0 1.956-1.951 3.53 3.53 0 0 0 .286-1.41c0-.498-.096-.967-.286-1.407a3.662 3.662 0 0 0-.791-1.164 3.728 3.728 0 0 0-1.165-.791 3.51 3.51 0 0 0-1.41-.286c-.498 0-.967.095-1.406.286-.44.19-.828.454-1.165.79-.337.335-.6.723-.791 1.165-.19.44-.286.909-.286 1.407s.096.968.286 1.41a3.728 3.728 0 0 0 1.956 1.951c.442.19.912.286 1.41.286Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction ControlKey() {\n  return (\n    <Key name=\"Control\">\n      <svg viewBox=\"0 0 13 14\" className=\"mt-0.5 ml-auto\">\n        <path d=\"m1.582 8.568 4.254-4.347a.604.604 0 0 1 .445-.2.54.54 0 0 1 .235.053.55.55 0 0 1 .199.147l4.26 4.347c.113.114.17.252.17.416a.56.56 0 0 1-.287.498.565.565 0 0 1-.293.077.573.573 0 0 1-.422-.17L5.994 5.135h.569L2.407 9.389a.554.554 0 0 1-.416.17.597.597 0 0 1-.299-.077.573.573 0 0 1-.234-.726.678.678 0 0 1 .123-.188Z\" />\n      </svg>\n      <svg viewBox=\"0 0 28 14\" className=\"mt-auto ml-auto\">\n        <path d=\"M25.746 10V4.387h.707V10h-.707ZM22.762 5.824c.34 0 .644.072.91.215.268.14.479.348.633.621.153.271.23.597.23.977v.61c0 .385-.077.714-.23.987-.154.271-.365.478-.633.621-.266.141-.57.211-.91.211-.347 0-.654-.07-.922-.21a1.543 1.543 0 0 1-.63-.618c-.15-.273-.226-.604-.226-.992v-.605c0-.373.077-.694.23-.965.155-.271.365-.48.634-.63.268-.148.573-.222.914-.222Zm.004.602a1 1 0 0 0-.575.164 1.06 1.06 0 0 0-.37.45c-.087.19-.13.41-.13.66v.495c0 .256.042.478.125.668.084.19.205.339.364.446.161.106.357.16.586.16a.993.993 0 0 0 .574-.16.985.985 0 0 0 .36-.446c.083-.19.124-.412.124-.668V7.7a1.6 1.6 0 0 0-.129-.664.974.974 0 0 0-.93-.61ZM18.121 10V5.89h.707v.633h.035c.037-.09.097-.19.18-.296.083-.107.202-.2.355-.278.154-.08.35-.12.59-.12.045 0 .088.002.13.007.044.003.086.006.128.012v.644a.838.838 0 0 0-.144-.015 2.165 2.165 0 0 0-.188-.008c-.24 0-.44.044-.602.133a.891.891 0 0 0-.359.355c-.08.148-.121.316-.121.504V10h-.71ZM15.273 5.902v-1.03h.7v1.03h.93v.575h-.93v2.367c0 .242.048.405.144.488.096.08.27.121.52.121.05 0 .108-.002.175-.008l.149-.011V10a4.384 4.384 0 0 1-.504.035c-.325 0-.574-.042-.746-.125a.684.684 0 0 1-.348-.39 2.046 2.046 0 0 1-.093-.672V6.477h-.63v-.575h.633ZM10.3 10V5.89h.7v.747h.04a1.32 1.32 0 0 1 .179-.352c.086-.125.213-.233.383-.324.169-.091.397-.137.683-.137.414 0 .75.114 1.008.34.258.227.387.57.387 1.031V10h-.707V7.418c0-.216-.037-.396-.11-.54a.718.718 0 0 0-.304-.327.976.976 0 0 0-.477-.11c-.24 0-.44.056-.602.168a1.007 1.007 0 0 0-.355.457 1.702 1.702 0 0 0-.117.645V10H10.3ZM7.34 5.824c.341 0 .644.072.91.215.268.14.48.348.633.621.153.271.23.597.23.977v.61c0 .385-.077.714-.23.987-.154.271-.365.478-.633.621-.266.141-.569.211-.91.211-.347 0-.654-.07-.922-.21a1.542 1.542 0 0 1-.629-.618c-.151-.273-.226-.604-.226-.992v-.605c0-.373.076-.694.23-.965.154-.271.365-.48.633-.63.268-.148.573-.222.914-.222Zm.004.602a1 1 0 0 0-.574.164 1.058 1.058 0 0 0-.372.45c-.085.19-.128.41-.128.66v.495c0 .256.041.478.125.668.083.19.204.339.363.446.161.106.357.16.586.16a.994.994 0 0 0 .574-.16.986.986 0 0 0 .36-.446c.083-.19.124-.412.124-.668V7.7c0-.252-.043-.474-.129-.664a.974.974 0 0 0-.93-.61ZM1.32 8.273v-.671c0-.36.07-.672.207-.938.141-.268.339-.475.594-.621.258-.146.563-.219.914-.219.344 0 .632.063.863.188.232.122.408.28.528.472.12.19.182.388.187.594h-.687a.888.888 0 0 0-.11-.27.668.668 0 0 0-.265-.257c-.123-.073-.29-.11-.504-.11-.333 0-.587.107-.762.32-.172.214-.258.502-.258.864v.656c0 .354.09.638.27.852.182.21.432.316.75.316.203 0 .366-.027.488-.082a.683.683 0 0 0 .285-.215.804.804 0 0 0 .133-.293h.688c-.008.2-.07.395-.188.582a1.283 1.283 0 0 1-.527.454c-.232.114-.529.171-.89.171-.352 0-.657-.073-.915-.218a1.502 1.502 0 0 1-.594-.621 2.058 2.058 0 0 1-.207-.954Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction OptionKey({ position }: { position: 'Left' | 'Right' }) {\n  return (\n    <Key name={`${position}Option`}>\n      <svg\n        viewBox=\"0 0 12 14\"\n        className={clsx('mt-0.5', position === 'Left' ? 'ml-auto' : 'mr-auto')}\n      >\n        <path d=\"M4.559 3.025a.979.979 0 0 1 .914.592l2.765 6.188c.063.129.172.193.328.193h1.811c.14 0 .262.047.363.14a.452.452 0 0 1 .153.346.47.47 0 0 1-.153.352.517.517 0 0 1-.363.14H8.309c-.22 0-.403-.046-.551-.14a.967.967 0 0 1-.358-.451L4.623 4.197a.31.31 0 0 0-.305-.193H2.502a.525.525 0 0 1-.37-.14.46.46 0 0 1-.146-.346c0-.141.05-.258.147-.352a.525.525 0 0 1 .369-.14h2.057Zm5.818 0c.144 0 .266.047.363.141.098.09.147.205.147.346 0 .14-.05.258-.147.351a.516.516 0 0 1-.363.135h-2.66a.516.516 0 0 1-.363-.135.477.477 0 0 1-.141-.351c0-.14.047-.256.14-.346a.504.504 0 0 1 .364-.14h2.66Z\" />\n      </svg>\n      <svg\n        viewBox=\"0 0 28 14\"\n        className={clsx('mt-auto', position === 'Left' ? 'ml-auto' : 'mr-auto')}\n      >\n        <path d=\"M21.871 10V5.89h.7v.747h.038a1.32 1.32 0 0 1 .18-.352c.086-.125.214-.233.383-.324.17-.091.397-.137.683-.137.415 0 .75.114 1.008.34.258.227.387.57.387 1.031V10h-.707V7.418c0-.216-.037-.396-.11-.54a.718.718 0 0 0-.304-.327.976.976 0 0 0-.477-.11c-.24 0-.44.056-.601.168a1.007 1.007 0 0 0-.356.457 1.702 1.702 0 0 0-.117.645V10h-.707ZM18.91 5.824c.341 0 .645.072.91.215.268.14.48.348.633.621.154.271.23.597.23.977v.61c0 .385-.076.714-.23.987-.154.271-.364.478-.633.621-.265.141-.569.211-.91.211-.346 0-.654-.07-.922-.21a1.542 1.542 0 0 1-.629-.618c-.15-.273-.226-.604-.226-.992v-.605c0-.373.077-.694.23-.965.154-.271.365-.48.633-.63.268-.148.573-.222.914-.222Zm.004.602a1 1 0 0 0-.574.164 1.058 1.058 0 0 0-.371.45c-.086.19-.13.41-.13.66v.495c0 .256.043.478.126.668.083.19.204.339.363.446.162.106.357.16.586.16a.994.994 0 0 0 .574-.16.986.986 0 0 0 .36-.446c.083-.19.125-.412.125-.668V7.7a1.6 1.6 0 0 0-.13-.664.974.974 0 0 0-.93-.61ZM15.133 10V5.89h.707V10h-.707Zm-.149-5.375c0-.143.047-.26.141-.352a.498.498 0 0 1 .355-.136c.149 0 .271.045.368.136a.463.463 0 0 1 .144.352c0 .14-.048.257-.144.348a.513.513 0 0 1-.368.136.498.498 0 0 1-.355-.136.464.464 0 0 1-.14-.348ZM12.25 5.902v-1.03h.7v1.03h.929v.575h-.93v2.367c0 .242.048.405.145.488.096.08.27.121.52.121.049 0 .107-.002.175-.008l.149-.011V10a4.387 4.387 0 0 1-.504.035c-.326 0-.575-.042-.747-.125a.683.683 0 0 1-.347-.39 2.046 2.046 0 0 1-.094-.672V6.477h-.629v-.575h.633ZM9.234 10.063c-.187 0-.359-.027-.515-.079a1.153 1.153 0 0 1-.399-.226 1.109 1.109 0 0 1-.265-.367H8.02v1.972h-.707V5.887h.68v.656h.034a1.006 1.006 0 0 1 .453-.523c.11-.063.23-.11.36-.141.13-.034.27-.05.418-.05.323 0 .601.07.836.214.234.143.415.346.543.61.127.262.191.575.191.937v.707c0 .364-.065.678-.195.941a1.407 1.407 0 0 1-.551.61 1.62 1.62 0 0 1-.848.214Zm-.144-.606c.219 0 .403-.05.555-.152a.955.955 0 0 0 .351-.438c.08-.193.121-.426.121-.699V7.73c0-.276-.04-.51-.12-.703a.944.944 0 0 0-.356-.441 1.024 1.024 0 0 0-.57-.152c-.217 0-.404.048-.563.144a.958.958 0 0 0-.363.414c-.084.177-.125.39-.125.637v.715c0 .219.044.413.132.582a.994.994 0 0 0 .38.39c.16.094.347.141.558.141ZM4.348 5.824c.34 0 .644.072.91.215.268.14.479.348.633.621.153.271.23.597.23.977v.61c0 .385-.077.714-.23.987-.154.271-.365.478-.633.621-.266.141-.57.211-.91.211-.347 0-.654-.07-.922-.21a1.542 1.542 0 0 1-.63-.618c-.15-.273-.226-.604-.226-.992v-.605c0-.373.077-.694.23-.965.154-.271.365-.48.634-.63.268-.148.573-.222.914-.222Zm.004.602a1 1 0 0 0-.575.164 1.058 1.058 0 0 0-.37.45c-.087.19-.13.41-.13.66v.495c0 .256.042.478.125.668.084.19.205.339.364.446.161.106.356.16.586.16a.994.994 0 0 0 .574-.16.986.986 0 0 0 .36-.446c.082-.19.124-.412.124-.668V7.7c0-.252-.043-.474-.129-.664a.974.974 0 0 0-.93-.61Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction CommandKey({ position }: { position: 'Left' | 'Right' }) {\n  return (\n    <Key name={`${position}Command`} width={50}>\n      <svg\n        viewBox=\"0 0 12 14\"\n        className={clsx('mt-0.5', position === 'Left' ? 'ml-auto' : 'mr-auto')}\n      >\n        <path d=\"M3.796 6.68h-.844a1.747 1.747 0 0 1-1.517-.85 1.677 1.677 0 0 1-.235-.876c0-.322.078-.616.235-.883a1.74 1.74 0 0 1 1.517-.876 1.74 1.74 0 0 1 1.51.876c.157.267.235.561.235.883v.844h1.949v-.844c0-.322.078-.616.235-.883.156-.27.366-.484.628-.64.267-.157.563-.236.889-.236a1.74 1.74 0 0 1 1.504.876c.161.267.242.561.242.883 0 .321-.08.613-.242.876-.156.262-.368.47-.634.622a1.7 1.7 0 0 1-.87.228h-.85v1.974h.85c.317 0 .607.079.87.235.266.152.478.36.634.622.161.258.242.548.242.87 0 .326-.08.622-.242.889a1.823 1.823 0 0 1-.634.634 1.665 1.665 0 0 1-.87.235c-.326 0-.622-.078-.889-.235a1.84 1.84 0 0 1-.628-.634 1.722 1.722 0 0 1-.235-.89v-.843H4.697v.844c0 .326-.078.622-.235.889a1.823 1.823 0 0 1-.634.634 1.677 1.677 0 0 1-.876.235 1.71 1.71 0 0 1-.883-.235 1.823 1.823 0 0 1-.634-.634 1.722 1.722 0 0 1-.235-.89c0-.32.078-.61.235-.869.156-.262.368-.47.634-.622a1.71 1.71 0 0 1 .883-.235h.844V6.68Zm-.838-.876h.838V4.96a.831.831 0 0 0-.254-.61.796.796 0 0 0-.59-.253.824.824 0 0 0-.603.254.82.82 0 0 0-.248.603c0 .232.085.433.254.603.17.165.37.247.603.247Zm5.427 0a.82.82 0 0 0 .603-.247.834.834 0 0 0 .248-.603.82.82 0 0 0-.248-.603.796.796 0 0 0-.59-.254.824.824 0 0 0-.603.254.842.842 0 0 0-.248.609v.844h.838ZM4.697 8.667H6.64V6.674H4.697v1.993Zm-1.739.857a.824.824 0 0 0-.603.254.803.803 0 0 0-.254.596.842.842 0 0 0 .85.857.806.806 0 0 0 .591-.247.838.838 0 0 0 .254-.616v-.844h-.838Zm5.427 0h-.838v.844a.85.85 0 0 0 .851.863.806.806 0 0 0 .59-.247.842.842 0 0 0 .248-.61.813.813 0 0 0-.248-.596.81.81 0 0 0-.603-.254Z\" />\n      </svg>\n      <svg\n        viewBox=\"0 0 40 14\"\n        className={clsx('mt-auto', position === 'Left' ? 'ml-auto' : 'mr-auto')}\n      >\n        <path d=\"M35.672 5.828c.195 0 .372.027.531.082.159.052.294.13.406.23.115.102.2.222.254.36h.035V4.387h.707V10h-.68v-.656h-.034a.745.745 0 0 1-.157.293c-.075.086-.169.16-.28.222-.11.063-.233.111-.368.145a1.644 1.644 0 0 1-.43.055c-.32 0-.599-.07-.836-.211a1.431 1.431 0 0 1-.547-.598 1.986 1.986 0 0 1-.195-.906v-.797c0-.35.066-.651.2-.906.132-.258.318-.457.558-.598.24-.143.518-.215.836-.215Zm.144.606a.976.976 0 0 0-.55.152.966.966 0 0 0-.352.43 1.656 1.656 0 0 0-.125.675v.5c0 .266.042.494.125.684.086.188.207.33.363.43.157.099.343.148.559.148.216 0 .402-.048.559-.144.158-.1.282-.237.37-.414.09-.18.133-.392.133-.637v-.711a1.15 1.15 0 0 0-.14-.574 1.062 1.062 0 0 0-.941-.54ZM29.527 10V5.89h.7v.747h.039c.034-.11.093-.227.18-.352.085-.125.213-.233.382-.324.17-.091.397-.137.684-.137.414 0 .75.114 1.008.34.257.227.386.57.386 1.031V10H32.2V7.418c0-.216-.036-.396-.11-.54a.718.718 0 0 0-.304-.327.977.977 0 0 0-.476-.11c-.24 0-.44.056-.602.168a1.007 1.007 0 0 0-.355.457 1.7 1.7 0 0 0-.118.645V10h-.707ZM26.27 10.066c-.399 0-.723-.114-.973-.343-.248-.232-.371-.538-.371-.918 0-.36.122-.65.367-.871.247-.224.607-.336 1.078-.336h1.074V7.19c0-.263-.078-.457-.234-.582-.154-.125-.362-.187-.625-.187-.174 0-.318.02-.43.062a.624.624 0 0 0-.258.172.718.718 0 0 0-.132.254h-.684c.016-.174.06-.328.133-.46.075-.136.178-.25.308-.34.13-.094.287-.165.47-.212.181-.049.387-.074.616-.074.284 0 .542.044.774.133.234.089.422.233.562.434.14.2.211.468.211.804V10h-.68v-.547h-.03c-.068.11-.156.211-.262.305-.105.094-.232.17-.383.226a1.56 1.56 0 0 1-.531.082Zm.195-.593c.187 0 .354-.038.5-.114a.939.939 0 0 0 .348-.312.799.799 0 0 0 .132-.453v-.457H26.43c-.279 0-.48.06-.602.183a.632.632 0 0 0-.18.461c0 .237.082.412.247.524a.999.999 0 0 0 .57.168ZM18.16 10V5.89h.7v.657h.038c.032-.1.086-.203.165-.313a.96.96 0 0 1 .343-.289c.151-.08.353-.12.606-.12.208 0 .389.033.543.1.156.068.284.16.383.278.101.117.173.249.214.395h.032a1.28 1.28 0 0 1 .242-.375c.11-.12.247-.216.414-.29.166-.072.366-.109.598-.109.421 0 .752.112.992.336.24.222.36.538.36.95V10h-.708V7.309a.995.995 0 0 0-.098-.457.686.686 0 0 0-.285-.305.931.931 0 0 0-.469-.11.893.893 0 0 0-.785.46.993.993 0 0 0-.12.498V10h-.7V7.336c0-.18-.035-.337-.105-.473a.741.741 0 0 0-.301-.312.898.898 0 0 0-.465-.114.882.882 0 0 0-.461.122.819.819 0 0 0-.313.332 1.047 1.047 0 0 0-.113.496V10h-.707ZM11.16 10V5.89h.7v.657h.038c.032-.1.086-.203.165-.313a.96.96 0 0 1 .343-.289c.151-.08.353-.12.606-.12.208 0 .389.033.543.1.156.068.284.16.383.278.101.117.173.249.214.395h.032a1.28 1.28 0 0 1 .242-.375c.11-.12.247-.216.414-.29.167-.072.366-.109.598-.109.421 0 .752.112.992.336.24.222.36.538.36.95V10h-.708V7.309a.995.995 0 0 0-.098-.457.686.686 0 0 0-.285-.305.931.931 0 0 0-.469-.11.893.893 0 0 0-.785.46.993.993 0 0 0-.12.498V10h-.7V7.336c0-.18-.035-.337-.105-.473a.741.741 0 0 0-.301-.312.898.898 0 0 0-.465-.114.882.882 0 0 0-.461.122.819.819 0 0 0-.313.332 1.047 1.047 0 0 0-.113.496V10h-.707ZM8.2 5.824c.34 0 .644.072.91.215.268.14.479.348.632.621.154.271.23.597.23.977v.61c0 .385-.076.714-.23.987-.153.271-.364.478-.633.621-.265.141-.569.211-.91.211-.346 0-.653-.07-.922-.21a1.542 1.542 0 0 1-.629-.618c-.15-.273-.226-.604-.226-.992v-.605c0-.373.077-.694.23-.965.154-.271.365-.48.633-.63.268-.148.573-.222.914-.222Zm.003.602a1 1 0 0 0-.574.164 1.058 1.058 0 0 0-.371.45c-.086.19-.13.41-.13.66v.495c0 .256.043.478.126.668.083.19.204.339.363.446.162.106.357.16.586.16a.994.994 0 0 0 .574-.16.986.986 0 0 0 .36-.446c.083-.19.125-.412.125-.668V7.7c0-.252-.043-.474-.13-.664a.974.974 0 0 0-.93-.61ZM2.18 8.273v-.671c0-.36.069-.672.207-.938.14-.268.338-.475.593-.621.258-.146.563-.219.915-.219.343 0 .631.063.863.188.232.122.407.28.527.472.12.19.182.388.188.594h-.688a.888.888 0 0 0-.11-.27.668.668 0 0 0-.265-.257c-.122-.073-.29-.11-.504-.11-.333 0-.587.107-.761.32-.172.214-.258.502-.258.864v.656c0 .354.09.638.27.852.182.21.432.316.75.316.202 0 .365-.027.488-.082a.683.683 0 0 0 .285-.215.804.804 0 0 0 .133-.293H5.5c-.008.2-.07.395-.188.582a1.283 1.283 0 0 1-.527.454c-.232.114-.528.171-.89.171-.352 0-.657-.073-.915-.218a1.501 1.501 0 0 1-.593-.621 2.058 2.058 0 0 1-.207-.954Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction SpaceKey() {\n  return <Key name=\"Space\" width={212} />\n}\n\nfunction LeftKey() {\n  return (\n    <Key name=\"Left\">\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M5.059 10.004c0 .096-.046.169-.137.219a.277.277 0 0 1-.267 0L1.333 8.576a.23.23 0 0 1-.116-.15.391.391 0 0 1 0-.206.255.255 0 0 1 .123-.15l3.322-1.648c.082-.04.169-.036.26.014.091.045.137.116.137.212v3.356Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction UpKey() {\n  return (\n    <Key name=\"Up\" className=\"rounded-b-[1px]\">\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M5.045 10.168H1.682a.224.224 0 0 1-.164-.069.35.35 0 0 1-.082-.164.263.263 0 0 1 .02-.178l1.661-3.595a.211.211 0 0 1 .144-.11.316.316 0 0 1 .205 0 .211.211 0 0 1 .143.11l1.662 3.595c.045.082.043.171-.007.267-.046.096-.119.144-.22.144Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction DownKey() {\n  return (\n    <Key name=\"Down\" className=\"rounded-t-[1px]\">\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M1.682 6.14h3.363a.21.21 0 0 1 .164.069.35.35 0 0 1 .082.164.263.263 0 0 1-.02.178L3.608 10.14a.234.234 0 0 1-.143.116.392.392 0 0 1-.205 0 .234.234 0 0 1-.144-.116L1.456 6.55a.29.29 0 0 1 .007-.266c.05-.096.123-.143.219-.143Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nfunction RightKey() {\n  return (\n    <Key name=\"Right\" className=\"rounded-br-xl\">\n      <svg viewBox=\"0 0 7 14\">\n        <path d=\"M1.668 10.004V6.648c0-.096.043-.167.13-.212a.278.278 0 0 1 .266-.014L5.387 8.07a.227.227 0 0 1 .123.15.392.392 0 0 1 0 .205.23.23 0 0 1-.116.15L2.07 10.224a.277.277 0 0 1-.266 0c-.091-.05-.137-.123-.137-.219Z\" />\n      </svg>\n    </Key>\n  )\n}\n\nexport function Keyboard({ highlighted = [] }: { highlighted?: string[] }) {\n  return (\n    <KeyboardContext.Provider value={{ highlighted }}>\n      <div aria-hidden=\"true\" className=\"flex flex-col gap-2\">\n        <Row>\n          <EscapeKey />\n          <F1Key />\n          <F2Key />\n          <F3Key />\n          <F4Key />\n          <F5Key />\n          <F6Key />\n          <F7Key />\n          <F8Key />\n          <F9Key />\n          <F10Key />\n          <F11Key />\n          <F12Key />\n          <LockKey />\n        </Row>\n        <Row>\n          <BacktickKey />\n          <OneKey />\n          <TwoKey />\n          <ThreeKey />\n          <FourKey />\n          <FiveKey />\n          <SixKey />\n          <SevenKey />\n          <EightKey />\n          <NineKey />\n          <ZeroKey />\n          <DashKey />\n          <EqualsKey />\n          <DeleteKey />\n        </Row>\n        <Row>\n          <TabKey />\n          <QKey />\n          <WKey />\n          <EKey />\n          <RKey />\n          <TKey />\n          <YKey />\n          <UKey />\n          <IKey />\n          <OKey />\n          <PKey />\n          <LeftSquareBracketKey />\n          <RightSquareBracketKey />\n          <BackSlashKey />\n        </Row>\n        <Row>\n          <CapsLockKey />\n          <AKey />\n          <SKey />\n          <DKey />\n          <FKey />\n          <GKey />\n          <HKey />\n          <JKey />\n          <KKey />\n          <LKey />\n          <SemicolonKey />\n          <SingleQuoteKey />\n          <ReturnKey />\n        </Row>\n        <Row>\n          <ShiftKey position=\"Left\" />\n          <ZKey />\n          <XKey />\n          <CKey />\n          <VKey />\n          <BKey />\n          <NKey />\n          <MKey />\n          <CommaKey />\n          <PeriodKey />\n          <ForwardSlashKey />\n          <ShiftKey position=\"Right\" />\n        </Row>\n        <Row>\n          <FunctionKey />\n          <ControlKey />\n          <OptionKey position=\"Left\" />\n          <CommandKey position=\"Left\" />\n          <SpaceKey />\n          <CommandKey position=\"Right\" />\n          <OptionKey position=\"Right\" />\n          <LeftKey />\n          <KeyGroup>\n            <UpKey />\n            <DownKey />\n          </KeyGroup>\n          <RightKey />\n        </Row>\n      </div>\n    </KeyboardContext.Provider>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/link.tsx",
    "content": "import * as Headless from '@headlessui/react'\nimport NextLink, { type LinkProps } from 'next/link'\nimport { forwardRef } from 'react'\n\nexport const Link = forwardRef(function Link(\n  props: LinkProps & React.ComponentPropsWithoutRef<'a'>,\n  ref: React.ForwardedRef<HTMLAnchorElement>,\n) {\n  return (\n    <Headless.DataInteractive>\n      <NextLink ref={ref} {...props} />\n    </Headless.DataInteractive>\n  )\n})\n"
  },
  {
    "path": "apps/web/src/components/linked-avatars.tsx",
    "content": "'use client'\n\nimport { CheckIcon } from '@heroicons/react/16/solid'\nimport { clsx } from 'clsx'\nimport { motion } from 'framer-motion'\n\nconst transition = {\n  duration: 0.75,\n  repeat: Infinity,\n  repeatDelay: 1.25,\n}\n\nfunction Rings() {\n  return (\n    <svg\n      viewBox=\"0 0 500 500\"\n      fill=\"none\"\n      className={clsx(\n        'col-start-1 row-start-1 size-full',\n        '[mask-composite:intersect] [mask-image:linear-gradient(to_bottom,black_90%,transparent),radial-gradient(circle,rgba(0,0,0,1)_0%,rgba(0,0,0,0)_100%)]',\n      )}\n    >\n      {Array.from(Array(42).keys()).map((n) => (\n        <motion.circle\n          variants={{\n            idle: {\n              scale: 1,\n              strokeOpacity: 0.15,\n            },\n            active: {\n              scale: [1, 1.08, 1],\n              strokeOpacity: [0.15, 0.3, 0.15],\n              transition: { ...transition, delay: n * 0.05 },\n            },\n          }}\n          key={n}\n          cx=\"250\"\n          cy=\"250\"\n          r={n * 14 + 4}\n          className=\"stroke-white\"\n        />\n      ))}\n    </svg>\n  )\n}\n\nfunction Checkmark() {\n  return (\n    <div className=\"z-10 col-start-1 row-start-1 flex items-center justify-center\">\n      <motion.div\n        variants={{\n          idle: { scale: 1 },\n          active: {\n            scale: [1, 1.15, 1],\n            transition: { ...transition, duration: 0.75 },\n          },\n        }}\n        className=\"flex size-6 items-center justify-center rounded-full bg-linear-to-t from-green-500 to-green-300 shadow-sm\"\n      >\n        <CheckIcon className=\"size-4 fill-white\" />\n      </motion.div>\n    </div>\n  )\n}\n\nfunction Photos() {\n  return (\n    <div className=\"z-10 col-start-1 row-start-1\">\n      <div className=\"mx-auto flex size-full max-w-md items-center justify-around\">\n        <img\n          alt=\"\"\n          src=\"/linked-avatars/customer.jpg\"\n          className=\"size-20 rounded-full bg-white/15 ring-4 ring-white/10\"\n        />\n        <img\n          alt=\"\"\n          src=\"/linked-avatars/manager.jpg\"\n          className=\"size-20 rounded-full bg-white/15 ring-4 ring-white/10\"\n        />\n      </div>\n    </div>\n  )\n}\n\nexport function LinkedAvatars() {\n  return (\n    <div aria-hidden=\"true\" className=\"isolate mx-auto grid h-full grid-cols-1\">\n      <Rings />\n      <Photos />\n      <Checkmark />\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/logo-cloud.tsx",
    "content": "import { clsx } from 'clsx'\n\nexport function LogoCloud({\n  className,\n}: React.ComponentPropsWithoutRef<'div'>) {\n  return (\n    <div\n      className={clsx(\n        className,\n        'flex justify-between max-sm:mx-auto max-sm:max-w-md max-sm:flex-wrap max-sm:justify-evenly max-sm:gap-x-4 max-sm:gap-y-4',\n      )}\n    >\n      <img\n        alt=\"SavvyCal\"\n        src=\"/logo-cloud/savvycal.svg\"\n        className=\"h-9 max-sm:mx-auto sm:h-8 lg:h-12\"\n      />\n      <img\n        alt=\"Laravel\"\n        src=\"/logo-cloud/laravel.svg\"\n        className=\"h-9 max-sm:mx-auto sm:h-8 lg:h-12\"\n      />\n      <img\n        alt=\"Tuple\"\n        src=\"/logo-cloud/tuple.svg\"\n        className=\"h-9 max-sm:mx-auto sm:h-8 lg:h-12\"\n      />\n      <img\n        alt=\"Transistor\"\n        src=\"/logo-cloud/transistor.svg\"\n        className=\"h-9 max-sm:mx-auto sm:h-8 lg:h-12\"\n      />\n      <img\n        alt=\"Statamic\"\n        src=\"/logo-cloud/statamic.svg\"\n        className=\"h-9 max-sm:mx-auto sm:h-8 lg:h-12\"\n      />\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/logo-cluster.tsx",
    "content": "'use client'\n\nimport { clsx } from 'clsx'\nimport { motion } from 'framer-motion'\nimport { Mark } from './logo'\n\nfunction Circle({\n  size,\n  delay,\n  opacity,\n}: {\n  size: number\n  delay: number\n  opacity: string\n}) {\n  return (\n    <motion.div\n      variants={{\n        idle: { width: `${size}px`, height: `${size}px` },\n        active: {\n          width: [`${size}px`, `${size + 10}px`, `${size}px`],\n          height: [`${size}px`, `${size + 10}px`, `${size}px`],\n          transition: {\n            duration: 0.75,\n            repeat: Infinity,\n            repeatDelay: 1.25,\n            ease: 'easeInOut',\n            delay,\n          },\n        },\n      }}\n      style={{ '--opacity': opacity } as React.CSSProperties}\n      className={clsx(\n        'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full',\n        'bg-[radial-gradient(circle,transparent_25%,color-mix(in_srgb,var(--color-blue-500)_var(--opacity),transparent)_100%)]',\n        'ring-1 ring-blue-500/[8%] ring-inset',\n      )}\n    />\n  )\n}\n\nfunction Circles() {\n  return (\n    <div className=\"absolute inset-0\">\n      <Circle size={528} opacity=\"3%\" delay={0.45} />\n      <Circle size={400} opacity=\"5%\" delay={0.3} />\n      <Circle size={272} opacity=\"5%\" delay={0.15} />\n      <Circle size={144} opacity=\"10%\" delay={0} />\n      <div className=\"absolute inset-0 bg-linear-to-t from-white to-35%\" />\n    </div>\n  )\n}\n\nfunction MainLogo() {\n  return (\n    <div className=\"absolute top-32 left-44 flex size-16 items-center justify-center rounded-full bg-white ring-1 shadow-sm ring-black/5\">\n      <Mark className=\"h-9 fill-black\" />\n    </div>\n  )\n}\n\nfunction Logo({\n  src,\n  left,\n  top,\n  hover,\n}: {\n  src: string\n  left: number\n  top: number\n  hover: { x: number; y: number; rotate: number; delay: number }\n}) {\n  return (\n    <motion.img\n      variants={{\n        idle: { x: 0, y: 0, rotate: 0 },\n        active: {\n          x: [0, hover.x, 0],\n          y: [0, hover.y, 0],\n          rotate: [0, hover.rotate, 0],\n          transition: {\n            duration: 0.75,\n            repeat: Infinity,\n            repeatDelay: 1.25,\n            ease: 'easeInOut',\n            delay: hover.delay,\n          },\n        },\n      }}\n      alt=\"\"\n      src={src}\n      style={{ left, top } as React.CSSProperties}\n      className=\"absolute size-16 rounded-full bg-white ring-1 shadow-sm ring-black/5\"\n    />\n  )\n}\n\nexport function LogoCluster() {\n  return (\n    <div aria-hidden=\"true\" className=\"relative h-full overflow-hidden\">\n      <Circles />\n      <div className=\"absolute left-1/2 h-full w-[26rem] -translate-x-1/2\">\n        <MainLogo />\n        <Logo\n          src=\"/logo-cluster/career-builder.svg\"\n          left={360}\n          top={144}\n          hover={{ x: 6, y: 1, rotate: 5, delay: 0.38 }}\n        />\n        <Logo\n          src=\"/logo-cluster/dribbble.svg\"\n          left={285}\n          top={20}\n          hover={{ x: 4, y: -5, rotate: 6, delay: 0.3 }}\n        />\n        <Logo\n          src=\"/logo-cluster/glassdoor.svg\"\n          left={255}\n          top={210}\n          hover={{ x: 3, y: 5, rotate: 7, delay: 0.2 }}\n        />\n        <Logo\n          src=\"/logo-cluster/linkedin.svg\"\n          left={144}\n          top={40}\n          hover={{ x: -2, y: -5, rotate: -6, delay: 0.15 }}\n        />\n        <Logo\n          src=\"/logo-cluster/upwork.svg\"\n          left={36}\n          top={56}\n          hover={{ x: -4, y: -5, rotate: -6, delay: 0.35 }}\n        />\n        <Logo\n          src=\"/logo-cluster/we-work-remotely.svg\"\n          left={96}\n          top={176}\n          hover={{ x: -3, y: 5, rotate: 3, delay: 0.15 }}\n        />\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/logo-timeline.tsx",
    "content": "import { clsx } from 'clsx'\nimport { Mark } from './logo'\n\nfunction Row({ children }: { children: React.ReactNode }) {\n  return (\n    <div className=\"group relative\">\n      <div className=\"absolute inset-x-0 top-1/2 h-0.5 bg-linear-to-r from-white/15 from-[2px] to-[2px] bg-[length:12px_100%]\" />\n      <div className=\"absolute inset-x-0 bottom-0 h-0.5 bg-linear-to-r from-white/5 from-[2px] to-[2px] bg-[length:12px_100%] group-last:hidden\" />\n      {children}\n    </div>\n  )\n}\n\nfunction Logo({\n  label,\n  src,\n  className,\n}: {\n  label: string\n  src: string\n  className: string\n}) {\n  return (\n    <div\n      className={clsx(\n        className,\n        'absolute top-2 grid grid-cols-[1rem_1fr] items-center gap-2 px-3 py-1 whitespace-nowrap',\n        'rounded-full bg-linear-to-t from-gray-800 from-50% to-gray-700 ring-1 ring-white/10 ring-inset',\n        '[--move-x-from:-100%] [--move-x-to:calc(100%+100cqw)] [animation-iteration-count:infinite] [animation-name:move-x] [animation-play-state:paused] [animation-timing-function:linear] group-hover:[animation-play-state:running]',\n      )}\n    >\n      <img alt=\"\" src={src} className=\"size-4\" />\n      <span className=\"text-sm/6 font-medium text-white\">{label}</span>\n    </div>\n  )\n}\n\nexport function LogoTimeline() {\n  return (\n    <div aria-hidden=\"true\" className=\"relative h-full overflow-hidden\">\n      <div className=\"absolute inset-0 top-8 z-10 flex items-center justify-center\">\n        <div\n          className=\"absolute inset-0 backdrop-blur-md\"\n          style={{\n            maskImage: `url('data:image/svg+xml,<svg width=\"96\" height=\"96\" viewBox=\"0 0 96 96\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"96\" height=\"96\" rx=\"12\" fill=\"black\"/></svg>')`,\n            maskPosition: 'center',\n            maskRepeat: 'no-repeat',\n          }}\n        />\n        <div className=\"relative flex size-24 items-center justify-center rounded-xl bg-linear-to-t from-white/5 to-white/25 ring-1 shadow-sm ring-white/10 outline outline-offset-[-5px] outline-white/5 ring-inset\">\n          <Mark className=\"h-9 fill-white\" />\n        </div>\n      </div>\n      <div className=\"[container-type:inline-size] absolute inset-0 grid grid-cols-1 pt-8\">\n        <Row>\n          <Logo\n            label=\"Loom\"\n            src=\"/logo-timeline/loom.svg\"\n            className=\"[animation-delay:-26s] [animation-duration:30s]\"\n          />\n          <Logo\n            label=\"Gmail\"\n            src=\"/logo-timeline/gmail.svg\"\n            className=\"[animation-delay:-8s] [animation-duration:30s]\"\n          />\n        </Row>\n        <Row>\n          <Logo\n            label=\"Asana\"\n            src=\"/logo-timeline/asana.svg\"\n            className=\"[animation-delay:-40s] [animation-duration:40s]\"\n          />\n          <Logo\n            label=\"Microsoft Teams\"\n            src=\"/logo-timeline/microsoft-teams.svg\"\n            className=\"[animation-delay:-20s] [animation-duration:40s]\"\n          />\n        </Row>\n        <Row>\n          <Logo\n            label=\"Google Calendar\"\n            src=\"/logo-timeline/google-calendar.svg\"\n            className=\"[animation-delay:-10s] [animation-duration:40s]\"\n          />\n          <Logo\n            label=\"Google Drive\"\n            src=\"/logo-timeline/google-drive.svg\"\n            className=\"[animation-delay:-32s] [animation-duration:40s]\"\n          />\n        </Row>\n        <Row>\n          <Logo\n            label=\"Basecamp\"\n            src=\"/logo-timeline/basecamp.svg\"\n            className=\"[animation-delay:-45s] [animation-duration:45s]\"\n          />\n          <Logo\n            label=\"Discord\"\n            src=\"/logo-timeline/discord.svg\"\n            className=\"[animation-delay:-23s] [animation-duration:45s]\"\n          />\n        </Row>\n        <Row>\n          <Logo\n            label=\"Hubspot\"\n            src=\"/logo-timeline/hubspot.svg\"\n            className=\"[animation-delay:-55s] [animation-duration:60s]\"\n          />\n          <Logo\n            label=\"Slack\"\n            src=\"/logo-timeline/slack.svg\"\n            className=\"[animation-delay:-20s] [animation-duration:60s]\"\n          />\n        </Row>\n        <Row>\n          <Logo\n            label=\"Adobe Creative Cloud\"\n            src=\"/logo-timeline/adobe-creative-cloud.svg\"\n            className=\"[animation-delay:-9s] [animation-duration:40s]\"\n          />\n          <Logo\n            label=\"Zoom\"\n            src=\"/logo-timeline/zoom.svg\"\n            className=\"[animation-delay:-28s] [animation-duration:40s]\"\n          />\n        </Row>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/logo.tsx",
    "content": "'use client'\n\nimport { clsx } from 'clsx'\nimport { motion } from 'framer-motion'\n\nexport function Logo({ className }: { className?: string }) {\n  const transition = {\n    duration: 0.5,\n    ease: 'easeInOut',\n  }\n\n  return (\n    <motion.svg\n      variants={{ idle: {}, active: {} }}\n      initial=\"idle\"\n      whileHover=\"active\"\n      width={127}\n      height={34}\n      viewBox=\"0 0 127 34\"\n      className={clsx(className, 'overflow-visible')}\n    >\n      <motion.g\n        variants={{\n          idle: { scale: 1, opacity: 1 },\n          active: {\n            scale: [1, 1.15, 1],\n            opacity: [1, 0.75, 1],\n            transition: {\n              ...transition,\n              delay: 0,\n            },\n          },\n        }}\n      >\n        <motion.path d=\"M19.5986 18.5005C18.7702 19.9354 16.9354 20.427 15.5005 19.5986C14.0656 18.7701 13.574 16.9354 14.4024 15.5005C15.2309 14.0656 17.0656 13.574 18.5005 14.4024C19.9354 15.2308 20.427 17.0656 19.5986 18.5005Z\" />\n      </motion.g>\n      <motion.g\n        variants={{\n          idle: { scale: 1, opacity: 1 },\n          active: {\n            scale: [1, 1.1, 1],\n            opacity: [1, 0.75, 1],\n            transition: {\n              ...transition,\n              delay: 0.15,\n            },\n          },\n        }}\n      >\n        <path d=\"M23.2324 10.2074C22.6801 11.1639 21.4569 11.4917 20.5003 10.9394C19.5437 10.3871 19.216 9.16395 19.7683 8.20736C20.3205 7.25078 21.5437 6.92303 22.5003 7.47531C23.4569 8.0276 23.7846 9.25078 23.2324 10.2074Z\" />\n        <path d=\"M19.7683 25.7933C19.216 24.8367 19.5437 23.6135 20.5003 23.0612C21.4569 22.5089 22.6801 22.8367 23.2324 23.7933C23.7847 24.7498 23.4569 25.973 22.5003 26.5253C21.5437 27.0776 20.3206 26.7498 19.7683 25.7933Z\" />\n        <path d=\"M26 19C24.8954 19 24 18.1046 24 17C24 15.8955 24.8954 15 26 15C27.1046 15 28 15.8955 28 17C28 18.1046 27.1046 19 26 19Z\" />\n        <path d=\"M14.2324 25.7933C13.6801 26.7499 12.4569 27.0777 11.5003 26.5254C10.5437 25.9731 10.216 24.7499 10.7683 23.7933C11.3205 22.8367 12.5437 22.509 13.5003 23.0613C14.4569 23.6136 14.7846 24.8367 14.2324 25.7933Z\" />\n        <path d=\"M10.7682 10.2073C10.216 9.25078 10.5437 8.0276 11.5003 7.47532C12.4569 6.92303 13.6801 7.25078 14.2323 8.20737C14.7846 9.16395 14.4569 10.3871 13.5003 10.9394C12.5437 11.4917 11.3205 11.1639 10.7682 10.2073Z\" />\n        <path d=\"M8 19C6.89543 19 6 18.1045 6 17C6 15.8954 6.89543 15 8 15C9.10457 15 10 15.8954 10 17C10 18.1045 9.10457 19 8 19Z\" />\n      </motion.g>\n      <motion.g\n        variants={{\n          idle: { scale: 1, opacity: 1 },\n          active: {\n            scale: [1, 1.1, 1],\n            opacity: [1, 0.75, 1],\n            transition: {\n              ...transition,\n              delay: 0.3,\n            },\n          },\n        }}\n      >\n        <path d=\"M25.8662 3.6447C25.5901 4.12299 24.9785 4.28686 24.5002 4.01072C24.0219 3.73458 23.858 3.12299 24.1342 2.6447C24.4103 2.1664 25.0219 2.00253 25.5002 2.27867C25.9785 2.55481 26.1424 3.1664 25.8662 3.6447Z\" />\n        <path d=\"M33 18C32.4477 18 32 17.5522 32 17C32 16.4477 32.4477 16 33 16C33.5522 16 34 16.4477 34 17C34 17.5522 33.5522 18 33 18Z\" />\n        <path d=\"M31.3556 9.86619C30.8773 10.1424 30.2658 9.97846 29.9896 9.50017C29.7135 9.02187 29.8773 8.41028 30.3556 8.13414C30.8339 7.858 31.4455 8.02187 31.7217 8.50017C31.9978 8.97846 31.8339 9.59005 31.3556 9.86619Z\" />\n        <path d=\"M30.3556 25.8662C29.8773 25.5901 29.7134 24.9785 29.9896 24.5002C30.2657 24.0219 30.8773 23.858 31.3556 24.1342C31.8339 24.4103 31.9978 25.0219 31.7216 25.5002C31.4455 25.9785 30.8339 26.1424 30.3556 25.8662Z\" />\n        <path d=\"M16 33C16 32.4477 16.4477 32 17 32C17.5523 32 18 32.4477 18 33C18 33.5523 17.5523 34 17 34C16.4477 34 16 33.5523 16 33Z\" />\n        <path d=\"M24.1341 31.3557C23.858 30.8774 24.0219 30.2658 24.5002 29.9896C24.9785 29.7135 25.5901 29.8774 25.8662 30.3557C26.1423 30.834 25.9785 31.4455 25.5002 31.7217C25.0219 31.9978 24.4103 31.834 24.1341 31.3557Z\" />\n        <path d=\"M9.8662 31.3556C9.59005 31.8339 8.97846 31.9978 8.50017 31.7216C8.02188 31.4455 7.858 30.8339 8.13415 30.3556C8.41029 29.8773 9.02188 29.7134 9.50017 29.9896C9.97846 30.2657 10.1424 30.8773 9.8662 31.3556Z\" />\n        <path d=\"M1 18C0.447715 18 -3.44684e-08 17.5523 0 17C3.44684e-08 16.4477 0.447715 16 1 16C1.55228 16 2 16.4477 2 17C2 17.5523 1.55228 18 1 18Z\" />\n        <path d=\"M3.6447 25.8662C3.1664 26.1424 2.55481 25.9785 2.27867 25.5002C2.00253 25.0219 2.1664 24.4103 2.6447 24.1342C3.12299 23.858 3.73458 24.0219 4.01072 24.5002C4.28686 24.9785 4.12299 25.5901 3.6447 25.8662Z\" />\n        <path d=\"M2.6447 9.8662C2.1664 9.59005 2.00253 8.97846 2.27867 8.50017C2.55481 8.02188 3.1664 7.858 3.6447 8.13415C4.12299 8.41029 4.28686 9.02188 4.01072 9.50017C3.73458 9.97846 3.12299 10.1424 2.6447 9.8662Z\" />\n        <path d=\"M16 1C16 0.447715 16.4477 -4.87226e-08 17 0C17.5523 4.87226e-08 18 0.447715 18 1C18 1.55228 17.5523 2 17 2C16.4477 2 16 1.55228 16 1Z\" />\n        <path d=\"M8.13415 3.6447C7.858 3.16641 8.02188 2.55482 8.50017 2.27867C8.97846 2.00253 9.59005 2.16641 9.8662 2.6447C10.1424 3.12299 9.97846 3.73458 9.50017 4.01072C9.02188 4.28687 8.41029 4.12299 8.13415 3.6447Z\" />\n      </motion.g>\n      <path d=\"M51.606 25.5V10.54H54.07V25.5H51.606ZM57.436 18.988H53.388V17.47H57.106C58.03 17.47 58.734 17.25 59.218 16.81C59.7167 16.3553 59.966 15.7467 59.966 14.984C59.966 14.236 59.7167 13.642 59.218 13.202C58.734 12.7473 58.0447 12.52 57.15 12.52H53.388V10.54H57.216C58.8293 10.54 60.1127 10.9287 61.066 11.706C62.0193 12.4687 62.496 13.5173 62.496 14.852C62.496 16.2013 62.034 17.228 61.11 17.932C60.2007 18.636 58.976 18.988 57.436 18.988ZM60.604 25.5L59.702 21.496C59.57 20.924 59.394 20.4913 59.174 20.198C58.9687 19.9047 58.69 19.7067 58.338 19.604C57.986 19.5013 57.5387 19.45 56.996 19.45H53.806V17.866H57.348C58.3453 17.866 59.152 17.9613 59.768 18.152C60.3987 18.328 60.89 18.6433 61.242 19.098C61.594 19.5527 61.8727 20.1907 62.078 21.012L63.2 25.5H60.604ZM67.5094 17.602H65.1554C65.2874 16.7367 65.5661 15.996 65.9914 15.38C66.4168 14.764 66.9888 14.2947 67.7074 13.972C68.4261 13.6347 69.2694 13.466 70.2374 13.466C71.2934 13.466 72.1734 13.664 72.8774 14.06C73.5961 14.4413 74.1314 14.9767 74.4834 15.666C74.8354 16.3553 75.0114 17.1547 75.0114 18.064V22.64C75.0114 23.344 75.0408 23.916 75.0994 24.356C75.1728 24.796 75.2754 25.1773 75.4074 25.5H72.9434C72.8114 25.2067 72.7308 24.84 72.7014 24.4C72.6721 23.96 72.6574 23.5127 72.6574 23.058V18.086C72.6574 17.2353 72.4448 16.5827 72.0194 16.128C71.6088 15.6733 70.9561 15.446 70.0614 15.446C69.2988 15.446 68.7048 15.644 68.2794 16.04C67.8688 16.4213 67.6121 16.942 67.5094 17.602ZM72.9874 18.35V19.956C71.7848 19.956 70.8021 20.0147 70.0394 20.132C69.2768 20.2493 68.6828 20.418 68.2574 20.638C67.8321 20.8433 67.5388 21.0927 67.3774 21.386C67.2161 21.6647 67.1354 21.98 67.1354 22.332C67.1354 22.8307 67.3261 23.2267 67.7074 23.52C68.0888 23.7987 68.5874 23.938 69.2034 23.938C69.8634 23.938 70.4501 23.784 70.9634 23.476C71.4914 23.1533 71.9021 22.728 72.1954 22.2C72.5034 21.672 72.6574 21.1 72.6574 20.484H73.7134C73.6548 21.4227 73.4714 22.2293 73.1634 22.904C72.8554 23.5787 72.4594 24.1287 71.9754 24.554C71.5061 24.9793 70.9928 25.2947 70.4354 25.5C69.8781 25.6907 69.3208 25.786 68.7634 25.786C67.9714 25.786 67.2748 25.6613 66.6734 25.412C66.0721 25.1627 65.6028 24.7887 65.2654 24.29C64.9281 23.7913 64.7594 23.168 64.7594 22.42C64.7594 21.6133 64.9794 20.924 65.4194 20.352C65.8594 19.78 66.5194 19.34 67.3994 19.032C68.2208 18.7533 69.0714 18.57 69.9514 18.482C70.8314 18.394 71.8434 18.35 72.9874 18.35ZM82.3632 25.786C81.3365 25.786 80.4492 25.544 79.7012 25.06C78.9532 24.5613 78.3812 23.85 77.9852 22.926C77.5892 22.002 77.3912 20.9093 77.3912 19.648C77.3912 17.6533 77.8385 16.128 78.7332 15.072C79.6279 14.0013 80.8379 13.466 82.3632 13.466C83.6832 13.466 84.7319 13.994 85.5092 15.05C86.3012 16.106 86.6972 17.6387 86.6972 19.648C86.6972 20.9093 86.5212 22.002 86.1692 22.926C85.8172 23.8353 85.3112 24.5393 84.6512 25.038C84.0059 25.5367 83.2432 25.786 82.3632 25.786ZM82.6712 23.806C83.6099 23.806 84.3212 23.454 84.8052 22.75C85.3039 22.0313 85.5532 20.9973 85.5532 19.648C85.5532 18.2987 85.2965 17.2647 84.7832 16.546C84.2845 15.8127 83.5805 15.446 82.6712 15.446C81.7325 15.446 81.0139 15.8053 80.5152 16.524C80.0312 17.2427 79.7892 18.284 79.7892 19.648C79.7892 20.9827 80.0385 22.0093 80.5372 22.728C81.0505 23.4467 81.7619 23.806 82.6712 23.806ZM85.5532 25.5V9.66H87.9072V25.5H85.5532ZM90.7464 25.5V13.774H93.1004V25.5H90.7464ZM90.7464 12.322V9.66H93.1004V12.322H90.7464ZM98.1676 17.602H95.8136C95.9456 16.7367 96.2243 15.996 96.6496 15.38C97.075 14.764 97.647 14.2947 98.3656 13.972C99.0843 13.6347 99.9276 13.466 100.896 13.466C101.952 13.466 102.832 13.664 103.536 14.06C104.254 14.4413 104.79 14.9767 105.142 15.666C105.494 16.3553 105.67 17.1547 105.67 18.064V22.64C105.67 23.344 105.699 23.916 105.758 24.356C105.831 24.796 105.934 25.1773 106.066 25.5H103.602C103.47 25.2067 103.389 24.84 103.36 24.4C103.33 23.96 103.316 23.5127 103.316 23.058V18.086C103.316 17.2353 103.103 16.5827 102.678 16.128C102.267 15.6733 101.614 15.446 100.72 15.446C99.957 15.446 99.363 15.644 98.9376 16.04C98.527 16.4213 98.2703 16.942 98.1676 17.602ZM103.646 18.35V19.956C102.443 19.956 101.46 20.0147 100.698 20.132C99.935 20.2493 99.341 20.418 98.9156 20.638C98.4903 20.8433 98.197 21.0927 98.0356 21.386C97.8743 21.6647 97.7936 21.98 97.7936 22.332C97.7936 22.8307 97.9843 23.2267 98.3656 23.52C98.747 23.7987 99.2456 23.938 99.8616 23.938C100.522 23.938 101.108 23.784 101.622 23.476C102.15 23.1533 102.56 22.728 102.854 22.2C103.162 21.672 103.316 21.1 103.316 20.484H104.372C104.313 21.4227 104.13 22.2293 103.822 22.904C103.514 23.5787 103.118 24.1287 102.634 24.554C102.164 24.9793 101.651 25.2947 101.094 25.5C100.536 25.6907 99.979 25.786 99.4216 25.786C98.6296 25.786 97.933 25.6613 97.3316 25.412C96.7303 25.1627 96.261 24.7887 95.9236 24.29C95.5863 23.7913 95.4176 23.168 95.4176 22.42C95.4176 21.6133 95.6376 20.924 96.0776 20.352C96.5176 19.78 97.1776 19.34 98.0576 19.032C98.879 18.7533 99.7296 18.57 100.61 18.482C101.49 18.394 102.502 18.35 103.646 18.35ZM108.621 25.5V13.774H110.975V25.5H108.621ZM110.975 18.614H110.051C110.125 17.47 110.352 16.5167 110.733 15.754C111.129 14.9913 111.65 14.4193 112.295 14.038C112.941 13.6567 113.681 13.466 114.517 13.466C115.339 13.466 116.043 13.6273 116.629 13.95C117.216 14.258 117.671 14.7273 117.993 15.358C118.316 15.974 118.477 16.744 118.477 17.668V25.5H116.145V18.152C116.145 17.58 116.057 17.096 115.881 16.7C115.705 16.2893 115.449 15.974 115.111 15.754C114.774 15.534 114.334 15.424 113.791 15.424C113.351 15.424 112.955 15.5193 112.603 15.71C112.251 15.886 111.951 16.1207 111.701 16.414C111.467 16.7073 111.283 17.0447 111.151 17.426C111.034 17.8073 110.975 18.2033 110.975 18.614ZM121.648 22.002V11.068H124.002V21.87C124.002 22.5447 124.134 23.0433 124.398 23.366C124.677 23.6887 125.219 23.85 126.026 23.85H126.488V25.566C126.371 25.6393 126.173 25.6907 125.894 25.72C125.63 25.764 125.337 25.786 125.014 25.786C123.885 25.786 123.041 25.4707 122.484 24.84C121.927 24.2093 121.648 23.2633 121.648 22.002ZM120.064 15.6V13.774H126.532V15.6H120.064Z\" />\n    </motion.svg>\n  )\n}\n\nexport function Mark({ className }: { className?: string }) {\n  return (\n    <svg viewBox=\"-1 0 35 34\" fill=\"none\" className={className}>\n      <path d=\"M17 13L21 17L17 21L13 17L17 13Z\" />\n      <path d=\"M21.5 6L23.5 8L21.5 10L19.5 8L21.5 6Z\" />\n      <path d=\"M21.5 24L23.5 26L21.5 28L19.5 26L21.5 24Z\" />\n      <path d=\"M26 15L28 17L26 19L24 17L26 15Z\" />\n      <path d=\"M12.5 24L14.5 26L12.5 28L10.5 26L12.5 24Z\" />\n      <path d=\"M12.5 6L14.5 8L12.5 10L10.5 8L12.5 6Z\" />\n      <path d=\"M8 15L10 17L8 19L6 17L8 15Z\" />\n      <path d=\"M25 2L26 3L25 4L24 3L25 2Z\" />\n      <path d=\"M32 16L33 17L32 18L31 17L32 16Z\" />\n      <path d=\"M30.5 8L31.5 9L30.5 10L29.5 9L30.5 8Z\" />\n      <path d=\"M30.5 24L31.5 25L30.5 26L29.5 25L30.5 24Z\" />\n      <path d=\"M16 32L17 33L16 34L15 33L16 32Z\" />\n      <path d=\"M24.5 30L25.5 31L24.5 32L23.5 31L24.5 30Z\" />\n      <path d=\"M9.5 30L10.5 31L9.5 32L8.5 31L9.5 30Z\" />\n      <path d=\"M0 16L1 17L0 18L-1 17L0 16Z\" />\n      <path d=\"M2.5 24L3.5 25L2.5 26L1.5 25L2.5 24Z\" />\n      <path d=\"M2.5 8L3.5 9L2.5 10L1.5 9L2.5 8Z\" />\n      <path d=\"M16 0L17 1L16 2L15 1L16 0Z\" />\n      <path d=\"M8.5 2L9.5 3L8.5 4L7.5 3L8.5 2Z\" />\n    </svg>\n  )\n}"
  },
  {
    "path": "apps/web/src/components/map.tsx",
    "content": "'use client'\n\nimport { motion } from 'framer-motion'\n\nfunction Marker({\n  src,\n  top,\n  offset,\n  delay,\n}: {\n  src: string\n  top: number\n  offset: number\n  delay: number\n}) {\n  return (\n    <motion.div\n      variants={{\n        idle: { scale: 0, opacity: 0, rotateX: 0, rotate: 0, y: 0 },\n        active: { y: [-20, 0, 4, 0], scale: [0.75, 1], opacity: [0, 1] },\n      }}\n      transition={{ duration: 0.25, delay, ease: 'easeOut' }}\n      style={{ '--offset': `${offset}px`, top } as React.CSSProperties}\n      className=\"absolute left-[calc(50%+var(--offset))] size-[38px] drop-shadow-[0_3px_1px_rgba(0,0,0,.15)]\"\n    >\n      <svg fill=\"none\" viewBox=\"0 0 38 38\" className=\"absolute size-full\">\n        <path\n          d=\"M29.607 5.193c5.858 5.857 5.858 15.355 0 21.213l-9.9 9.9-.707.706-.708-.708-9.899-9.898c-5.857-5.858-5.857-15.356 0-21.213 5.858-5.858 15.356-5.858 21.214 0Z\"\n          className=\"fill-black/5\"\n        />\n        <path\n          d=\"m28.9 25.698-9.9 9.9-9.9-9.9C3.634 20.232 3.634 11.367 9.1 5.9 14.569.432 23.433.432 28.9 5.9c5.467 5.468 5.467 14.332 0 19.8Z\"\n          className=\"fill-white\"\n        />\n      </svg>\n      <img\n        alt=\"\"\n        src={src}\n        className=\"absolute top-[4px] left-[7px] size-6 rounded-full\"\n      />\n    </motion.div>\n  )\n}\n\nexport function Map() {\n  return (\n    <div aria-hidden=\"true\" className=\"relative size-full\">\n      <div className=\"absolute inset-0 bg-[url(/map.png)] bg-[length:530px_430px] bg-[center_-75px] bg-no-repeat [mask-image:linear-gradient(to_bottom,black_50%,transparent)]\" />\n      <div className=\"absolute inset-0\">\n        <Marker src=\"/map/1.jpg\" top={96} offset={-128} delay={0.15} />\n        <Marker src=\"/map/2.jpg\" top={160} offset={-16} delay={0.4} />\n        <Marker src=\"/map/3.jpg\" top={144} offset={96} delay={0.3} />\n        <Marker src=\"/map/4.jpg\" top={192} offset={64} delay={0.6} />\n        <Marker src=\"/map/5.jpg\" top={224} offset={-32} delay={0.8} />\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/markdown-text.tsx",
    "content": "\"use client\";\n\nimport \"@assistant-ui/react-markdown/styles/dot.css\";\n\nimport {\n  type CodeHeaderProps,\n  MarkdownTextPrimitive,\n  unstable_memoizeMarkdownComponents as memoizeMarkdownComponents,\n  useIsMarkdownCodeBlock,\n} from \"@assistant-ui/react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { type FC, memo, useState } from \"react\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\n\nimport { TooltipIconButton } from \"@/components/tooltip-icon-button\";\nimport { cn } from \"@/utils/misc-utils\";\n\nconst MarkdownTextImpl = () => {\n  return (\n    <MarkdownTextPrimitive\n      remarkPlugins={[remarkGfm]}\n      className=\"aui-md\"\n      components={defaultComponents}\n    />\n  );\n};\n\nexport const MarkdownText = memo(MarkdownTextImpl);\n\nconst CodeHeader: FC<CodeHeaderProps> = ({ language, code }) => {\n  const { isCopied, copyToClipboard } = useCopyToClipboard();\n  const onCopy = () => {\n    if (!code || isCopied) return;\n    copyToClipboard(code);\n  };\n\n  return (\n    <div className=\"flex items-center justify-between gap-4 rounded-t-lg bg-zinc-900 px-4 py-2 text-sm font-semibold text-white\">\n      <span className=\"lowercase [&>span]:text-xs\">{language}</span>\n      <TooltipIconButton tooltip=\"Copy\" onClick={onCopy}>\n        {!isCopied && <CopyIcon />}\n        {isCopied && <CheckIcon />}\n      </TooltipIconButton>\n    </div>\n  );\n};\n\nconst useCopyToClipboard = ({\n  copiedDuration = 3000,\n}: {\n  copiedDuration?: number;\n} = {}) => {\n  const [isCopied, setIsCopied] = useState<boolean>(false);\n\n  const copyToClipboard = (value: string) => {\n    if (!value) return;\n\n    navigator.clipboard.writeText(value).then(() => {\n      setIsCopied(true);\n      setTimeout(() => setIsCopied(false), copiedDuration);\n    });\n  };\n\n  return { isCopied, copyToClipboard };\n};\n\nconst defaultComponents = memoizeMarkdownComponents({\n  h1: ({ className, ...props }) => (\n    <h1 className={cn(\"mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0\", className)} {...props} />\n  ),\n  h2: ({ className, ...props }) => (\n    <h2 className={cn(\"mb-4 mt-8 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0\", className)} {...props} />\n  ),\n  h3: ({ className, ...props }) => (\n    <h3 className={cn(\"mb-4 mt-6 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0\", className)} {...props} />\n  ),\n  h4: ({ className, ...props }) => (\n    <h4 className={cn(\"mb-4 mt-6 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0\", className)} {...props} />\n  ),\n  h5: ({ className, ...props }) => (\n    <h5 className={cn(\"my-4 text-lg font-semibold first:mt-0 last:mb-0\", className)} {...props} />\n  ),\n  h6: ({ className, ...props }) => (\n    <h6 className={cn(\"my-4 font-semibold first:mt-0 last:mb-0\", className)} {...props} />\n  ),\n  p: ({ className, ...props }) => (\n    <p className={cn(\"mb-5 mt-5 leading-7 first:mt-0 last:mb-0\", className)} {...props} />\n  ),\n  a: ({ className, ...props }) => (\n    <a className={cn(\"text-primary font-medium underline underline-offset-4\", className)} {...props} />\n  ),\n  blockquote: ({ className, ...props }) => (\n    <blockquote className={cn(\"border-l-2 pl-6 italic\", className)} {...props} />\n  ),\n  ul: ({ className, ...props }) => (\n    <ul className={cn(\"my-5 ml-6 list-disc [&>li]:mt-2\", className)} {...props} />\n  ),\n  ol: ({ className, ...props }) => (\n    <ol className={cn(\"my-5 ml-6 list-decimal [&>li]:mt-2\", className)} {...props} />\n  ),\n  hr: ({ className, ...props }) => (\n    <hr className={cn(\"my-5 border-b\", className)} {...props} />\n  ),\n  table: ({ className, ...props }) => (\n    <table className={cn(\"my-5 w-full border-separate border-spacing-0 overflow-y-auto\", className)} {...props} />\n  ),\n  th: ({ className, ...props }) => (\n    <th className={cn(\"bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [&[align=center]]:text-center [&[align=right]]:text-right\", className)} {...props} />\n  ),\n  td: ({ className, ...props }) => (\n    <td className={cn(\"border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right\", className)} {...props} />\n  ),\n  tr: ({ className, ...props }) => (\n    <tr className={cn(\"m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg\", className)} {...props} />\n  ),\n  sup: ({ className, ...props }) => (\n    <sup className={cn(\"[&>a]:text-xs [&>a]:no-underline\", className)} {...props} />\n  ),\n  pre: ({ className, ...props }) => (\n    <pre className={cn(\"overflow-x-auto rounded-b-lg bg-black p-4 text-white\", className)} {...props} />\n  ),\n  code: function Code({ className, ...props }) {\n    const isCodeBlock = useIsMarkdownCodeBlock();\n    return (\n      <code\n        className={cn(!isCodeBlock && \"bg-muted rounded border font-semibold\", className)}\n        {...props}\n      />\n    );\n  },\n  CodeHeader,\n});\n"
  },
  {
    "path": "apps/web/src/components/navbar.tsx",
    "content": "'use client'\n\nimport {\n  Disclosure,\n  DisclosureButton,\n  DisclosurePanel,\n} from '@headlessui/react'\nimport { Bars2Icon } from '@heroicons/react/24/solid'\nimport { ArrowTopRightOnSquareIcon } from '@heroicons/react/24/outline'\nimport { motion } from 'framer-motion'\nimport { Link } from './link'\nimport { PlusGrid, PlusGridItem, PlusGridRow } from './plus-grid'\nimport { AppLogo } from '@/components/shared/app-logo'\nimport { LogoText } from './LogoText' // Import the new component\nimport { useEffect, useState } from 'react'\nimport { supabase } from '@/utils/supabase/client'\nimport CONFIG from '../../config'\nimport { TooltipIconButton } from './tooltip-icon-button'\n\n// Define types for our navigation links\ninterface NavLink {\n  href: string;\n  label: string;\n  external?: boolean;\n  isLogout?: boolean;\n}\n\nconst staticLinks: NavLink[] = [\n  // { href: '/pricing', label: 'Pricing' },\n  // { href: CONFIG.docsURL, label: 'Docs', external: true },\n  // { href: '/company', label: 'Company' },\n  // { href: '/blog', label: 'Blog' },\n]\n\nfunction GitHubIcon(props: React.ComponentPropsWithoutRef<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n      <path d=\"M12 2C6.477 2 2 6.484 2 12.021c0 4.426 2.865 8.184 6.839 9.504.5.092.682-.217.682-.483 0-.237-.009-.868-.014-1.703-2.782.605-3.369-1.342-3.369-1.342-.454-1.155-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.004.07 1.532 1.032 1.532 1.032.892 1.53 2.341 1.088 2.91.832.091-.647.35-1.088.636-1.339-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.025A9.564 9.564 0 0 1 12 6.844c.85.004 1.705.115 2.504.337 1.909-1.295 2.748-1.025 2.748-1.025.546 1.378.202 2.397.1 2.65.64.7 1.028 1.595 1.028 2.688 0 3.847-2.338 4.695-4.566 4.944.36.31.68.921.68 1.857 0 1.34-.012 2.421-.012 2.751 0 .268.18.579.688.481C19.138 20.2 22 16.447 22 12.021 22 6.484 17.523 2 12 2Z\" />\n    </svg>\n  )\n}\n\nfunction DesktopNav() {\n  const [isLoggedIn, setIsLoggedIn] = useState(false)\n  const [isLoading, setIsLoading] = useState(true)\n  \n  useEffect(() => {\n    const checkAuthStatus = async () => {\n      try {\n        const { data } = await supabase.auth.getSession()\n        setIsLoggedIn(!!data.session)\n      } catch (error) {\n        console.error('Error checking auth status:', error)\n      } finally {\n        setIsLoading(false)\n      }\n    }\n    \n    checkAuthStatus()\n  }, [])\n  \n  const authLinks: NavLink[] = isLoggedIn \n    ? [\n        { href: '/dashboard', label: 'Go to Dashboard' },\n        { href: '#', label: 'Log Out', isLogout: true }\n      ]\n    : [{ href: '/login', label: 'Login' }]\n  \n  const links = [...staticLinks, ...authLinks]\n  \n  return (\n    <nav className=\"relative hidden lg:flex\">\n      {!isLoading && (\n        links.map(({ href, label, external, isLogout }) => (\n          <PlusGridItem key={`${href}-${label}`} className=\"relative flex\">\n            {isLogout ? (\n              <button\n                onClick={async () => {\n                  await supabase.auth.signOut();\n                  window.location.href = '/';\n                }}\n                className=\"flex items-center px-4 py-3 text-base font-medium text-gray-950 bg-blend-multiply data-hover:bg-black/[2.5%] cursor-pointer\"\n              >\n                {label}\n              </button>\n            ) : (\n              <Link\n                href={href}\n                target={external ? '_blank' : undefined}\n                rel={external ? 'noopener noreferrer' : undefined}\n                className=\"flex items-center px-4 py-3 text-base font-medium text-gray-950 bg-blend-multiply data-hover:bg-black/[2.5%]\"\n              >\n                {label}\n                {external && (\n                  <ArrowTopRightOnSquareIcon className=\"ml-1 h-4 w-4 text-gray-500\" aria-hidden=\"true\" />\n                )}\n              </Link>\n            )}\n          </PlusGridItem>\n        ))\n      )}\n    </nav>\n  )\n}\n\nfunction MobileNavButton() {\n  return (\n    <DisclosureButton\n      className=\"flex size-12 items-center justify-center self-center rounded-lg data-hover:bg-black/5 lg:hidden\"\n      aria-label=\"Open main menu\"\n    >\n      <Bars2Icon className=\"size-6\" />\n    </DisclosureButton>\n  )\n}\n\nfunction MobileNav({ githubIcon }: { githubIcon?: React.ReactNode }) {\n  const [isLoggedIn, setIsLoggedIn] = useState(false)\n  const [isLoading, setIsLoading] = useState(true)\n  \n  useEffect(() => {\n    const checkAuthStatus = async () => {\n      try {\n        const { data } = await supabase.auth.getSession()\n        setIsLoggedIn(!!data.session)\n      } catch (error) {\n        console.error('Error checking auth status:', error)\n      } finally {\n        setIsLoading(false)\n      }\n    }\n    \n    checkAuthStatus()\n  }, [])\n  \n  const authLinks: NavLink[] = isLoggedIn \n    ? [\n        { href: '/dashboard', label: 'Go to Dashboard' },\n        { href: '#', label: 'Log Out', isLogout: true }\n      ]\n    : [{ href: '/login', label: 'Login' }]\n  \n  const links = [...staticLinks, ...authLinks]\n  \n  return (\n    <DisclosurePanel className=\"lg:hidden\">\n      <div className=\"flex flex-col gap-6 py-4\">\n        {githubIcon && (\n          <div className=\"flex justify-start\">{githubIcon}</div>\n        )}\n        {!isLoading && (\n          links.map(({ href, label, external, isLogout }, linkIndex) => (\n          <motion.div\n            initial={{ opacity: 0, rotateX: -90 }}\n            animate={{ opacity: 1, rotateX: 0 }}\n            transition={{\n              duration: 0.15,\n              ease: 'easeInOut',\n              rotateX: { duration: 0.3, delay: linkIndex * 0.1 },\n            }}\n            key={`${href}-${label}`}\n          >\n            {isLogout ? (\n              <button\n                onClick={async () => {\n                  await supabase.auth.signOut();\n                  window.location.href = '/';\n                }}\n                className=\"flex items-center text-base font-medium text-gray-950 cursor-pointer\"\n              >\n                {label}\n              </button>\n            ) : (\n              <Link \n                href={href} \n                target={external ? '_blank' : undefined}\n                rel={external ? 'noopener noreferrer' : undefined}\n                className=\"flex items-center text-base font-medium text-gray-950\"\n              >\n                {label}\n                {external && (\n                  <ArrowTopRightOnSquareIcon className=\"ml-1 h-4 w-4 text-gray-500\" aria-hidden=\"true\" />\n                )}\n              </Link>\n            )}\n          </motion.div>\n        )))\n        }\n      </div>\n      <div className=\"absolute left-1/2 w-screen -translate-x-1/2\">\n        <div className=\"absolute inset-x-0 top-0 border-t border-black/5\" />\n        <div className=\"absolute inset-x-0 top-2 border-t border-black/5\" />\n      </div>\n    </DisclosurePanel>\n  )\n}\n\nexport function Navbar({ banner }: { banner?: React.ReactNode }) {\n  return (\n    <Disclosure as=\"header\" className=\"pt-12 sm:pt-16\">\n      <PlusGrid>\n        <PlusGridRow className=\"relative flex justify-between items-center\">\n          <div className=\"relative flex gap-3 items-center\">\n            <PlusGridItem className=\"py-3\">\n              <Link href=\"/\" title=\"Home\" className=\"flex items-center gap-3\">\n                <AppLogo size=\"large\" className=\"h-9\" />\n                <LogoText/>\n              </Link>\n            </PlusGridItem>\n          </div>\n          <div className=\"flex items-center gap-4\">\n            {/*\n            <a\n              href=\"https://github.com/cyberdesk-hq/cyberdesk\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"flex items-center\"\n              aria-label=\"Star us on GitHub\"\n            >\n              <TooltipIconButton\n                tooltip=\"Star us on GitHub\"\n                className=\"text-gray-950 hover:text-black transition-colors duration-200\"\n                style={{ padding: 0 }}\n              >\n                <GitHubIcon className=\"size-9\" />\n              </TooltipIconButton>\n            </a>\n            */}\n            <DesktopNav />\n            <MobileNavButton />\n          </div>\n        </PlusGridRow>\n      </PlusGrid>\n      {/*\n      <MobileNav githubIcon={\n        <a\n          href=\"https://github.com/cyberdesk-hq/cyberdesk\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          aria-label=\"Star us on GitHub\"\n          className=\"flex items-center mb-4\"\n        >\n          <TooltipIconButton\n            tooltip=\"Star us on GitHub\"\n            className=\"text-gray-950 hover:text-black transition-colors duration-200\"\n            style={{ padding: 0 }}\n          >\n            <GitHubIcon className=\"size-9\" />\n          </TooltipIconButton>\n        </a>\n      } />\n      */}\n      <MobileNav />\n    </Disclosure>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/playground/chat-error.tsx",
    "content": "import React from \"react\";\nimport { FaExclamationTriangle } from \"react-icons/fa\";\n\nexport function ChatError({ error, onRetry }: { error: unknown, onRetry: () => void }) {\n  if (!error) return null;\n  return (\n    <div className=\"flex flex-col items-center gap-1 rounded-lg border border-red-200 bg-white/90 shadow-sm px-6 py-2 mb-2 w-full mx-auto\">\n      <div className=\"flex items-center gap-2 text-red-600\">\n        <FaExclamationTriangle className=\"text-base\" />\n        <span className=\"font-semibold text-sm\">An error occurred</span>\n      </div>\n      <button\n        type=\"button\"\n        onClick={onRetry}\n        className=\"mt-1 px-4 py-1.5 rounded-md bg-red-600 hover:bg-red-700 text-white font-medium shadow transition-colors focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-offset-2 disabled:opacity-60 disabled:cursor-not-allowed text-sm\"\n      >\n        Retry\n      </button>\n    </div>\n  );\n} "
  },
  {
    "path": "apps/web/src/components/playground/icons.tsx",
    "content": "import Link from \"next/link\";\n\nexport const BotIcon = () => {\n  return (\n    <svg\n      height=\"16\"\n      strokeLinejoin=\"round\"\n      viewBox=\"0 0 16 16\"\n      width=\"16\"\n      style={{ color: \"currentcolor\" }}\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M8.75 2.79933C9.19835 2.53997 9.5 2.05521 9.5 1.5C9.5 0.671573 8.82843 0 8 0C7.17157 0 6.5 0.671573 6.5 1.5C6.5 2.05521 6.80165 2.53997 7.25 2.79933V5H7C4.027 5 1.55904 7.16229 1.08296 10H0V13H1V14.5V16H2.5H13.5H15V14.5V13H16V10H14.917C14.441 7.16229 11.973 5 9 5H8.75V2.79933ZM7 6.5C4.51472 6.5 2.5 8.51472 2.5 11V14.5H13.5V11C13.5 8.51472 11.4853 6.5 9 6.5H7ZM7.25 11.25C7.25 12.2165 6.4665 13 5.5 13C4.5335 13 3.75 12.2165 3.75 11.25C3.75 10.2835 4.5335 9.5 5.5 9.5C6.4665 9.5 7.25 10.2835 7.25 11.25ZM10.5 13C11.4665 13 12.25 12.2165 12.25 11.25C12.25 10.2835 11.4665 9.5 10.5 9.5C9.5335 9.5 8.75 10.2835 8.75 11.25C8.75 12.2165 9.5335 13 10.5 13Z\"\n        fill=\"currentColor\"\n      ></path>\n    </svg>\n  );\n};\n\nexport const AISDKLogo = () => {\n  return (\n    <div className=\"flex justify-between items-center\">\n      <div className=\"flex flex-row items-center gap-2 shrink-0 \">\n        <span className=\"flex flex-row items-center gap-2 home-links\">\n          <div className=\"flex flex-row items-center gap-4\">\n            <Link className=\"flex flex-row items-center gap-2\" href=\"/\">\n              <div className=\"flex flex-row items-center gap-2\">\n                <div className=\"text-zinc-800 dark:text-zinc-100\">\n                  <svg\n                    data-testid=\"geist-icon\"\n                    height={20}\n                    strokeLinejoin=\"round\"\n                    viewBox=\"0 0 16 16\"\n                    width={20}\n                    style={{ color: \"currentcolor\" }}\n                  >\n                    <path\n                      d=\"M2.5 0.5V0H3.5V0.5C3.5 1.60457 4.39543 2.5 5.5 2.5H6V3V3.5H5.5C4.39543 3.5 3.5 4.39543 3.5 5.5V6H3H2.5V5.5C2.5 4.39543 1.60457 3.5 0.5 3.5H0V3V2.5H0.5C1.60457 2.5 2.5 1.60457 2.5 0.5Z\"\n                      fill=\"currentColor\"\n                    />\n                    <path\n                      d=\"M14.5 4.5V5H13.5V4.5C13.5 3.94772 13.0523 3.5 12.5 3.5H12V3V2.5H12.5C13.0523 2.5 13.5 2.05228 13.5 1.5V1H14H14.5V1.5C14.5 2.05228 14.9477 2.5 15.5 2.5H16V3V3.5H15.5C14.9477 3.5 14.5 3.94772 14.5 4.5Z\"\n                      fill=\"currentColor\"\n                    />\n                    <path\n                      d=\"M8.40706 4.92939L8.5 4H9.5L9.59294 4.92939C9.82973 7.29734 11.7027 9.17027 14.0706 9.40706L15 9.5V10.5L14.0706 10.5929C11.7027 10.8297 9.82973 12.7027 9.59294 15.0706L9.5 16H8.5L8.40706 15.0706C8.17027 12.7027 6.29734 10.8297 3.92939 10.5929L3 10.5V9.5L3.92939 9.40706C6.29734 9.17027 8.17027 7.29734 8.40706 4.92939Z\"\n                      fill=\"currentColor\"\n                    />\n                  </svg>\n                </div>\n                <div className=\"text-2xl font-bold text-zinc-800 dark:text-zinc-100\">\n                  AI{\" \"}\n                  <span className=\"hidden min-[385px]:inline\">\n                    SDK\n                  </span>\n                </div>\n              </div>\n            </Link>\n          </div>\n        </span>\n      </div>\n    </div>\n  );\n};\n\nexport const UserIcon = () => {\n  return (\n    <svg\n      data-testid=\"geist-icon\"\n      height=\"16\"\n      strokeLinejoin=\"round\"\n      viewBox=\"0 0 16 16\"\n      width=\"16\"\n      style={{ color: \"currentcolor\" }}\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M7.75 0C5.95507 0 4.5 1.45507 4.5 3.25V3.75C4.5 5.54493 5.95507 7 7.75 7H8.25C10.0449 7 11.5 5.54493 11.5 3.75V3.25C11.5 1.45507 10.0449 0 8.25 0H7.75ZM6 3.25C6 2.2835 6.7835 1.5 7.75 1.5H8.25C9.2165 1.5 10 2.2835 10 3.25V3.75C10 4.7165 9.2165 5.5 8.25 5.5H7.75C6.7835 5.5 6 4.7165 6 3.75V3.25ZM2.5 14.5V13.1709C3.31958 11.5377 4.99308 10.5 6.82945 10.5H9.17055C11.0069 10.5 12.6804 11.5377 13.5 13.1709V14.5H2.5ZM6.82945 9C4.35483 9 2.10604 10.4388 1.06903 12.6857L1 12.8353V13V15.25V16H1.75H14.25H15V15.25V13V12.8353L14.931 12.6857C13.894 10.4388 11.6452 9 9.17055 9H6.82945Z\"\n        fill=\"currentColor\"\n      ></path>\n    </svg>\n  );\n};\n\nexport const VercelIcon = ({ size = 16 }: { size: number }) => {\n  return (\n    <svg\n      height={size}\n      strokeLinejoin=\"round\"\n      viewBox=\"0 0 16 16\"\n      width={size}\n      style={{ color: \"currentcolor\" }}\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M8 1L16 15H0L8 1Z\"\n        fill=\"currentColor\"\n      ></path>\n    </svg>\n  );\n};\n\nexport const ObjectIcon = () => {\n  return (\n    <svg\n      height=\"16\"\n      strokeLinejoin=\"round\"\n      viewBox=\"0 0 16 16\"\n      width=\"16\"\n      style={{ color: \"currentcolor\" }}\n    >\n      <path\n        fillRule=\"evenodd\"\n        clipRule=\"evenodd\"\n        d=\"M2.5 3.5C2.5 2.94771 2.94772 2.5 3.5 2.5H4.25V1H3.5C2.11929 1 1 2.11929 1 3.5V6.29449C1 6.65016 0.881575 6.86927 0.738252 7.00305C0.587949 7.14333 0.344525 7.24999 0 7.24999V8.74999C0.344525 8.74999 0.587948 8.85665 0.738251 8.99694C0.881575 9.13071 1 9.34982 1 9.70549V12.5C1 13.8807 2.11929 15 3.5 15H4.25V13.5H3.5C2.94772 13.5 2.5 13.0523 2.5 12.5V9.70549C2.5 9.03542 2.27894 8.44137 1.86198 7.99999C2.27894 7.55861 2.5 6.96457 2.5 6.29449V3.5ZM12.5 1H11.75V2.5H12.5C13.0523 2.5 13.5 2.94772 13.5 3.5V6.29449C13.5 6.96453 13.7212 7.5586 14.1382 7.99999C13.7212 8.44139 13.5 9.03545 13.5 9.70549V12.5C13.5 13.0523 13.0523 13.5 12.5 13.5H11.75V15H12.5C13.8807 15 15 13.8807 15 12.5V9.70549C15 9.35012 15.1184 9.13095 15.2618 8.99706C15.4122 8.85668 15.6556 8.74999 16 8.74999V7.24999C15.6556 7.24999 15.4122 7.1433 15.2618 7.00292C15.1184 6.86903 15 6.64986 15 6.29449V3.5C15 2.11928 13.8807 1 12.5 1ZM8.75 10.25V9.5H7.25V10.25V12.5986C7.25 13.0383 7.11985 13.4681 6.87596 13.834L6.45994 14.458L7.70801 15.2901L8.12404 14.666C8.5322 14.0538 8.75 13.3344 8.75 12.5986V10.25ZM8 7C8.69036 7 9.25 6.44036 9.25 5.75C9.25 5.05964 8.69036 4.5 8 4.5C7.30964 4.5 6.75 5.05964 6.75 5.75C6.75 6.44036 7.30964 7 8 7Z\"\n        fill=\"currentColor\"\n      ></path>\n    </svg>\n  );\n};\n\nexport const GitIcon = () => {\n  return (\n    <svg\n      height=\"16\"\n      strokeLinejoin=\"round\"\n      viewBox=\"0 0 16 16\"\n      width=\"16\"\n      style={{ color: \"currentcolor\" }}\n    >\n      <g clipPath=\"url(#clip0_872_3147)\">\n        <path\n          fillRule=\"evenodd\"\n          clipRule=\"evenodd\"\n          d=\"M8 0C3.58 0 0 3.57879 0 7.99729C0 11.5361 2.29 14.5251 5.47 15.5847C5.87 15.6547 6.02 15.4148 6.02 15.2049C6.02 15.0149 6.01 14.3851 6.01 13.7154C4 14.0852 3.48 13.2255 3.32 12.7757C3.23 12.5458 2.84 11.836 2.5 11.6461C2.22 11.4961 1.82 11.1262 2.49 11.1162C3.12 11.1062 3.57 11.696 3.72 11.936C4.44 13.1455 5.59 12.8057 6.05 12.5957C6.12 12.0759 6.33 11.726 6.56 11.5261C4.78 11.3262 2.92 10.6364 2.92 7.57743C2.92 6.70773 3.23 5.98797 3.74 5.42816C3.66 5.22823 3.38 4.40851 3.82 3.30888C3.82 3.30888 4.49 3.09895 6.02 4.1286C6.66 3.94866 7.34 3.85869 8.02 3.85869C8.7 3.85869 9.38 3.94866 10.02 4.1286C11.55 3.08895 12.22 3.30888 12.22 3.30888C12.66 4.40851 12.38 5.22823 12.3 5.42816C12.81 5.98797 13.12 6.69773 13.12 7.57743C13.12 10.6464 11.25 11.3262 9.47 11.5261C9.76 11.776 10.01 12.2558 10.01 13.0056C10.01 14.0752 10 14.9349 10 15.2049C10 15.4148 10.15 15.6647 10.55 15.5847C12.1381 15.0488 13.5182 14.0284 14.4958 12.6673C15.4735 11.3062 15.9996 9.67293 16 7.99729C16 3.57879 12.42 0 8 0Z\"\n          fill=\"currentColor\"\n        ></path>\n      </g>\n      <defs>\n        <clipPath id=\"clip0_872_3147\">\n          <rect width=\"16\" height=\"16\" fill=\"white\"></rect>\n        </clipPath>\n      </defs>\n    </svg>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/playground/input.tsx",
    "content": "import { ArrowUp } from \"lucide-react\";\nimport { Input as ShadcnInput } from \"@/components/ui/input\";\n\ninterface InputProps {\n  input: string;\n  handleInputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;\n  isInitializing: boolean;\n  isLoading: boolean;\n  status: string;\n  stop: () => void;\n}\n\nexport const Input = ({\n  input,\n  handleInputChange,\n  isInitializing,\n  isLoading,\n  status,\n  stop,\n}: InputProps) => {\n  return (\n    <div className=\"relative w-full\">\n      <ShadcnInput\n        className=\"bg-secondary py-6 w-full rounded-xl pr-12\"\n        value={input}\n        autoFocus\n        placeholder={\"Tell me what to do...\"}\n        onChange={handleInputChange}\n        disabled={isLoading || isInitializing}\n      />\n      {status === \"streaming\" || status === \"submitted\" ? (\n        <button\n          type=\"button\"\n          onClick={stop}\n          className=\"cursor-pointer absolute right-2 top-1/2 -translate-y-1/2 rounded-full p-2 bg-black hover:bg-zinc-800 disabled:bg-zinc-300 disabled:cursor-not-allowed transition-colors\"\n        >\n          <div className=\"animate-spin h-4 w-4\">\n            <svg className=\"h-4 w-4 text-white\" viewBox=\"0 0 24 24\">\n              <circle\n                className=\"opacity-25\"\n                cx=\"12\"\n                cy=\"12\"\n                r=\"10\"\n                stroke=\"currentColor\"\n                strokeWidth=\"4\"\n                fill=\"none\"\n              />\n              <path\n                className=\"opacity-75\"\n                fill=\"currentColor\"\n                d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n              />\n            </svg>\n          </div>\n        </button>\n      ) : (\n        <button\n          type=\"submit\"\n          disabled={isLoading || !input.trim() || isInitializing}\n          className=\"absolute right-2 top-1/2 -translate-y-1/2 rounded-full p-2 bg-black hover:bg-zinc-800 disabled:bg-zinc-300 disabled:cursor-not-allowed transition-colors\"\n        >\n          <ArrowUp className=\"h-4 w-4 text-white\" />\n        </button>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/playground/markdown.tsx",
    "content": "/* eslint-disable @typescript-eslint/no-unused-vars */\nimport Link from \"next/link\";\nimport React, { memo } from \"react\";\nimport ReactMarkdown, { type Components } from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\n\nconst components: Partial<Components> = {\n  pre: ({ children }) => <>{children}</>,\n  ol: ({ node, children, ...props }) => {\n    return (\n      <ol className=\"list-decimal list-outside ml-4\" {...props}>\n        {children}\n      </ol>\n    );\n  },\n  li: ({ node, children, ...props }) => {\n    return (\n      <li className=\"py-1\" {...props}>\n        {children}\n      </li>\n    );\n  },\n  ul: ({ node, children, ...props }) => {\n    return (\n      <ul className=\"list-decimal list-outside ml-4\" {...props}>\n        {children}\n      </ul>\n    );\n  },\n  strong: ({ node, children, ...props }) => {\n    return (\n      <span className=\"font-semibold\" {...props}>\n        {children}\n      </span>\n    );\n  },\n  a: ({ node, children, ...props }) => {\n    return (\n      // @ts-expect-error error\n      <Link\n        className=\"text-blue-500 hover:underline\"\n        target=\"_blank\"\n        rel=\"noreferrer\"\n        {...props}\n      >\n        {children}\n      </Link>\n    );\n  },\n  h1: ({ node, children, ...props }) => {\n    return (\n      <h1 className=\"text-3xl font-semibold mt-6 mb-2\" {...props}>\n        {children}\n      </h1>\n    );\n  },\n  h2: ({ node, children, ...props }) => {\n    return (\n      <h2 className=\"text-2xl font-semibold mt-6 mb-2\" {...props}>\n        {children}\n      </h2>\n    );\n  },\n  h3: ({ node, children, ...props }) => {\n    return (\n      <h3 className=\"text-xl font-semibold mt-6 mb-2\" {...props}>\n        {children}\n      </h3>\n    );\n  },\n  h4: ({ node, children, ...props }) => {\n    return (\n      <h4 className=\"text-lg font-semibold mt-6 mb-2\" {...props}>\n        {children}\n      </h4>\n    );\n  },\n  h5: ({ node, children, ...props }) => {\n    return (\n      <h5 className=\"text-base font-semibold mt-6 mb-2\" {...props}>\n        {children}\n      </h5>\n    );\n  },\n  h6: ({ node, children, ...props }) => {\n    return (\n      <h6 className=\"text-sm font-semibold mt-6 mb-2\" {...props}>\n        {children}\n      </h6>\n    );\n  },\n};\n\nconst remarkPlugins = [remarkGfm];\n\nconst NonMemoizedMarkdown = ({ children }: { children: string }) => {\n  return (\n    <ReactMarkdown remarkPlugins={remarkPlugins} components={components}>\n      {children}\n    </ReactMarkdown>\n  );\n};\n\nexport const Markdown = memo(\n  NonMemoizedMarkdown,\n  (prevProps, nextProps) => prevProps.children === nextProps.children,\n);"
  },
  {
    "path": "apps/web/src/components/playground/message.tsx",
    "content": "\"use client\";\n\nimport type { Message } from \"ai\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { memo } from \"react\";\nimport equal from \"fast-deep-equal\";\n\nimport { Markdown } from \"@/components/playground/markdown\";\nimport { ABORTED } from \"@/utils/playground/misc-demo-utils\";\nimport { cn } from \"@/utils/misc-utils\";\nimport {\n  Camera,\n  CheckCircle,\n  CircleSlash,\n  Clock,\n  Keyboard,\n  KeyRound,\n  Loader2,\n  MousePointer,\n  MousePointerClick,\n  ScrollText,\n  StopCircle,\n} from \"lucide-react\";\n\nconst PurePreviewMessage = ({\n  message,\n  isLatestMessage,\n  status,\n}: {\n  message: Message;\n  isLoading: boolean;\n  status: \"error\" | \"submitted\" | \"streaming\" | \"ready\";\n  isLatestMessage: boolean;\n}) => {\n  return (\n    <AnimatePresence key={message.id}>\n      <motion.div\n        className=\"w-full mx-auto px-4 group/message\"\n        initial={{ y: 5, opacity: 0 }}\n        animate={{ y: 0, opacity: 1 }}\n        key={`message-${message.id}`}\n        data-role={message.role}\n      >\n        <div\n          className={cn(\n            \"flex gap-4 w-full group-data-[role=user]/message:ml-auto group-data-[role=user]/message:max-w-2xl\",\n            \"group-data-[role=user]/message:w-fit\",\n          )}\n        >\n          {/* {message.role === \"assistant\" && (\n            <div className=\"size-8 flex items-center rounded-full justify-center ring-1 shrink-0 ring-border bg-background\">\n              <div className=\"translate-y-px\">\n                <SparklesIcon size={14} />\n              </div>\n            </div>\n          )} */}\n\n          <div className=\"flex flex-col w-full\">\n            {message.parts?.map((part, i) => {\n              switch (part.type) {\n                case \"text\":\n                  return (\n                    <motion.div\n                      initial={{ y: 5, opacity: 0 }}\n                      animate={{ y: 0, opacity: 1 }}\n                      key={`message-${message.id}-part-${i}`}\n                      className=\"flex flex-row gap-2 items-start w-full pb-4\"\n                    >\n                      <div\n                        className={cn(\"flex flex-col gap-4\", {\n                          \"bg-secondary text-secondary-foreground px-3 py-2 rounded-xl\":\n                            message.role === \"user\",\n                        })}\n                      >\n                        <Markdown>{part.text}</Markdown>\n                      </div>\n                    </motion.div>\n                  );\n                case \"tool-invocation\":\n                  const { toolName, toolCallId, state, args } =\n                    part.toolInvocation;\n\n                  if (toolName === \"computer\") {\n                    const {\n                      action,\n                      coordinate,\n                      text,\n                      duration,\n                      scroll_amount,\n                      scroll_direction,\n                    } = args;\n                    let actionLabel = \"\";\n                    let actionDetail = \"\";\n                    let ActionIcon = null;\n\n                    switch (action) {\n                      case \"screenshot\":\n                        actionLabel = \"Taking screenshot\";\n                        ActionIcon = Camera;\n                        break;\n                      case \"left_click\":\n                        actionLabel = \"Left clicking\";\n                        actionDetail = coordinate\n                          ? `at (${coordinate[0]}, ${coordinate[1]})`\n                          : \"\";\n                        ActionIcon = MousePointer;\n                        break;\n                      case \"right_click\":\n                        actionLabel = \"Right clicking\";\n                        actionDetail = coordinate\n                          ? `at (${coordinate[0]}, ${coordinate[1]})`\n                          : \"\";\n                        ActionIcon = MousePointerClick;\n                        break;\n                      case \"double_click\":\n                        actionLabel = \"Double clicking\";\n                        actionDetail = coordinate\n                          ? `at (${coordinate[0]}, ${coordinate[1]})`\n                          : \"\";\n                        ActionIcon = MousePointerClick;\n                        break;\n                      case \"mouse_move\":\n                        actionLabel = \"Moving mouse\";\n                        actionDetail = coordinate\n                          ? `to (${coordinate[0]}, ${coordinate[1]})`\n                          : \"\";\n                        ActionIcon = MousePointer;\n                        break;\n                      case \"type\":\n                        actionLabel = \"Typing\";\n                        actionDetail = text ? `\"${text}\"` : \"\";\n                        ActionIcon = Keyboard;\n                        break;\n                      case \"key\":\n                        actionLabel = \"Pressing key\";\n                        actionDetail = text ? `\"${text}\"` : \"\";\n                        ActionIcon = KeyRound;\n                        break;\n                      case \"wait\":\n                        actionLabel = \"Waiting\";\n                        actionDetail = duration ? `${duration} seconds` : \"\";\n                        ActionIcon = Clock;\n                        break;\n                      case \"scroll\":\n                        actionLabel = \"Scrolling\";\n                        actionDetail =\n                          scroll_direction && scroll_amount\n                            ? `${scroll_direction} by ${scroll_amount}`\n                            : \"\";\n                        ActionIcon = ScrollText;\n                        break;\n                      default:\n                        actionLabel = action;\n                        ActionIcon = MousePointer;\n                        break;\n                    }\n\n                    return (\n                      <motion.div\n                        initial={{ y: 5, opacity: 0 }}\n                        animate={{ y: 0, opacity: 1 }}\n                        key={`message-${message.id}-part-${i}`}\n                        className=\"flex flex-col gap-2 p-2 mb-3 text-sm bg-zinc-50 dark:bg-zinc-900 rounded-md border border-zinc-200 dark:border-zinc-800\"\n                      >\n                        <div className=\"flex-1 flex items-center justify-center\">\n                          <div className=\"flex items-center justify-center w-8 h-8 bg-zinc-50 dark:bg-zinc-800 rounded-full\">\n                            {ActionIcon && <ActionIcon className=\"w-4 h-4\" />}\n                          </div>\n                          <div className=\"flex-1\">\n                            <div className=\"font-medium font-mono flex items-baseline gap-2\">\n                              {actionLabel}\n                              {actionDetail && (\n                                <span className=\"text-xs text-zinc-500 dark:text-zinc-400 font-normal\">\n                                  {actionDetail}\n                                </span>\n                              )}\n                            </div>\n                          </div>\n                          <div className=\"w-5 h-5 flex items-center justify-center\">\n                            {state === \"call\" ? (\n                              isLatestMessage && status !== \"ready\" ? (\n                                <Loader2 className=\"animate-spin h-4 w-4 text-zinc-500\" />\n                              ) : (\n                                <StopCircle className=\"h-4 w-4 text-red-500\" />\n                              )\n                            ) : state === \"result\" ? (\n                              part.toolInvocation.result === ABORTED ? (\n                                <CircleSlash\n                                size={14}\n                                className=\"text-amber-600\"\n                                />                              ) : (\n                                <CheckCircle\n                                  size={14}\n                                  className=\"text-green-600\"\n                                />\n                              )\n                            ) : null}\n                          </div>\n                        </div>\n                        {state === \"result\" ? (\n                          part.toolInvocation.result.type === \"image\" && (\n                            <div className=\"p-2\">\n                              {/* eslint-disable-next-line @next/next/no-img-element */}\n                              <img\n                                src={`data:image/png;base64,${part.toolInvocation.result.data}`}\n                                alt=\"Generated Image\"\n                                className=\"w-full aspect-[1024/768] rounded-sm\"\n                              />\n                            </div>\n                          )\n                        ) : action === \"screenshot\" ? (\n                          <div className=\"w-full aspect-[1024/768] rounded-sm bg-zinc-200 dark:bg-zinc-800 animate-pulse\"></div>\n                        ) : null}\n                      </motion.div>\n                    );\n                  }\n                  if (toolName === \"bash\") {\n                    const { command } = args;\n\n                    return (\n                      <motion.div\n                        initial={{ y: 5, opacity: 0 }}\n                        animate={{ y: 0, opacity: 1 }}\n                        key={`message-${message.id}-part-${i}`}\n                        className=\"flex items-center gap-2 p-2 mb-3 text-sm bg-zinc-50 dark:bg-zinc-900 rounded-md border border-zinc-200 dark:border-zinc-800\"\n                      >\n                        <div className=\"flex items-center justify-center w-8 h-8 bg-zinc-50 dark:bg-zinc-800 rounded-full\">\n                          <ScrollText className=\"w-4 h-4\" />\n                        </div>\n                        <div className=\"flex-1\">\n                          <div className=\"font-medium flex items-baseline gap-2\">\n                            Running command\n                            <span className=\"text-xs text-zinc-500 dark:text-zinc-400 font-normal\">\n                              {command.slice(0, 40)}...\n                            </span>\n                          </div>\n                        </div>\n                        <div className=\"w-5 h-5 flex items-center justify-center\">\n                          {state === \"call\" ? (\n                            isLatestMessage && status !== \"ready\" ? (\n                              <Loader2 className=\"animate-spin h-4 w-4 text-zinc-500\" />\n                            ) : (\n                              <StopCircle className=\"h-4 w-4 text-red-500\" />\n                            )\n                          ) : state === \"result\" ? (\n                            <CheckCircle size={14} className=\"text-green-600\" />\n                          ) : null}\n                        </div>\n                      </motion.div>\n                    );\n                  }\n                  return (\n                    <div key={toolCallId}>\n                      <h3>\n                        {toolName}: {state}\n                      </h3>\n                      <pre>{JSON.stringify(args, null, 2)}</pre>\n                    </div>\n                  );\n\n                default:\n                  return null;\n              }\n            })}\n          </div>\n        </div>\n      </motion.div>\n    </AnimatePresence>\n  );\n};\n\nexport const PreviewMessage = memo(\n  PurePreviewMessage,\n  (prevProps, nextProps) => {\n    if (prevProps.status !== nextProps.status) return false;\n    if (prevProps.message.annotations !== nextProps.message.annotations)\n      return false;\n    // if (prevProps.message.content !== nextProps.message.content) return false;\n    if (!equal(prevProps.message.parts, nextProps.message.parts)) return false;\n\n    return true;\n  },\n);\n"
  },
  {
    "path": "apps/web/src/components/playground/project-info.tsx",
    "content": "import { motion } from \"motion/react\";\nimport { VercelIcon } from \"@/components/playground/icons\";\nimport { ComputerIcon } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { AppLogo } from \"../shared/app-logo\";\n\nexport const ProjectInfo = () => {\n  return (\n    <motion.div className=\"w-full px-4\">\n      <div className=\"rounded-lg border-border border p-6 flex flex-col gap-4 text-center text-base dark:text-zinc-400\">\n        <p className=\"flex flex-row justify-center gap-4 items-center text-zinc-900 dark:text-zinc-50\">\n          <AppLogo />\n          <span>+</span>\n          <ComputerIcon />\n        </p>\n        <h3 className=\"text-center text-2xl font-bold\">Cyberdesk Agent</h3>\n        <p>\n          This demo showcases a Computer Agent built with the{\" \"}\n          <StyledLink href=\"https://www.anthropic.com/claude/sonnet\">\n            Anthropic Claude Sonnet 3.7\n          </StyledLink>\n          and <StyledLink href=\"https://cyberdesk.io\">Cyberdesk</StyledLink>.\n        </p>\n        <p>\n          {\" \"}\n          The code for this demo is open source on{\" \"}\n          <Link\n            className=\"text-blue-500 dark:text-blue-400\"\n            href=\"https://github.com/cyberdesk-hq/cyberdesk-ai-sdk-starter\"\n            target=\"_blank\"\n          >\n            GitHub\n          </Link>\n          .\n        </p>\n      </div>\n    </motion.div>\n  );\n};\n\nconst StyledLink = ({\n  children,\n  href,\n}: {\n  children: React.ReactNode;\n  href: string;\n}) => {\n  return (\n    <Link\n      className=\"text-blue-500 dark:text-blue-400\"\n      href={href}\n      target=\"_blank\"\n    >\n      {children}\n    </Link>\n  );\n};\n\n// const Code = ({ text }: { text: string }) => {\n//   return <code className=\"\">{text}</code>;\n// };\n"
  },
  {
    "path": "apps/web/src/components/playground/prompt-suggestions.tsx",
    "content": "import { ArrowUpRight } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\n\nconst suggestions = [\n  {\n    text: \"Create a website about big floppas\",\n    prompt: \"Create a website about big floppas using Next.js, Tailwind CSS, and TypeScript\",\n  },\n  {\n    text: \"Create a new text file\",\n    prompt: \"Open a text editor and create a new file called notes.txt and write 'let's go cyberdesk!'\",\n  },\n  {\n    text: \"Research the latest trends in AI and write a report\",\n    prompt: \"Research the latest trends in AI and write a report\",\n  },\n];\n\nexport const PromptSuggestions = ({\n  submitPrompt,\n  disabled,\n}: {\n  submitPrompt: (prompt: string) => void;\n  disabled: boolean;\n}) => {\n  return (\n    <div className=\"flex flex-wrap items-center gap-3 px-4\">\n      {suggestions.map((suggestion, index) => (\n        <Button\n          key={index}\n          variant=\"pill\"\n          size=\"pill\"\n          onClick={() => submitPrompt(suggestion.prompt)}\n          disabled={disabled}\n        >\n          <span>\n            <span className=\"text-black text-sm\">\n              {suggestion.text.toLowerCase()}\n            </span>\n          </span>\n          <ArrowUpRight className=\"ml-1 h-2 w-2 sm:h-3 sm:w-3 text-zinc-500 group-hover:opacity-70\" />\n        </Button>\n      ))}\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/plus-grid.tsx",
    "content": "import { clsx } from 'clsx'\n\nexport function PlusGrid({\n  className = '',\n  children,\n}: {\n  className?: string\n  children: React.ReactNode\n}) {\n  return <div className={className}>{children}</div>\n}\n\nexport function PlusGridRow({\n  className = '',\n  children,\n}: {\n  className?: string\n  children: React.ReactNode\n}) {\n  return (\n    <div\n      className={clsx(\n        className,\n        'group/row relative isolate pt-[calc(--spacing(2)+1px)] last:pb-[calc(--spacing(2)+1px)]',\n      )}\n    >\n      <div\n        aria-hidden=\"true\"\n        className=\"absolute inset-y-0 left-1/2 -z-10 w-screen -translate-x-1/2\"\n      >\n        <div className=\"absolute inset-x-0 top-0 border-t border-black/5\"></div>\n        <div className=\"absolute inset-x-0 top-2 border-t border-black/5\"></div>\n        <div className=\"absolute inset-x-0 bottom-0 hidden border-b border-black/5 group-last/row:block\"></div>\n        <div className=\"absolute inset-x-0 bottom-2 hidden border-b border-black/5 group-last/row:block\"></div>\n      </div>\n      {children}\n    </div>\n  )\n}\n\nexport function PlusGridItem({\n  className = '',\n  children,\n}: {\n  className?: string\n  children: React.ReactNode\n}) {\n  return (\n    <div className={clsx(className, 'group/item relative')}>\n      <PlusGridIcon\n        placement=\"top left\"\n        className=\"hidden group-first/item:block\"\n      />\n      <PlusGridIcon placement=\"top right\" />\n      <PlusGridIcon\n        placement=\"bottom left\"\n        className=\"hidden group-first/item:group-last/row:block\"\n      />\n      <PlusGridIcon\n        placement=\"bottom right\"\n        className=\"hidden group-last/row:block\"\n      />\n      {children}\n    </div>\n  )\n}\n\nexport function PlusGridIcon({\n  className = '',\n  placement,\n}: {\n  className?: string\n  placement: `${'top' | 'bottom'} ${'right' | 'left'}`\n}) {\n  const [yAxis, xAxis] = placement.split(' ')\n\n  const yClass = yAxis === 'top' ? '-top-2' : '-bottom-2'\n  const xClass = xAxis === 'left' ? '-left-2' : '-right-2'\n\n  return (\n    <svg\n      viewBox=\"0 0 15 15\"\n      aria-hidden=\"true\"\n      className={clsx(\n        className,\n        'absolute size-[15px] fill-black/10',\n        yClass,\n        xClass,\n      )}\n    >\n      <path d=\"M8 0H7V7H0V8H7V15H8V8H15V7H8V0Z\" />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/screenshot.tsx",
    "content": "import { clsx } from 'clsx'\n\nexport function Screenshot({\n  width,\n  height,\n  src,\n  className,\n}: {\n  width: number\n  height: number\n  src: string\n  className?: string\n}) {\n  return (\n    <div\n      style={{ '--width': width, '--height': height } as React.CSSProperties}\n      className={clsx(\n        className,\n        'relative aspect-[var(--width)/var(--height)] [--radius:var(--radius-xl)]',\n      )}\n    >\n      <div className=\"absolute -inset-[var(--padding)] rounded-[calc(var(--radius)+var(--padding))] ring-1 shadow-xs ring-black/5 [--padding:--spacing(2)]\" />\n      <img\n        alt=\"\"\n        src={src}\n        className=\"h-full rounded-[var(--radius)] ring-1 shadow-2xl ring-black/10\"\n      />\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/shared/app-logo.tsx",
    "content": "'use client'\n\nimport { Mark } from '@/components/logo'\nimport { clsx } from 'clsx'\n\nexport interface AppLogoProps {\n  className?: string\n  size?: 'small' | 'medium' | 'large'\n  variant?: 'light' | 'dark'\n}\n\nexport function AppLogo({ \n  className, \n  size = 'medium', \n  variant = 'dark' \n}: AppLogoProps) {\n  const sizeClasses = {\n    small: 'h-6 w-auto',\n    medium: 'h-8 w-auto',\n    large: 'h-10 w-auto',\n  }\n\n  // Use the Mark component from the existing logo.tsx\n  return (\n    <Mark \n      className={clsx(\n        sizeClasses[size],\n        variant === 'light' ? 'fill-white' : 'fill-black',\n        className\n      )} \n    />\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/stripe/checkout-button.tsx",
    "content": "\"use client\";\n\nimport { useState } from 'react';\nimport { Button } from '@/components/button';\nimport type { User } from '@supabase/supabase-js';\nimport type { Profile } from '@/types/database';\nimport type { Tier } from '@/utils/stripe/tiers';\n\nexport interface CheckoutButtonProps {\n  tier: Tier;\n  user: User | null;\n  profile: Profile | null;\n  children?: React.ReactNode;\n  className?: string;\n  variant?: \"primary\" | \"secondary\" | \"outline\";\n}\n\nexport function CheckoutButton({ \n  tier, \n  user, \n  profile,\n  children,\n  className,\n  variant\n}: CheckoutButtonProps) {\n  const [isLoading, setIsLoading] = useState(false);\n  \n  // Determine the button text based on user authentication and subscription status\n  let buttonText = children || 'Start a free trial';\n  let buttonAction: 'link' | 'checkout' | 'disabled' = 'link';\n  let buttonHref = tier.href;\n  \n  if (!user) {\n    // User is not logged in\n    buttonText = children || 'Get Started';\n    buttonHref = '/login';\n    buttonAction = 'link';\n  } else if (profile && profile.subscription_status === 'active') {\n    // User is logged in and has an active subscription\n    buttonText = children || 'You are already subscribed to this plan';\n    buttonAction = 'disabled';\n  } else if (user) {\n    // User is logged in but not subscribed\n    buttonText = children || `Subscribe to ${tier.name}`;\n    buttonAction = 'checkout';\n  }\n  \n  const handleCheckout = async () => {\n    try {\n      setIsLoading(true);\n      \n      const response = await fetch('/api/stripe/checkout', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          // You can customize success and cancel URLs if needed\n          successUrl: `${window.location.origin}/dashboard?payment=success`,\n          cancelUrl: `${window.location.origin}/pricing?payment=cancelled`,\n          stripeCustomerId: profile?.stripe_customer_id || null,\n        }),\n      });\n\n      const { url } = await response.json();\n      \n      if (url) {\n        window.location.href = url;\n      } else {\n        throw new Error('Failed to create checkout session');\n      }\n    } catch (error) {\n      console.error('Error during checkout:', error);\n      alert('Something went wrong. Please try again later.');\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <>\n      {buttonAction === 'disabled' ? (\n        <div className={`inline-flex items-center justify-center w-full rounded-md px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 cursor-not-allowed ${className || ''}`}>\n          {buttonText}\n        </div>\n      ) : buttonAction === 'checkout' ? (\n        <Button \n          onClick={handleCheckout}\n          disabled={isLoading}\n          className={className}\n          variant={variant}\n        >\n          {isLoading ? 'Processing...' : buttonText}\n        </Button>\n      ) : (\n        <Button href={buttonHref} className={className} variant={variant}>\n          {buttonText}\n        </Button>\n      )}\n    </>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/stripe/client-pricing-card.tsx",
    "content": "\"use client\";\n\nimport { useState } from 'react';\nimport { Button } from '@/components/button';\nimport { Subheading } from '@/components/text';\nimport type { User } from '@supabase/supabase-js';\nimport type { Profile } from '@/types/database';\nimport { CheckoutButton } from './checkout-button';\n\n// Import the FeatureItem component from the same file\nfunction FeatureItem({\n  description,\n  disabled = false,\n}: {\n  description: string;\n  disabled?: boolean;\n}) {\n  return (\n    <li\n      className={`flex gap-3 ${\n        disabled ? 'text-gray-950/50' : 'text-gray-950/80'\n      }`}\n    >\n      <span className=\"inline-flex h-6 items-center\">\n        <PlusIcon className=\"size-[0.9375rem] shrink-0 fill-gray-950/25\" />\n      </span>\n      {disabled && <span className=\"sr-only\">Not included:</span>}\n      {description}\n    </li>\n  );\n}\n\nfunction PlusIcon(props: React.ComponentPropsWithoutRef<'svg'>) {\n  return (\n    <svg viewBox=\"0 0 15 15\" aria-hidden=\"true\" {...props}>\n      <path clipRule=\"evenodd\" d=\"M8 0H7v7H0v1h7v7h1V8h7V7H8V0z\" />\n    </svg>\n  );\n}\n\n// Define the tier type\ninterface Tier {\n  name: string;\n  slug: string;\n  description: string;\n  priceMonthly: number;\n  href: string;\n  highlights: { description: string; disabled?: boolean }[];\n  features: { section: string; name: string; value: string | number | boolean; }[];\n}\n\nexport function PricingCard({ \n  tier, \n  user, \n  profile \n}: { \n  tier: Tier; \n  user: User | null; \n  profile: Profile | null;\n}) {\n  return (\n    <div className=\"-m-2 grid grid-cols-1 rounded-4xl ring-1 shadow-[inset_0_0_2px_1px_#ffffff4d] ring-black/5 max-lg:mx-auto max-lg:w-full max-lg:max-w-sm\">\n      <div className=\"grid grid-cols-1 rounded-4xl p-2 shadow-md shadow-black/5\">\n        <div className=\"rounded-3xl bg-white p-10 pb-9 ring-1 shadow-2xl ring-black/5\">\n          <Subheading>{tier.name}</Subheading>\n          <p className=\"mt-2 text-sm/6 text-gray-950/75\">{tier.description}</p>\n          <div className=\"mt-8 flex items-center gap-4\">\n            <div className=\"text-5xl font-medium text-gray-950\">\n              ${tier.priceMonthly}\n            </div>\n            <div className=\"text-sm/5 text-gray-950/75\">\n              <p>USD</p>\n              <p>per month</p>\n            </div>\n          </div>\n          <div className=\"mt-8\">\n            <CheckoutButton tier={tier} user={user} profile={profile} />\n          </div>\n          <div className=\"mt-8\">\n            <h3 className=\"text-sm/6 font-medium text-gray-950\">\n              You get access to:\n            </h3>\n            <ul className=\"mt-3 space-y-3\">\n              {tier.highlights.map((props, featureIndex) => (\n                <FeatureItem key={featureIndex} {...props} />\n              ))}\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/stripe/client-pricing-cards.tsx",
    "content": "\"use client\";\n\nimport { Container } from '@/components/container';\nimport { Gradient } from '@/components/gradient';\nimport type { User } from '@supabase/supabase-js';\nimport type { Profile } from '@/types/database';\nimport { PricingCard } from './client-pricing-card';\nimport { useEffect, useState } from 'react';\nimport { supabase } from '@/utils/supabase/client';\nimport { Subheading } from '@/components/text';\nimport CONFIG from '../../../config';\n\n// Define the tier type\ninterface Tier {\n  name: string;\n  slug: string;\n  description: string;\n  priceMonthly: number;\n  href: string;\n  highlights: { description: string; disabled?: boolean }[];\n  features: { section: string; name: string; value: string | number | boolean; }[];\n}\n\nexport default function ClientPricingCards({ \n  tiers,\n  user,\n  profile\n}: { \n  tiers: Tier[];\n  user: User | null;\n  profile: Profile | null;\n}) {\n  const [activeSubscriptionsCount, setActiveSubscriptionsCount] = useState<number | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const SUBSCRIPTION_LIMIT = CONFIG.subscriptionLimit;\n  \n  useEffect(() => {\n    // Only check subscription count for logged-in users\n    if (user) {\n      async function fetchActiveSubscriptions() {\n        try {\n          setIsLoading(true);\n          const { count, error } = await supabase\n            .from('profiles')\n            .select('*', { count: 'exact', head: true })\n            .eq('subscription_status', 'active');\n            \n          if (error) {\n            console.error('Error fetching active subscriptions:', error);\n          } else {\n            setActiveSubscriptionsCount(count);\n          }\n        } catch (err) {\n          console.error('Error in fetchActiveSubscriptions:', err);\n        } finally {\n          setIsLoading(false);\n        }\n      }\n      \n      fetchActiveSubscriptions();\n    }\n  }, [user]);\n  \n  const isSoldOut = user && activeSubscriptionsCount !== null && activeSubscriptionsCount >= SUBSCRIPTION_LIMIT;\n  \n  // Custom component for sold-out tier card\n  const SoldOutPricingCard = ({ tier }: { tier: Tier }) => (\n    <div className=\"-m-2 grid grid-cols-1 rounded-4xl ring-1 shadow-[inset_0_0_2px_1px_#ffffff4d] ring-black/5 max-lg:mx-auto max-lg:w-full max-lg:max-w-sm\">\n      <div className=\"grid grid-cols-1 rounded-4xl p-2 shadow-md shadow-black/5\">\n        <div className=\"rounded-3xl bg-white p-10 pb-9 ring-1 shadow-2xl ring-black/5\">\n          <Subheading>{tier.name}</Subheading>\n          <p className=\"mt-2 text-sm/6 text-gray-950/75\">{tier.description}</p>\n          <div className=\"mt-8 flex items-center gap-4\">\n            <div className=\"text-5xl font-medium text-gray-950\">\n              ${tier.priceMonthly}\n            </div>\n            <div className=\"text-sm/5 text-gray-950/75\">\n              <p>USD</p>\n              <p>per month</p>\n            </div>\n          </div>\n          <div className=\"mt-8\">\n            <div className=\"bg-amber-50 border border-amber-200 text-amber-800 px-4 py-3 rounded-md\">\n              <p className=\"font-medium\">Thank you for your interest!</p>\n              <p className=\"mt-1 text-sm\">We&apos;re sold out but we&apos;re working on adding more capacity!</p>\n            </div>\n          </div>\n          <div className=\"mt-8\">\n            <h3 className=\"text-sm/6 font-medium text-gray-950\">\n              You get access to:\n            </h3>\n            <ul className=\"mt-3 space-y-3\">\n              {tier.highlights.map((props, featureIndex) => (\n                <li key={featureIndex} className={`flex gap-3 ${props.disabled ? 'text-gray-950/50' : 'text-gray-950/80'}`}>\n                  <span className=\"inline-flex h-6 items-center\">\n                    <svg viewBox=\"0 0 15 15\" className=\"size-[0.9375rem] shrink-0 fill-gray-950/25\" aria-hidden=\"true\">\n                      <path clipRule=\"evenodd\" d=\"M8 0H7v7H0v1h7v7h1V8h7V7H8V0z\" />\n                    </svg>\n                  </span>\n                  {props.disabled && <span className=\"sr-only\">Not included:</span>}\n                  {props.description}\n                </li>\n              ))}\n            </ul>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n\n  return (\n    <div className=\"relative py-24\">\n      <Gradient className=\"absolute inset-x-2 top-48 bottom-0 rounded-4xl ring-1 ring-black/5 ring-inset\" />\n      <Container className=\"relative\">\n        <div className=\"grid grid-cols-1 gap-8 lg:grid-cols-3\">\n          {tiers.map((tier, tierIndex) => {\n            // If user is logged in and subscriptions are sold out, show sold out message\n            if (isSoldOut) {\n              return <SoldOutPricingCard key={tierIndex} tier={tier} />;\n            }\n            \n            // Otherwise show normal pricing card\n            return (\n              <PricingCard \n                key={tierIndex} \n                tier={tier} \n                user={user} \n                profile={profile} \n              />\n            );\n          })}\n        </div>\n      </Container>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/stripe/payment-success.tsx",
    "content": "import { Button } from '@/components/button';\nimport { Link } from '@/components/link';\nimport { CheckIcon } from '@heroicons/react/24/outline';\n\ninterface PaymentSuccessProps {\n  className?: string;\n}\n\nexport function PaymentSuccess({ className }: PaymentSuccessProps) {\n  return (\n    <div className={`flex flex-col items-center justify-center text-center p-8 ${className}`}>\n      <div className=\"rounded-full bg-green-100 p-3 mb-4\">\n        <CheckIcon className=\"h-8 w-8 text-green-600\" />\n      </div>\n      \n      <h2 className=\"text-2xl font-bold mb-2\">Payment Successful!</h2>\n      \n      <p className=\"text-gray-600 mb-6 max-w-md\">\n        Thank you for your subscription. Your account has been upgraded and you now have access to all features.\n      </p>\n      \n      <div className=\"flex gap-4\">\n        <Button href=\"/dashboard\">\n          Go to Dashboard\n        </Button>\n        \n        <Button as=\"a\" href=\"mailto:support@example.com\" variant=\"outline\">\n          Need Help?\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/stripe/subscription-management.tsx",
    "content": "\"use client\";\nimport { Button } from '@/components/button';\nimport { useState } from 'react';\n\ninterface SubscriptionManagementProps {\n  customerId: string;\n  className?: string;\n}\n\nexport function SubscriptionManagement({\n  customerId,\n  className,\n}: SubscriptionManagementProps) {\n  const [isLoading, setIsLoading] = useState(false);\n\n  const handleManageSubscription = async () => {\n    try {\n      setIsLoading(true);\n      \n      const response = await fetch('/api/stripe/portal', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          customerId,\n          returnUrl: `${window.location.origin}/dashboard`,\n        }),\n      });\n\n      const { url } = await response.json();\n      \n      if (url) {\n        window.location.href = url;\n      } else {\n        throw new Error('Failed to create portal session');\n      }\n    } catch (error) {\n      console.error('Error opening customer portal:', error);\n      alert('Something went wrong. Please try again later.');\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className={className}>\n      <Button\n        onClick={handleManageSubscription}\n        disabled={isLoading}\n        variant=\"outline\"\n      >\n        {isLoading ? 'Loading...' : 'Manage Subscription'}\n      </Button>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/testimonials.tsx",
    "content": "'use client'\n\nimport * as Headless from '@headlessui/react'\nimport { ArrowLongRightIcon } from '@heroicons/react/20/solid'\nimport { clsx } from 'clsx'\nimport {\n  MotionValue,\n  motion,\n  useMotionValueEvent,\n  useScroll,\n  useSpring,\n  type HTMLMotionProps,\n} from 'framer-motion'\nimport { useCallback, useLayoutEffect, useRef, useState } from 'react'\nimport useMeasure, { type RectReadOnly } from 'react-use-measure'\nimport { Container } from './container'\nimport { Link } from './link'\nimport { Heading, Subheading } from './text'\n\nconst testimonials = [\n  {\n    img: '/testimonials/tina-yards.jpg',\n    name: 'Tina Yards',\n    title: 'VP of Sales, Protocol',\n    quote:\n      'Thanks to Radiant, we’re finding new leads that we never would have found with legal methods.',\n  },\n  {\n    img: '/testimonials/conor-neville.jpg',\n    name: 'Conor Neville',\n    title: 'Head of Customer Success, TaxPal',\n    quote:\n      'Radiant made undercutting all of our competitors an absolute breeze.',\n  },\n  {\n    img: '/testimonials/amy-chase.jpg',\n    name: 'Amy Chase',\n    title: 'Head of GTM, Pocket',\n    quote:\n      'We closed a deal in literally a few minutes because we knew their exact budget.',\n  },\n  {\n    img: '/testimonials/veronica-winton.jpg',\n    name: 'Veronica Winton',\n    title: 'CSO, Planeteria',\n    quote:\n      'We’ve managed to put two of our main competitors out of business in 6 months.',\n  },\n  {\n    img: '/testimonials/dillon-lenora.jpg',\n    name: 'Dillon Lenora',\n    title: 'VP of Sales, Detax',\n    quote: 'I was able to replace 80% of my team with RadiantAI bots.',\n  },\n  {\n    img: '/testimonials/harriet-arron.jpg',\n    name: 'Harriet Arron',\n    title: 'Account Manager, Commit',\n    quote:\n      'I’ve smashed all my targets without having to speak to a lead in months.',\n  },\n]\n\nfunction TestimonialCard({\n  name,\n  title,\n  img,\n  children,\n  bounds,\n  scrollX,\n  ...props\n}: {\n  img: string\n  name: string\n  title: string\n  children: React.ReactNode\n  bounds: RectReadOnly\n  scrollX: MotionValue<number>\n} & HTMLMotionProps<'div'>) {\n  const ref = useRef<HTMLDivElement | null>(null)\n\n  const computeOpacity = useCallback(() => {\n    const element = ref.current\n    if (!element || bounds.width === 0) return 1\n\n    const rect = element.getBoundingClientRect()\n\n    if (rect.left < bounds.left) {\n      const diff = bounds.left - rect.left\n      const percent = diff / rect.width\n      return Math.max(0.5, 1 - percent)\n    } else if (rect.right > bounds.right) {\n      const diff = rect.right - bounds.right\n      const percent = diff / rect.width\n      return Math.max(0.5, 1 - percent)\n    } else {\n      return 1\n    }\n  }, [ref, bounds.width, bounds.left, bounds.right])\n\n  const opacity = useSpring(computeOpacity(), {\n    stiffness: 154,\n    damping: 23,\n  })\n\n  useLayoutEffect(() => {\n    opacity.set(computeOpacity())\n  }, [computeOpacity, opacity])\n\n  useMotionValueEvent(scrollX, 'change', () => {\n    opacity.set(computeOpacity())\n  })\n\n  return (\n    <motion.div\n      ref={ref}\n      style={{ opacity }}\n      {...props}\n      className=\"relative flex aspect-9/16 w-72 shrink-0 snap-start scroll-ml-[var(--scroll-padding)] flex-col justify-end overflow-hidden rounded-3xl sm:aspect-3/4 sm:w-96\"\n    >\n      <img\n        alt=\"\"\n        src={img}\n        className=\"absolute inset-x-0 top-0 aspect-square w-full object-cover\"\n      />\n      <div\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 rounded-3xl bg-linear-to-t from-black from-[calc(7/16*100%)] ring-1 ring-gray-950/10 ring-inset sm:from-25%\"\n      />\n      <figure className=\"relative p-10\">\n        <blockquote>\n          <p className=\"relative text-xl/7 text-white\">\n            <span aria-hidden=\"true\" className=\"absolute -translate-x-full\">\n              “\n            </span>\n            {children}\n            <span aria-hidden=\"true\" className=\"absolute\">\n              ”\n            </span>\n          </p>\n        </blockquote>\n        <figcaption className=\"mt-6 border-t border-white/20 pt-6\">\n          <p className=\"text-sm/6 font-medium text-white\">{name}</p>\n          <p className=\"text-sm/6 font-medium\">\n            <span className=\"bg-linear-to-r from-[#fff1be] from-28% via-[#ee87cb] via-70% to-[#b060ff] bg-clip-text text-transparent\">\n              {title}\n            </span>\n          </p>\n        </figcaption>\n      </figure>\n    </motion.div>\n  )\n}\n\nfunction CallToAction() {\n  return (\n    <div>\n      <p className=\"max-w-sm text-sm/6 text-gray-600\">\n        Join the best sellers in the business and start using Radiant to hit\n        your targets today.\n      </p>\n      <div className=\"mt-2\">\n        <Link\n          href=\"#\"\n          className=\"inline-flex items-center gap-2 text-sm/6 font-medium text-pink-600\"\n        >\n          Get started\n          <ArrowLongRightIcon className=\"size-5\" />\n        </Link>\n      </div>\n    </div>\n  )\n}\n\nexport function Testimonials() {\n  const scrollRef = useRef<HTMLDivElement | null>(null)\n  const { scrollX } = useScroll({ container: scrollRef })\n  const [setReferenceWindowRef, bounds] = useMeasure()\n  const [activeIndex, setActiveIndex] = useState(0)\n\n  useMotionValueEvent(scrollX, 'change', (x) => {\n    setActiveIndex(Math.floor(x / scrollRef.current!.children[0].clientWidth))\n  })\n\n  function scrollTo(index: number) {\n    const gap = 32\n    const width = (scrollRef.current!.children[0] as HTMLElement).offsetWidth\n    scrollRef.current!.scrollTo({ left: (width + gap) * index })\n  }\n\n  return (\n    <div className=\"overflow-hidden py-32\">\n      <Container>\n        <div ref={setReferenceWindowRef}>\n          <Subheading>What everyone is saying</Subheading>\n          <Heading as=\"h3\" className=\"mt-2\">\n            Trusted by professionals.\n          </Heading>\n        </div>\n      </Container>\n      <div\n        ref={scrollRef}\n        className={clsx([\n          'mt-16 flex gap-8 px-[var(--scroll-padding)]',\n          '[scrollbar-width:none] [&::-webkit-scrollbar]:hidden',\n          'snap-x snap-mandatory overflow-x-auto overscroll-x-contain scroll-smooth',\n          '[--scroll-padding:max(--spacing(6),calc((100vw-(var(--container-2xl)))/2))] lg:[--scroll-padding:max(--spacing(8),calc((100vw-(var(--container-7xl)))/2))]',\n        ])}\n      >\n        {testimonials.map(({ img, name, title, quote }, testimonialIndex) => (\n          <TestimonialCard\n            key={testimonialIndex}\n            name={name}\n            title={title}\n            img={img}\n            bounds={bounds}\n            scrollX={scrollX}\n            onClick={() => scrollTo(testimonialIndex)}\n          >\n            {quote}\n          </TestimonialCard>\n        ))}\n        <div className=\"w-[42rem] shrink-0 sm:w-[54rem]\" />\n      </div>\n      <Container className=\"mt-16\">\n        <div className=\"flex justify-between\">\n          <CallToAction />\n          <div className=\"hidden sm:flex sm:gap-2\">\n            {testimonials.map(({ name }, testimonialIndex) => (\n              <Headless.Button\n                key={testimonialIndex}\n                onClick={() => scrollTo(testimonialIndex)}\n                data-active={\n                  activeIndex === testimonialIndex ? true : undefined\n                }\n                aria-label={`Scroll to testimonial from ${name}`}\n                className={clsx(\n                  'size-2.5 rounded-full border border-transparent bg-gray-300 transition',\n                  'data-active:bg-gray-400 data-hover:bg-gray-400',\n                  'forced-colors:data-active:bg-[Highlight] forced-colors:data-focus:outline-offset-4',\n                )}\n              />\n            ))}\n          </div>\n        </div>\n      </Container>\n    </div>\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/text.tsx",
    "content": "import { clsx } from 'clsx'\n\ntype HeadingProps = {\n  as?: 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'\n  dark?: boolean\n} & React.ComponentPropsWithoutRef<\n  'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'\n>\n\nexport function Heading({\n  className,\n  as: Element = 'h2',\n  dark = false,\n  ...props\n}: HeadingProps) {\n  return (\n    <Element\n      {...props}\n      data-dark={dark ? 'true' : undefined}\n      className={clsx(\n        className,\n        'text-4xl font-medium tracking-tighter text-pretty text-gray-950 data-dark:text-white sm:text-6xl',\n      )}\n    />\n  )\n}\n\nexport function Subheading({\n  className,\n  as: Element = 'h2',\n  dark = false,\n  ...props\n}: HeadingProps) {\n  return (\n    <Element\n      {...props}\n      data-dark={dark ? 'true' : undefined}\n      className={clsx(\n        className,\n        'font-mono text-xs/5 font-semibold tracking-widest text-gray-500 uppercase data-dark:text-gray-400',\n      )}\n    />\n  )\n}\n\nexport function Lead({\n  className,\n  ...props\n}: React.ComponentPropsWithoutRef<'p'>) {\n  return (\n    <p\n      className={clsx(className, 'text-2xl font-medium text-gray-500')}\n      {...props}\n    />\n  )\n}\n"
  },
  {
    "path": "apps/web/src/components/thread-list.tsx",
    "content": "import type { FC } from \"react\";\nimport {\n  ThreadListItemPrimitive,\n  ThreadListPrimitive,\n} from \"@assistant-ui/react\";\nimport { ArchiveIcon, PlusIcon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { TooltipIconButton } from \"@/components/tooltip-icon-button\";\n\nexport const ThreadList: FC = () => {\n  return (\n    <ThreadListPrimitive.Root className=\"flex flex-col items-stretch gap-1.5\">\n      <ThreadListNew />\n      <ThreadListItems />\n    </ThreadListPrimitive.Root>\n  );\n};\n\nconst ThreadListNew: FC = () => {\n  return (\n    <ThreadListPrimitive.New asChild>\n      <Button className=\"data-[active]:bg-muted hover:bg-muted flex items-center justify-start gap-1 rounded-lg px-2.5 py-2 text-start\" variant=\"ghost\">\n        <PlusIcon />\n        New Thread\n      </Button>\n    </ThreadListPrimitive.New>\n  );\n};\n\nconst ThreadListItems: FC = () => {\n  return <ThreadListPrimitive.Items components={{ ThreadListItem }} />;\n};\n\nconst ThreadListItem: FC = () => {\n  return (\n    <ThreadListItemPrimitive.Root className=\"data-[active]:bg-muted hover:bg-muted focus-visible:bg-muted focus-visible:ring-ring flex items-center gap-2 rounded-lg transition-all focus-visible:outline-none focus-visible:ring-2\">\n      <ThreadListItemPrimitive.Trigger className=\"flex-grow px-3 py-2 text-start\">\n        <ThreadListItemTitle />\n      </ThreadListItemPrimitive.Trigger>\n      <ThreadListItemArchive />\n    </ThreadListItemPrimitive.Root>\n  );\n};\n\nconst ThreadListItemTitle: FC = () => {\n  return (\n    <p className=\"text-sm\">\n      <ThreadListItemPrimitive.Title fallback=\"New Chat\" />\n    </p>\n  );\n};\n\nconst ThreadListItemArchive: FC = () => {\n  return (\n    <ThreadListItemPrimitive.Archive asChild>\n      <TooltipIconButton\n        className=\"hover:text-primary text-foreground ml-auto mr-3 size-4 p-0\"\n        variant=\"ghost\"\n        tooltip=\"Archive thread\"\n      >\n        <ArchiveIcon />\n      </TooltipIconButton>\n    </ThreadListItemPrimitive.Archive>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/thread.tsx",
    "content": "import {\n  ActionBarPrimitive,\n  BranchPickerPrimitive,\n  ComposerPrimitive,\n  MessagePrimitive,\n  ThreadPrimitive,\n} from \"@assistant-ui/react\";\nimport type { FC } from \"react\";\nimport {\n  ArrowDownIcon,\n  CheckIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  CopyIcon,\n  PencilIcon,\n  RefreshCwIcon,\n  SendHorizontalIcon,\n} from \"lucide-react\";\nimport { cn } from \"@/utils/misc-utils\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { MarkdownText } from \"@/components/markdown-text\";\nimport { TooltipIconButton } from \"@/components/tooltip-icon-button\";\n\nexport const Thread: FC = () => {\n  return (\n    <ThreadPrimitive.Root\n      className=\"bg-background flex h-full flex-col\"\n      style={{\n        [\"--thread-max-width\" as string]: \"100%\",\n      }}\n    >\n      <ThreadPrimitive.Viewport className=\"flex h-full flex-col items-center overflow-y-scroll scroll-smooth bg-inherit px-4 pt-8\">\n        <ThreadWelcome />\n\n        <ThreadPrimitive.Messages\n          components={{\n            UserMessage: UserMessage,\n            EditComposer: EditComposer,\n            AssistantMessage: AssistantMessage,\n          }}\n        />\n\n        <ThreadPrimitive.If empty={false}>\n          <div className=\"min-h-8 flex-grow\" />\n        </ThreadPrimitive.If>\n\n        <div className=\"sticky bottom-0 mt-3 flex w-full max-w-[var(--thread-max-width)] flex-col items-center justify-end rounded-t-lg bg-inherit pb-4\">\n          <ThreadScrollToBottom />\n          <Composer />\n        </div>\n      </ThreadPrimitive.Viewport>\n    </ThreadPrimitive.Root>\n  );\n};\n\nconst ThreadScrollToBottom: FC = () => {\n  return (\n    <ThreadPrimitive.ScrollToBottom asChild>\n      <TooltipIconButton\n        tooltip=\"Scroll to bottom\"\n        variant=\"outline\"\n        className=\"absolute -top-8 rounded-full disabled:invisible\"\n      >\n        <ArrowDownIcon />\n      </TooltipIconButton>\n    </ThreadPrimitive.ScrollToBottom>\n  );\n};\n\nconst ThreadWelcome: FC = () => {\n  return (\n    <ThreadPrimitive.Empty>\n      <div className=\"flex w-full max-w-[var(--thread-max-width)] flex-grow flex-col\">\n        <div className=\"flex w-full flex-grow flex-col items-center justify-center\">\n          <p className=\"mt-4 font-medium\">\n            What should I do on this virtual desktop?\n          </p>\n        </div>\n        <ThreadWelcomeSuggestions />\n      </div>\n    </ThreadPrimitive.Empty>\n  );\n};\n\nconst ThreadWelcomeSuggestions: FC = () => {\n  return (\n    <div className=\"mt-3 flex w-full items-stretch justify-center gap-4\">\n      <ThreadPrimitive.Suggestion\n        className=\"hover:bg-muted/80 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-lg border p-3 transition-colors ease-in\"\n        prompt=\"What is the weather in Tokyo?\"\n        method=\"replace\"\n        autoSend\n      >\n        <span className=\"line-clamp-2 text-ellipsis text-sm font-semibold\">\n          What is the weather in Tokyo?\n        </span>\n      </ThreadPrimitive.Suggestion>\n      <ThreadPrimitive.Suggestion\n        className=\"hover:bg-muted/80 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-lg border p-3 transition-colors ease-in\"\n        prompt=\"Find a cute cat for me on Google Images.\"\n        method=\"replace\"\n        autoSend\n      >\n        <span className=\"line-clamp-2 text-ellipsis text-sm font-semibold\">\n          Find a cute cat\n        </span>\n      </ThreadPrimitive.Suggestion>\n    </div>\n  );\n};\n\nconst Composer: FC = () => {\n  return (\n    <ComposerPrimitive.Root className=\"focus-within:border-ring/20 flex w-full flex-wrap items-end rounded-lg border bg-inherit px-2.5 shadow-sm transition-colors ease-in\">\n      <ComposerPrimitive.Input\n        rows={1}\n        autoFocus\n        placeholder=\"Write a message...\"\n        className=\"placeholder:text-muted-foreground max-h-40 flex-grow resize-none border-none bg-transparent px-2 py-4 text-sm outline-none focus:ring-0 disabled:cursor-not-allowed\"\n      />\n      <ComposerAction />\n    </ComposerPrimitive.Root>\n  );\n};\n\nconst ComposerAction: FC = () => {\n  return (\n    <>\n      <ThreadPrimitive.If running={false}>\n        <ComposerPrimitive.Send asChild>\n          <TooltipIconButton\n            tooltip=\"Send\"\n            variant=\"default\"\n            className=\"my-2.5 size-8 p-2 transition-opacity ease-in\"\n          >\n            <SendHorizontalIcon />\n          </TooltipIconButton>\n        </ComposerPrimitive.Send>\n      </ThreadPrimitive.If>\n      <ThreadPrimitive.If running>\n        <ComposerPrimitive.Cancel asChild>\n          <TooltipIconButton\n            tooltip=\"Cancel\"\n            variant=\"default\"\n            className=\"my-2.5 size-8 p-2 transition-opacity ease-in\"\n          >\n            <CircleStopIcon />\n          </TooltipIconButton>\n        </ComposerPrimitive.Cancel>\n      </ThreadPrimitive.If>\n    </>\n  );\n};\n\nconst UserMessage: FC = () => {\n  return (\n    <MessagePrimitive.Root className=\"grid auto-rows-auto grid-cols-[minmax(72px,1fr)_auto] gap-y-2 [&:where(>*)]:col-start-2 w-full max-w-[var(--thread-max-width)] py-4\">\n      <UserActionBar />\n\n      <div className=\"bg-muted text-foreground max-w-[calc(var(--thread-max-width)*0.8)] break-words rounded-3xl px-5 py-2.5 col-start-2 row-start-2\">\n        <MessagePrimitive.Content />\n      </div>\n\n      <BranchPicker className=\"col-span-full col-start-1 row-start-3 -mr-1 justify-end\" />\n    </MessagePrimitive.Root>\n  );\n};\n\nconst UserActionBar: FC = () => {\n  return (\n    <ActionBarPrimitive.Root\n      hideWhenRunning\n      autohide=\"not-last\"\n      className=\"flex flex-col items-end col-start-1 row-start-2 mr-3 mt-2.5\"\n    >\n      <ActionBarPrimitive.Edit asChild>\n        <TooltipIconButton tooltip=\"Edit\">\n          <PencilIcon />\n        </TooltipIconButton>\n      </ActionBarPrimitive.Edit>\n    </ActionBarPrimitive.Root>\n  );\n};\n\nconst EditComposer: FC = () => {\n  return (\n    <ComposerPrimitive.Root className=\"bg-muted my-4 flex w-full max-w-[var(--thread-max-width)] flex-col gap-2 rounded-xl\">\n      <ComposerPrimitive.Input className=\"text-foreground flex h-8 w-full resize-none bg-transparent p-4 pb-0 outline-none\" />\n\n      <div className=\"mx-3 mb-3 flex items-center justify-center gap-2 self-end\">\n        <ComposerPrimitive.Cancel asChild>\n          <Button variant=\"ghost\">Cancel</Button>\n        </ComposerPrimitive.Cancel>\n        <ComposerPrimitive.Send asChild>\n          <Button>Send</Button>\n        </ComposerPrimitive.Send>\n      </div>\n    </ComposerPrimitive.Root>\n  );\n};\n\nconst AssistantMessage: FC = () => {\n  return (\n    <MessagePrimitive.Root className=\"grid grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] relative w-full max-w-[var(--thread-max-width)] py-4\">\n      <div className=\"text-foreground max-w-[calc(var(--thread-max-width)*0.8)] break-words leading-7 col-span-2 col-start-2 row-start-1 my-1.5\">\n        <MessagePrimitive.Content components={{ Text: MarkdownText }} />\n      </div>\n\n      <AssistantActionBar />\n\n      <BranchPicker className=\"col-start-2 row-start-2 -ml-2 mr-2\" />\n    </MessagePrimitive.Root>\n  );\n};\n\nconst AssistantActionBar: FC = () => {\n  return (\n    <ActionBarPrimitive.Root\n      hideWhenRunning\n      autohide=\"not-last\"\n      autohideFloat=\"single-branch\"\n      className=\"text-muted-foreground flex gap-1 col-start-3 row-start-2 -ml-1 data-[floating]:bg-background data-[floating]:absolute data-[floating]:rounded-md data-[floating]:border data-[floating]:p-1 data-[floating]:shadow-sm\"\n    >\n      <ActionBarPrimitive.Copy asChild>\n        <TooltipIconButton tooltip=\"Copy\">\n          <MessagePrimitive.If copied>\n            <CheckIcon />\n          </MessagePrimitive.If>\n          <MessagePrimitive.If copied={false}>\n            <CopyIcon />\n          </MessagePrimitive.If>\n        </TooltipIconButton>\n      </ActionBarPrimitive.Copy>\n      <ActionBarPrimitive.Reload asChild>\n        <TooltipIconButton tooltip=\"Refresh\">\n          <RefreshCwIcon />\n        </TooltipIconButton>\n      </ActionBarPrimitive.Reload>\n    </ActionBarPrimitive.Root>\n  );\n};\n\nconst BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({\n  className,\n  ...rest\n}) => {\n  return (\n    <BranchPickerPrimitive.Root\n      hideWhenSingleBranch\n      className={cn(\"text-muted-foreground inline-flex items-center text-xs\", className)}\n      {...rest}\n    >\n      <BranchPickerPrimitive.Previous asChild>\n        <TooltipIconButton tooltip=\"Previous\">\n          <ChevronLeftIcon />\n        </TooltipIconButton>\n      </BranchPickerPrimitive.Previous>\n      <span className=\"font-medium\">\n        <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />\n      </span>\n      <BranchPickerPrimitive.Next asChild>\n        <TooltipIconButton tooltip=\"Next\">\n          <ChevronRightIcon />\n        </TooltipIconButton>\n      </BranchPickerPrimitive.Next>\n    </BranchPickerPrimitive.Root>\n  );\n};\n\nconst CircleStopIcon = () => {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      viewBox=\"0 0 16 16\"\n      fill=\"currentColor\"\n      width=\"16\"\n      height=\"16\"\n    >\n      <rect width=\"10\" height=\"10\" x=\"3\" y=\"3\" rx=\"2\" />\n    </svg>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/tooltip-icon-button.tsx",
    "content": "\"use client\";\n\nimport { type ComponentPropsWithoutRef, forwardRef } from \"react\";\n\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/utils/misc-utils\";\n\nexport type TooltipIconButtonProps = ComponentPropsWithoutRef<typeof Button> & {\n  tooltip: string;\n  side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n};\n\nexport const TooltipIconButton = forwardRef<\n  HTMLButtonElement,\n  TooltipIconButtonProps\n>(({ children, tooltip, side = \"bottom\", className, ...rest }, ref) => {\n  return (\n    <TooltipProvider>\n      <Tooltip>\n        <TooltipTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            {...rest}\n            className={cn(\"size-6 p-1\", className)}\n            ref={ref}\n          >\n            {children}\n            <span className=\"sr-only\">{tooltip}</span>\n          </Button>\n        </TooltipTrigger>\n        <TooltipContent side={side}>{tooltip}</TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  );\n});\n\nTooltipIconButton.displayName = \"TooltipIconButton\";\n"
  },
  {
    "path": "apps/web/src/components/ui/button.tsx",
    "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/utils/misc-utils\"\n\nconst buttonVariants = cva(\n  \"cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40\",\n        outline:\n          \"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground\",\n        secondary:\n          \"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n        pill: \"flex items-center justify-between px-2 rounded-lg py-1 bg-secondary text-sm hover:opacity-70 group transition-opacity duration-200 font-normal\"\n      },\n      size: {\n        default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n        sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n        lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n        icon: \"size-9\",\n        pill: \"h-8 text-sm\"\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Button({\n  className,\n  variant,\n  size,\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean\n  }) {\n  const Comp = asChild ? Slot : \"button\"\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "apps/web/src/components/ui/input.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/utils/misc-utils\"\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n  return (\n    <input\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        \"border-input file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n        \"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[2px]\",\n        \"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Input }"
  },
  {
    "path": "apps/web/src/components/ui/resizable.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { GripVerticalIcon } from \"lucide-react\"\nimport * as ResizablePrimitive from \"react-resizable-panels\"\n\nimport { cn } from \"@/utils/misc-utils\"\n\nfunction ResizablePanelGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {\n  return (\n    <ResizablePrimitive.PanelGroup\n      data-slot=\"resizable-panel-group\"\n      className={cn(\n        \"flex h-full w-full data-[panel-group-direction=vertical]:flex-col\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction ResizablePanel({\n  ...props\n}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {\n  return <ResizablePrimitive.Panel data-slot=\"resizable-panel\" {...props} />\n}\n\nfunction ResizableHandle({\n  withHandle,\n  className,\n  ...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {\n  withHandle?: boolean\n}) {\n  return (\n    <ResizablePrimitive.PanelResizeHandle\n      data-slot=\"resizable-handle\"\n      className={cn(\n        \"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90\",\n        className\n      )}\n      {...props}\n    >\n      {withHandle && (\n        <div className=\"bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border\">\n          <GripVerticalIcon className=\"size-2.5\" />\n        </div>\n      )}\n    </ResizablePrimitive.PanelResizeHandle>\n  )\n}\n\nexport { ResizablePanelGroup, ResizablePanel, ResizableHandle }\n"
  },
  {
    "path": "apps/web/src/components/ui/sonner.tsx",
    "content": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\"\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme()\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      toastOptions={{\n        classNames: {\n          toast:\n            \"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg\",\n          description: \"group-[.toast]:text-muted-foreground\",\n          actionButton:\n            \"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground font-medium\",\n          cancelButton:\n            \"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground font-medium\",\n        },\n      }}\n      {...props}\n    />\n  )\n}\n\nexport { Toaster }\n"
  },
  {
    "path": "apps/web/src/components/ui/tooltip.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } from \"@/utils/misc-utils\"\n\nfunction TooltipProvider({\n  delayDuration = 0,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n  return (\n    <TooltipPrimitive.Provider\n      data-slot=\"tooltip-provider\"\n      delayDuration={delayDuration}\n      {...props}\n    />\n  )\n}\n\nfunction Tooltip({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n  return (\n    <TooltipProvider>\n      <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n    </TooltipProvider>\n  )\n}\n\nfunction TooltipTrigger({\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />\n}\n\nfunction TooltipContent({\n  className,\n  sideOffset = 0,\n  children,\n  ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Content\n        data-slot=\"tooltip-content\"\n        sideOffset={sideOffset}\n        className={cn(\n          \"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance\",\n          className\n        )}\n        {...props}\n      >\n        {children}\n        <TooltipPrimitive.Arrow className=\"bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\" />\n      </TooltipPrimitive.Content>\n    </TooltipPrimitive.Portal>\n  )\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }\n"
  },
  {
    "path": "apps/web/src/components/yc-banner.tsx",
    "content": "import React from 'react'\n\nexport function YCBanner() {\n  return (\n    <div className=\"flex justify-center my-5\">\n      <a\n        href=\"https://www.ycombinator.com/\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        className=\"flex items-center gap-3 rounded-lg px-6 py-3 hover:bg-gray-50 transition-colors\"\n      >\n        <span className=\"text-xl font-medium text-gray-800\">Backed by</span>\n        <span className=\"flex items-center gap-1\">\n          <span className=\"inline-flex items-center justify-center bg-[#FF6600] px-3 py-2\">\n            <span className=\"text-xl font-semibold text-white leading-none\">Y</span>\n          </span>\n          <span className=\"text-xl font-medium text-gray-800 ml-1\">Combinator</span>\n        </span>\n      </a>\n    </div>\n  )\n} "
  },
  {
    "path": "apps/web/src/sanity/client.ts",
    "content": "import { createClient, type QueryParams } from 'next-sanity'\nimport { apiVersion, dataset, projectId } from './env'\n\nconst isDevelopment = process.env.NODE_ENV === 'development'\n\nexport const client = createClient({\n  projectId,\n  dataset,\n  apiVersion,\n  useCdn: isDevelopment ? false : true,\n})\n\nexport async function sanityFetch<const QueryString extends string>({\n  query,\n  params = {},\n  revalidate = 60,\n  tags = [],\n}: {\n  query: QueryString\n  params?: QueryParams\n  revalidate?: number | false\n  tags?: string[]\n}) {\n  return client.fetch(query, params, {\n    next: {\n      revalidate: isDevelopment || tags.length ? false : revalidate,\n      tags,\n    },\n  })\n}\n"
  },
  {
    "path": "apps/web/src/sanity/env.ts",
    "content": "export const apiVersion =\n  process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2024-07-25'\n\nexport const dataset = assertValue(\n  process.env.NEXT_PUBLIC_SANITY_DATASET,\n  'Missing environment variable: NEXT_PUBLIC_SANITY_DATASET',\n)\n\nexport const projectId = assertValue(\n  process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,\n  'Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_ID',\n)\n\nfunction assertValue<T>(v: T | undefined, errorMessage: string): T {\n  if (v === undefined) {\n    throw new Error(errorMessage)\n  }\n\n  return v\n}\n"
  },
  {
    "path": "apps/web/src/sanity/image.ts",
    "content": "import createImageUrlBuilder from '@sanity/image-url'\nimport type { SanityImageSource } from '@sanity/image-url/lib/types/types'\nimport { dataset, projectId } from './env'\n\nconst builder = createImageUrlBuilder({ projectId, dataset })\n\nexport function image(source: SanityImageSource) {\n  return builder.image(source).auto('format')\n}\n"
  },
  {
    "path": "apps/web/src/sanity/queries.ts",
    "content": "import { defineQuery } from 'next-sanity'\nimport { sanityFetch } from './client'\n\nconst TOTAL_POSTS_QUERY = defineQuery(/* groq */ `count(*[\n  _type == \"post\"\n  && defined(slug.current)\n  && (isFeatured != true || defined($category))\n  && select(defined($category) => $category in categories[]->slug.current, true)\n])`)\n\nexport async function getPostsCount(category?: string) {\n  return await sanityFetch({\n    query: TOTAL_POSTS_QUERY,\n    params: { category: category ?? null },\n  })\n}\n\nconst POSTS_QUERY = defineQuery(/* groq */ `*[\n  _type == \"post\"\n  && defined(slug.current)\n  && (isFeatured != true || defined($category))\n  && select(defined($category) => $category in categories[]->slug.current, true)\n]|order(publishedAt desc)[$startIndex...$endIndex]{\n  title,\n  \"slug\": slug.current,\n  publishedAt,\n  excerpt,\n  author->{\n    name,\n    image,\n  },\n}`)\n\nexport async function getPosts(\n  startIndex: number,\n  endIndex: number,\n  category?: string,\n) {\n  return await sanityFetch({\n    query: POSTS_QUERY,\n    params: {\n      startIndex,\n      endIndex,\n      category: category ?? null,\n    },\n  })\n}\n\nconst FEATURED_POSTS_QUERY = defineQuery(/* groq */ `*[\n  _type == \"post\"\n  && isFeatured == true\n  && defined(slug.current)\n]|order(publishedAt desc)[0...$quantity]{\n  title,\n  \"slug\": slug.current,\n  publishedAt,\n  mainImage,\n  excerpt,\n  author->{\n    name,\n    image,\n  },\n}`)\n\nexport async function getFeaturedPosts(quantity: number) {\n  return await sanityFetch({\n    query: FEATURED_POSTS_QUERY,\n    params: { quantity },\n  })\n}\n\nconst FEED_POSTS_QUERY = defineQuery(/* groq */ `*[\n  _type == \"post\"\n  && defined(slug.current)\n]|order(isFeatured, publishedAt desc){\n  title,\n  \"slug\": slug.current,\n  publishedAt,\n  mainImage,\n  excerpt,\n  author->{\n    name,\n  },\n}`)\n\nexport async function getPostsForFeed() {\n  return await sanityFetch({\n    query: FEED_POSTS_QUERY,\n  })\n}\n\nconst POST_QUERY = defineQuery(/* groq */ `*[\n  _type == \"post\"\n  && slug.current == $slug\n][0]{\n  publishedAt,\n  title,\n  mainImage,\n  excerpt,\n  body,\n  author->{\n    name,\n    image,\n  },\n  categories[]->{\n    title,\n    \"slug\": slug.current,\n  }\n}\n`)\n\nexport async function getPost(slug: string) {\n  return await sanityFetch({\n    query: POST_QUERY,\n    params: { slug },\n  })\n}\n\nconst CATEGORIES_QUERY = defineQuery(/* groq */ `*[\n  _type == \"category\"\n  && count(*[_type == \"post\" && defined(slug.current) && ^._id in categories[]._ref]) > 0\n]|order(title asc){\n  title,\n  \"slug\": slug.current,\n}`)\n\nexport async function getCategories() {\n  return await sanityFetch({\n    query: CATEGORIES_QUERY,\n  })\n}\n"
  },
  {
    "path": "apps/web/src/sanity/schema.ts",
    "content": "import type { SchemaTypeDefinition } from 'sanity'\n\nimport { authorType } from './types/author'\nimport { blockContentType } from './types/block-content'\nimport { categoryType } from './types/category'\nimport { postType } from './types/post'\n\nexport const schema: { types: SchemaTypeDefinition[] } = {\n  types: [blockContentType, categoryType, postType, authorType],\n}\n"
  },
  {
    "path": "apps/web/src/sanity/types/author.ts",
    "content": "import { UserIcon } from '@heroicons/react/16/solid'\nimport { defineField, defineType } from 'sanity'\n\nexport const authorType = defineType({\n  name: 'author',\n  title: 'Author',\n  type: 'document',\n  icon: UserIcon,\n  fields: [\n    defineField({\n      name: 'name',\n      type: 'string',\n    }),\n    defineField({\n      name: 'slug',\n      type: 'slug',\n      options: {\n        source: 'name',\n        maxLength: 96,\n      },\n    }),\n    defineField({\n      name: 'image',\n      type: 'image',\n      options: {\n        hotspot: true,\n      },\n    }),\n  ],\n  preview: {\n    select: {\n      title: 'name',\n      media: 'image',\n    },\n  },\n})\n"
  },
  {
    "path": "apps/web/src/sanity/types/block-content.ts",
    "content": "import { ImageIcon } from '@sanity/icons'\nimport { defineArrayMember, defineType } from 'sanity'\n\nexport const blockContentType = defineType({\n  title: 'Block Content',\n  name: 'blockContent',\n  type: 'array',\n  of: [\n    defineArrayMember({\n      type: 'block',\n      styles: [\n        { title: 'Normal', value: 'normal' },\n        { title: 'H2', value: 'h2' },\n        { title: 'H3', value: 'h3' },\n        { title: 'Quote', value: 'blockquote' },\n      ],\n      marks: {\n        decorators: [\n          { title: 'Strong', value: 'strong' },\n          { title: 'Emphasis', value: 'em' },\n          { title: 'Code', value: 'code' },\n        ],\n        annotations: [\n          {\n            title: 'URL',\n            name: 'link',\n            type: 'object',\n            fields: [\n              {\n                title: 'URL',\n                name: 'href',\n                type: 'url',\n              },\n            ],\n          },\n        ],\n      },\n    }),\n    defineArrayMember({\n      title: 'Separator',\n      name: 'separator',\n      type: 'object',\n      fields: [\n        {\n          name: 'style',\n          title: 'Style',\n          type: 'string',\n          options: {\n            list: [\n              { title: 'Line', value: 'line' },\n              { title: 'Space', value: 'space' },\n            ],\n          },\n        },\n      ],\n    }),\n    defineArrayMember({\n      type: 'image',\n      icon: ImageIcon,\n      options: { hotspot: true },\n      fields: [\n        {\n          name: 'alt',\n          type: 'string',\n          title: 'Alternative Text',\n        },\n      ],\n    }),\n  ],\n})\n"
  },
  {
    "path": "apps/web/src/sanity/types/category.ts",
    "content": "import { TagIcon } from '@heroicons/react/16/solid'\nimport { defineField, defineType } from 'sanity'\n\nexport const categoryType = defineType({\n  name: 'category',\n  type: 'document',\n  icon: TagIcon,\n  fields: [\n    defineField({\n      name: 'title',\n      type: 'string',\n    }),\n    defineField({\n      name: 'slug',\n      type: 'slug',\n      options: {\n        source: 'title',\n      },\n    }),\n  ],\n})\n"
  },
  {
    "path": "apps/web/src/sanity/types/post.ts",
    "content": "import { DocumentIcon } from '@heroicons/react/16/solid'\nimport { groq } from 'next-sanity'\nimport { defineField, defineType } from 'sanity'\nimport { apiVersion } from '../env'\n\nexport const postType = defineType({\n  name: 'post',\n  title: 'Post',\n  type: 'document',\n  icon: DocumentIcon,\n  fields: [\n    defineField({\n      name: 'title',\n      type: 'string',\n      validation: (Rule) => Rule.required(),\n    }),\n    defineField({\n      name: 'slug',\n      type: 'slug',\n      options: {\n        source: 'title',\n      },\n      validation: (Rule) =>\n        Rule.required().error('A slug is required for the post URL.'),\n    }),\n    defineField({\n      name: 'publishedAt',\n      type: 'datetime',\n      validation: (Rule) =>\n        Rule.required().error(\n          'A publication date is required for ordering posts.',\n        ),\n    }),\n    defineField({\n      name: 'isFeatured',\n      type: 'boolean',\n      initialValue: false,\n      validation: (Rule) =>\n        Rule.custom(async (isFeatured, { getClient }) => {\n          if (isFeatured !== true) {\n            return true\n          }\n\n          const featuredPosts = await getClient({ apiVersion })\n            .withConfig({ perspective: 'previewDrafts' })\n            .fetch<number>(\n              groq`count(*[_type == 'post' && isFeatured == true])`,\n            )\n\n          return featuredPosts > 3\n            ? 'Only 3 posts can be featured at a time.'\n            : true\n        }),\n    }),\n    defineField({\n      name: 'author',\n      type: 'reference',\n      to: { type: 'author' },\n    }),\n    defineField({\n      name: 'mainImage',\n      type: 'image',\n      options: {\n        hotspot: true,\n      },\n      fields: [\n        {\n          name: 'alt',\n          type: 'string',\n          title: 'Alternative text',\n        },\n      ],\n    }),\n    defineField({\n      name: 'categories',\n      type: 'array',\n      of: [{ type: 'reference', to: { type: 'category' } }],\n    }),\n    defineField({\n      name: 'excerpt',\n      type: 'text',\n      rows: 3,\n    }),\n    defineField({\n      name: 'body',\n      type: 'blockContent',\n    }),\n  ],\n  preview: {\n    select: {\n      title: 'title',\n      media: 'mainImage',\n      author: 'author.name',\n      isFeatured: 'isFeatured',\n    },\n    prepare({ title, author, media, isFeatured }) {\n      return {\n        title,\n        subtitle: [isFeatured && 'Featured', author && `By ${author}`]\n          .filter(Boolean)\n          .join(' | '),\n        media,\n      }\n    },\n  },\n  orderings: [\n    {\n      name: 'isFeaturedAndPublishedAtDesc',\n      title: 'Featured & Latest Published',\n      by: [\n        { field: 'isFeatured', direction: 'desc' },\n        { field: 'publishedAt', direction: 'desc' },\n      ],\n    },\n  ],\n})\n"
  },
  {
    "path": "apps/web/src/sanity/types.ts",
    "content": "/**\n * ---------------------------------------------------------------------------------\n * This file has been generated by Sanity TypeGen.\n * Command: `sanity typegen generate`\n *\n * Any modifications made directly to this file will be overwritten the next time\n * the TypeScript definitions are generated. Please make changes to the Sanity\n * schema definitions and/or GROQ queries if you need to update these types.\n *\n * For more information on how to use Sanity TypeGen, visit the official documentation:\n * https://www.sanity.io/docs/sanity-typegen\n * ---------------------------------------------------------------------------------\n */\n\n// Source: schema.json\nexport type SanityImagePaletteSwatch = {\n  _type: 'sanity.imagePaletteSwatch'\n  background?: string\n  foreground?: string\n  population?: number\n  title?: string\n}\n\nexport type SanityImagePalette = {\n  _type: 'sanity.imagePalette'\n  darkMuted?: SanityImagePaletteSwatch\n  lightVibrant?: SanityImagePaletteSwatch\n  darkVibrant?: SanityImagePaletteSwatch\n  vibrant?: SanityImagePaletteSwatch\n  dominant?: SanityImagePaletteSwatch\n  lightMuted?: SanityImagePaletteSwatch\n  muted?: SanityImagePaletteSwatch\n}\n\nexport type SanityImageDimensions = {\n  _type: 'sanity.imageDimensions'\n  height?: number\n  width?: number\n  aspectRatio?: number\n}\n\nexport type SanityFileAsset = {\n  _id: string\n  _type: 'sanity.fileAsset'\n  _createdAt: string\n  _updatedAt: string\n  _rev: string\n  originalFilename?: string\n  label?: string\n  title?: string\n  description?: string\n  altText?: string\n  sha1hash?: string\n  extension?: string\n  mimeType?: string\n  size?: number\n  assetId?: string\n  uploadId?: string\n  path?: string\n  url?: string\n  source?: SanityAssetSourceData\n}\n\nexport type Geopoint = {\n  _type: 'geopoint'\n  lat?: number\n  lng?: number\n  alt?: number\n}\n\nexport type Post = {\n  _id: string\n  _type: 'post'\n  _createdAt: string\n  _updatedAt: string\n  _rev: string\n  title?: string\n  slug?: Slug\n  publishedAt?: string\n  isFeatured?: boolean\n  author?: {\n    _ref: string\n    _type: 'reference'\n    _weak?: boolean\n    [internalGroqTypeReferenceTo]?: 'author'\n  }\n  mainImage?: {\n    asset?: {\n      _ref: string\n      _type: 'reference'\n      _weak?: boolean\n      [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n    }\n    hotspot?: SanityImageHotspot\n    crop?: SanityImageCrop\n    alt?: string\n    _type: 'image'\n  }\n  categories?: Array<{\n    _ref: string\n    _type: 'reference'\n    _weak?: boolean\n    _key: string\n    [internalGroqTypeReferenceTo]?: 'category'\n  }>\n  excerpt?: string\n  body?: Array<\n    | {\n        children?: Array<{\n          marks?: Array<string>\n          text?: string\n          _type: 'span'\n          _key: string\n        }>\n        style?: 'normal' | 'h2' | 'h3' | 'blockquote'\n        listItem?: 'bullet' | 'number'\n        markDefs?: Array<{\n          href?: string\n          _type: 'link'\n          _key: string\n        }>\n        level?: number\n        _type: 'block'\n        _key: string\n      }\n    | {\n        asset?: {\n          _ref: string\n          _type: 'reference'\n          _weak?: boolean\n          [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n        }\n        hotspot?: SanityImageHotspot\n        crop?: SanityImageCrop\n        alt?: string\n        _type: 'image'\n        _key: string\n      }\n  >\n}\n\nexport type Author = {\n  _id: string\n  _type: 'author'\n  _createdAt: string\n  _updatedAt: string\n  _rev: string\n  name?: string\n  slug?: Slug\n  image?: {\n    asset?: {\n      _ref: string\n      _type: 'reference'\n      _weak?: boolean\n      [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n    }\n    hotspot?: SanityImageHotspot\n    crop?: SanityImageCrop\n    _type: 'image'\n  }\n}\n\nexport type Category = {\n  _id: string\n  _type: 'category'\n  _createdAt: string\n  _updatedAt: string\n  _rev: string\n  title?: string\n  slug?: Slug\n}\n\nexport type Slug = {\n  _type: 'slug'\n  current?: string\n  source?: string\n}\n\nexport type BlockContent = Array<\n  | {\n      children?: Array<{\n        marks?: Array<string>\n        text?: string\n        _type: 'span'\n        _key: string\n      }>\n      style?: 'normal' | 'h2' | 'h3' | 'blockquote'\n      listItem?: 'bullet' | 'number'\n      markDefs?: Array<{\n        href?: string\n        _type: 'link'\n        _key: string\n      }>\n      level?: number\n      _type: 'block'\n      _key: string\n    }\n  | {\n      asset?: {\n        _ref: string\n        _type: 'reference'\n        _weak?: boolean\n        [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n      }\n      hotspot?: SanityImageHotspot\n      crop?: SanityImageCrop\n      alt?: string\n      _type: 'image'\n      _key: string\n    }\n>\n\nexport type SanityImageCrop = {\n  _type: 'sanity.imageCrop'\n  top?: number\n  bottom?: number\n  left?: number\n  right?: number\n}\n\nexport type SanityImageHotspot = {\n  _type: 'sanity.imageHotspot'\n  x?: number\n  y?: number\n  height?: number\n  width?: number\n}\n\nexport type SanityImageAsset = {\n  _id: string\n  _type: 'sanity.imageAsset'\n  _createdAt: string\n  _updatedAt: string\n  _rev: string\n  originalFilename?: string\n  label?: string\n  title?: string\n  description?: string\n  altText?: string\n  sha1hash?: string\n  extension?: string\n  mimeType?: string\n  size?: number\n  assetId?: string\n  uploadId?: string\n  path?: string\n  url?: string\n  metadata?: SanityImageMetadata\n  source?: SanityAssetSourceData\n}\n\nexport type SanityAssetSourceData = {\n  _type: 'sanity.assetSourceData'\n  name?: string\n  id?: string\n  url?: string\n}\n\nexport type SanityImageMetadata = {\n  _type: 'sanity.imageMetadata'\n  location?: Geopoint\n  dimensions?: SanityImageDimensions\n  palette?: SanityImagePalette\n  lqip?: string\n  blurHash?: string\n  hasAlpha?: boolean\n  isOpaque?: boolean\n}\n\nexport type AllSanitySchemaTypes =\n  | SanityImagePaletteSwatch\n  | SanityImagePalette\n  | SanityImageDimensions\n  | SanityFileAsset\n  | Geopoint\n  | Post\n  | Author\n  | Category\n  | Slug\n  | BlockContent\n  | SanityImageCrop\n  | SanityImageHotspot\n  | SanityImageAsset\n  | SanityAssetSourceData\n  | SanityImageMetadata\nexport declare const internalGroqTypeReferenceTo: unique symbol\n// Source: ./src/sanity/queries.ts\n// Variable: TOTAL_POSTS_QUERY\n// Query: count(*[  _type == \"post\"  && defined(slug.current)  && (isFeatured != true || defined($category))  && select(defined($category) => $category in categories[]->slug.current, true)])\nexport type TOTAL_POSTS_QUERYResult = number\n// Variable: POSTS_QUERY\n// Query: *[  _type == \"post\"  && defined(slug.current)  && (isFeatured != true || defined($category))  && select(defined($category) => $category in categories[]->slug.current, true)]|order(publishedAt desc)[$startIndex...$endIndex]{  title,  \"slug\": slug.current,  publishedAt,  excerpt,  author->{    name,    image,  },}\nexport type POSTS_QUERYResult = Array<{\n  title: string | null\n  slug: string | null\n  publishedAt: string | null\n  excerpt: string | null\n  author: {\n    name: string | null\n    image: {\n      asset?: {\n        _ref: string\n        _type: 'reference'\n        _weak?: boolean\n        [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n      }\n      hotspot?: SanityImageHotspot\n      crop?: SanityImageCrop\n      _type: 'image'\n    } | null\n  } | null\n}>\n// Variable: FEATURED_POSTS_QUERY\n// Query: *[  _type == \"post\"  && isFeatured == true  && defined(slug.current)]|order(publishedAt desc)[0...$quantity]{  title,  \"slug\": slug.current,  publishedAt,  mainImage,  excerpt,  author->{    name,    image,  },}\nexport type FEATURED_POSTS_QUERYResult = Array<{\n  title: string | null\n  slug: string | null\n  publishedAt: string | null\n  mainImage: {\n    asset?: {\n      _ref: string\n      _type: 'reference'\n      _weak?: boolean\n      [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n    }\n    hotspot?: SanityImageHotspot\n    crop?: SanityImageCrop\n    alt?: string\n    _type: 'image'\n  } | null\n  excerpt: string | null\n  author: {\n    name: string | null\n    image: {\n      asset?: {\n        _ref: string\n        _type: 'reference'\n        _weak?: boolean\n        [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n      }\n      hotspot?: SanityImageHotspot\n      crop?: SanityImageCrop\n      _type: 'image'\n    } | null\n  } | null\n}>\n// Variable: FEED_POSTS_QUERY\n// Query: *[  _type == \"post\"  && defined(slug.current)]|order(isFeatured, publishedAt desc){  title,  \"slug\": slug.current,  publishedAt,  mainImage,  excerpt,  author->{    name,  },}\nexport type FEED_POSTS_QUERYResult = Array<{\n  title: string | null\n  slug: string | null\n  publishedAt: string | null\n  mainImage: {\n    asset?: {\n      _ref: string\n      _type: 'reference'\n      _weak?: boolean\n      [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n    }\n    hotspot?: SanityImageHotspot\n    crop?: SanityImageCrop\n    alt?: string\n    _type: 'image'\n  } | null\n  excerpt: string | null\n  author: {\n    name: string | null\n  } | null\n}>\n// Variable: POST_QUERY\n// Query: *[  _type == \"post\"  && slug.current == $slug][0]{  publishedAt,  title,  mainImage,  excerpt,  body,  author->{    name,    image,  },  categories[]->{    title,    \"slug\": slug.current,  }}\nexport type POST_QUERYResult = {\n  publishedAt: string | null\n  title: string | null\n  mainImage: {\n    asset?: {\n      _ref: string\n      _type: 'reference'\n      _weak?: boolean\n      [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n    }\n    hotspot?: SanityImageHotspot\n    crop?: SanityImageCrop\n    alt?: string\n    _type: 'image'\n  } | null\n  excerpt: string | null\n  body: Array<\n    | {\n        children?: Array<{\n          marks?: Array<string>\n          text?: string\n          _type: 'span'\n          _key: string\n        }>\n        style?: 'blockquote' | 'h2' | 'h3' | 'normal'\n        listItem?: 'bullet' | 'number'\n        markDefs?: Array<{\n          href?: string\n          _type: 'link'\n          _key: string\n        }>\n        level?: number\n        _type: 'block'\n        _key: string\n      }\n    | {\n        asset?: {\n          _ref: string\n          _type: 'reference'\n          _weak?: boolean\n          [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n        }\n        hotspot?: SanityImageHotspot\n        crop?: SanityImageCrop\n        alt?: string\n        _type: 'image'\n        _key: string\n      }\n  > | null\n  author: {\n    name: string | null\n    image: {\n      asset?: {\n        _ref: string\n        _type: 'reference'\n        _weak?: boolean\n        [internalGroqTypeReferenceTo]?: 'sanity.imageAsset'\n      }\n      hotspot?: SanityImageHotspot\n      crop?: SanityImageCrop\n      _type: 'image'\n    } | null\n  } | null\n  categories: Array<{\n    title: string | null\n    slug: string | null\n  }> | null\n} | null\n// Variable: CATEGORIES_QUERY\n// Query: *[  _type == \"category\"  && count(*[_type == \"post\" && defined(slug.current) && ^._id in categories[]._ref]) > 0]|order(title asc){  title,  \"slug\": slug.current,}\nexport type CATEGORIES_QUERYResult = Array<{\n  title: string | null\n  slug: string | null\n}>\n\n// Query TypeMap\nimport '@sanity/client'\ndeclare module '@sanity/client' {\n  interface SanityQueries {\n    'count(*[\\n  _type == \"post\"\\n  && defined(slug.current)\\n  && (isFeatured != true || defined($category))\\n  && select(defined($category) => $category in categories[]->slug.current, true)\\n])': TOTAL_POSTS_QUERYResult\n    '*[\\n  _type == \"post\"\\n  && defined(slug.current)\\n  && (isFeatured != true || defined($category))\\n  && select(defined($category) => $category in categories[]->slug.current, true)\\n]|order(publishedAt desc)[$startIndex...$endIndex]{\\n  title,\\n  \"slug\": slug.current,\\n  publishedAt,\\n  excerpt,\\n  author->{\\n    name,\\n    image,\\n  },\\n}': POSTS_QUERYResult\n    '*[\\n  _type == \"post\"\\n  && isFeatured == true\\n  && defined(slug.current)\\n]|order(publishedAt desc)[0...$quantity]{\\n  title,\\n  \"slug\": slug.current,\\n  publishedAt,\\n  mainImage,\\n  excerpt,\\n  author->{\\n    name,\\n    image,\\n  },\\n}': FEATURED_POSTS_QUERYResult\n    '*[\\n  _type == \"post\"\\n  && defined(slug.current)\\n]|order(isFeatured, publishedAt desc){\\n  title,\\n  \"slug\": slug.current,\\n  publishedAt,\\n  mainImage,\\n  excerpt,\\n  author->{\\n    name,\\n  },\\n}': FEED_POSTS_QUERYResult\n    '*[\\n  _type == \"post\"\\n  && slug.current == $slug\\n][0]{\\n  publishedAt,\\n  title,\\n  mainImage,\\n  excerpt,\\n  body,\\n  author->{\\n    name,\\n    image,\\n  },\\n  categories[]->{\\n    title,\\n    \"slug\": slug.current,\\n  }\\n}\\n': POST_QUERYResult\n    '*[\\n  _type == \"category\"\\n  && count(*[_type == \"post\" && defined(slug.current) && ^._id in categories[]._ref]) > 0\\n]|order(title asc){\\n  title,\\n  \"slug\": slug.current,\\n}': CATEGORIES_QUERYResult\n  }\n}\n"
  },
  {
    "path": "apps/web/src/styles/tailwind.css",
    "content": "@import 'tailwindcss';\n@import \"tw-animate-css\";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme {\n  --font-sans: Switzer, system-ui, sans-serif;\n\n  --radius-4xl: 2rem;\n}\n\n@keyframes move-x {\n  0% {\n    transform: translateX(var(--move-x-from));\n  }\n  100% {\n    transform: translateX(var(--move-x-to));\n  }\n}\n\n:root {\n  --radius: 0.625rem;\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.145 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.145 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.145 0 0);\n  --primary: oklch(0.205 0 0);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.97 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.97 0 0);\n  --muted-foreground: oklch(0.556 0 0);\n  --accent: oklch(0.97 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.577 0.245 27.325);\n  --border: oklch(0.922 0 0);\n  --input: oklch(0.922 0 0);\n  --ring: oklch(0.708 0 0);\n  --chart-1: oklch(0.646 0.222 41.116);\n  --chart-2: oklch(0.6 0.118 184.704);\n  --chart-3: oklch(0.398 0.07 227.392);\n  --chart-4: oklch(0.828 0.189 84.429);\n  --chart-5: oklch(0.769 0.188 70.08);\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.145 0 0);\n  --sidebar-primary: oklch(0.205 0 0);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.97 0 0);\n  --sidebar-accent-foreground: oklch(0.205 0 0);\n  --sidebar-border: oklch(0.922 0 0);\n  --sidebar-ring: oklch(0.708 0 0);\n}\n\n.dark {\n  --background: oklch(0.145 0 0);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.205 0 0);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.205 0 0);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.922 0 0);\n  --primary-foreground: oklch(0.205 0 0);\n  --secondary: oklch(0.269 0 0);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.269 0 0);\n  --muted-foreground: oklch(0.708 0 0);\n  --accent: oklch(0.269 0 0);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.556 0 0);\n  --chart-1: oklch(0.488 0.243 264.376);\n  --chart-2: oklch(0.696 0.17 162.48);\n  --chart-3: oklch(0.769 0.188 70.08);\n  --chart-4: oklch(0.627 0.265 303.9);\n  --chart-5: oklch(0.645 0.246 16.439);\n  --sidebar: oklch(0.205 0 0);\n  --sidebar-foreground: oklch(0.985 0 0);\n  --sidebar-primary: oklch(0.488 0.243 264.376);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.269 0 0);\n  --sidebar-accent-foreground: oklch(0.985 0 0);\n  --sidebar-border: oklch(1 0 0 / 10%);\n  --sidebar-ring: oklch(0.556 0 0);\n}\n\n@theme inline {\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --color-chart-1: var(--chart-1);\n  --color-chart-2: var(--chart-2);\n  --color-chart-3: var(--chart-3);\n  --color-chart-4: var(--chart-4);\n  --color-chart-5: var(--chart-5);\n  --color-sidebar: var(--sidebar);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-ring: var(--sidebar-ring);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n\n@layer utilities {\n  .b {\n    @apply border-2 border-red-500;\n  }\n}\n"
  },
  {
    "path": "apps/web/src/types/database.ts",
    "content": "export interface Profile {\n  id: string;\n  unkey_key_id?: string;\n  stripe_customer_id?: string;\n  stripe_subscription_id?: string;\n  current_period_end?: Date;\n  subscription_status?: string;\n  plan_id?: string;\n  cancel_at_period_end?: boolean;\n  created_at?: Date;\n  updated_at?: Date;\n}\n\n// This interface matches the backend schema for cyberdesk_instances (see apps/api/src/db/schema.ts)\nexport interface CyberdeskInstance {\n  id: string;\n  user_id: string;\n  created_at: string; // ISO string from DB\n  updated_at?: string | null; // nullable\n  status: 'pending' | 'running' | 'terminated' | 'error';\n  timeout_at: string; // ISO string from DB\n  stream_url?: string | null;\n}"
  },
  {
    "path": "apps/web/src/utils/misc-utils.ts",
    "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"
  },
  {
    "path": "apps/web/src/utils/playground/cyberdesk-client.ts",
    "content": "import { createCyberdeskClient } from \"cyberdesk\";\n\nconst client = createCyberdeskClient({\n    apiKey: process.env.CYBERDESK_API_KEY || '',\n    baseUrl: process.env.CYBERDESK_API_BASE_URL || 'https://api.cyberdesk.io'\n});\n\nexport default client;"
  },
  {
    "path": "apps/web/src/utils/playground/misc-demo-utils.ts",
    "content": "import type { UIMessage } from \"ai\";\nexport const ABORTED = \"User aborted\";\n\nexport const prunedMessages = (messages: UIMessage[]): UIMessage[] => {\n  if (messages.at(-1)?.role === \"assistant\") {\n    return messages;\n  }\n\n  return messages.map((message) => {\n    // check if last message part is a tool invocation in a call state, then append a part with the tool result\n    message.parts = message.parts.map((part) => {\n      if (part.type === \"tool-invocation\") {\n        if (\n          part.toolInvocation.toolName === \"computer\" &&\n          part.toolInvocation.args.action === \"screenshot\"\n        ) {\n          return {\n            ...part,\n            toolInvocation: {\n              ...part.toolInvocation,\n              result: {\n                type: \"text\",\n                text: \"Image redacted to save input tokens\",\n              },\n            },\n          };\n        }\n        return part;\n      }\n      return part;\n    });\n    return message;\n  });\n};\n"
  },
  {
    "path": "apps/web/src/utils/playground/server-actions.ts",
    "content": "\"use server\";\n\nimport client from \"@/utils/playground/cyberdesk-client\";\n\nexport const getDesktopURL = async (id?: string) => {\n  if (!id) throw new Error(\"Sandbox ID required for getDesktopURL\");\n  try {\n    const response = await client.getDesktop({\n      path: {\n        id,\n      },\n    });\n    \n    const streamUrl = response.data?.stream_url;\n\n    return { streamUrl, id };\n  } catch (error) {\n    console.error(\"Error in getDesktopURL:\", error);\n    throw error;\n  }\n};\n\nexport const startDesktop = async () => {\n  try {\n    const response = await client.launchDesktop({\n      body: {\n        timeout_ms: 86400000,\n      }\n    });\n    if (!response.data || !response.data.id) {\n      throw new Error(\"Failed to start desktop: No ID returned from API\");\n    }\n    return response.data;\n  } catch (error) {\n    console.error(\"Error in startDesktop:\", error);\n    throw error;\n  }\n};\n\nexport const killDesktop = async (id?: string) => {\n  if (!id) throw new Error(\"Sandbox ID required for killDesktop\");\n  try {\n    const response = await client.terminateDesktop({\n      path: {\n        id,\n      },\n    });\n    if (!response.data) {\n      throw new Error(\"Failed to kill desktop: No data returned from API\");\n    }\n    return response.data;\n  } catch (error) {\n    console.error(\"Error in killDesktop:\", error);\n    throw error;\n  }\n};\n"
  },
  {
    "path": "apps/web/src/utils/playground/tools.ts",
    "content": "import { anthropic } from \"@ai-sdk/anthropic\";\nimport client from \"@/utils/playground/cyberdesk-client\";\n\nconst wait = async (seconds: number) => {\n  await new Promise((resolve) => setTimeout(resolve, seconds * 1000));\n};\n\nexport const resolution = { x: 1024, y: 768 };\n\nexport const computerTool = (sandboxId: string) =>\n  anthropic.tools.computer_20250124({\n    displayWidthPx: resolution.x,\n    displayHeightPx: resolution.y,\n    displayNumber: 1,\n    execute: async ({\n      action,\n      coordinate,\n      text,\n      duration,\n      scroll_amount,\n      scroll_direction,\n      start_coordinate,\n    }) => {\n      // console.log(\"action\", action);\n      // console.log(\"coordinate\", coordinate);\n      // console.log(\"text\", text);\n      // console.log(\"duration\", duration);\n      // console.log(\"scroll_amount\", scroll_amount);\n      // console.log(\"scroll_direction\", scroll_direction);\n      // console.log(\"start_coordinate\", start_coordinate);\n      switch (action) {\n        case \"screenshot\": {\n          const response = await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"screenshot\",\n            },\n          });\n\n          const base64Image = response.data?.base64_image;\n          if (!base64Image) throw new Error(\"No image data received\");\n          return {\n            type: \"image\" as const,\n            data: base64Image,\n          };\n        }\n        case \"wait\": {\n          if (!duration) throw new Error(\"Duration required for wait action\");\n          const actualDuration = Math.min(duration, 2);\n          await wait(actualDuration);\n          return {\n            type: \"text\" as const,\n            text: `Waited for ${actualDuration} seconds`,\n          };\n        }\n        case \"left_click\": {\n          if (!coordinate)\n            throw new Error(\"Coordinate required for left click action\");\n          const [x, y] = coordinate;\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"click_mouse\",\n              x,\n              y,\n              num_of_clicks: 1,\n              button: \"left\",\n              click_type: \"click\",\n            },\n          });\n          return {\n            type: \"text\" as const,\n            text: `Left clicked at ${x}, ${y}`,\n          };\n        }\n        case \"double_click\": {\n          if (!coordinate)\n            throw new Error(\"Coordinate required for double click action\");\n          const [x, y] = coordinate;\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"click_mouse\",\n              x,\n              y,\n              num_of_clicks: 2,\n            },\n          });\n          return {\n            type: \"text\" as const,\n            text: `Double clicked at ${x}, ${y}`,\n          };\n        }\n        case \"triple_click\": {\n          if (!coordinate)\n            throw new Error(\"Coordinate required for triple click action\");\n          const [x, y] = coordinate;\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"click_mouse\",\n              x,\n              y,\n              num_of_clicks: 3,\n            },\n          });\n          return {\n            type: \"text\" as const,\n            text: `Triple clicked at ${x}, ${y}`,\n          };\n        }\n        case \"right_click\": {\n          if (!coordinate)\n            throw new Error(\"Coordinate required for right click action\");\n          const [x, y] = coordinate;\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"click_mouse\",\n              x,\n              y,\n              num_of_clicks: 1,\n              button: \"right\",\n              click_type: \"click\",\n            },\n          });\n          return {\n            type: \"text\" as const,\n            text: `Right clicked at ${x}, ${y}`,\n          };\n        }\n        case \"mouse_move\": {\n          if (!coordinate)\n            throw new Error(\"Coordinate required for mouse move action\");\n          const [x, y] = coordinate;\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"move_mouse\",\n              x,\n              y,\n            },\n          });\n          return {\n            type: \"text\" as const,\n            text: `Moved mouse to ${x}, ${y}`,\n          };\n        }\n        case \"type\": {\n          if (!text) throw new Error(\"Text required for type action\");\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"type\",\n              text,\n            }\n          })\n          return { type: \"text\" as const, text: `Typed: ${text}` };\n        }\n        case \"key\": {\n          if (!text) throw new Error(\"Key required for key action\");\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"press_keys\",\n              keys: text\n            },\n          });\n          return { type: \"text\" as const, text: `Pressed key: ${text}` };\n        }\n        case \"scroll\": {\n          if (!scroll_direction)\n            throw new Error(\"Scroll direction required for scroll action\");\n          if (!scroll_amount)\n            throw new Error(\"Scroll amount required for scroll action\");\n\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"scroll\",\n              direction: scroll_direction,\n              amount: scroll_amount,\n            },\n          });\n          return { type: \"text\" as const, text: `Scrolled ${text}` };\n        }\n        case \"left_click_drag\": {\n          if (!start_coordinate || !coordinate)\n            throw new Error(\"Coordinate required for mouse move action\");\n          const [startX, startY] = start_coordinate;\n          const [endX, endY] = coordinate;\n\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"drag_mouse\",\n              start: { x: startX, y: startY },\n              end: { x: endX, y: endY },\n            },\n          });\n          return {\n            type: \"text\" as const,\n            text: `Dragged mouse from ${startX}, ${startY} to ${endX}, ${endY}`,\n          };\n        }\n        case \"cursor_position\": {\n          const response = await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"get_cursor_position\",\n            },\n          });\n\n          if (!response.data?.output) throw new Error(\"No output received\");\n\n          return {\n            type: \"text\" as const,\n            text: `Cursor position data: ${response.data?.output}`,\n          };\n        }\n        case \"hold_key\": {\n          if (!text) throw new Error(\"Key required for hold key action\");\n          if (!duration) throw new Error(\"Duration required for hold key action\");\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"press_keys\",\n              keys: text,\n              key_action_type: \"down\",\n            },\n          });\n\n          await wait(duration);\n\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"press_keys\",\n              keys: text,\n              key_action_type: \"up\",\n            },\n          });\n          return { type: \"text\" as const, text: `Held key ${text} for ${duration} seconds` };\n        }\n        case \"left_mouse_down\": {\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"click_mouse\",\n              button: \"left\",\n              click_type: \"down\",\n            },\n          });\n          return { type: \"text\" as const, text: `Left mouse button down` };\n        }\n        case \"left_mouse_up\": {\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"click_mouse\",\n              button: \"left\",\n              click_type: \"up\",\n            },\n          });\n          return { type: \"text\" as const, text: `Left mouse button up` };\n        }\n        case \"middle_click\": {\n          await client.executeComputerAction({\n            path: {\n              id: sandboxId,\n            },\n            body: {\n              type: \"click_mouse\",\n              button: \"middle\",\n              click_type: \"click\",\n            },\n          });\n          return { type: \"text\" as const, text: `Middle mouse button clicked` };\n        }\n        default:\n          throw new Error(`Unsupported action: ${action}`);\n      }\n    },\n    experimental_toToolResultContent(result) {\n      if (typeof result === \"string\") {\n        return [{ type: \"text\", text: result }];\n      }\n      if (result.type === \"image\" && result.data) {\n        return [\n          {\n            type: \"image\",\n            data: result.data,\n            mimeType: \"image/jpeg\",\n          },\n        ];\n      }\n      if (result.type === \"text\" && result.text) {\n        return [{ type: \"text\", text: result.text }];\n      }\n      throw new Error(\"Invalid result format\");\n    },\n  });\n\nexport const bashTool = (sandboxId?: string) =>\n  anthropic.tools.bash_20250124({\n    execute: async ({ command }) => {\n      if (!sandboxId) throw new Error(\"Sandbox ID required for bash action\");\n      try {\n        const result = await client.executeBashAction({\n          path: {\n            id: sandboxId,\n          },\n          body: {\n            command,\n          },\n        });\n\n        if (result.data?.error) {\n          throw new Error(result.data.error);\n        }\n        \n        return (\n          result.data?.output || \"(Command executed successfully with no output)\"\n        );\n      } catch (error) {\n        console.error(\"Bash command failed:\", error);\n        if (error instanceof Error) {\n          return `Error executing command: ${error.message}`;\n        } else {\n          return `Error executing command: ${String(error)}`;\n        }\n      }\n    },\n  });\n"
  },
  {
    "path": "apps/web/src/utils/playground/use-scroll-to-bottom.ts",
    "content": "import { useEffect, useRef, type RefObject } from 'react';\n\nexport function useScrollToBottom(): [\n  RefObject<HTMLDivElement>,\n  RefObject<HTMLDivElement>,\n] {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const endRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const container = containerRef.current;\n\n    if (container) {\n      const observer = new MutationObserver(() => {\n        // Directly set scrollTop to scrollHeight to scroll to the bottom of the container.\n        // This manipulation is localized to the 'container' element.\n        container.scrollTop = container.scrollHeight;\n      });\n\n      // Observe changes that would typically require scrolling in a chat interface\n      observer.observe(container, {\n        childList: true,    // For new messages being added\n        subtree: true,      // For changes within messages (e.g., streaming content updating)\n        characterData: true // Specifically for text changes during streaming\n      });\n\n      // Initial scroll to bottom in case there's pre-existing content \n      // that might not trigger an immediate mutation but should be scrolled past.\n      // Use a microtask to ensure layout has been calculated.\n      queueMicrotask(() => {\n        if (containerRef.current) {\n          containerRef.current.scrollTop = containerRef.current.scrollHeight;\n        }\n      });\n\n      return () => observer.disconnect();\n    }\n  }, []); // Effect runs once on mount\n\n  return [containerRef, endRef];\n}"
  },
  {
    "path": "apps/web/src/utils/posthog/posthog.ts",
    "content": "import { PostHog } from \"posthog-node\"\n\nexport default function PostHogClient() {\n  const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {\n    host: \"https://us.i.posthog.com\",\n    flushAt: 1,\n    flushInterval: 0,\n  })\n  return posthogClient\n}\n"
  },
  {
    "path": "apps/web/src/utils/stripe/stripe-server.ts",
    "content": "import Stripe from 'stripe';\n\n// This file should only be imported in server components or API routes\nif (typeof window !== 'undefined') {\n  throw new Error('This file should only be imported in server components or API routes');\n}\n\n// Initialize Stripe with the secret key from environment variables\nexport const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {\n  apiVersion: '2025-02-24.acacia', // Use the latest API version\n});\n\n// Price ID for the Pro subscription tier\nexport const STRIPE_PRICE_ID = process.env.STRIPE_PRICE_ID_PRO || '';"
  },
  {
    "path": "apps/web/src/utils/stripe/stripe.ts",
    "content": "// Helper function for client-side use\nexport const formatPrice = (price: number) => {\n  return new Intl.NumberFormat('en-US', {\n    style: 'currency',\n    currency: 'USD',\n  }).format(price / 100);\n};\n"
  },
  {
    "path": "apps/web/src/utils/stripe/tiers.ts",
    "content": "// Define the tier type\nexport interface Tier {\n  name: string;\n  slug: string;\n  description: string;\n  priceMonthly: number;\n  href: string;\n  highlights: { description: string; disabled?: boolean }[];\n  features: {\n    section: string;\n    name: string;\n    value: string | number | boolean;\n  }[];\n}\n\n// Define the tiers configuration\nexport const tiers: Tier[] = [\n  {\n    name: 'Pro',\n    slug: 'pro',\n    description: 'Get started with deploying AI computer agents on up to 20 concurrent desktops',\n    priceMonthly: 19.99,\n    href: '#',\n    highlights: [\n      { description: '24 hour session per desktop launch' },\n      { description: 'Up to 20 concurrent desktops' },\n      { description: 'Unlimited desktop actions' },\n      // { description: 'RadiantAI integrations', disabled: true },\n      // { description: 'Competitor analysis', disabled: true },\n    ],\n    features: [\n      { section: 'Features', name: 'Desktops', value: 3 },\n      { section: 'Features', name: 'Deal progress boards', value: 5 },\n      { section: 'Features', name: 'Sourcing platforms', value: 'Select' },\n      { section: 'Features', name: 'Contacts', value: 100 },\n      { section: 'Features', name: 'AI assisted outreach', value: false },\n      { section: 'Analysis', name: 'Competitor analysis', value: false },\n      { section: 'Analysis', name: 'Dashboard reporting', value: false },\n      { section: 'Analysis', name: 'Community insights', value: false },\n      { section: 'Analysis', name: 'Performance analysis', value: false },\n      { section: 'Support', name: 'Email support', value: true },\n      { section: 'Support', name: '24 / 7 call center support', value: false },\n      { section: 'Support', name: 'Dedicated account manager', value: false },\n    ],\n  }\n];\n"
  },
  {
    "path": "apps/web/src/utils/supabase/client.ts",
    "content": "import { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n  return createBrowserClient(\n    process.env.NEXT_PUBLIC_SUPABASE_URL || '',\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ''\n  )\n}\n\n// For backward compatibility with existing code\nexport const supabase = createClient()\n"
  },
  {
    "path": "apps/web/src/utils/supabase/middleware.ts",
    "content": "import { createServerClient } from '@supabase/ssr'\nimport { NextResponse, type NextRequest } from 'next/server'\n\nexport async function updateSession(request: NextRequest) {\n  let supabaseResponse = NextResponse.next({\n    request,\n  })\n\n  const supabase = createServerClient(\n    process.env.NEXT_PUBLIC_SUPABASE_URL!,\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n    {\n      cookies: {\n        getAll() {\n          return request.cookies.getAll()\n        },\n        setAll(cookiesToSet) {\n          cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))\n          supabaseResponse = NextResponse.next({\n            request,\n          })\n          cookiesToSet.forEach(({ name, value, options }) =>\n            supabaseResponse.cookies.set(name, value, options)\n          )\n        },\n      },\n    }\n  )\n\n  // Do not run code between createServerClient and\n  // supabase.auth.getUser(). A simple mistake could make it very hard to debug\n  // issues with users being randomly logged out.\n\n  // IMPORTANT: DO NOT REMOVE auth.getUser()\n\n  const {\n    data: { user },\n  } = await supabase.auth.getUser()\n\n  if (\n    !user &&\n    !request.nextUrl.pathname.startsWith('/login') &&\n    !request.nextUrl.pathname.startsWith('/auth')\n  ) {\n    // no user, potentially respond by redirecting the user to the login page\n    const url = request.nextUrl.clone()\n    url.pathname = '/login'\n    return NextResponse.redirect(url)\n  }\n\n  // IMPORTANT: You *must* return the supabaseResponse object as it is.\n  // If you're creating a new response object with NextResponse.next() make sure to:\n  // 1. Pass the request in it, like so:\n  //    const myNewResponse = NextResponse.next({ request })\n  // 2. Copy over the cookies, like so:\n  //    myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())\n  // 3. Change the myNewResponse object to fit your needs, but avoid changing\n  //    the cookies!\n  // 4. Finally:\n  //    return myNewResponse\n  // If this is not done, you may be causing the browser and server to go out\n  // of sync and terminate the user's session prematurely!\n\n  return supabaseResponse\n}"
  },
  {
    "path": "apps/web/src/utils/supabase/server.ts",
    "content": "import { createServerClient } from '@supabase/ssr'\nimport { cookies } from 'next/headers'\n\nexport function createClient() {\n  const cookieStore = cookies()\n\n  return createServerClient(\n    process.env.NEXT_PUBLIC_SUPABASE_URL || '',\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '',\n    {\n      cookies: {\n        getAll() {\n          return cookieStore.getAll()\n        },\n        setAll(cookiesToSet) {\n          try {\n            cookiesToSet.forEach(({ name, value, options }) =>\n              cookieStore.set(name, value, options)\n            )\n          } catch {\n            // The `setAll` method was called from a Server Component.\n            // This can be ignored if you have middleware refreshing\n            // user sessions.\n          }\n        },\n      },\n    }\n  )\n}\n"
  },
  {
    "path": "apps/web/src/utils/supabase/supabaseClient.js",
    "content": "// supabaseClient.js\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n  return createBrowserClient(\n    process.env.NEXT_PUBLIC_SUPABASE_URL || '',\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ''\n  )\n}\n"
  },
  {
    "path": "apps/web/src/utils/supabase/supabaseServerClient.ts",
    "content": "import { createServerClient } from '@supabase/ssr'\nimport { cookies } from 'next/headers'\n\nexport function createClient() {\n  const cookieStore = cookies()\n\n  return createServerClient(\n    process.env.NEXT_PUBLIC_SUPABASE_URL!,\n    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n    {\n      cookies: {\n        getAll() {\n          return cookieStore.getAll()\n        },\n        setAll(cookiesToSet) {\n          try {\n            cookiesToSet.forEach(({ name, value, options }) =>\n              cookieStore.set(name, value, options)\n            )\n          } catch {\n            // The `setAll` method was called from a Server Component.\n            // This can be ignored if you have middleware refreshing\n            // user sessions.\n          }\n        },\n      },\n    }\n  )\n}\n"
  },
  {
    "path": "apps/web/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"verbatimModuleSyntax\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    },\n    \"target\": \"ES2017\"\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "cyberdesk-architecture.md",
    "content": "# Cyberdesk Architecture\n\n## System Overview\n\nThis document outlines the architecture for Cyberdesk,a scalable cloud desktop platform that allows users to create and interact with isolated virtual machine environments. The system is designed to support thousands of concurrent VMs while maintaining security, performance, and ease of use.\n\n## Architecture Components\n\n```\n┌───────────────┐     ┌───────────────┐     ┌─────────────────────────────────────────┐\n│   Frontend    │     │ API Backend   │     │          Kubernetes Cluster             │\n│  (Next.js on  │───▶│  (Node.js      │───▶│┌───────────┐┌──────────┐┌─────────────┐ │\n│    Vercel)    │     │   on          │     ││  Gateway  ││ Instance ││  Kubevirt   │ │\n└───────────────┘     │   Fly.io)     │     ││  Service  ││ Operator ││    VMs      │ │\n                      └───────────────┘     │└───────────┘└──────────┘└─────────────┘ │\n                                            └─────────────────────────────────────────┘\n```\n\n### 1. Frontend (Next.js on Vercel)\n- Landing page\n- Docs\n- User authentication and account management\n- Desktop creation and management interface\n- Playground\n\n### 2. API Backend (Node.js on Fly.io)\n- RESTful API for desktop management and control (makes calls to Gateway Service)\n- API key auth provided by Unkey\n- Billing with Stripe\n- Usage tracking and limit enforcing\n- Event logging and analytics with Posthog\n\n### 3. Kubernetes Cluster (AKS with Kubevirt)\n\n- **Gateway Service**\n  - Single entry point into the cluster\n  - CRUD endpoints for CyberdeskInstance K8s resources\n  - Endpoint to route commands to running VMs\n  - Exposed for public access from the FastAPI backend, via K8s External Service\n\n- **Instance Operator**\n  - Listens to changes to CyberdeskInstances and does all relevant business logic to create the underlying VirtualMachineInstance using Kubevirt\n  - Enforce timeout and compute hour credits\n  - Apply snapshots, etc\n  \n- **Kubevirt VMs**\n  - Isolated sandbox environments\n  - Each running the custom execD service, which accepts requests from Gateway Service\n  - Network isolation via Kubernetes networking\n\n## Other Key Components\n\n### 1. execD Service (Inside Each VM)\n\n#### Implementation\n- Custom lightweight HTTP service\n- Written in FastAPI\n- Runs on private-internal-ip:port inside each VM, only Gateway Service can access\n- Starts automatically when VM boots\n\n#### Features\n- Command execution\n- File system operations\n\n### 2. CyberdeskInstance K8s Custom Resource Definition\n\n#### Implementation\n- YAML file that defines a desktop instance and it's parameters\n- Instances of this custom resource are listened to by Instance Operator\n\n### 3. JS / Python SDKs\n\n#### Implementation\n- Automatically generated from OpenAPI spec\n\n### 4. Supabase Database\n- Handles all database functionalities, authentication\n\n## Scaling Considerations\n\n### Horizontal Scaling\n- Frontend: Automatic scaling via Vercel\n- API Backend: Auto-scaling on Fly.io\n- Gateway Service: Kubernetes HPA up to 20+ pods\n- Kubernetes Cluster: Multiple node pools, auto-scaling\n\n"
  },
  {
    "path": "infra/README.md",
    "content": "# Cyberdesk AKS/KubeVirt Deployment Guide\n\nThis guide walks you through deploying Cyberdesk on Azure Kubernetes Service (AKS) with KubeVirt, including all required infrastructure, snapshotting, ingress, and certificate management. **Follow the steps in order for a successful deployment.**\n\n---\n\n## Prerequisites\n- [Terraform](https://www.terraform.io/downloads.html)\n- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)\n- [kubectl](https://kubernetes.io/docs/tasks/tools/)\n- [kubelogin](https://github.com/Azure/kubelogin)\n- [virtctl](https://kubevirt.io/user-guide/operations/virtctl_client_tool/)\n- Azure subscription with permissions\n\n---\n\n## 1. Authenticate and Set Up Terraform Workspaces\n\n### 1.1 Authenticate with Azure\nBefore running any Terraform commands, authenticate with Azure CLI:\n```bash\naz login\naz account set --subscription <your-subscription-id>\n```\nReplace `<your-subscription-id>` with the correct Azure subscription for your environment (this may be auto-set if you're using the Azure CLI to login).\n\n### 1.2 Initialize Terraform\nNavigate to the terraform directory and initialize:\n```bash\ncd ./terraform\nterraform init\n```\n\n### 1.3 Using Workspaces for Dev and Prod\nTerraform workspaces allow you to manage separate state for different environments (e.g., dev and prod).\n\n#### **Create and Use the Dev Workspace**\n```bash\n# Create the dev workspace if it doesn't exist\nterraform workspace new dev\n# Switch to the dev workspace\nterraform workspace select dev\n# Apply the dev configuration\nterraform apply -var-file=\"dev.tfvars\" -auto-approve\n```\n\n#### **Create and Use the Prod Workspace**\n```bash\n# Create the prod workspace if it doesn't exist\nterraform workspace new prod\n# Switch to the prod workspace\nterraform workspace select prod\n# Apply the prod configuration\nterraform apply -var-file=\"prod.tfvars\" -auto-approve\n```\n\n- Always make sure you are in the correct workspace before running `plan` or `apply`.\n- Each workspace maintains its own state, so dev and prod resources are managed separately.\n\n---\n\n## 2. Configure kubectl Access\n\nAfter deploying your environment, configure kubectl access to your AKS cluster. You will need the resource group name and AKS cluster name, which are defined in your tfvars file for the environment you just deployed (either dev.tfvars or prod.tfvars).\n\n- For **dev**:\n  - Use the values of `resource_group_name` and `aks_cluster_name` from `dev.tfvars`.\n- For **prod**:\n  - Use the values of `resource_group_name` and `aks_cluster_name` from `prod.tfvars`.\n\nExample:\n```bash\naz aks get-credentials --resource-group <resource_group_name_from_tfvars> --name <aks_cluster_name_from_tfvars>\n```\nReplace the placeholders with the actual values from your tfvars file.\n\n---\n\n## 3. Deploy KubeVirt Operator and CR\n\nNote: From now on, you'll want to navigate to the correct folders to apply the correct YAMLs. For the most part, you'll be in the `infra/kubernetes` folder.\n\n```bash\nkubectl apply -f kubevirt-operator.yaml\nkubectl apply -f kubevirt-cr.yaml\n```\n\n---\n\n## 4. Deploy Containerized Data Importer (CDI)\n\nCDI is required for KubeVirt features like cloning PVCs and importing disk images. This is the recommended way to create VM root disks with specific sizes.\n\n```bash\nkubectl apply -f cdi-operator.yaml\nkubectl apply -f cdi-cr.yaml\n\n# Wait for CDI pods to be ready (optional check)\nkubectl wait --for=condition=Ready pod -l cdi.kubevirt.io -n cdi --timeout=300s\n```\n\n---\n\n## 5. Apply Azure Disk Snapshot Class (REQUIRED for KubeVirt Snapshots)\n\n```bash\nkubectl apply -f azure-snapshot-class.yaml\n```\n\nThis enables snapshotting for Azure disks and is required for KubeVirt VM snapshots and cloning.\n\n---\n\n## 6. Create the Golden VM and Snapshot (REQUIRED for Cyberdesk Operator)\n\n> **Note:** The golden VM manifest is gitignored. Reference Notion or ask a team member for `golden-vm-deploy.yaml`\n\n1. **Apply the Golden VM:**\n   ```bash\n   kubectl apply -f golden-vm-deploy.yaml\n   ```\n2. **Wait for the VM to fully boot and complete cloud-init.**\n   - Check with:\n     ```bash\n     # Wait for the VM to be Running\n     kubectl get vmi -n kubevirt\n     \n     # Access the VM console (to login, reference the golden-vm-deploy.yaml file to see the credentials)\n     virtctl console golden-vm -n kubevirt\n\n     # Inside the VM, if there doesn't seem to be any cloud-init logs, run this and see if \"status:done\" is printed\n     cloud-init status\n\n     # Once done, exit the VM console with ctrl / cmd + ]\n\n     # Then, VNC into the VM\n     virtctl port-forward golden-vm 5900:5900 -n kubevirt\n\n     # Use RealVNC (download if you don't have it for free) and connect to localhost:5900\n\n     # Install any applications and set any settings you'd like in all VMs, such as DuckDuckGo for search, VSCode, etc.\n     # Current list:\n     # - DuckDuckGo as the default search engine\n     # - VSCode\n     # - Change desktop background to something nice\n     # - Delete browser history and cookies\n     ```\n     - Once done, exit RealVNC and stop the port-forward:\n3. **Stop the Golden VM:**\n   ```bash\n   virtctl stop golden-vm -n kubevirt\n   # Wait for the VM to be fully stopped\n   kubectl get vm golden-vm -n kubevirt\n   ```\n4. **Create the Golden Snapshot:**\n   ```bash\n   kubectl apply -f golden-vm-snapshot-request.yaml\n   ```\n   - Wait for the snapshot to be ready:\n     ```bash\n     kubectl get vmsnapshot -n kubevirt\n     ```\n\n---\n\n## 7. Install ingress-nginx (YAML, NOT Helm)\n\n```bash\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.1/deploy/static/provider/cloud/deploy.yaml\n```\n- Wait for all pods in `ingress-nginx` namespace to be Running.\n\n```bash\nkubectl get pods -n ingress-nginx\n```\n\n---\n\n## 8. Deploy the Default Backend and Catch-All Ingress\n\n1. **Apply the default-backend DaemonSet, Service, and catch-all Ingress:**\n   (See `default-backend.yaml` in this repo or request from a team member)\n   This ensures that Azure Load Balancer health probes are healthy for all nodes, and that unmatched traffic is routed to the default backend. If you don't do this, the Gateway will not be accessible via https.\n   ```bash\n   kubectl apply -f default-backend.yaml\n   ```\n2. **Optional: Verify Azure Load Balancer health probes are healthy for all nodes** (in Azure Portal).\n   - They probably are, but if you're having trouble connecting to the Gateway, check this.\n\n---\n\n## 9. Install cert-manager (YAML, NOT Helm)\n\n```bash\nkubectl apply --validate=false -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.1/cert-manager.yaml\n```\n- Wait for all pods in `cert-manager` namespace to be Running.\n\n```bash\nkubectl get pods -n cert-manager\n```\n\n---\n\n## 10. Build and Push Operator and Gateway Images\n\nIf you have made changes to the operator or gateway code, you need to rebuild and push the images with a **unique, specific tag** (e.g., using Semantic Versioning like `v1.2.3` or a Git commit SHA like `a1b2c3d4`). **Avoid using the `:latest` tag**.\n\n### Cyberdesk Operator\n\n**Bash:**\n```bash\n# Navigate to the operator directory\ncd ../../services/cyberdesk-operator\n# Build the docker image with a specific tag (ensure Docker daemon is running)\ndocker build -t cyberdesk/cyberdesk-operator:NEW_VERSION_TAG_HERE .\n# Push the image to Docker Hub\ndocker push cyberdesk/cyberdesk-operator:NEW_VERSION_TAG_HERE\n```\n**PowerShell:**\n```powershell\ncd ../services/cyberdesk-operator\ndocker build -t \"cyberdesk/cyberdesk-operator:NEW_VERSION_TAG_HERE\" .\ndocker push cyberdesk/cyberdesk-operator:NEW_VERSION_TAG_HERE\n```\n**Important:** After building and pushing, you **must update** the `image:` field in `infra/kubernetes/cyberdesk-operator.yaml` to reference the **specific tag** you just used.\n\n### Gateway\n\n**Bash:**\n```bash\ncd ../services/gateway\ndocker build -t cyberdesk/gateway:NEW_GATEWAY_TAG_HERE .\ndocker push cyberdesk/gateway:NEW_GATEWAY_TAG_HERE\n```\n**PowerShell:**\n```powershell\ncd ../services/gateway\ndocker build -t \"cyberdesk/gateway:NEW_GATEWAY_TAG_HERE\" .\ndocker push cyberdesk/gateway:NEW_GATEWAY_TAG_HERE\n```\n**Important:** After building and pushing, you **must update** the `image:` field in `infra/kubernetes/gateway-deploy.yaml` to reference the **specific tag** you just used.\n\n---\n\n## 11. Deploy Cyberdesk Operator and Gateway\n\n1. **Apply the Supabase Secret:**\n   (Ask a team member for `cyberdesk-secret.yaml`)\n   ```bash\n   kubectl apply -f cyberdesk-secret.yaml\n   ```\n2. **Deploy the Cyberdesk Operator:**\n   ```bash\n   kubectl apply -f cyberdesk-operator.yaml\n   ```\n3. **Deploy the Gateway:**\n   ```bash\n   kubectl apply -f gateway-deploy.yaml\n   ```\n4. **Trigger Operator Setup:**\n   ```bash\n   kubectl apply -f start-cyberdesk-operator-cr.yaml\n   ```\n\n---\n\n## 12. Apply the ClusterIssuer and Gateway Ingress\n\nBefore applying your Ingress resources, you must first create the ClusterIssuer, which is defined in a separate file (`cluster-issuer.yaml`). This ClusterIssuer is required for cert-manager to issue certificates for your Ingress resources.\n\n- **Step 1: Apply the ClusterIssuer**\n\n  ```bash\n  kubectl apply -f cluster-issuer.yaml\n  ```\n\n- **Step 2: Apply the Gateway Ingress**\n\n  For **dev** (in your dev cluster):\n\n  ```bash\n  kubectl apply -f gateway-ingress-dev.yaml\n  ```\n\n  For **prod** (in your prod cluster):\n\n  ```bash\n  kubectl apply -f gateway-ingress-prod.yaml\n  ```\n\nWait for the Ingress to be assigned an external IP:\n\n```bash\nkubectl get ingress -n cyberdesk-system\n```\n\n---\n\n## 13. Set Up DNS for Gateway\n\nOnce your Ingress has an external IP, set up your DNS records (Cyberdesk manages it in Cloudflare, see credentials in Notion):\n\n- For **dev**:  \n  Create an A record for `dev-gateway.cyberdesk.io` pointing to your dev cluster's Ingress external IP. Disable proxy on the record, if it's enabled.\n- For **prod**:  \n  Create an A record for `gateway.cyberdesk.io` pointing to your prod cluster's Ingress external IP. Disable proxy on the record, if it's enabled.\n\nUpdate your DNS provider with the correct IP.\n\nYour DNS is ready when nslookup returns the correct IP (should be relatively quick).\n\n```bash\nnslookup gateway.cyberdesk.io (or dev-gateway.cyberdesk.io)\n```\n---\n\n## 14. Verify Ingress and Certificate\n\nCheck that your Ingress and certificate resources are created and progressing:\n\n```bash\nkubectl get ingress -n cyberdesk-system\nkubectl get clusterissuer\nkubectl get certificate -n cyberdesk-system\nkubectl describe certificate <name> -n cyberdesk-system\n```\n\n- The certificate may take a few minutes to become `READY` after DNS is set up.\n- If it does not, check DNS, Ingress, and cert-manager logs for troubleshooting.\n\n---\n\n## 15. Initiate Virtual Machine Warm Pool\n\nBefore starting the warm pool, you must update `warm-pool.yaml` to reference the correct snapshot name for the golden VM's root disk.\n\n1. **Get the name of the new root disk snapshot:**\n   ```bash\n   kubectl get volumesnapshots -n kubevirt\n   ```\n   - Look for the snapshot associated with your golden VM (it will look like `vmsnapshot-<uuid>-volume-rootdisk`).\n\n2. **Edit `warm-pool.yaml`:**\n   - Update the `snapshot: name:` field to match the name you found above.\n   - Check the YAML to see if the desired amount of replicas are set. In dev, usually 1 is enough. In prod, this should probably be much, much higher.\n3. **Apply the warm pool:**\n   ```bash\n   kubectl apply -f warm-pool.yaml\n   ```\n\n## 16. Verify Everything\n\n- Check pod status in all namespaces:\n  ```bash\n  kubectl get pods -A\n  ```\n- Check if the warm pool is running:\n  ```bash\n  kubectl get vms -n kubevirt # should see 1 or more warm pool VMs starting up and eventually running\n  ```\n- Check if the Gateway is accessible:\n  ```bash\n  curl -k https://gateway.cyberdesk.io/healthz # (or dev-gateway.cyberdesk.io if you're in dev)\n  ```\n\n## 17. Delete the Golden VM\nSince we've snapshotted it, we can delete it now. \n```bash\nkubectl delete vm golden-vm -n kubevirt\n```\n\n## 18. Local Development\n- Head to /apps/api, and make sure your .env contains the correct values, but most importantly, make sure the `GATEWAY_URL` is set to the correct URL (dev-gateway.cyberdesk.io or gateway.cyberdesk.io). Note: if set to dev-gateway.cyberdesk.io, stream URL's returned from the Gateway service will be overridden to use the dev-gateway.cyberdesk.io domain.\n- Run `npm run dev` to start the API.\n- Head to /apps/web, and make sure your .env contains the correct values, but most importantly, make sure the `CYBERDESK_API_BASE_URL` is set to http://localhost:3001 (or whatever port your API is running on).\n- Run `npm run dev` to start the web app.\n\nYou're now set to start developing! Make sure you branch off from `dev` and create a new branch for your work.\n\n## 19. Deploying to Prod\n- Make a PR to merge your changes into `dev`.\n- Once approved, merge your PR into `dev`.\n- Carefully plan how you will bring the changes you made into the production cluster. Make sure to switch kubeconfig to prod before you start, using `az aks get-credentials --resource-group <resource-group-name> --name <aks-cluster-name>`.\n- Make final tests in the dev cluster to ensure everything is working as expected.\n- Once you're ready, make a PR to merge your changes into `prod`.\n- Once approved, merge your PR into `prod`.\n- Apply the changes to the production cluster using kubectl (for example, if the Gateway has a new image, you can do kubectl rollout restart deployment gateway -n cyberdesk-system).\n- Make sure corresponding changes to the developer API / web app / docs are also being pushed to live via Vercel / Fly.io.\nTODO: Figure out how to orchestrate this better. Right now, there is discrepency between when cluster changes are pushed vs when the developer API / web app are pushed to live (since we have so many different hosting environments)."
  },
  {
    "path": "infra/kubernetes/azure-snapshot-class.yaml",
    "content": "apiVersion: snapshot.storage.k8s.io/v1\nkind: VolumeSnapshotClass\nmetadata:\n  name: csi-azure-disk-snapshot-class\n  annotations:\n    snapshot.storage.kubernetes.io/is-default-class: \"true\" # Make this the default\ndriver: disk.csi.azure.com\ndeletionPolicy: Delete # Options: Delete, Retain. Delete removes the underlying snapshot when this object is deleted. Retain keeps it. "
  },
  {
    "path": "infra/kubernetes/cdi-cr.yaml",
    "content": "apiVersion: cdi.kubevirt.io/v1beta1\nkind: CDI\nmetadata:\n  name: cdi\nspec:\n  config:\n    featureGates:\n    - HonorWaitForFirstConsumer\n  imagePullPolicy: IfNotPresent\n  infra:\n    nodeSelector:\n      kubernetes.io/os: linux\n    tolerations:\n    - key: CriticalAddonsOnly\n      operator: Exists\n  workload:\n    nodeSelector:\n      kubernetes.io/os: linux\n"
  },
  {
    "path": "infra/kubernetes/cdi-operator.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    cdi.kubevirt.io: \"\"\n  name: cdi\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.14.0\n  name: cdis.cdi.kubevirt.io\nspec:\n  group: cdi.kubevirt.io\n  names:\n    kind: CDI\n    listKind: CDIList\n    plural: cdis\n    shortNames:\n    - cdi\n    - cdis\n    singular: cdi\n  scope: Cluster\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    - jsonPath: .status.phase\n      name: Phase\n      type: string\n    name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: CDI is the CDI Operator CRD\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: CDISpec defines our specification for the CDI installation\n            properties:\n              certConfig:\n                description: certificate configuration\n                properties:\n                  ca:\n                    description: |-\n                      CA configuration\n                      CA certs are kept in the CA bundle as long as they are valid\n                    properties:\n                      duration:\n                        description: The requested 'duration' (i.e. lifetime) of the\n                          Certificate.\n                        type: string\n                      renewBefore:\n                        description: |-\n                          The amount of time before the currently issued certificate's `notAfter`\n                          time that we will begin to attempt to renew the certificate.\n                        type: string\n                    type: object\n                  client:\n                    description: |-\n                      Client configuration\n                      Certs are rotated and discarded\n                    properties:\n                      duration:\n                        description: The requested 'duration' (i.e. lifetime) of the\n                          Certificate.\n                        type: string\n                      renewBefore:\n                        description: |-\n                          The amount of time before the currently issued certificate's `notAfter`\n                          time that we will begin to attempt to renew the certificate.\n                        type: string\n                    type: object\n                  server:\n                    description: |-\n                      Server configuration\n                      Certs are rotated and discarded\n                    properties:\n                      duration:\n                        description: The requested 'duration' (i.e. lifetime) of the\n                          Certificate.\n                        type: string\n                      renewBefore:\n                        description: |-\n                          The amount of time before the currently issued certificate's `notAfter`\n                          time that we will begin to attempt to renew the certificate.\n                        type: string\n                    type: object\n                type: object\n              cloneStrategyOverride:\n                description: 'Clone strategy override: should we use a host-assisted\n                  copy even if snapshots are available?'\n                enum:\n                - copy\n                - snapshot\n                - csi-clone\n                type: string\n              config:\n                description: CDIConfig at CDI level\n                properties:\n                  dataVolumeTTLSeconds:\n                    description: |-\n                      DataVolumeTTLSeconds is the time in seconds after DataVolume completion it can be garbage collected. Disabled by default.\n                      Deprecated: Removed in v1.62.\n                    format: int32\n                    type: integer\n                  featureGates:\n                    description: FeatureGates are a list of specific enabled feature\n                      gates\n                    items:\n                      type: string\n                    type: array\n                  filesystemOverhead:\n                    description: FilesystemOverhead describes the space reserved for\n                      overhead when using Filesystem volumes. A value is between 0\n                      and 1, if not defined it is 0.055 (5.5% overhead)\n                    properties:\n                      global:\n                        description: Global is how much space of a Filesystem volume\n                          should be reserved for overhead. This value is used unless\n                          overridden by a more specific value (per storageClass)\n                        pattern: ^(0(?:\\.\\d{1,3})?|1)$\n                        type: string\n                      storageClass:\n                        additionalProperties:\n                          description: |-\n                            Percent is a string that can only be a value between [0,1)\n                            (Note: we actually rely on reconcile to reject invalid values)\n                          pattern: ^(0(?:\\.\\d{1,3})?|1)$\n                          type: string\n                        description: StorageClass specifies how much space of a Filesystem\n                          volume should be reserved for safety. The keys are the storageClass\n                          and the values are the overhead. This value overrides the\n                          global value\n                        type: object\n                    type: object\n                  imagePullSecrets:\n                    description: The imagePullSecrets used to pull the container images\n                    items:\n                      description: |-\n                        LocalObjectReference contains enough information to let you locate the\n                        referenced object inside the same namespace.\n                      properties:\n                        name:\n                          default: \"\"\n                          description: |-\n                            Name of the referent.\n                            This field is effectively required, but due to backwards compatibility is\n                            allowed to be empty. Instances of this type with an empty value here are\n                            almost certainly wrong.\n                            TODO: Add other useful fields. apiVersion, kind, uid?\n                            More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.\n                          type: string\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    type: array\n                  importProxy:\n                    description: ImportProxy contains importer pod proxy configuration.\n                    properties:\n                      HTTPProxy:\n                        description: HTTPProxy is the URL http://<username>:<pswd>@<ip>:<port>\n                          of the import proxy for HTTP requests.  Empty means unset\n                          and will not result in the import pod env var.\n                        type: string\n                      HTTPSProxy:\n                        description: HTTPSProxy is the URL https://<username>:<pswd>@<ip>:<port>\n                          of the import proxy for HTTPS requests.  Empty means unset\n                          and will not result in the import pod env var.\n                        type: string\n                      noProxy:\n                        description: NoProxy is a comma-separated list of hostnames\n                          and/or CIDRs for which the proxy should not be used. Empty\n                          means unset and will not result in the import pod env var.\n                        type: string\n                      trustedCAProxy:\n                        description: \"TrustedCAProxy is the name of a ConfigMap in\n                          the cdi namespace that contains a user-provided trusted\n                          certificate authority (CA) bundle.\\nThe TrustedCAProxy ConfigMap\n                          is consumed by the DataImportCron controller for creating\n                          cronjobs, and by the import controller referring a copy\n                          of the ConfigMap in the import namespace.\\nHere is an example\n                          of the ConfigMap (in yaml):\\n\\n\\napiVersion: v1\\nkind: ConfigMap\\nmetadata:\\n\n                          \\ name: my-ca-proxy-cm\\n  namespace: cdi\\ndata:\\n  ca.pem:\n                          |\\n    -----BEGIN CERTIFICATE-----\\n\\t   ... <base64 encoded\n                          cert> ...\\n\\t   -----END CERTIFICATE-----\"\n                        type: string\n                    type: object\n                  insecureRegistries:\n                    description: InsecureRegistries is a list of TLS disabled registries\n                    items:\n                      type: string\n                    type: array\n                  logVerbosity:\n                    description: LogVerbosity overrides the default verbosity level\n                      used to initialize loggers\n                    format: int32\n                    type: integer\n                  podResourceRequirements:\n                    description: ResourceRequirements describes the compute resource\n                      requirements.\n                    properties:\n                      claims:\n                        description: |-\n                          Claims lists the names of resources, defined in spec.resourceClaims,\n                          that are used by this container.\n\n\n                          This is an alpha field and requires enabling the\n                          DynamicResourceAllocation feature gate.\n\n\n                          This field is immutable. It can only be set for containers.\n                        items:\n                          description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                          properties:\n                            name:\n                              description: |-\n                                Name must match the name of one entry in pod.spec.resourceClaims of\n                                the Pod where this field is used. It makes that resource available\n                                inside a container.\n                              type: string\n                          required:\n                          - name\n                          type: object\n                        type: array\n                        x-kubernetes-list-map-keys:\n                        - name\n                        x-kubernetes-list-type: map\n                      limits:\n                        additionalProperties:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                          x-kubernetes-int-or-string: true\n                        description: |-\n                          Limits describes the maximum amount of compute resources allowed.\n                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                        type: object\n                      requests:\n                        additionalProperties:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                          x-kubernetes-int-or-string: true\n                        description: |-\n                          Requests describes the minimum amount of compute resources required.\n                          If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,\n                          otherwise to an implementation-defined value. Requests cannot exceed Limits.\n                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                        type: object\n                    type: object\n                  preallocation:\n                    description: Preallocation controls whether storage for DataVolumes\n                      should be allocated in advance.\n                    type: boolean\n                  scratchSpaceStorageClass:\n                    description: 'Override the storage class to used for scratch space\n                      during transfer operations. The scratch space storage class\n                      is determined in the following order: 1. value of scratchSpaceStorageClass,\n                      if that doesn''t exist, use the default storage class, if there\n                      is no default storage class, use the storage class of the DataVolume,\n                      if no storage class specified, use no storage class for scratch\n                      space'\n                    type: string\n                  tlsSecurityProfile:\n                    description: TLSSecurityProfile is used by operators to apply\n                      cluster-wide TLS security settings to operands.\n                    properties:\n                      custom:\n                        description: |-\n                          custom is a user-defined TLS security profile. Be extremely careful using a custom\n                          profile as invalid configurations can be catastrophic. An example custom profile\n                          looks like this:\n\n\n                            ciphers:\n                              - ECDHE-ECDSA-CHACHA20-POLY1305\n                              - ECDHE-RSA-CHACHA20-POLY1305\n                              - ECDHE-RSA-AES128-GCM-SHA256\n                              - ECDHE-ECDSA-AES128-GCM-SHA256\n                            minTLSVersion: VersionTLS11\n                        nullable: true\n                        properties:\n                          ciphers:\n                            description: |-\n                              ciphers is used to specify the cipher algorithms that are negotiated\n                              during the TLS handshake.  Operators may remove entries their operands\n                              do not support.  For example, to use DES-CBC3-SHA  (yaml):\n\n\n                                ciphers:\n                                  - DES-CBC3-SHA\n                            items:\n                              type: string\n                            type: array\n                          minTLSVersion:\n                            description: |-\n                              minTLSVersion is used to specify the minimal version of the TLS protocol\n                              that is negotiated during the TLS handshake. For example, to use TLS\n                              versions 1.1, 1.2 and 1.3 (yaml):\n\n\n                                minTLSVersion: VersionTLS11\n\n\n                              NOTE: currently the highest minTLSVersion allowed is VersionTLS12\n                            enum:\n                            - VersionTLS10\n                            - VersionTLS11\n                            - VersionTLS12\n                            - VersionTLS13\n                            type: string\n                        required:\n                        - ciphers\n                        - minTLSVersion\n                        type: object\n                      intermediate:\n                        description: |-\n                          intermediate is a TLS security profile based on:\n\n\n                          https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29\n\n\n                          and looks like this (yaml):\n\n\n                            ciphers:\n                              - TLS_AES_128_GCM_SHA256\n                              - TLS_AES_256_GCM_SHA384\n                              - TLS_CHACHA20_POLY1305_SHA256\n                              - ECDHE-ECDSA-AES128-GCM-SHA256\n                              - ECDHE-RSA-AES128-GCM-SHA256\n                              - ECDHE-ECDSA-AES256-GCM-SHA384\n                              - ECDHE-RSA-AES256-GCM-SHA384\n                              - ECDHE-ECDSA-CHACHA20-POLY1305\n                              - ECDHE-RSA-CHACHA20-POLY1305\n                              - DHE-RSA-AES128-GCM-SHA256\n                              - DHE-RSA-AES256-GCM-SHA384\n                            minTLSVersion: VersionTLS12\n                        nullable: true\n                        type: object\n                      modern:\n                        description: |-\n                          modern is a TLS security profile based on:\n\n\n                          https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility\n\n\n                          and looks like this (yaml):\n\n\n                            ciphers:\n                              - TLS_AES_128_GCM_SHA256\n                              - TLS_AES_256_GCM_SHA384\n                              - TLS_CHACHA20_POLY1305_SHA256\n                            minTLSVersion: VersionTLS13\n\n\n                          NOTE: Currently unsupported.\n                        nullable: true\n                        type: object\n                      old:\n                        description: |-\n                          old is a TLS security profile based on:\n\n\n                          https://wiki.mozilla.org/Security/Server_Side_TLS#Old_backward_compatibility\n\n\n                          and looks like this (yaml):\n\n\n                            ciphers:\n                              - TLS_AES_128_GCM_SHA256\n                              - TLS_AES_256_GCM_SHA384\n                              - TLS_CHACHA20_POLY1305_SHA256\n                              - ECDHE-ECDSA-AES128-GCM-SHA256\n                              - ECDHE-RSA-AES128-GCM-SHA256\n                              - ECDHE-ECDSA-AES256-GCM-SHA384\n                              - ECDHE-RSA-AES256-GCM-SHA384\n                              - ECDHE-ECDSA-CHACHA20-POLY1305\n                              - ECDHE-RSA-CHACHA20-POLY1305\n                              - DHE-RSA-AES128-GCM-SHA256\n                              - DHE-RSA-AES256-GCM-SHA384\n                              - DHE-RSA-CHACHA20-POLY1305\n                              - ECDHE-ECDSA-AES128-SHA256\n                              - ECDHE-RSA-AES128-SHA256\n                              - ECDHE-ECDSA-AES128-SHA\n                              - ECDHE-RSA-AES128-SHA\n                              - ECDHE-ECDSA-AES256-SHA384\n                              - ECDHE-RSA-AES256-SHA384\n                              - ECDHE-ECDSA-AES256-SHA\n                              - ECDHE-RSA-AES256-SHA\n                              - DHE-RSA-AES128-SHA256\n                              - DHE-RSA-AES256-SHA256\n                              - AES128-GCM-SHA256\n                              - AES256-GCM-SHA384\n                              - AES128-SHA256\n                              - AES256-SHA256\n                              - AES128-SHA\n                              - AES256-SHA\n                              - DES-CBC3-SHA\n                            minTLSVersion: VersionTLS10\n                        nullable: true\n                        type: object\n                      type:\n                        description: |-\n                          type is one of Old, Intermediate, Modern or Custom. Custom provides\n                          the ability to specify individual TLS security profile parameters.\n                          Old, Intermediate and Modern are TLS security profiles based on:\n\n\n                          https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations\n\n\n                          The profiles are intent based, so they may change over time as new ciphers are developed and existing ciphers\n                          are found to be insecure.  Depending on precisely which ciphers are available to a process, the list may be\n                          reduced.\n\n\n                          Note that the Modern profile is currently not supported because it is not\n                          yet well adopted by common software libraries.\n                        enum:\n                        - Old\n                        - Intermediate\n                        - Modern\n                        - Custom\n                        type: string\n                    type: object\n                  uploadProxyURLOverride:\n                    description: Override the URL used when uploading to a DataVolume\n                    type: string\n                type: object\n              customizeComponents:\n                description: CustomizeComponents defines patches for components deployed\n                  by the CDI operator.\n                properties:\n                  flags:\n                    description: Configure the value used for deployment and daemonset\n                      resources\n                    properties:\n                      api:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      controller:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      uploadProxy:\n                        additionalProperties:\n                          type: string\n                        type: object\n                    type: object\n                  patches:\n                    items:\n                      description: CustomizeComponentsPatch defines a patch for some\n                        resource.\n                      properties:\n                        patch:\n                          type: string\n                        resourceName:\n                          minLength: 1\n                          type: string\n                        resourceType:\n                          minLength: 1\n                          type: string\n                        type:\n                          description: PatchType defines the patch type.\n                          type: string\n                      required:\n                      - patch\n                      - resourceName\n                      - resourceType\n                      - type\n                      type: object\n                    type: array\n                    x-kubernetes-list-type: atomic\n                type: object\n              imagePullPolicy:\n                description: PullPolicy describes a policy for if/when to pull a container\n                  image\n                enum:\n                - Always\n                - IfNotPresent\n                - Never\n                type: string\n              infra:\n                description: Selectors and tolerations that should apply to cdi infrastructure\n                  components\n                properties:\n                  affinity:\n                    description: |-\n                      affinity enables pod affinity/anti-affinity placement expanding the types of constraints\n                      that can be expressed with nodeSelector.\n                      affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector\n                      See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity\n                    properties:\n                      nodeAffinity:\n                        description: Describes node affinity scheduling rules for\n                          the pod.\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node matches the corresponding matchExpressions; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: |-\n                                An empty preferred scheduling term matches all objects with implicit weight 0\n                                (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).\n                              properties:\n                                preference:\n                                  description: A node selector term, associated with\n                                    the corresponding weight.\n                                  properties:\n                                    matchExpressions:\n                                      description: A list of node selector requirements\n                                        by node's labels.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchFields:\n                                      description: A list of node selector requirements\n                                        by node's fields.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                weight:\n                                  description: Weight associated with matching the\n                                    corresponding nodeSelectorTerm, in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - preference\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to an update), the system\n                              may or may not try to eventually evict the pod from its node.\n                            properties:\n                              nodeSelectorTerms:\n                                description: Required. A list of node selector terms.\n                                  The terms are ORed.\n                                items:\n                                  description: |-\n                                    A null or empty node selector term matches no objects. The requirements of\n                                    them are ANDed.\n                                    The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.\n                                  properties:\n                                    matchExpressions:\n                                      description: A list of node selector requirements\n                                        by node's labels.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchFields:\n                                      description: A list of node selector requirements\n                                        by node's fields.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            required:\n                            - nodeSelectorTerms\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      podAffinity:\n                        description: Describes pod affinity scheduling rules (e.g.\n                          co-locate this pod in the same node, zone, etc. as some\n                          other pod(s)).\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: The weights of all of the matched WeightedPodAffinityTerm\n                                fields are added per-node to find the most preferred\n                                node(s)\n                              properties:\n                                podAffinityTerm:\n                                  description: Required. A pod affinity term, associated\n                                    with the corresponding weight.\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                weight:\n                                  description: |-\n                                    weight associated with matching the corresponding podAffinityTerm,\n                                    in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - podAffinityTerm\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to a pod label update), the\n                              system may or may not try to eventually evict the pod from its node.\n                              When there are multiple elements, the lists of nodes corresponding to each\n                              podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                            items:\n                              description: |-\n                                Defines a set of pods (namely those matching the labelSelector\n                                relative to the given namespace(s)) that this pod should be\n                                co-located (affinity) or not co-located (anti-affinity) with,\n                                where co-located is defined as running on a node whose value of\n                                the label with key <topologyKey> matches that of any node on which\n                                a pod of the set of pods is running\n                              properties:\n                                labelSelector:\n                                  description: |-\n                                    A label query over a set of resources, in this case pods.\n                                    If it's null, this PodAffinityTerm matches with no Pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                matchLabelKeys:\n                                  description: |-\n                                    MatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                mismatchLabelKeys:\n                                  description: |-\n                                    MismatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                namespaceSelector:\n                                  description: |-\n                                    A label query over the set of namespaces that the term applies to.\n                                    The term is applied to the union of the namespaces selected by this field\n                                    and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list means \"this pod's namespace\".\n                                    An empty selector ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: |-\n                                    namespaces specifies a static list of namespace names that the term applies to.\n                                    The term is applied to the union of the namespaces listed in this field\n                                    and the ones selected by namespaceSelector.\n                                    null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                topologyKey:\n                                  description: |-\n                                    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                    the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey matches that of any node on which any of the\n                                    selected pods is running.\n                                    Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        type: object\n                      podAntiAffinity:\n                        description: Describes pod anti-affinity scheduling rules\n                          (e.g. avoid putting this pod in the same node, zone, etc.\n                          as some other pod(s)).\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the anti-affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling anti-affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: The weights of all of the matched WeightedPodAffinityTerm\n                                fields are added per-node to find the most preferred\n                                node(s)\n                              properties:\n                                podAffinityTerm:\n                                  description: Required. A pod affinity term, associated\n                                    with the corresponding weight.\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                weight:\n                                  description: |-\n                                    weight associated with matching the corresponding podAffinityTerm,\n                                    in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - podAffinityTerm\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the anti-affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the anti-affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to a pod label update), the\n                              system may or may not try to eventually evict the pod from its node.\n                              When there are multiple elements, the lists of nodes corresponding to each\n                              podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                            items:\n                              description: |-\n                                Defines a set of pods (namely those matching the labelSelector\n                                relative to the given namespace(s)) that this pod should be\n                                co-located (affinity) or not co-located (anti-affinity) with,\n                                where co-located is defined as running on a node whose value of\n                                the label with key <topologyKey> matches that of any node on which\n                                a pod of the set of pods is running\n                              properties:\n                                labelSelector:\n                                  description: |-\n                                    A label query over a set of resources, in this case pods.\n                                    If it's null, this PodAffinityTerm matches with no Pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                matchLabelKeys:\n                                  description: |-\n                                    MatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                mismatchLabelKeys:\n                                  description: |-\n                                    MismatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                namespaceSelector:\n                                  description: |-\n                                    A label query over the set of namespaces that the term applies to.\n                                    The term is applied to the union of the namespaces selected by this field\n                                    and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list means \"this pod's namespace\".\n                                    An empty selector ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: |-\n                                    namespaces specifies a static list of namespace names that the term applies to.\n                                    The term is applied to the union of the namespaces listed in this field\n                                    and the ones selected by namespaceSelector.\n                                    null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                topologyKey:\n                                  description: |-\n                                    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                    the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey matches that of any node on which any of the\n                                    selected pods is running.\n                                    Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        type: object\n                    type: object\n                  apiServerReplicas:\n                    description: ApiserverReplicas set Replicas for cdi-apiserver\n                    format: int32\n                    type: integer\n                  deploymentReplicas:\n                    description: DeploymentReplicas set Replicas for cdi-deployment\n                    format: int32\n                    type: integer\n                  nodeSelector:\n                    additionalProperties:\n                      type: string\n                    description: |-\n                      nodeSelector is the node selector applied to the relevant kind of pods\n                      It specifies a map of key-value pairs: for the pod to be eligible to run on a node,\n                      the node must have each of the indicated key-value pairs as labels\n                      (it can have additional labels as well).\n                      See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector\n                    type: object\n                  tolerations:\n                    description: |-\n                      tolerations is a list of tolerations applied to the relevant kind of pods\n                      See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info.\n                      These are additional tolerations other than default ones.\n                    items:\n                      description: |-\n                        The pod this Toleration is attached to tolerates any taint that matches\n                        the triple <key,value,effect> using the matching operator <operator>.\n                      properties:\n                        effect:\n                          description: |-\n                            Effect indicates the taint effect to match. Empty means match all taint effects.\n                            When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.\n                          type: string\n                        key:\n                          description: |-\n                            Key is the taint key that the toleration applies to. Empty means match all taint keys.\n                            If the key is empty, operator must be Exists; this combination means to match all values and all keys.\n                          type: string\n                        operator:\n                          description: |-\n                            Operator represents a key's relationship to the value.\n                            Valid operators are Exists and Equal. Defaults to Equal.\n                            Exists is equivalent to wildcard for value, so that a pod can\n                            tolerate all taints of a particular category.\n                          type: string\n                        tolerationSeconds:\n                          description: |-\n                            TolerationSeconds represents the period of time the toleration (which must be\n                            of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,\n                            it is not set, which means tolerate the taint forever (do not evict). Zero and\n                            negative values will be treated as 0 (evict immediately) by the system.\n                          format: int64\n                          type: integer\n                        value:\n                          description: |-\n                            Value is the taint value the toleration matches to.\n                            If the operator is Exists, the value should be empty, otherwise just a regular string.\n                          type: string\n                      type: object\n                    type: array\n                  uploadProxyReplicas:\n                    description: UploadproxyReplicas set Replicas for cdi-uploadproxy\n                    format: int32\n                    type: integer\n                type: object\n              priorityClass:\n                description: PriorityClass of the CDI control plane\n                type: string\n              uninstallStrategy:\n                description: CDIUninstallStrategy defines the state to leave CDI on\n                  uninstall\n                enum:\n                - RemoveWorkloads\n                - BlockUninstallIfWorkloadsExist\n                type: string\n              workload:\n                description: Restrict on which nodes CDI workload pods will be scheduled\n                properties:\n                  affinity:\n                    description: |-\n                      affinity enables pod affinity/anti-affinity placement expanding the types of constraints\n                      that can be expressed with nodeSelector.\n                      affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector\n                      See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity\n                    properties:\n                      nodeAffinity:\n                        description: Describes node affinity scheduling rules for\n                          the pod.\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node matches the corresponding matchExpressions; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: |-\n                                An empty preferred scheduling term matches all objects with implicit weight 0\n                                (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).\n                              properties:\n                                preference:\n                                  description: A node selector term, associated with\n                                    the corresponding weight.\n                                  properties:\n                                    matchExpressions:\n                                      description: A list of node selector requirements\n                                        by node's labels.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchFields:\n                                      description: A list of node selector requirements\n                                        by node's fields.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                weight:\n                                  description: Weight associated with matching the\n                                    corresponding nodeSelectorTerm, in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - preference\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to an update), the system\n                              may or may not try to eventually evict the pod from its node.\n                            properties:\n                              nodeSelectorTerms:\n                                description: Required. A list of node selector terms.\n                                  The terms are ORed.\n                                items:\n                                  description: |-\n                                    A null or empty node selector term matches no objects. The requirements of\n                                    them are ANDed.\n                                    The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.\n                                  properties:\n                                    matchExpressions:\n                                      description: A list of node selector requirements\n                                        by node's labels.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchFields:\n                                      description: A list of node selector requirements\n                                        by node's fields.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            required:\n                            - nodeSelectorTerms\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      podAffinity:\n                        description: Describes pod affinity scheduling rules (e.g.\n                          co-locate this pod in the same node, zone, etc. as some\n                          other pod(s)).\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: The weights of all of the matched WeightedPodAffinityTerm\n                                fields are added per-node to find the most preferred\n                                node(s)\n                              properties:\n                                podAffinityTerm:\n                                  description: Required. A pod affinity term, associated\n                                    with the corresponding weight.\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                weight:\n                                  description: |-\n                                    weight associated with matching the corresponding podAffinityTerm,\n                                    in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - podAffinityTerm\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to a pod label update), the\n                              system may or may not try to eventually evict the pod from its node.\n                              When there are multiple elements, the lists of nodes corresponding to each\n                              podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                            items:\n                              description: |-\n                                Defines a set of pods (namely those matching the labelSelector\n                                relative to the given namespace(s)) that this pod should be\n                                co-located (affinity) or not co-located (anti-affinity) with,\n                                where co-located is defined as running on a node whose value of\n                                the label with key <topologyKey> matches that of any node on which\n                                a pod of the set of pods is running\n                              properties:\n                                labelSelector:\n                                  description: |-\n                                    A label query over a set of resources, in this case pods.\n                                    If it's null, this PodAffinityTerm matches with no Pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                matchLabelKeys:\n                                  description: |-\n                                    MatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                mismatchLabelKeys:\n                                  description: |-\n                                    MismatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                namespaceSelector:\n                                  description: |-\n                                    A label query over the set of namespaces that the term applies to.\n                                    The term is applied to the union of the namespaces selected by this field\n                                    and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list means \"this pod's namespace\".\n                                    An empty selector ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: |-\n                                    namespaces specifies a static list of namespace names that the term applies to.\n                                    The term is applied to the union of the namespaces listed in this field\n                                    and the ones selected by namespaceSelector.\n                                    null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                topologyKey:\n                                  description: |-\n                                    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                    the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey matches that of any node on which any of the\n                                    selected pods is running.\n                                    Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        type: object\n                      podAntiAffinity:\n                        description: Describes pod anti-affinity scheduling rules\n                          (e.g. avoid putting this pod in the same node, zone, etc.\n                          as some other pod(s)).\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the anti-affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling anti-affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: The weights of all of the matched WeightedPodAffinityTerm\n                                fields are added per-node to find the most preferred\n                                node(s)\n                              properties:\n                                podAffinityTerm:\n                                  description: Required. A pod affinity term, associated\n                                    with the corresponding weight.\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                weight:\n                                  description: |-\n                                    weight associated with matching the corresponding podAffinityTerm,\n                                    in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - podAffinityTerm\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the anti-affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the anti-affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to a pod label update), the\n                              system may or may not try to eventually evict the pod from its node.\n                              When there are multiple elements, the lists of nodes corresponding to each\n                              podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                            items:\n                              description: |-\n                                Defines a set of pods (namely those matching the labelSelector\n                                relative to the given namespace(s)) that this pod should be\n                                co-located (affinity) or not co-located (anti-affinity) with,\n                                where co-located is defined as running on a node whose value of\n                                the label with key <topologyKey> matches that of any node on which\n                                a pod of the set of pods is running\n                              properties:\n                                labelSelector:\n                                  description: |-\n                                    A label query over a set of resources, in this case pods.\n                                    If it's null, this PodAffinityTerm matches with no Pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                matchLabelKeys:\n                                  description: |-\n                                    MatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                mismatchLabelKeys:\n                                  description: |-\n                                    MismatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                namespaceSelector:\n                                  description: |-\n                                    A label query over the set of namespaces that the term applies to.\n                                    The term is applied to the union of the namespaces selected by this field\n                                    and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list means \"this pod's namespace\".\n                                    An empty selector ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: |-\n                                    namespaces specifies a static list of namespace names that the term applies to.\n                                    The term is applied to the union of the namespaces listed in this field\n                                    and the ones selected by namespaceSelector.\n                                    null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                topologyKey:\n                                  description: |-\n                                    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                    the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey matches that of any node on which any of the\n                                    selected pods is running.\n                                    Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        type: object\n                    type: object\n                  nodeSelector:\n                    additionalProperties:\n                      type: string\n                    description: |-\n                      nodeSelector is the node selector applied to the relevant kind of pods\n                      It specifies a map of key-value pairs: for the pod to be eligible to run on a node,\n                      the node must have each of the indicated key-value pairs as labels\n                      (it can have additional labels as well).\n                      See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector\n                    type: object\n                  tolerations:\n                    description: |-\n                      tolerations is a list of tolerations applied to the relevant kind of pods\n                      See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info.\n                      These are additional tolerations other than default ones.\n                    items:\n                      description: |-\n                        The pod this Toleration is attached to tolerates any taint that matches\n                        the triple <key,value,effect> using the matching operator <operator>.\n                      properties:\n                        effect:\n                          description: |-\n                            Effect indicates the taint effect to match. Empty means match all taint effects.\n                            When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.\n                          type: string\n                        key:\n                          description: |-\n                            Key is the taint key that the toleration applies to. Empty means match all taint keys.\n                            If the key is empty, operator must be Exists; this combination means to match all values and all keys.\n                          type: string\n                        operator:\n                          description: |-\n                            Operator represents a key's relationship to the value.\n                            Valid operators are Exists and Equal. Defaults to Equal.\n                            Exists is equivalent to wildcard for value, so that a pod can\n                            tolerate all taints of a particular category.\n                          type: string\n                        tolerationSeconds:\n                          description: |-\n                            TolerationSeconds represents the period of time the toleration (which must be\n                            of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,\n                            it is not set, which means tolerate the taint forever (do not evict). Zero and\n                            negative values will be treated as 0 (evict immediately) by the system.\n                          format: int64\n                          type: integer\n                        value:\n                          description: |-\n                            Value is the taint value the toleration matches to.\n                            If the operator is Exists, the value should be empty, otherwise just a regular string.\n                          type: string\n                      type: object\n                    type: array\n                type: object\n            type: object\n          status:\n            description: CDIStatus defines the status of the installation\n            properties:\n              conditions:\n                description: A list of current conditions of the resource\n                items:\n                  description: |-\n                    Condition represents the state of the operator's\n                    reconciliation functionality.\n                  properties:\n                    lastHeartbeatTime:\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      type: string\n                    reason:\n                      type: string\n                    status:\n                      type: string\n                    type:\n                      description: ConditionType is the state of the operator's reconciliation\n                        functionality.\n                      type: string\n                  required:\n                  - status\n                  - type\n                  type: object\n                type: array\n              observedVersion:\n                description: The observed version of the resource\n                type: string\n              operatorVersion:\n                description: The version of the resource as defined by the operator\n                type: string\n              phase:\n                description: Phase is the current phase of the deployment\n                type: string\n              targetVersion:\n                description: The desired version of the resource\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources: {}\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    - jsonPath: .status.phase\n      name: Phase\n      type: string\n    name: v1beta1\n    schema:\n      openAPIV3Schema:\n        description: CDI is the CDI Operator CRD\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: CDISpec defines our specification for the CDI installation\n            properties:\n              certConfig:\n                description: certificate configuration\n                properties:\n                  ca:\n                    description: |-\n                      CA configuration\n                      CA certs are kept in the CA bundle as long as they are valid\n                    properties:\n                      duration:\n                        description: The requested 'duration' (i.e. lifetime) of the\n                          Certificate.\n                        type: string\n                      renewBefore:\n                        description: |-\n                          The amount of time before the currently issued certificate's `notAfter`\n                          time that we will begin to attempt to renew the certificate.\n                        type: string\n                    type: object\n                  client:\n                    description: |-\n                      Client configuration\n                      Certs are rotated and discarded\n                    properties:\n                      duration:\n                        description: The requested 'duration' (i.e. lifetime) of the\n                          Certificate.\n                        type: string\n                      renewBefore:\n                        description: |-\n                          The amount of time before the currently issued certificate's `notAfter`\n                          time that we will begin to attempt to renew the certificate.\n                        type: string\n                    type: object\n                  server:\n                    description: |-\n                      Server configuration\n                      Certs are rotated and discarded\n                    properties:\n                      duration:\n                        description: The requested 'duration' (i.e. lifetime) of the\n                          Certificate.\n                        type: string\n                      renewBefore:\n                        description: |-\n                          The amount of time before the currently issued certificate's `notAfter`\n                          time that we will begin to attempt to renew the certificate.\n                        type: string\n                    type: object\n                type: object\n              cloneStrategyOverride:\n                description: 'Clone strategy override: should we use a host-assisted\n                  copy even if snapshots are available?'\n                enum:\n                - copy\n                - snapshot\n                - csi-clone\n                type: string\n              config:\n                description: CDIConfig at CDI level\n                properties:\n                  dataVolumeTTLSeconds:\n                    description: |-\n                      DataVolumeTTLSeconds is the time in seconds after DataVolume completion it can be garbage collected. Disabled by default.\n                      Deprecated: Removed in v1.62.\n                    format: int32\n                    type: integer\n                  featureGates:\n                    description: FeatureGates are a list of specific enabled feature\n                      gates\n                    items:\n                      type: string\n                    type: array\n                  filesystemOverhead:\n                    description: FilesystemOverhead describes the space reserved for\n                      overhead when using Filesystem volumes. A value is between 0\n                      and 1, if not defined it is 0.055 (5.5% overhead)\n                    properties:\n                      global:\n                        description: Global is how much space of a Filesystem volume\n                          should be reserved for overhead. This value is used unless\n                          overridden by a more specific value (per storageClass)\n                        pattern: ^(0(?:\\.\\d{1,3})?|1)$\n                        type: string\n                      storageClass:\n                        additionalProperties:\n                          description: |-\n                            Percent is a string that can only be a value between [0,1)\n                            (Note: we actually rely on reconcile to reject invalid values)\n                          pattern: ^(0(?:\\.\\d{1,3})?|1)$\n                          type: string\n                        description: StorageClass specifies how much space of a Filesystem\n                          volume should be reserved for safety. The keys are the storageClass\n                          and the values are the overhead. This value overrides the\n                          global value\n                        type: object\n                    type: object\n                  imagePullSecrets:\n                    description: The imagePullSecrets used to pull the container images\n                    items:\n                      description: |-\n                        LocalObjectReference contains enough information to let you locate the\n                        referenced object inside the same namespace.\n                      properties:\n                        name:\n                          default: \"\"\n                          description: |-\n                            Name of the referent.\n                            This field is effectively required, but due to backwards compatibility is\n                            allowed to be empty. Instances of this type with an empty value here are\n                            almost certainly wrong.\n                            TODO: Add other useful fields. apiVersion, kind, uid?\n                            More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                            TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.\n                          type: string\n                      type: object\n                      x-kubernetes-map-type: atomic\n                    type: array\n                  importProxy:\n                    description: ImportProxy contains importer pod proxy configuration.\n                    properties:\n                      HTTPProxy:\n                        description: HTTPProxy is the URL http://<username>:<pswd>@<ip>:<port>\n                          of the import proxy for HTTP requests.  Empty means unset\n                          and will not result in the import pod env var.\n                        type: string\n                      HTTPSProxy:\n                        description: HTTPSProxy is the URL https://<username>:<pswd>@<ip>:<port>\n                          of the import proxy for HTTPS requests.  Empty means unset\n                          and will not result in the import pod env var.\n                        type: string\n                      noProxy:\n                        description: NoProxy is a comma-separated list of hostnames\n                          and/or CIDRs for which the proxy should not be used. Empty\n                          means unset and will not result in the import pod env var.\n                        type: string\n                      trustedCAProxy:\n                        description: \"TrustedCAProxy is the name of a ConfigMap in\n                          the cdi namespace that contains a user-provided trusted\n                          certificate authority (CA) bundle.\\nThe TrustedCAProxy ConfigMap\n                          is consumed by the DataImportCron controller for creating\n                          cronjobs, and by the import controller referring a copy\n                          of the ConfigMap in the import namespace.\\nHere is an example\n                          of the ConfigMap (in yaml):\\n\\n\\napiVersion: v1\\nkind: ConfigMap\\nmetadata:\\n\n                          \\ name: my-ca-proxy-cm\\n  namespace: cdi\\ndata:\\n  ca.pem:\n                          |\\n    -----BEGIN CERTIFICATE-----\\n\\t   ... <base64 encoded\n                          cert> ...\\n\\t   -----END CERTIFICATE-----\"\n                        type: string\n                    type: object\n                  insecureRegistries:\n                    description: InsecureRegistries is a list of TLS disabled registries\n                    items:\n                      type: string\n                    type: array\n                  logVerbosity:\n                    description: LogVerbosity overrides the default verbosity level\n                      used to initialize loggers\n                    format: int32\n                    type: integer\n                  podResourceRequirements:\n                    description: ResourceRequirements describes the compute resource\n                      requirements.\n                    properties:\n                      claims:\n                        description: |-\n                          Claims lists the names of resources, defined in spec.resourceClaims,\n                          that are used by this container.\n\n\n                          This is an alpha field and requires enabling the\n                          DynamicResourceAllocation feature gate.\n\n\n                          This field is immutable. It can only be set for containers.\n                        items:\n                          description: ResourceClaim references one entry in PodSpec.ResourceClaims.\n                          properties:\n                            name:\n                              description: |-\n                                Name must match the name of one entry in pod.spec.resourceClaims of\n                                the Pod where this field is used. It makes that resource available\n                                inside a container.\n                              type: string\n                          required:\n                          - name\n                          type: object\n                        type: array\n                        x-kubernetes-list-map-keys:\n                        - name\n                        x-kubernetes-list-type: map\n                      limits:\n                        additionalProperties:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                          x-kubernetes-int-or-string: true\n                        description: |-\n                          Limits describes the maximum amount of compute resources allowed.\n                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                        type: object\n                      requests:\n                        additionalProperties:\n                          anyOf:\n                          - type: integer\n                          - type: string\n                          pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                          x-kubernetes-int-or-string: true\n                        description: |-\n                          Requests describes the minimum amount of compute resources required.\n                          If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,\n                          otherwise to an implementation-defined value. Requests cannot exceed Limits.\n                          More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                        type: object\n                    type: object\n                  preallocation:\n                    description: Preallocation controls whether storage for DataVolumes\n                      should be allocated in advance.\n                    type: boolean\n                  scratchSpaceStorageClass:\n                    description: 'Override the storage class to used for scratch space\n                      during transfer operations. The scratch space storage class\n                      is determined in the following order: 1. value of scratchSpaceStorageClass,\n                      if that doesn''t exist, use the default storage class, if there\n                      is no default storage class, use the storage class of the DataVolume,\n                      if no storage class specified, use no storage class for scratch\n                      space'\n                    type: string\n                  tlsSecurityProfile:\n                    description: TLSSecurityProfile is used by operators to apply\n                      cluster-wide TLS security settings to operands.\n                    properties:\n                      custom:\n                        description: |-\n                          custom is a user-defined TLS security profile. Be extremely careful using a custom\n                          profile as invalid configurations can be catastrophic. An example custom profile\n                          looks like this:\n\n\n                            ciphers:\n                              - ECDHE-ECDSA-CHACHA20-POLY1305\n                              - ECDHE-RSA-CHACHA20-POLY1305\n                              - ECDHE-RSA-AES128-GCM-SHA256\n                              - ECDHE-ECDSA-AES128-GCM-SHA256\n                            minTLSVersion: VersionTLS11\n                        nullable: true\n                        properties:\n                          ciphers:\n                            description: |-\n                              ciphers is used to specify the cipher algorithms that are negotiated\n                              during the TLS handshake.  Operators may remove entries their operands\n                              do not support.  For example, to use DES-CBC3-SHA  (yaml):\n\n\n                                ciphers:\n                                  - DES-CBC3-SHA\n                            items:\n                              type: string\n                            type: array\n                          minTLSVersion:\n                            description: |-\n                              minTLSVersion is used to specify the minimal version of the TLS protocol\n                              that is negotiated during the TLS handshake. For example, to use TLS\n                              versions 1.1, 1.2 and 1.3 (yaml):\n\n\n                                minTLSVersion: VersionTLS11\n\n\n                              NOTE: currently the highest minTLSVersion allowed is VersionTLS12\n                            enum:\n                            - VersionTLS10\n                            - VersionTLS11\n                            - VersionTLS12\n                            - VersionTLS13\n                            type: string\n                        required:\n                        - ciphers\n                        - minTLSVersion\n                        type: object\n                      intermediate:\n                        description: |-\n                          intermediate is a TLS security profile based on:\n\n\n                          https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29\n\n\n                          and looks like this (yaml):\n\n\n                            ciphers:\n                              - TLS_AES_128_GCM_SHA256\n                              - TLS_AES_256_GCM_SHA384\n                              - TLS_CHACHA20_POLY1305_SHA256\n                              - ECDHE-ECDSA-AES128-GCM-SHA256\n                              - ECDHE-RSA-AES128-GCM-SHA256\n                              - ECDHE-ECDSA-AES256-GCM-SHA384\n                              - ECDHE-RSA-AES256-GCM-SHA384\n                              - ECDHE-ECDSA-CHACHA20-POLY1305\n                              - ECDHE-RSA-CHACHA20-POLY1305\n                              - DHE-RSA-AES128-GCM-SHA256\n                              - DHE-RSA-AES256-GCM-SHA384\n                            minTLSVersion: VersionTLS12\n                        nullable: true\n                        type: object\n                      modern:\n                        description: |-\n                          modern is a TLS security profile based on:\n\n\n                          https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility\n\n\n                          and looks like this (yaml):\n\n\n                            ciphers:\n                              - TLS_AES_128_GCM_SHA256\n                              - TLS_AES_256_GCM_SHA384\n                              - TLS_CHACHA20_POLY1305_SHA256\n                            minTLSVersion: VersionTLS13\n\n\n                          NOTE: Currently unsupported.\n                        nullable: true\n                        type: object\n                      old:\n                        description: |-\n                          old is a TLS security profile based on:\n\n\n                          https://wiki.mozilla.org/Security/Server_Side_TLS#Old_backward_compatibility\n\n\n                          and looks like this (yaml):\n\n\n                            ciphers:\n                              - TLS_AES_128_GCM_SHA256\n                              - TLS_AES_256_GCM_SHA384\n                              - TLS_CHACHA20_POLY1305_SHA256\n                              - ECDHE-ECDSA-AES128-GCM-SHA256\n                              - ECDHE-RSA-AES128-GCM-SHA256\n                              - ECDHE-ECDSA-AES256-GCM-SHA384\n                              - ECDHE-RSA-AES256-GCM-SHA384\n                              - ECDHE-ECDSA-CHACHA20-POLY1305\n                              - ECDHE-RSA-CHACHA20-POLY1305\n                              - DHE-RSA-AES128-GCM-SHA256\n                              - DHE-RSA-AES256-GCM-SHA384\n                              - DHE-RSA-CHACHA20-POLY1305\n                              - ECDHE-ECDSA-AES128-SHA256\n                              - ECDHE-RSA-AES128-SHA256\n                              - ECDHE-ECDSA-AES128-SHA\n                              - ECDHE-RSA-AES128-SHA\n                              - ECDHE-ECDSA-AES256-SHA384\n                              - ECDHE-RSA-AES256-SHA384\n                              - ECDHE-ECDSA-AES256-SHA\n                              - ECDHE-RSA-AES256-SHA\n                              - DHE-RSA-AES128-SHA256\n                              - DHE-RSA-AES256-SHA256\n                              - AES128-GCM-SHA256\n                              - AES256-GCM-SHA384\n                              - AES128-SHA256\n                              - AES256-SHA256\n                              - AES128-SHA\n                              - AES256-SHA\n                              - DES-CBC3-SHA\n                            minTLSVersion: VersionTLS10\n                        nullable: true\n                        type: object\n                      type:\n                        description: |-\n                          type is one of Old, Intermediate, Modern or Custom. Custom provides\n                          the ability to specify individual TLS security profile parameters.\n                          Old, Intermediate and Modern are TLS security profiles based on:\n\n\n                          https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations\n\n\n                          The profiles are intent based, so they may change over time as new ciphers are developed and existing ciphers\n                          are found to be insecure.  Depending on precisely which ciphers are available to a process, the list may be\n                          reduced.\n\n\n                          Note that the Modern profile is currently not supported because it is not\n                          yet well adopted by common software libraries.\n                        enum:\n                        - Old\n                        - Intermediate\n                        - Modern\n                        - Custom\n                        type: string\n                    type: object\n                  uploadProxyURLOverride:\n                    description: Override the URL used when uploading to a DataVolume\n                    type: string\n                type: object\n              customizeComponents:\n                description: CustomizeComponents defines patches for components deployed\n                  by the CDI operator.\n                properties:\n                  flags:\n                    description: Configure the value used for deployment and daemonset\n                      resources\n                    properties:\n                      api:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      controller:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      uploadProxy:\n                        additionalProperties:\n                          type: string\n                        type: object\n                    type: object\n                  patches:\n                    items:\n                      description: CustomizeComponentsPatch defines a patch for some\n                        resource.\n                      properties:\n                        patch:\n                          type: string\n                        resourceName:\n                          minLength: 1\n                          type: string\n                        resourceType:\n                          minLength: 1\n                          type: string\n                        type:\n                          description: PatchType defines the patch type.\n                          type: string\n                      required:\n                      - patch\n                      - resourceName\n                      - resourceType\n                      - type\n                      type: object\n                    type: array\n                    x-kubernetes-list-type: atomic\n                type: object\n              imagePullPolicy:\n                description: PullPolicy describes a policy for if/when to pull a container\n                  image\n                enum:\n                - Always\n                - IfNotPresent\n                - Never\n                type: string\n              infra:\n                description: Selectors and tolerations that should apply to cdi infrastructure\n                  components\n                properties:\n                  affinity:\n                    description: |-\n                      affinity enables pod affinity/anti-affinity placement expanding the types of constraints\n                      that can be expressed with nodeSelector.\n                      affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector\n                      See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity\n                    properties:\n                      nodeAffinity:\n                        description: Describes node affinity scheduling rules for\n                          the pod.\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node matches the corresponding matchExpressions; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: |-\n                                An empty preferred scheduling term matches all objects with implicit weight 0\n                                (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).\n                              properties:\n                                preference:\n                                  description: A node selector term, associated with\n                                    the corresponding weight.\n                                  properties:\n                                    matchExpressions:\n                                      description: A list of node selector requirements\n                                        by node's labels.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchFields:\n                                      description: A list of node selector requirements\n                                        by node's fields.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                weight:\n                                  description: Weight associated with matching the\n                                    corresponding nodeSelectorTerm, in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - preference\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to an update), the system\n                              may or may not try to eventually evict the pod from its node.\n                            properties:\n                              nodeSelectorTerms:\n                                description: Required. A list of node selector terms.\n                                  The terms are ORed.\n                                items:\n                                  description: |-\n                                    A null or empty node selector term matches no objects. The requirements of\n                                    them are ANDed.\n                                    The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.\n                                  properties:\n                                    matchExpressions:\n                                      description: A list of node selector requirements\n                                        by node's labels.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchFields:\n                                      description: A list of node selector requirements\n                                        by node's fields.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            required:\n                            - nodeSelectorTerms\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      podAffinity:\n                        description: Describes pod affinity scheduling rules (e.g.\n                          co-locate this pod in the same node, zone, etc. as some\n                          other pod(s)).\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: The weights of all of the matched WeightedPodAffinityTerm\n                                fields are added per-node to find the most preferred\n                                node(s)\n                              properties:\n                                podAffinityTerm:\n                                  description: Required. A pod affinity term, associated\n                                    with the corresponding weight.\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                weight:\n                                  description: |-\n                                    weight associated with matching the corresponding podAffinityTerm,\n                                    in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - podAffinityTerm\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to a pod label update), the\n                              system may or may not try to eventually evict the pod from its node.\n                              When there are multiple elements, the lists of nodes corresponding to each\n                              podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                            items:\n                              description: |-\n                                Defines a set of pods (namely those matching the labelSelector\n                                relative to the given namespace(s)) that this pod should be\n                                co-located (affinity) or not co-located (anti-affinity) with,\n                                where co-located is defined as running on a node whose value of\n                                the label with key <topologyKey> matches that of any node on which\n                                a pod of the set of pods is running\n                              properties:\n                                labelSelector:\n                                  description: |-\n                                    A label query over a set of resources, in this case pods.\n                                    If it's null, this PodAffinityTerm matches with no Pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                matchLabelKeys:\n                                  description: |-\n                                    MatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                mismatchLabelKeys:\n                                  description: |-\n                                    MismatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                namespaceSelector:\n                                  description: |-\n                                    A label query over the set of namespaces that the term applies to.\n                                    The term is applied to the union of the namespaces selected by this field\n                                    and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list means \"this pod's namespace\".\n                                    An empty selector ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: |-\n                                    namespaces specifies a static list of namespace names that the term applies to.\n                                    The term is applied to the union of the namespaces listed in this field\n                                    and the ones selected by namespaceSelector.\n                                    null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                topologyKey:\n                                  description: |-\n                                    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                    the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey matches that of any node on which any of the\n                                    selected pods is running.\n                                    Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        type: object\n                      podAntiAffinity:\n                        description: Describes pod anti-affinity scheduling rules\n                          (e.g. avoid putting this pod in the same node, zone, etc.\n                          as some other pod(s)).\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the anti-affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling anti-affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: The weights of all of the matched WeightedPodAffinityTerm\n                                fields are added per-node to find the most preferred\n                                node(s)\n                              properties:\n                                podAffinityTerm:\n                                  description: Required. A pod affinity term, associated\n                                    with the corresponding weight.\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                weight:\n                                  description: |-\n                                    weight associated with matching the corresponding podAffinityTerm,\n                                    in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - podAffinityTerm\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the anti-affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the anti-affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to a pod label update), the\n                              system may or may not try to eventually evict the pod from its node.\n                              When there are multiple elements, the lists of nodes corresponding to each\n                              podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                            items:\n                              description: |-\n                                Defines a set of pods (namely those matching the labelSelector\n                                relative to the given namespace(s)) that this pod should be\n                                co-located (affinity) or not co-located (anti-affinity) with,\n                                where co-located is defined as running on a node whose value of\n                                the label with key <topologyKey> matches that of any node on which\n                                a pod of the set of pods is running\n                              properties:\n                                labelSelector:\n                                  description: |-\n                                    A label query over a set of resources, in this case pods.\n                                    If it's null, this PodAffinityTerm matches with no Pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                matchLabelKeys:\n                                  description: |-\n                                    MatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                mismatchLabelKeys:\n                                  description: |-\n                                    MismatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                namespaceSelector:\n                                  description: |-\n                                    A label query over the set of namespaces that the term applies to.\n                                    The term is applied to the union of the namespaces selected by this field\n                                    and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list means \"this pod's namespace\".\n                                    An empty selector ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: |-\n                                    namespaces specifies a static list of namespace names that the term applies to.\n                                    The term is applied to the union of the namespaces listed in this field\n                                    and the ones selected by namespaceSelector.\n                                    null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                topologyKey:\n                                  description: |-\n                                    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                    the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey matches that of any node on which any of the\n                                    selected pods is running.\n                                    Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        type: object\n                    type: object\n                  apiServerReplicas:\n                    description: ApiserverReplicas set Replicas for cdi-apiserver\n                    format: int32\n                    type: integer\n                  deploymentReplicas:\n                    description: DeploymentReplicas set Replicas for cdi-deployment\n                    format: int32\n                    type: integer\n                  nodeSelector:\n                    additionalProperties:\n                      type: string\n                    description: |-\n                      nodeSelector is the node selector applied to the relevant kind of pods\n                      It specifies a map of key-value pairs: for the pod to be eligible to run on a node,\n                      the node must have each of the indicated key-value pairs as labels\n                      (it can have additional labels as well).\n                      See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector\n                    type: object\n                  tolerations:\n                    description: |-\n                      tolerations is a list of tolerations applied to the relevant kind of pods\n                      See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info.\n                      These are additional tolerations other than default ones.\n                    items:\n                      description: |-\n                        The pod this Toleration is attached to tolerates any taint that matches\n                        the triple <key,value,effect> using the matching operator <operator>.\n                      properties:\n                        effect:\n                          description: |-\n                            Effect indicates the taint effect to match. Empty means match all taint effects.\n                            When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.\n                          type: string\n                        key:\n                          description: |-\n                            Key is the taint key that the toleration applies to. Empty means match all taint keys.\n                            If the key is empty, operator must be Exists; this combination means to match all values and all keys.\n                          type: string\n                        operator:\n                          description: |-\n                            Operator represents a key's relationship to the value.\n                            Valid operators are Exists and Equal. Defaults to Equal.\n                            Exists is equivalent to wildcard for value, so that a pod can\n                            tolerate all taints of a particular category.\n                          type: string\n                        tolerationSeconds:\n                          description: |-\n                            TolerationSeconds represents the period of time the toleration (which must be\n                            of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,\n                            it is not set, which means tolerate the taint forever (do not evict). Zero and\n                            negative values will be treated as 0 (evict immediately) by the system.\n                          format: int64\n                          type: integer\n                        value:\n                          description: |-\n                            Value is the taint value the toleration matches to.\n                            If the operator is Exists, the value should be empty, otherwise just a regular string.\n                          type: string\n                      type: object\n                    type: array\n                  uploadProxyReplicas:\n                    description: UploadproxyReplicas set Replicas for cdi-uploadproxy\n                    format: int32\n                    type: integer\n                type: object\n              priorityClass:\n                description: PriorityClass of the CDI control plane\n                type: string\n              uninstallStrategy:\n                description: CDIUninstallStrategy defines the state to leave CDI on\n                  uninstall\n                enum:\n                - RemoveWorkloads\n                - BlockUninstallIfWorkloadsExist\n                type: string\n              workload:\n                description: Restrict on which nodes CDI workload pods will be scheduled\n                properties:\n                  affinity:\n                    description: |-\n                      affinity enables pod affinity/anti-affinity placement expanding the types of constraints\n                      that can be expressed with nodeSelector.\n                      affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector\n                      See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity\n                    properties:\n                      nodeAffinity:\n                        description: Describes node affinity scheduling rules for\n                          the pod.\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node matches the corresponding matchExpressions; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: |-\n                                An empty preferred scheduling term matches all objects with implicit weight 0\n                                (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).\n                              properties:\n                                preference:\n                                  description: A node selector term, associated with\n                                    the corresponding weight.\n                                  properties:\n                                    matchExpressions:\n                                      description: A list of node selector requirements\n                                        by node's labels.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchFields:\n                                      description: A list of node selector requirements\n                                        by node's fields.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                weight:\n                                  description: Weight associated with matching the\n                                    corresponding nodeSelectorTerm, in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - preference\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to an update), the system\n                              may or may not try to eventually evict the pod from its node.\n                            properties:\n                              nodeSelectorTerms:\n                                description: Required. A list of node selector terms.\n                                  The terms are ORed.\n                                items:\n                                  description: |-\n                                    A null or empty node selector term matches no objects. The requirements of\n                                    them are ANDed.\n                                    The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.\n                                  properties:\n                                    matchExpressions:\n                                      description: A list of node selector requirements\n                                        by node's labels.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchFields:\n                                      description: A list of node selector requirements\n                                        by node's fields.\n                                      items:\n                                        description: |-\n                                          A node selector requirement is a selector that contains values, a key, and an operator\n                                          that relates the key and values.\n                                        properties:\n                                          key:\n                                            description: The label key that the selector\n                                              applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              Represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              An array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. If the operator is Gt or Lt, the values\n                                              array must have a single element, which will be interpreted as an integer.\n                                              This array is replaced during a strategic merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            required:\n                            - nodeSelectorTerms\n                            type: object\n                            x-kubernetes-map-type: atomic\n                        type: object\n                      podAffinity:\n                        description: Describes pod affinity scheduling rules (e.g.\n                          co-locate this pod in the same node, zone, etc. as some\n                          other pod(s)).\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: The weights of all of the matched WeightedPodAffinityTerm\n                                fields are added per-node to find the most preferred\n                                node(s)\n                              properties:\n                                podAffinityTerm:\n                                  description: Required. A pod affinity term, associated\n                                    with the corresponding weight.\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                weight:\n                                  description: |-\n                                    weight associated with matching the corresponding podAffinityTerm,\n                                    in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - podAffinityTerm\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to a pod label update), the\n                              system may or may not try to eventually evict the pod from its node.\n                              When there are multiple elements, the lists of nodes corresponding to each\n                              podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                            items:\n                              description: |-\n                                Defines a set of pods (namely those matching the labelSelector\n                                relative to the given namespace(s)) that this pod should be\n                                co-located (affinity) or not co-located (anti-affinity) with,\n                                where co-located is defined as running on a node whose value of\n                                the label with key <topologyKey> matches that of any node on which\n                                a pod of the set of pods is running\n                              properties:\n                                labelSelector:\n                                  description: |-\n                                    A label query over a set of resources, in this case pods.\n                                    If it's null, this PodAffinityTerm matches with no Pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                matchLabelKeys:\n                                  description: |-\n                                    MatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                mismatchLabelKeys:\n                                  description: |-\n                                    MismatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                namespaceSelector:\n                                  description: |-\n                                    A label query over the set of namespaces that the term applies to.\n                                    The term is applied to the union of the namespaces selected by this field\n                                    and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list means \"this pod's namespace\".\n                                    An empty selector ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: |-\n                                    namespaces specifies a static list of namespace names that the term applies to.\n                                    The term is applied to the union of the namespaces listed in this field\n                                    and the ones selected by namespaceSelector.\n                                    null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                topologyKey:\n                                  description: |-\n                                    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                    the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey matches that of any node on which any of the\n                                    selected pods is running.\n                                    Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        type: object\n                      podAntiAffinity:\n                        description: Describes pod anti-affinity scheduling rules\n                          (e.g. avoid putting this pod in the same node, zone, etc.\n                          as some other pod(s)).\n                        properties:\n                          preferredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              The scheduler will prefer to schedule pods to nodes that satisfy\n                              the anti-affinity expressions specified by this field, but it may choose\n                              a node that violates one or more of the expressions. The node that is\n                              most preferred is the one with the greatest sum of weights, i.e.\n                              for each node that meets all of the scheduling requirements (resource\n                              request, requiredDuringScheduling anti-affinity expressions, etc.),\n                              compute a sum by iterating through the elements of this field and adding\n                              \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                              node(s) with the highest sum are the most preferred.\n                            items:\n                              description: The weights of all of the matched WeightedPodAffinityTerm\n                                fields are added per-node to find the most preferred\n                                node(s)\n                              properties:\n                                podAffinityTerm:\n                                  description: Required. A pod affinity term, associated\n                                    with the corresponding weight.\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                weight:\n                                  description: |-\n                                    weight associated with matching the corresponding podAffinityTerm,\n                                    in the range 1-100.\n                                  format: int32\n                                  type: integer\n                              required:\n                              - podAffinityTerm\n                              - weight\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          requiredDuringSchedulingIgnoredDuringExecution:\n                            description: |-\n                              If the anti-affinity requirements specified by this field are not met at\n                              scheduling time, the pod will not be scheduled onto the node.\n                              If the anti-affinity requirements specified by this field cease to be met\n                              at some point during pod execution (e.g. due to a pod label update), the\n                              system may or may not try to eventually evict the pod from its node.\n                              When there are multiple elements, the lists of nodes corresponding to each\n                              podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                            items:\n                              description: |-\n                                Defines a set of pods (namely those matching the labelSelector\n                                relative to the given namespace(s)) that this pod should be\n                                co-located (affinity) or not co-located (anti-affinity) with,\n                                where co-located is defined as running on a node whose value of\n                                the label with key <topologyKey> matches that of any node on which\n                                a pod of the set of pods is running\n                              properties:\n                                labelSelector:\n                                  description: |-\n                                    A label query over a set of resources, in this case pods.\n                                    If it's null, this PodAffinityTerm matches with no Pods.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                matchLabelKeys:\n                                  description: |-\n                                    MatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                    Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                mismatchLabelKeys:\n                                  description: |-\n                                    MismatchLabelKeys is a set of pod label keys to select which pods will\n                                    be taken into consideration. The keys are used to lookup values from the\n                                    incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)`\n                                    to select the group of existing pods which pods will be taken into consideration\n                                    for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                    pod labels will be ignored. The default value is empty.\n                                    The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                    Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                    This is an alpha field and requires enabling MatchLabelKeysInPodAffinity feature gate.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                namespaceSelector:\n                                  description: |-\n                                    A label query over the set of namespaces that the term applies to.\n                                    The term is applied to the union of the namespaces selected by this field\n                                    and the ones listed in the namespaces field.\n                                    null selector and null or empty namespaces list means \"this pod's namespace\".\n                                    An empty selector ({}) matches all namespaces.\n                                  properties:\n                                    matchExpressions:\n                                      description: matchExpressions is a list of label\n                                        selector requirements. The requirements are\n                                        ANDed.\n                                      items:\n                                        description: |-\n                                          A label selector requirement is a selector that contains values, a key, and an operator that\n                                          relates the key and values.\n                                        properties:\n                                          key:\n                                            description: key is the label key that\n                                              the selector applies to.\n                                            type: string\n                                          operator:\n                                            description: |-\n                                              operator represents a key's relationship to a set of values.\n                                              Valid operators are In, NotIn, Exists and DoesNotExist.\n                                            type: string\n                                          values:\n                                            description: |-\n                                              values is an array of string values. If the operator is In or NotIn,\n                                              the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                              the values array must be empty. This array is replaced during a strategic\n                                              merge patch.\n                                            items:\n                                              type: string\n                                            type: array\n                                            x-kubernetes-list-type: atomic\n                                        required:\n                                        - key\n                                        - operator\n                                        type: object\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    matchLabels:\n                                      additionalProperties:\n                                        type: string\n                                      description: |-\n                                        matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                        map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                        operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                      type: object\n                                  type: object\n                                  x-kubernetes-map-type: atomic\n                                namespaces:\n                                  description: |-\n                                    namespaces specifies a static list of namespace names that the term applies to.\n                                    The term is applied to the union of the namespaces listed in this field\n                                    and the ones selected by namespaceSelector.\n                                    null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                                topologyKey:\n                                  description: |-\n                                    This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                    the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                    whose value of the label with key topologyKey matches that of any node on which any of the\n                                    selected pods is running.\n                                    Empty topologyKey is not allowed.\n                                  type: string\n                              required:\n                              - topologyKey\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                        type: object\n                    type: object\n                  nodeSelector:\n                    additionalProperties:\n                      type: string\n                    description: |-\n                      nodeSelector is the node selector applied to the relevant kind of pods\n                      It specifies a map of key-value pairs: for the pod to be eligible to run on a node,\n                      the node must have each of the indicated key-value pairs as labels\n                      (it can have additional labels as well).\n                      See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector\n                    type: object\n                  tolerations:\n                    description: |-\n                      tolerations is a list of tolerations applied to the relevant kind of pods\n                      See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info.\n                      These are additional tolerations other than default ones.\n                    items:\n                      description: |-\n                        The pod this Toleration is attached to tolerates any taint that matches\n                        the triple <key,value,effect> using the matching operator <operator>.\n                      properties:\n                        effect:\n                          description: |-\n                            Effect indicates the taint effect to match. Empty means match all taint effects.\n                            When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.\n                          type: string\n                        key:\n                          description: |-\n                            Key is the taint key that the toleration applies to. Empty means match all taint keys.\n                            If the key is empty, operator must be Exists; this combination means to match all values and all keys.\n                          type: string\n                        operator:\n                          description: |-\n                            Operator represents a key's relationship to the value.\n                            Valid operators are Exists and Equal. Defaults to Equal.\n                            Exists is equivalent to wildcard for value, so that a pod can\n                            tolerate all taints of a particular category.\n                          type: string\n                        tolerationSeconds:\n                          description: |-\n                            TolerationSeconds represents the period of time the toleration (which must be\n                            of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,\n                            it is not set, which means tolerate the taint forever (do not evict). Zero and\n                            negative values will be treated as 0 (evict immediately) by the system.\n                          format: int64\n                          type: integer\n                        value:\n                          description: |-\n                            Value is the taint value the toleration matches to.\n                            If the operator is Exists, the value should be empty, otherwise just a regular string.\n                          type: string\n                      type: object\n                    type: array\n                type: object\n            type: object\n          status:\n            description: CDIStatus defines the status of the installation\n            properties:\n              conditions:\n                description: A list of current conditions of the resource\n                items:\n                  description: |-\n                    Condition represents the state of the operator's\n                    reconciliation functionality.\n                  properties:\n                    lastHeartbeatTime:\n                      format: date-time\n                      type: string\n                    lastTransitionTime:\n                      format: date-time\n                      type: string\n                    message:\n                      type: string\n                    reason:\n                      type: string\n                    status:\n                      type: string\n                    type:\n                      description: ConditionType is the state of the operator's reconciliation\n                        functionality.\n                      type: string\n                  required:\n                  - status\n                  - type\n                  type: object\n                type: array\n              observedVersion:\n                description: The observed version of the resource\n                type: string\n              operatorVersion:\n                description: The version of the resource as defined by the operator\n                type: string\n              phase:\n                description: Phase is the current phase of the deployment\n                type: string\n              targetVersion:\n                description: The desired version of the resource\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources: {}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    operator.cdi.kubevirt.io: \"\"\n  name: cdi-operator-cluster\nrules:\n- apiGroups:\n  - rbac.authorization.k8s.io\n  resources:\n  - clusterrolebindings\n  - clusterroles\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n- apiGroups:\n  - security.openshift.io\n  resources:\n  - securitycontextconstraints\n  verbs:\n  - get\n  - list\n  - watch\n  - update\n  - create\n- apiGroups:\n  - apiextensions.k8s.io\n  resources:\n  - customresourcedefinitions\n  - customresourcedefinitions/status\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n- apiGroups:\n  - cdi.kubevirt.io\n  - upload.cdi.kubevirt.io\n  resources:\n  - '*'\n  verbs:\n  - '*'\n- apiGroups:\n  - admissionregistration.k8s.io\n  resources:\n  - validatingwebhookconfigurations\n  - mutatingwebhookconfigurations\n  verbs:\n  - create\n  - list\n  - watch\n- apiGroups:\n  - admissionregistration.k8s.io\n  resourceNames:\n  - cdi-api-dataimportcron-validate\n  - cdi-api-populator-validate\n  - cdi-api-datavolume-validate\n  - cdi-api-validate\n  - objecttransfer-api-validate\n  resources:\n  - validatingwebhookconfigurations\n  verbs:\n  - get\n  - update\n  - delete\n- apiGroups:\n  - admissionregistration.k8s.io\n  resourceNames:\n  - cdi-api-datavolume-mutate\n  - cdi-api-pvc-mutate\n  resources:\n  - mutatingwebhookconfigurations\n  verbs:\n  - get\n  - update\n  - delete\n- apiGroups:\n  - apiregistration.k8s.io\n  resources:\n  - apiservices\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - persistentvolumeclaims\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - persistentvolumes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - storage.k8s.io\n  resources:\n  - storageclasses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  verbs:\n  - get\n- apiGroups:\n  - snapshot.storage.k8s.io\n  resources:\n  - volumesnapshots\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - datavolumes\n  verbs:\n  - list\n  - get\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - datasources\n  verbs:\n  - get\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - volumeclonesources\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - storageprofiles\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - cdis\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - cdiconfigs\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - cdis/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - persistentvolumeclaims\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n  - deletecollection\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - persistentvolumes\n  verbs:\n  - get\n  - list\n  - watch\n  - update\n- apiGroups:\n  - \"\"\n  resources:\n  - persistentvolumeclaims/finalizers\n  - pods/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  - services\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - create\n- apiGroups:\n  - storage.k8s.io\n  resources:\n  - storageclasses\n  - csidrivers\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - config.openshift.io\n  resources:\n  - proxies\n  - infrastructures\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - config.openshift.io\n  resources:\n  - clusterversions\n  verbs:\n  - get\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - '*'\n  verbs:\n  - '*'\n- apiGroups:\n  - snapshot.storage.k8s.io\n  resources:\n  - volumesnapshots\n  - volumesnapshotclasses\n  - volumesnapshotcontents\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n- apiGroups:\n  - snapshot.storage.k8s.io\n  resources:\n  - volumesnapshots\n  verbs:\n  - update\n  - deletecollection\n- apiGroups:\n  - apiextensions.k8s.io\n  resources:\n  - customresourcedefinitions\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - scheduling.k8s.io\n  resources:\n  - priorityclasses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - image.openshift.io\n  resources:\n  - imagestreams\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  verbs:\n  - create\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachines/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - forklift.cdi.kubevirt.io\n  resources:\n  - ovirtvolumepopulators\n  - openstackvolumepopulators\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - persistentvolumeclaims\n  verbs:\n  - get\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - dataimportcrons\n  verbs:\n  - get\n  - list\n  - update\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    operator.cdi.kubevirt.io: \"\"\n  name: cdi-operator\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cdi-operator-cluster\nsubjects:\n- kind: ServiceAccount\n  name: cdi-operator\n  namespace: cdi\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    operator.cdi.kubevirt.io: \"\"\n  name: cdi-operator\n  namespace: cdi\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app: containerized-data-importer\n    app.kubernetes.io/component: storage\n    app.kubernetes.io/managed-by: cdi-operator\n    cdi.kubevirt.io: \"\"\n  name: cdi-operator\n  namespace: cdi\nrules:\n- apiGroups:\n  - rbac.authorization.k8s.io\n  resources:\n  - rolebindings\n  - roles\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - serviceaccounts\n  - configmaps\n  - events\n  - secrets\n  - services\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  - deployments/finalizers\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n- apiGroups:\n  - route.openshift.io\n  resources:\n  - routes\n  - routes/custom-host\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n- apiGroups:\n  - config.openshift.io\n  resources:\n  - proxies\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - monitoring.coreos.com\n  resources:\n  - servicemonitors\n  - prometheusrules\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n  - update\n  - patch\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - create\n  - update\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - batch\n  resources:\n  - cronjobs\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - deletecollection\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - create\n  - deletecollection\n  - list\n  - watch\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - create\n  - update\n- apiGroups:\n  - networking.k8s.io\n  resources:\n  - ingresses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - route.openshift.io\n  resources:\n  - routes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n- apiGroups:\n  - \"\"\n  resources:\n  - services\n  - endpoints\n  - pods\n  verbs:\n  - get\n  - list\n  - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app: containerized-data-importer\n    app.kubernetes.io/component: storage\n    app.kubernetes.io/managed-by: cdi-operator\n    cdi.kubevirt.io: \"\"\n  name: cdi-operator\n  namespace: cdi\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: cdi-operator\nsubjects:\n- kind: ServiceAccount\n  name: cdi-operator\n  namespace: cdi\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    cdi.kubevirt.io: cdi-operator\n    name: cdi-operator\n    operator.cdi.kubevirt.io: \"\"\n    prometheus.cdi.kubevirt.io: \"true\"\n  name: cdi-operator\n  namespace: cdi\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      name: cdi-operator\n      operator.cdi.kubevirt.io: \"\"\n  strategy: {}\n  template:\n    metadata:\n      annotations:\n        openshift.io/required-scc: restricted-v2\n      labels:\n        cdi.kubevirt.io: cdi-operator\n        name: cdi-operator\n        operator.cdi.kubevirt.io: \"\"\n        prometheus.cdi.kubevirt.io: \"true\"\n    spec:\n      affinity:\n        podAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: cdi.kubevirt.io\n                  operator: In\n                  values:\n                  - cdi-operator\n              topologyKey: kubernetes.io/hostname\n            weight: 1\n      containers:\n      - env:\n        - name: DEPLOY_CLUSTER_RESOURCES\n          value: \"true\"\n        - name: OPERATOR_VERSION\n          value: v1.62.0\n        - name: CONTROLLER_IMAGE\n          value: quay.io/kubevirt/cdi-controller:v1.62.0\n        - name: IMPORTER_IMAGE\n          value: quay.io/kubevirt/cdi-importer:v1.62.0\n        - name: CLONER_IMAGE\n          value: quay.io/kubevirt/cdi-cloner:v1.62.0\n        - name: OVIRT_POPULATOR_IMAGE\n          value: quay.io/kubevirt/cdi-importer:v1.62.0\n        - name: APISERVER_IMAGE\n          value: quay.io/kubevirt/cdi-apiserver:v1.62.0\n        - name: UPLOAD_SERVER_IMAGE\n          value: quay.io/kubevirt/cdi-uploadserver:v1.62.0\n        - name: UPLOAD_PROXY_IMAGE\n          value: quay.io/kubevirt/cdi-uploadproxy:v1.62.0\n        - name: VERBOSITY\n          value: \"1\"\n        - name: PULL_POLICY\n          value: IfNotPresent\n        - name: MONITORING_NAMESPACE\n        image: quay.io/kubevirt/cdi-operator:v1.62.0\n        imagePullPolicy: IfNotPresent\n        name: cdi-operator\n        ports:\n        - containerPort: 8080\n          name: metrics\n          protocol: TCP\n        resources:\n          requests:\n            cpu: 100m\n            memory: 150Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          runAsNonRoot: true\n          seccompProfile:\n            type: RuntimeDefault\n        terminationMessagePath: /dev/termination-log\n        terminationMessagePolicy: File\n      nodeSelector:\n        kubernetes.io/os: linux\n      securityContext:\n        runAsNonRoot: true\n      serviceAccountName: cdi-operator\n      tolerations:\n      - key: CriticalAddonsOnly\n        operator: Exists\n"
  },
  {
    "path": "infra/kubernetes/cluster-issuer.yaml",
    "content": "apiVersion: cert-manager.io/v1\nkind: ClusterIssuer\nmetadata:\n  name: letsencrypt-prod\nspec:\n  acme:\n    server: https://acme-v02.api.letsencrypt.org/directory\n    email: dev@cyberdesk.io\n    privateKeySecretRef:\n      name: le-prod-key\n    solvers:\n      - http01:\n          ingress:\n            class: nginx"
  },
  {
    "path": "infra/kubernetes/cyberdesk-cr-v2.yaml",
    "content": "apiVersion: cyberdesk.io/v1alpha1\nkind: Cyberdesk\nmetadata:\n  name: 3840362a-cbc5-4f35-ab21-cccfa9275ce4\n  namespace: cyberdesk-system\nspec:\n  timeoutMs: 86400000  # 24 hour timeout@"
  },
  {
    "path": "infra/kubernetes/cyberdesk-cr.yaml",
    "content": "apiVersion: cyberdesk.io/v1alpha1\nkind: Cyberdesk\nmetadata:\n  name: 6ebf5303-1e81-4203-b870-ccfeb590d02f\n  namespace: cyberdesk-system\nspec:\n  timeoutMs: 86400000  # 24 hour timeout"
  },
  {
    "path": "infra/kubernetes/cyberdesk-operator.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: cyberdesk-system\n  labels:\n    app.kubernetes.io/name: cyberdesk-system\n    app.kubernetes.io/part-of: cyberdesk\n    app.kubernetes.io/component: operator\n  annotations:\n    description: \"Dedicated namespace for the Cyberdesk Operator and related resources\" \n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: cyberdesk-operator\n  namespace: cyberdesk-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: cyberdesk-operator\nrules:\n# Kopf framework requires these permissions\n- apiGroups: [\"\"]\n  resources: [\"events\"]\n  verbs: [\"create\", \"patch\", \"update\"]\n- apiGroups: [\"\", \"coordination.k8s.io\"]\n  resources: [\"configmaps\", \"leases\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n- apiGroups: [\"\"]\n  resources: [\"secrets\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n\n# Access to the trigger CRD\n- apiGroups: [\"cyberdesk.io\"]\n  resources: [\"startcyberdeskoperators\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n\n# Access to Cyberdesk CRDs\n- apiGroups: [\"cyberdesk.io\"]\n  resources: [\"cyberdesks\", \"cyberdesks/status\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n\n# Access to KubeVirt resources\n- apiGroups: [\"kubevirt.io\"]\n  resources: [\"virtualmachines\", \"virtualmachineinstances\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n\n# NEW: Access to KubeVirt Snapshot resources\n- apiGroups: [\"snapshot.kubevirt.io\"]\n  resources: [\"virtualmachinesnapshots\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n\n# NEW: Access to KubeVirt Clone resources\n- apiGroups: [\"clone.kubevirt.io\"]\n  resources: [\"virtualmachineclones\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n\n# Permission to manage CRDs\n- apiGroups: [\"apiextensions.k8s.io\"]\n  resources: [\"customresourcedefinitions\"]\n  verbs: [\"get\", \"list\", \"watch\", \"create\", \"update\", \"patch\", \"delete\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: cyberdesk-operator\nsubjects:\n- kind: ServiceAccount\n  name: cyberdesk-operator\n  namespace: cyberdesk-system\nroleRef:\n  kind: ClusterRole\n  name: cyberdesk-operator\n  apiGroup: rbac.authorization.k8s.io \n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: cyberdesk-operator\n  namespace: cyberdesk-system\n  labels:\n    app: cyberdesk-operator\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: cyberdesk-operator\n  template:\n    metadata:\n      labels:\n        app: cyberdesk-operator\n    spec:\n      serviceAccountName: cyberdesk-operator\n      containers:\n      - name: operator\n        image: cyberdesk/cyberdesk-operator:v0.2.2\n        imagePullPolicy: IfNotPresent\n        ports:\n        - containerPort: 8080\n          name: health\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: health\n          initialDelaySeconds: 15\n          periodSeconds: 30\n        resources:\n          limits:\n            cpu: \"500m\"\n            memory: \"512Mi\"\n          requests:\n            cpu: \"200m\"\n            memory: \"256Mi\"\n        envFrom:\n        - secretRef:\n            name: supabase-credentials\n---\n# CRD for the trigger resource\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: startcyberdeskoperators.cyberdesk.io\nspec:\n  group: cyberdesk.io\n  names:\n    kind: StartCyberdeskOperator\n    plural: startcyberdeskoperators\n    singular: startcyberdeskoperator\n    shortNames:\n      - sco\n  scope: Namespaced # Keeping it namespaced as requested for minimal scope\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              description: \"Specification for triggering the Cyberdesk operator setup. Currently holds no fields.\"\n              # No specific fields needed for now, just the presence triggers the action\n"
  },
  {
    "path": "infra/kubernetes/default-backend.yaml",
    "content": "apiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: default-backend\n  namespace: ingress-nginx\nspec:\n  selector:\n    matchLabels:\n      app: default-backend\n  template:\n    metadata:\n      labels:\n        app: default-backend\n    spec:\n      containers:\n      - name: default-backend\n        image: k8s.gcr.io/defaultbackend-amd64:1.5\n        ports:\n        - containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: default-backend\n  namespace: ingress-nginx\nspec:\n  ports:\n  - port: 80\n    targetPort: 8080\n  selector:\n    app: default-backend\n---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: default-backend\n  namespace: ingress-nginx\nspec:\n  ingressClassName: nginx\n  rules:\n    - http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: default-http-backend\n                port:\n                  number: 80"
  },
  {
    "path": "infra/kubernetes/gateway-deploy.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: gateway\n  namespace: cyberdesk-system \n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: gateway-vnc-access\nrules:\n- apiGroups: [\"subresources.kubevirt.io\"]\n  resources: [\"virtualmachineinstances/vnc\"]\n  verbs: [\"get\"]\n- apiGroups: [\"\"] # Core API group\n  resources: [\"services\", \"services/status\"] # Combined service permissions\n  verbs: [\"get\", \"list\", \"watch\"] # Permissions needed for service status and IP lookup\n- apiGroups: [\"cyberdesk.io\"] # Add permissions for Cyberdesk CRs\n  resources: [\"cyberdesks\"]\n  verbs: [\"create\", \"delete\", \"get\", \"list\", \"patch\", \"update\", \"watch\"]\n- apiGroups: [\"kubevirt.io\"]\n  resources: [\"virtualmachineinstances\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: gateway-vnc-binding\n  namespace: cyberdesk-system\nsubjects:\n- kind: ServiceAccount\n  name: gateway\n  namespace: cyberdesk-system\nroleRef:\n  kind: ClusterRole\n  name: gateway-vnc-access\n  apiGroup: rbac.authorization.k8s.io\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: gateway\n  namespace: cyberdesk-system\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: gateway\n  template:\n    metadata:\n      labels:\n        app: gateway\n    spec:\n      serviceAccountName: gateway\n      containers:\n      - name: gateway\n        image: cyberdesk/gateway:v0.2.1\n        envFrom:\n        - secretRef:\n            name: supabase-credentials\n        ports:\n        - containerPort: 80\n        resources:\n          requests:\n            memory: \"128Mi\"\n            cpu: \"100m\"\n          limits:\n            memory: \"256Mi\"\n            cpu: \"500m\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: gateway\n  namespace: cyberdesk-system\nspec:\n  type: LoadBalancer\n  selector:\n    app: gateway\n  ports:\n  - port: 80\n    targetPort: 80\n    protocol: TCP\n    name: http"
  },
  {
    "path": "infra/kubernetes/gateway-ingress-dev.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: dev-gateway-ingress\n  namespace: cyberdesk-system\n  annotations:\n    cert-manager.io/cluster-issuer: letsencrypt-prod\n    nginx.ingress.kubernetes.io/ssl-redirect: \"true\"\nspec:\n  ingressClassName: nginx\n  tls:\n    - hosts:\n        - dev-gateway.cyberdesk.io\n      secretName: gateway-tls\n  rules:\n    - host: dev-gateway.cyberdesk.io\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: gateway\n                port:\n                  number: 80"
  },
  {
    "path": "infra/kubernetes/gateway-ingress-prod.yaml",
    "content": "apiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n  name: gateway-ingress\n  namespace: cyberdesk-system\n  annotations:\n    cert-manager.io/cluster-issuer: letsencrypt-prod\n    nginx.ingress.kubernetes.io/ssl-redirect: \"true\"\nspec:\n  ingressClassName: nginx\n  tls:\n    - hosts:\n        - gateway.cyberdesk.io\n      secretName: gateway-tls\n  rules:\n    - host: gateway.cyberdesk.io\n      http:\n        paths:\n          - path: /\n            pathType: Prefix\n            backend:\n              service:\n                name: gateway\n                port:\n                  number: 80 "
  },
  {
    "path": "infra/kubernetes/golden-vm-snapshot-request.yaml",
    "content": "apiVersion: snapshot.kubevirt.io/v1beta1\nkind: VirtualMachineSnapshot\nmetadata:\n  name: snapshot-golden-vm\n  namespace: kubevirt # Assuming golden-vm is in the kubevirt namespace\nspec:\n  source:\n    apiGroup: kubevirt.io\n    kind: VirtualMachine\n    name: golden-vm "
  },
  {
    "path": "infra/kubernetes/kubevirt-cr.yaml",
    "content": "---\napiVersion: kubevirt.io/v1\nkind: KubeVirt\nmetadata:\n  name: kubevirt\n  namespace: kubevirt\nspec:\n  certificateRotateStrategy: {}\n  configuration:\n    developerConfiguration:\n      featureGates: [\n        \"Snapshot\"\n      ]\n  customizeComponents: {}\n  imagePullPolicy: IfNotPresent\n  workloadUpdateStrategy: {}\n  infra:\n    nodePlacement:\n      tolerations:\n        - operator: Exists\n"
  },
  {
    "path": "infra/kubernetes/kubevirt-operator.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    kubevirt.io: \"\"\n    pod-security.kubernetes.io/enforce: \"privileged\"\n  name: kubevirt\n---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  labels:\n    operator.kubevirt.io: \"\"\n  name: kubevirts.kubevirt.io\nspec:\n  group: kubevirt.io\n  names:\n    categories:\n    - all\n    kind: KubeVirt\n    plural: kubevirts\n    shortNames:\n    - kv\n    - kvs\n    singular: kubevirt\n  scope: Namespaced\n  versions:\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    - jsonPath: .status.phase\n      name: Phase\n      type: string\n    name: v1\n    schema:\n      openAPIV3Schema:\n        description: KubeVirt represents the object deploying all KubeVirt resources\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              certificateRotateStrategy:\n                properties:\n                  selfSigned:\n                    properties:\n                      ca:\n                        description: |-\n                          CA configuration\n                          CA certs are kept in the CA bundle as long as they are valid\n                        properties:\n                          duration:\n                            description: The requested 'duration' (i.e. lifetime)\n                              of the Certificate.\n                            type: string\n                          renewBefore:\n                            description: |-\n                              The amount of time before the currently issued certificate's \"notAfter\"\n                              time that we will begin to attempt to renew the certificate.\n                            type: string\n                        type: object\n                      caOverlapInterval:\n                        description: Deprecated. Use CA.Duration and CA.RenewBefore\n                          instead\n                        type: string\n                      caRotateInterval:\n                        description: Deprecated. Use CA.Duration instead\n                        type: string\n                      certRotateInterval:\n                        description: Deprecated. Use Server.Duration instead\n                        type: string\n                      server:\n                        description: |-\n                          Server configuration\n                          Certs are rotated and discarded\n                        properties:\n                          duration:\n                            description: The requested 'duration' (i.e. lifetime)\n                              of the Certificate.\n                            type: string\n                          renewBefore:\n                            description: |-\n                              The amount of time before the currently issued certificate's \"notAfter\"\n                              time that we will begin to attempt to renew the certificate.\n                            type: string\n                        type: object\n                    type: object\n                type: object\n              configuration:\n                description: |-\n                  holds kubevirt configurations.\n                  same as the virt-configMap\n                properties:\n                  additionalGuestMemoryOverheadRatio:\n                    description: |-\n                      AdditionalGuestMemoryOverheadRatio can be used to increase the virtualization infrastructure\n                      overhead. This is useful, since the calculation of this overhead is not accurate and cannot\n                      be entirely known in advance. The ratio that is being set determines by which factor to increase\n                      the overhead calculated by Kubevirt. A higher ratio means that the VMs would be less compromised\n                      by node pressures, but would mean that fewer VMs could be scheduled to a node.\n                      If not set, the default is 1.\n                    type: string\n                  apiConfiguration:\n                    description: |-\n                      ReloadableComponentConfiguration holds all generic k8s configuration options which can\n                      be reloaded by components without requiring a restart.\n                    properties:\n                      restClient:\n                        description: RestClient can be used to tune certain aspects\n                          of the k8s client in use.\n                        properties:\n                          rateLimiter:\n                            description: RateLimiter allows selecting and configuring\n                              different rate limiters for the k8s client.\n                            properties:\n                              tokenBucketRateLimiter:\n                                properties:\n                                  burst:\n                                    description: |-\n                                      Maximum burst for throttle.\n                                      If it's zero, the component default will be used\n                                    type: integer\n                                  qps:\n                                    description: |-\n                                      QPS indicates the maximum QPS to the apiserver from this client.\n                                      If it's zero, the component default will be used\n                                    type: number\n                                required:\n                                - burst\n                                - qps\n                                type: object\n                            type: object\n                        type: object\n                    type: object\n                  architectureConfiguration:\n                    properties:\n                      amd64:\n                        properties:\n                          emulatedMachines:\n                            items:\n                              type: string\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          machineType:\n                            type: string\n                          ovmfPath:\n                            type: string\n                        type: object\n                      arm64:\n                        properties:\n                          emulatedMachines:\n                            items:\n                              type: string\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          machineType:\n                            type: string\n                          ovmfPath:\n                            type: string\n                        type: object\n                      defaultArchitecture:\n                        type: string\n                      ppc64le:\n                        properties:\n                          emulatedMachines:\n                            items:\n                              type: string\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          machineType:\n                            type: string\n                          ovmfPath:\n                            type: string\n                        type: object\n                    type: object\n                  autoCPULimitNamespaceLabelSelector:\n                    description: |-\n                      When set, AutoCPULimitNamespaceLabelSelector will set a CPU limit on virt-launcher for VMIs running inside\n                      namespaces that match the label selector.\n                      The CPU limit will equal the number of requested vCPUs.\n                      This setting does not apply to VMIs with dedicated CPUs.\n                    properties:\n                      matchExpressions:\n                        description: matchExpressions is a list of label selector\n                          requirements. The requirements are ANDed.\n                        items:\n                          description: |-\n                            A label selector requirement is a selector that contains values, a key, and an operator that\n                            relates the key and values.\n                          properties:\n                            key:\n                              description: key is the label key that the selector\n                                applies to.\n                              type: string\n                            operator:\n                              description: |-\n                                operator represents a key's relationship to a set of values.\n                                Valid operators are In, NotIn, Exists and DoesNotExist.\n                              type: string\n                            values:\n                              description: |-\n                                values is an array of string values. If the operator is In or NotIn,\n                                the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                the values array must be empty. This array is replaced during a strategic\n                                merge patch.\n                              items:\n                                type: string\n                              type: array\n                              x-kubernetes-list-type: atomic\n                          required:\n                          - key\n                          - operator\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      matchLabels:\n                        additionalProperties:\n                          type: string\n                        description: |-\n                          matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                          map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                          operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                        type: object\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  commonInstancetypesDeployment:\n                    description: CommonInstancetypesDeployment controls the deployment\n                      of common-instancetypes resources\n                    nullable: true\n                    properties:\n                      enabled:\n                        description: Enabled controls the deployment of common-instancetypes\n                          resources, defaults to True.\n                        nullable: true\n                        type: boolean\n                    type: object\n                  controllerConfiguration:\n                    description: |-\n                      ReloadableComponentConfiguration holds all generic k8s configuration options which can\n                      be reloaded by components without requiring a restart.\n                    properties:\n                      restClient:\n                        description: RestClient can be used to tune certain aspects\n                          of the k8s client in use.\n                        properties:\n                          rateLimiter:\n                            description: RateLimiter allows selecting and configuring\n                              different rate limiters for the k8s client.\n                            properties:\n                              tokenBucketRateLimiter:\n                                properties:\n                                  burst:\n                                    description: |-\n                                      Maximum burst for throttle.\n                                      If it's zero, the component default will be used\n                                    type: integer\n                                  qps:\n                                    description: |-\n                                      QPS indicates the maximum QPS to the apiserver from this client.\n                                      If it's zero, the component default will be used\n                                    type: number\n                                required:\n                                - burst\n                                - qps\n                                type: object\n                            type: object\n                        type: object\n                    type: object\n                  cpuModel:\n                    type: string\n                  cpuRequest:\n                    anyOf:\n                    - type: integer\n                    - type: string\n                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                    x-kubernetes-int-or-string: true\n                  defaultRuntimeClass:\n                    type: string\n                  developerConfiguration:\n                    description: DeveloperConfiguration holds developer options\n                    properties:\n                      cpuAllocationRatio:\n                        description: |-\n                          For each requested virtual CPU, CPUAllocationRatio defines how much physical CPU to request per VMI\n                          from the hosting node. The value is in fraction of a CPU thread (or core on non-hyperthreaded nodes).\n                          For example, a value of 1 means 1 physical CPU thread per VMI CPU thread.\n                          A value of 100 would be 1% of a physical thread allocated for each requested VMI thread.\n                          This option has no effect on VMIs that request dedicated CPUs. More information at:\n                          https://kubevirt.io/user-guide/operations/node_overcommit/#node-cpu-allocation-ratio\n                          Defaults to 10\n                        type: integer\n                      diskVerification:\n                        description: DiskVerification holds container disks verification\n                          limits\n                        properties:\n                          memoryLimit:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                        required:\n                        - memoryLimit\n                        type: object\n                      featureGates:\n                        description: FeatureGates is the list of experimental features\n                          to enable. Defaults to none\n                        items:\n                          type: string\n                        type: array\n                      logVerbosity:\n                        description: LogVerbosity sets log verbosity level of  various\n                          components\n                        properties:\n                          nodeVerbosity:\n                            additionalProperties:\n                              type: integer\n                            description: NodeVerbosity represents a map of nodes with\n                              a specific verbosity level\n                            type: object\n                          virtAPI:\n                            type: integer\n                          virtController:\n                            type: integer\n                          virtHandler:\n                            type: integer\n                          virtLauncher:\n                            type: integer\n                          virtOperator:\n                            type: integer\n                        type: object\n                      memoryOvercommit:\n                        description: |-\n                          MemoryOvercommit is the percentage of memory we want to give VMIs compared to the amount\n                          given to its parent pod (virt-launcher). For example, a value of 102 means the VMI will\n                          \"see\" 2% more memory than its parent pod. Values under 100 are effectively \"undercommits\".\n                          Overcommits can lead to memory exhaustion, which in turn can lead to crashes. Use carefully.\n                          Defaults to 100\n                        type: integer\n                      minimumClusterTSCFrequency:\n                        description: |-\n                          Allow overriding the automatically determined minimum TSC frequency of the cluster\n                          and fixate the minimum to this frequency.\n                        format: int64\n                        type: integer\n                      minimumReservePVCBytes:\n                        description: |-\n                          MinimumReservePVCBytes is the amount of space, in bytes, to leave unused on disks.\n                          Defaults to 131072 (128KiB)\n                        format: int64\n                        type: integer\n                      nodeSelectors:\n                        additionalProperties:\n                          type: string\n                        description: |-\n                          NodeSelectors allows restricting VMI creation to nodes that match a set of labels.\n                          Defaults to none\n                        type: object\n                      pvcTolerateLessSpaceUpToPercent:\n                        description: |-\n                          LessPVCSpaceToleration determines how much smaller, in percentage, disk PVCs are\n                          allowed to be compared to the requested size (to account for various overheads).\n                          Defaults to 10\n                        type: integer\n                      useEmulation:\n                        description: |-\n                          UseEmulation can be set to true to allow fallback to software emulation\n                          in case hardware-assisted emulation is not available. Defaults to false\n                        type: boolean\n                    type: object\n                  emulatedMachines:\n                    description: Deprecated. Use architectureConfiguration instead.\n                    items:\n                      type: string\n                    type: array\n                  evictionStrategy:\n                    description: |-\n                      EvictionStrategy defines at the cluster level if the VirtualMachineInstance should be\n                      migrated instead of shut-off in case of a node drain. If the VirtualMachineInstance specific\n                      field is set it overrides the cluster level one.\n                    type: string\n                  handlerConfiguration:\n                    description: |-\n                      ReloadableComponentConfiguration holds all generic k8s configuration options which can\n                      be reloaded by components without requiring a restart.\n                    properties:\n                      restClient:\n                        description: RestClient can be used to tune certain aspects\n                          of the k8s client in use.\n                        properties:\n                          rateLimiter:\n                            description: RateLimiter allows selecting and configuring\n                              different rate limiters for the k8s client.\n                            properties:\n                              tokenBucketRateLimiter:\n                                properties:\n                                  burst:\n                                    description: |-\n                                      Maximum burst for throttle.\n                                      If it's zero, the component default will be used\n                                    type: integer\n                                  qps:\n                                    description: |-\n                                      QPS indicates the maximum QPS to the apiserver from this client.\n                                      If it's zero, the component default will be used\n                                    type: number\n                                required:\n                                - burst\n                                - qps\n                                type: object\n                            type: object\n                        type: object\n                    type: object\n                  imagePullPolicy:\n                    description: PullPolicy describes a policy for if/when to pull\n                      a container image\n                    type: string\n                  instancetype:\n                    description: Instancetype configuration\n                    nullable: true\n                    properties:\n                      referencePolicy:\n                        description: |-\n                          ReferencePolicy defines how an instance type or preference should be referenced by the VM after submission, supported values are:\n                          reference (default) - Where a copy of the original object is stashed in a ControllerRevision and referenced by the VM.\n                          expand - Where the instance type or preference are expanded into the VM if no revisionNames have been populated.\n                          expandAll - Where the instance type or preference are expanded into the VM regardless of revisionNames previously being populated.\n                        enum:\n                        - reference\n                        - expand\n                        - expandAll\n                        nullable: true\n                        type: string\n                    type: object\n                  ksmConfiguration:\n                    description: KSMConfiguration holds the information regarding\n                      the enabling the KSM in the nodes (if available).\n                    properties:\n                      nodeLabelSelector:\n                        description: |-\n                          NodeLabelSelector is a selector that filters in which nodes the KSM will be enabled.\n                          Empty NodeLabelSelector will enable ksm for every node.\n                        properties:\n                          matchExpressions:\n                            description: matchExpressions is a list of label selector\n                              requirements. The requirements are ANDed.\n                            items:\n                              description: |-\n                                A label selector requirement is a selector that contains values, a key, and an operator that\n                                relates the key and values.\n                              properties:\n                                key:\n                                  description: key is the label key that the selector\n                                    applies to.\n                                  type: string\n                                operator:\n                                  description: |-\n                                    operator represents a key's relationship to a set of values.\n                                    Valid operators are In, NotIn, Exists and DoesNotExist.\n                                  type: string\n                                values:\n                                  description: |-\n                                    values is an array of string values. If the operator is In or NotIn,\n                                    the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                    the values array must be empty. This array is replaced during a strategic\n                                    merge patch.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            description: |-\n                              matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                              map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                              operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  liveUpdateConfiguration:\n                    description: LiveUpdateConfiguration holds defaults for live update\n                      features\n                    properties:\n                      maxCpuSockets:\n                        description: |-\n                          MaxCpuSockets provides a MaxSockets value for VMs that do not provide their own.\n                          For VMs with more sockets than maximum the MaxSockets will be set to equal number of sockets.\n                        format: int32\n                        type: integer\n                      maxGuest:\n                        anyOf:\n                        - type: integer\n                        - type: string\n                        description: |-\n                          MaxGuest defines the maximum amount memory that can be allocated\n                          to the guest using hotplug.\n                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                        x-kubernetes-int-or-string: true\n                      maxHotplugRatio:\n                        description: |-\n                          MaxHotplugRatio is the ratio used to define the max amount\n                          of a hotplug resource that can be made available to a VM\n                          when the specific Max* setting is not defined (MaxCpuSockets, MaxGuest)\n                          Example: VM is configured with 512Mi of guest memory, if MaxGuest is not\n                          defined and MaxHotplugRatio is 2 then MaxGuest = 1Gi\n                          defaults to 4\n                        format: int32\n                        type: integer\n                    type: object\n                  machineType:\n                    description: Deprecated. Use architectureConfiguration instead.\n                    type: string\n                  mediatedDevicesConfiguration:\n                    description: MediatedDevicesConfiguration holds information about\n                      MDEV types to be defined, if available\n                    properties:\n                      mediatedDeviceTypes:\n                        items:\n                          type: string\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      mediatedDevicesTypes:\n                        description: Deprecated. Use mediatedDeviceTypes instead.\n                        items:\n                          type: string\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      nodeMediatedDeviceTypes:\n                        items:\n                          description: NodeMediatedDeviceTypesConfig holds information\n                            about MDEV types to be defined in a specific node that\n                            matches the NodeSelector field.\n                          properties:\n                            mediatedDeviceTypes:\n                              items:\n                                type: string\n                              type: array\n                              x-kubernetes-list-type: atomic\n                            mediatedDevicesTypes:\n                              description: Deprecated. Use mediatedDeviceTypes instead.\n                              items:\n                                type: string\n                              type: array\n                              x-kubernetes-list-type: atomic\n                            nodeSelector:\n                              additionalProperties:\n                                type: string\n                              description: |-\n                                NodeSelector is a selector which must be true for the vmi to fit on a node.\n                                Selector which must match a node's labels for the vmi to be scheduled on that node.\n                                More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/\n                              type: object\n                          required:\n                          - nodeSelector\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                    type: object\n                  memBalloonStatsPeriod:\n                    format: int32\n                    type: integer\n                  migrations:\n                    description: |-\n                      MigrationConfiguration holds migration options.\n                      Can be overridden for specific groups of VMs though migration policies.\n                      Visit https://kubevirt.io/user-guide/operations/migration_policies/ for more information.\n                    properties:\n                      allowAutoConverge:\n                        description: |-\n                          AllowAutoConverge allows the platform to compromise performance/availability of VMIs to\n                          guarantee successful VMI live migrations. Defaults to false\n                        type: boolean\n                      allowPostCopy:\n                        description: |-\n                          AllowPostCopy enables post-copy live migrations. Such migrations allow even the busiest VMIs\n                          to successfully live-migrate. However, events like a network failure can cause a VMI crash.\n                          If set to true, migrations will still start in pre-copy, but switch to post-copy when\n                          CompletionTimeoutPerGiB triggers. Defaults to false\n                        type: boolean\n                      allowWorkloadDisruption:\n                        description: |-\n                          AllowWorkloadDisruption indicates that the migration shouldn't be\n                          canceled after acceptableCompletionTime is exceeded. Instead, if\n                          permitted, migration will be switched to post-copy or the VMI will be\n                          paused to allow the migration to complete\n                        type: boolean\n                      bandwidthPerMigration:\n                        anyOf:\n                        - type: integer\n                        - type: string\n                        description: |-\n                          BandwidthPerMigration limits the amount of network bandwidth live migrations are allowed to use.\n                          The value is in quantity per second. Defaults to 0 (no limit)\n                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                        x-kubernetes-int-or-string: true\n                      completionTimeoutPerGiB:\n                        description: |-\n                          CompletionTimeoutPerGiB is the maximum number of seconds per GiB a migration is allowed to take.\n                          If the timeout is reached, the migration will be either paused, switched\n                          to post-copy or cancelled depending on other settings. Defaults to 150\n                        format: int64\n                        type: integer\n                      disableTLS:\n                        description: |-\n                          When set to true, DisableTLS will disable the additional layer of live migration encryption\n                          provided by KubeVirt. This is usually a bad idea. Defaults to false\n                        type: boolean\n                      matchSELinuxLevelOnMigration:\n                        description: |-\n                          By default, the SELinux level of target virt-launcher pods is forced to the level of the source virt-launcher.\n                          When set to true, MatchSELinuxLevelOnMigration lets the CRI auto-assign a random level to the target.\n                          That will ensure the target virt-launcher doesn't share categories with another pod on the node.\n                          However, migrations will fail when using RWX volumes that don't automatically deal with SELinux levels.\n                        type: boolean\n                      network:\n                        description: |-\n                          Network is the name of the CNI network to use for live migrations. By default, migrations go\n                          through the pod network.\n                        type: string\n                      nodeDrainTaintKey:\n                        description: |-\n                          NodeDrainTaintKey defines the taint key that indicates a node should be drained.\n                          Note: this option relies on the deprecated node taint feature. Default: kubevirt.io/drain\n                        type: string\n                      parallelMigrationsPerCluster:\n                        description: |-\n                          ParallelMigrationsPerCluster is the total number of concurrent live migrations\n                          allowed cluster-wide. Defaults to 5\n                        format: int32\n                        type: integer\n                      parallelOutboundMigrationsPerNode:\n                        description: |-\n                          ParallelOutboundMigrationsPerNode is the maximum number of concurrent outgoing live migrations\n                          allowed per node. Defaults to 2\n                        format: int32\n                        type: integer\n                      progressTimeout:\n                        description: |-\n                          ProgressTimeout is the maximum number of seconds a live migration is allowed to make no progress.\n                          Hitting this timeout means a migration transferred 0 data for that many seconds. The migration is\n                          then considered stuck and therefore cancelled. Defaults to 150\n                        format: int64\n                        type: integer\n                      unsafeMigrationOverride:\n                        description: |-\n                          UnsafeMigrationOverride allows live migrations to occur even if the compatibility check\n                          indicates the migration will be unsafe to the guest. Defaults to false\n                        type: boolean\n                    type: object\n                  minCPUModel:\n                    type: string\n                  network:\n                    description: NetworkConfiguration holds network options\n                    properties:\n                      binding:\n                        additionalProperties:\n                          properties:\n                            computeResourceOverhead:\n                              description: |-\n                                ComputeResourceOverhead specifies the resource overhead that should be added to the compute container when using the binding.\n                                version: v1alphav1\n                              properties:\n                                limits:\n                                  additionalProperties:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  description: |-\n                                    Limits describes the maximum amount of compute resources allowed.\n                                    More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                                  type: object\n                                requests:\n                                  additionalProperties:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  description: |-\n                                    Requests describes the minimum amount of compute resources required.\n                                    If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,\n                                    otherwise to an implementation-defined value. Requests cannot exceed Limits.\n                                    More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                                  type: object\n                              type: object\n                            domainAttachmentType:\n                              description: |-\n                                DomainAttachmentType is a standard domain network attachment method kubevirt supports.\n                                Supported values: \"tap\", \"managedTap\" (since v1.4).\n                                The standard domain attachment can be used instead or in addition to the sidecarImage.\n                                version: 1alphav1\n                              type: string\n                            downwardAPI:\n                              description: |-\n                                DownwardAPI specifies what kind of data should be exposed to the binding plugin sidecar.\n                                Supported values: \"device-info\"\n                                version: v1alphav1\n                              type: string\n                            migration:\n                              description: |-\n                                Migration means the VM using the plugin can be safely migrated\n                                version: 1alphav1\n                              properties:\n                                method:\n                                  description: |-\n                                    Method defines a pre-defined migration methodology\n                                    version: 1alphav1\n                                  type: string\n                              type: object\n                            networkAttachmentDefinition:\n                              description: |-\n                                NetworkAttachmentDefinition references to a NetworkAttachmentDefinition CR object.\n                                Format: <name>, <namespace>/<name>.\n                                If namespace is not specified, VMI namespace is assumed.\n                                version: 1alphav1\n                              type: string\n                            sidecarImage:\n                              description: |-\n                                SidecarImage references a container image that runs in the virt-launcher pod.\n                                The sidecar handles (libvirt) domain configuration and optional services.\n                                version: 1alphav1\n                              type: string\n                          type: object\n                        type: object\n                      defaultNetworkInterface:\n                        type: string\n                      permitBridgeInterfaceOnPodNetwork:\n                        type: boolean\n                      permitSlirpInterface:\n                        description: |-\n                          DeprecatedPermitSlirpInterface is an alias for the deprecated PermitSlirpInterface.\n                          Deprecated: Removed in v1.3.\n                        type: boolean\n                    type: object\n                  obsoleteCPUModels:\n                    additionalProperties:\n                      type: boolean\n                    type: object\n                  ovmfPath:\n                    description: Deprecated. Use architectureConfiguration instead.\n                    type: string\n                  permittedHostDevices:\n                    description: PermittedHostDevices holds information about devices\n                      allowed for passthrough\n                    properties:\n                      mediatedDevices:\n                        items:\n                          description: MediatedHostDevice represents a host mediated\n                            device allowed for passthrough\n                          properties:\n                            externalResourceProvider:\n                              type: boolean\n                            mdevNameSelector:\n                              type: string\n                            resourceName:\n                              type: string\n                          required:\n                          - mdevNameSelector\n                          - resourceName\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      pciHostDevices:\n                        items:\n                          description: PciHostDevice represents a host PCI device\n                            allowed for passthrough\n                          properties:\n                            externalResourceProvider:\n                              description: |-\n                                If true, KubeVirt will leave the allocation and monitoring to an\n                                external device plugin\n                              type: boolean\n                            pciVendorSelector:\n                              description: The vendor_id:product_id tuple of the PCI\n                                device\n                              type: string\n                            resourceName:\n                              description: |-\n                                The name of the resource that is representing the device. Exposed by\n                                a device plugin and requested by VMs. Typically of the form\n                                vendor.com/product_name\n                              type: string\n                          required:\n                          - pciVendorSelector\n                          - resourceName\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      usb:\n                        items:\n                          properties:\n                            externalResourceProvider:\n                              description: |-\n                                If true, KubeVirt will leave the allocation and monitoring to an\n                                external device plugin\n                              type: boolean\n                            resourceName:\n                              description: |-\n                                Identifies the list of USB host devices.\n                                e.g: kubevirt.io/storage, kubevirt.io/bootable-usb, etc\n                              type: string\n                            selectors:\n                              items:\n                                properties:\n                                  product:\n                                    type: string\n                                  vendor:\n                                    type: string\n                                required:\n                                - product\n                                - vendor\n                                type: object\n                              type: array\n                              x-kubernetes-list-type: atomic\n                          required:\n                          - resourceName\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                    type: object\n                  seccompConfiguration:\n                    description: SeccompConfiguration holds Seccomp configuration\n                      for Kubevirt components\n                    properties:\n                      virtualMachineInstanceProfile:\n                        description: VirtualMachineInstanceProfile defines what profile\n                          should be used with virt-launcher. Defaults to none\n                        properties:\n                          customProfile:\n                            description: CustomProfile allows to request arbitrary\n                              profile for virt-launcher\n                            properties:\n                              localhostProfile:\n                                type: string\n                              runtimeDefaultProfile:\n                                type: boolean\n                            type: object\n                        type: object\n                    type: object\n                  selinuxLauncherType:\n                    type: string\n                  smbios:\n                    properties:\n                      family:\n                        type: string\n                      manufacturer:\n                        type: string\n                      product:\n                        type: string\n                      sku:\n                        type: string\n                      version:\n                        type: string\n                    type: object\n                  supportContainerResources:\n                    description: SupportContainerResources specifies the resource\n                      requirements for various types of supporting containers such\n                      as container disks/virtiofs/sidecars and hotplug attachment\n                      pods. If omitted a sensible default will be supplied.\n                    items:\n                      description: SupportContainerResources are used to specify the\n                        cpu/memory request and limits for the containers that support\n                        various features of Virtual Machines. These containers are\n                        usually idle and don't require a lot of memory or cpu.\n                      properties:\n                        resources:\n                          description: |-\n                            ResourceRequirementsWithoutClaims describes the compute resource requirements.\n                            This struct was taken from the k8s.ResourceRequirements and cleaned up the 'Claims' field.\n                          properties:\n                            limits:\n                              additionalProperties:\n                                anyOf:\n                                - type: integer\n                                - type: string\n                                pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                x-kubernetes-int-or-string: true\n                              description: |-\n                                Limits describes the maximum amount of compute resources allowed.\n                                More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                              type: object\n                            requests:\n                              additionalProperties:\n                                anyOf:\n                                - type: integer\n                                - type: string\n                                pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                x-kubernetes-int-or-string: true\n                              description: |-\n                                Requests describes the minimum amount of compute resources required.\n                                If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,\n                                otherwise to an implementation-defined value. Requests cannot exceed Limits.\n                                More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                              type: object\n                          type: object\n                        type:\n                          type: string\n                      required:\n                      - resources\n                      - type\n                      type: object\n                    type: array\n                    x-kubernetes-list-map-keys:\n                    - type\n                    x-kubernetes-list-type: map\n                  supportedGuestAgentVersions:\n                    description: deprecated\n                    items:\n                      type: string\n                    type: array\n                  tlsConfiguration:\n                    description: TLSConfiguration holds TLS options\n                    properties:\n                      ciphers:\n                        items:\n                          type: string\n                        type: array\n                        x-kubernetes-list-type: set\n                      minTLSVersion:\n                        description: |-\n                          MinTLSVersion is a way to specify the minimum protocol version that is acceptable for TLS connections.\n                          Protocol versions are based on the following most common TLS configurations:\n\n                            https://ssl-config.mozilla.org/\n\n                          Note that SSLv3.0 is not a supported protocol version due to well known\n                          vulnerabilities such as POODLE: https://en.wikipedia.org/wiki/POODLE\n                        enum:\n                        - VersionTLS10\n                        - VersionTLS11\n                        - VersionTLS12\n                        - VersionTLS13\n                        type: string\n                    type: object\n                  virtualMachineInstancesPerNode:\n                    type: integer\n                  virtualMachineOptions:\n                    description: VirtualMachineOptions holds the cluster level information\n                      regarding the virtual machine.\n                    properties:\n                      disableFreePageReporting:\n                        description: |-\n                          DisableFreePageReporting disable the free page reporting of\n                          memory balloon device https://libvirt.org/formatdomain.html#memory-balloon-device.\n                          This will have effect only if AutoattachMemBalloon is not false and the vmi is not\n                          requesting any high performance feature (dedicatedCPU/realtime/hugePages), in which free page reporting is always disabled.\n                        type: object\n                      disableSerialConsoleLog:\n                        description: |-\n                          DisableSerialConsoleLog disables logging the auto-attached default serial console.\n                          If not set, serial console logs will be written to a file and then streamed from a container named 'guest-console-log'.\n                          The value can be individually overridden for each VM, not relevant if AutoattachSerialConsole is disabled.\n                        type: object\n                    type: object\n                  vmRolloutStrategy:\n                    description: |-\n                      VMRolloutStrategy defines how live-updatable fields, like CPU sockets, memory,\n                      tolerations, and affinity, are propagated from a VM to its VMI.\n                    enum:\n                    - Stage\n                    - LiveUpdate\n                    nullable: true\n                    type: string\n                  vmStateStorageClass:\n                    description: VMStateStorageClass is the name of the storage class\n                      to use for the PVCs created to preserve VM state, like TPM.\n                    type: string\n                  webhookConfiguration:\n                    description: |-\n                      ReloadableComponentConfiguration holds all generic k8s configuration options which can\n                      be reloaded by components without requiring a restart.\n                    properties:\n                      restClient:\n                        description: RestClient can be used to tune certain aspects\n                          of the k8s client in use.\n                        properties:\n                          rateLimiter:\n                            description: RateLimiter allows selecting and configuring\n                              different rate limiters for the k8s client.\n                            properties:\n                              tokenBucketRateLimiter:\n                                properties:\n                                  burst:\n                                    description: |-\n                                      Maximum burst for throttle.\n                                      If it's zero, the component default will be used\n                                    type: integer\n                                  qps:\n                                    description: |-\n                                      QPS indicates the maximum QPS to the apiserver from this client.\n                                      If it's zero, the component default will be used\n                                    type: number\n                                required:\n                                - burst\n                                - qps\n                                type: object\n                            type: object\n                        type: object\n                    type: object\n                type: object\n              customizeComponents:\n                properties:\n                  flags:\n                    description: Configure the value used for deployment and daemonset\n                      resources\n                    properties:\n                      api:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      controller:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      handler:\n                        additionalProperties:\n                          type: string\n                        type: object\n                    type: object\n                  patches:\n                    items:\n                      properties:\n                        patch:\n                          type: string\n                        resourceName:\n                          minLength: 1\n                          type: string\n                        resourceType:\n                          minLength: 1\n                          type: string\n                        type:\n                          type: string\n                      required:\n                      - patch\n                      - resourceName\n                      - resourceType\n                      - type\n                      type: object\n                    type: array\n                    x-kubernetes-list-type: atomic\n                type: object\n              imagePullPolicy:\n                description: The ImagePullPolicy to use.\n                type: string\n              imagePullSecrets:\n                description: |-\n                  The imagePullSecrets to pull the container images from\n                  Defaults to none\n                items:\n                  description: |-\n                    LocalObjectReference contains enough information to let you locate the\n                    referenced object inside the same namespace.\n                  properties:\n                    name:\n                      default: \"\"\n                      description: |-\n                        Name of the referent.\n                        This field is effectively required, but due to backwards compatibility is\n                        allowed to be empty. Instances of this type with an empty value here are\n                        almost certainly wrong.\n                        More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                type: array\n                x-kubernetes-list-type: atomic\n              imageRegistry:\n                description: |-\n                  The image registry to pull the container images from\n                  Defaults to the same registry the operator's container image is pulled from.\n                type: string\n              imageTag:\n                description: |-\n                  The image tag to use for the continer images installed.\n                  Defaults to the same tag as the operator's container image.\n                type: string\n              infra:\n                description: selectors and tolerations that should apply to KubeVirt\n                  infrastructure components\n                properties:\n                  nodePlacement:\n                    description: |-\n                      nodePlacement describes scheduling configuration for specific\n                      KubeVirt components\n                    properties:\n                      affinity:\n                        description: |-\n                          affinity enables pod affinity/anti-affinity placement expanding the types of constraints\n                          that can be expressed with nodeSelector.\n                          affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector\n                          See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity\n                        properties:\n                          nodeAffinity:\n                            description: Describes node affinity scheduling rules\n                              for the pod.\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node matches the corresponding matchExpressions; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: |-\n                                    An empty preferred scheduling term matches all objects with implicit weight 0\n                                    (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).\n                                  properties:\n                                    preference:\n                                      description: A node selector term, associated\n                                        with the corresponding weight.\n                                      properties:\n                                        matchExpressions:\n                                          description: A list of node selector requirements\n                                            by node's labels.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchFields:\n                                          description: A list of node selector requirements\n                                            by node's fields.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    weight:\n                                      description: Weight associated with matching\n                                        the corresponding nodeSelectorTerm, in the\n                                        range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - preference\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to an update), the system\n                                  may or may not try to eventually evict the pod from its node.\n                                properties:\n                                  nodeSelectorTerms:\n                                    description: Required. A list of node selector\n                                      terms. The terms are ORed.\n                                    items:\n                                      description: |-\n                                        A null or empty node selector term matches no objects. The requirements of\n                                        them are ANDed.\n                                        The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.\n                                      properties:\n                                        matchExpressions:\n                                          description: A list of node selector requirements\n                                            by node's labels.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchFields:\n                                          description: A list of node selector requirements\n                                            by node's fields.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                required:\n                                - nodeSelectorTerms\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                          podAffinity:\n                            description: Describes pod affinity scheduling rules (e.g.\n                              co-locate this pod in the same node, zone, etc. as some\n                              other pod(s)).\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: The weights of all of the matched WeightedPodAffinityTerm\n                                    fields are added per-node to find the most preferred\n                                    node(s)\n                                  properties:\n                                    podAffinityTerm:\n                                      description: Required. A pod affinity term,\n                                        associated with the corresponding weight.\n                                      properties:\n                                        labelSelector:\n                                          description: |-\n                                            A label query over a set of resources, in this case pods.\n                                            If it's null, this PodAffinityTerm matches with no Pods.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        matchLabelKeys:\n                                          description: |-\n                                            MatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                            Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        mismatchLabelKeys:\n                                          description: |-\n                                            MismatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                            Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        namespaceSelector:\n                                          description: |-\n                                            A label query over the set of namespaces that the term applies to.\n                                            The term is applied to the union of the namespaces selected by this field\n                                            and the ones listed in the namespaces field.\n                                            null selector and null or empty namespaces list means \"this pod's namespace\".\n                                            An empty selector ({}) matches all namespaces.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        namespaces:\n                                          description: |-\n                                            namespaces specifies a static list of namespace names that the term applies to.\n                                            The term is applied to the union of the namespaces listed in this field\n                                            and the ones selected by namespaceSelector.\n                                            null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        topologyKey:\n                                          description: |-\n                                            This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                            the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                            whose value of the label with key topologyKey matches that of any node on which any of the\n                                            selected pods is running.\n                                            Empty topologyKey is not allowed.\n                                          type: string\n                                      required:\n                                      - topologyKey\n                                      type: object\n                                    weight:\n                                      description: |-\n                                        weight associated with matching the corresponding podAffinityTerm,\n                                        in the range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - podAffinityTerm\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to a pod label update), the\n                                  system may or may not try to eventually evict the pod from its node.\n                                  When there are multiple elements, the lists of nodes corresponding to each\n                                  podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                                items:\n                                  description: |-\n                                    Defines a set of pods (namely those matching the labelSelector\n                                    relative to the given namespace(s)) that this pod should be\n                                    co-located (affinity) or not co-located (anti-affinity) with,\n                                    where co-located is defined as running on a node whose value of\n                                    the label with key <topologyKey> matches that of any node on which\n                                    a pod of the set of pods is running\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            type: object\n                          podAntiAffinity:\n                            description: Describes pod anti-affinity scheduling rules\n                              (e.g. avoid putting this pod in the same node, zone,\n                              etc. as some other pod(s)).\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the anti-affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling anti-affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: The weights of all of the matched WeightedPodAffinityTerm\n                                    fields are added per-node to find the most preferred\n                                    node(s)\n                                  properties:\n                                    podAffinityTerm:\n                                      description: Required. A pod affinity term,\n                                        associated with the corresponding weight.\n                                      properties:\n                                        labelSelector:\n                                          description: |-\n                                            A label query over a set of resources, in this case pods.\n                                            If it's null, this PodAffinityTerm matches with no Pods.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        matchLabelKeys:\n                                          description: |-\n                                            MatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                            Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        mismatchLabelKeys:\n                                          description: |-\n                                            MismatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                            Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        namespaceSelector:\n                                          description: |-\n                                            A label query over the set of namespaces that the term applies to.\n                                            The term is applied to the union of the namespaces selected by this field\n                                            and the ones listed in the namespaces field.\n                                            null selector and null or empty namespaces list means \"this pod's namespace\".\n                                            An empty selector ({}) matches all namespaces.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        namespaces:\n                                          description: |-\n                                            namespaces specifies a static list of namespace names that the term applies to.\n                                            The term is applied to the union of the namespaces listed in this field\n                                            and the ones selected by namespaceSelector.\n                                            null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        topologyKey:\n                                          description: |-\n                                            This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                            the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                            whose value of the label with key topologyKey matches that of any node on which any of the\n                                            selected pods is running.\n                                            Empty topologyKey is not allowed.\n                                          type: string\n                                      required:\n                                      - topologyKey\n                                      type: object\n                                    weight:\n                                      description: |-\n                                        weight associated with matching the corresponding podAffinityTerm,\n                                        in the range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - podAffinityTerm\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the anti-affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the anti-affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to a pod label update), the\n                                  system may or may not try to eventually evict the pod from its node.\n                                  When there are multiple elements, the lists of nodes corresponding to each\n                                  podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                                items:\n                                  description: |-\n                                    Defines a set of pods (namely those matching the labelSelector\n                                    relative to the given namespace(s)) that this pod should be\n                                    co-located (affinity) or not co-located (anti-affinity) with,\n                                    where co-located is defined as running on a node whose value of\n                                    the label with key <topologyKey> matches that of any node on which\n                                    a pod of the set of pods is running\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            type: object\n                        type: object\n                      nodeSelector:\n                        additionalProperties:\n                          type: string\n                        description: |-\n                          nodeSelector is the node selector applied to the relevant kind of pods\n                          It specifies a map of key-value pairs: for the pod to be eligible to run on a node,\n                          the node must have each of the indicated key-value pairs as labels\n                          (it can have additional labels as well).\n                          See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector\n                        type: object\n                      tolerations:\n                        description: |-\n                          tolerations is a list of tolerations applied to the relevant kind of pods\n                          See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info.\n                          These are additional tolerations other than default ones.\n                        items:\n                          description: |-\n                            The pod this Toleration is attached to tolerates any taint that matches\n                            the triple <key,value,effect> using the matching operator <operator>.\n                          properties:\n                            effect:\n                              description: |-\n                                Effect indicates the taint effect to match. Empty means match all taint effects.\n                                When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.\n                              type: string\n                            key:\n                              description: |-\n                                Key is the taint key that the toleration applies to. Empty means match all taint keys.\n                                If the key is empty, operator must be Exists; this combination means to match all values and all keys.\n                              type: string\n                            operator:\n                              description: |-\n                                Operator represents a key's relationship to the value.\n                                Valid operators are Exists and Equal. Defaults to Equal.\n                                Exists is equivalent to wildcard for value, so that a pod can\n                                tolerate all taints of a particular category.\n                              type: string\n                            tolerationSeconds:\n                              description: |-\n                                TolerationSeconds represents the period of time the toleration (which must be\n                                of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,\n                                it is not set, which means tolerate the taint forever (do not evict). Zero and\n                                negative values will be treated as 0 (evict immediately) by the system.\n                              format: int64\n                              type: integer\n                            value:\n                              description: |-\n                                Value is the taint value the toleration matches to.\n                                If the operator is Exists, the value should be empty, otherwise just a regular string.\n                              type: string\n                          type: object\n                        type: array\n                    type: object\n                  replicas:\n                    description: |-\n                      replicas indicates how many replicas should be created for each KubeVirt infrastructure\n                      component (like virt-api or virt-controller). Defaults to 2.\n                      WARNING: this is an advanced feature that prevents auto-scaling for core kubevirt components. Please use with caution!\n                    type: integer\n                type: object\n              monitorAccount:\n                description: |-\n                  The name of the Prometheus service account that needs read-access to KubeVirt endpoints\n                  Defaults to prometheus-k8s\n                type: string\n              monitorNamespace:\n                description: |-\n                  The namespace Prometheus is deployed in\n                  Defaults to openshift-monitor\n                type: string\n              productComponent:\n                description: |-\n                  Designate the apps.kubevirt.io/component label for KubeVirt components.\n                  Useful if KubeVirt is included as part of a product.\n                  If ProductComponent is not specified, the component label default value is kubevirt.\n                type: string\n              productName:\n                description: |-\n                  Designate the apps.kubevirt.io/part-of label for KubeVirt components.\n                  Useful if KubeVirt is included as part of a product.\n                  If ProductName is not specified, the part-of label will be omitted.\n                type: string\n              productVersion:\n                description: |-\n                  Designate the apps.kubevirt.io/version label for KubeVirt components.\n                  Useful if KubeVirt is included as part of a product.\n                  If ProductVersion is not specified, KubeVirt's version will be used.\n                type: string\n              serviceMonitorNamespace:\n                description: |-\n                  The namespace the service monitor will be deployed\n                   When ServiceMonitorNamespace is set, then we'll install the service monitor object in that namespace\n                  otherwise we will use the monitoring namespace.\n                type: string\n              uninstallStrategy:\n                description: |-\n                  Specifies if kubevirt can be deleted if workloads are still present.\n                  This is mainly a precaution to avoid accidental data loss\n                type: string\n              workloadUpdateStrategy:\n                description: |-\n                  WorkloadUpdateStrategy defines at the cluster level how to handle\n                  automated workload updates\n                properties:\n                  batchEvictionInterval:\n                    description: |-\n                      BatchEvictionInterval Represents the interval to wait before issuing the next\n                      batch of shutdowns\n\n                      Defaults to 1 minute\n                    type: string\n                  batchEvictionSize:\n                    description: |-\n                      BatchEvictionSize Represents the number of VMIs that can be forced updated per\n                      the BatchShutdownInteral interval\n\n                      Defaults to 10\n                    type: integer\n                  workloadUpdateMethods:\n                    description: |-\n                      WorkloadUpdateMethods defines the methods that can be used to disrupt workloads\n                      during automated workload updates.\n                      When multiple methods are present, the least disruptive method takes\n                      precedence over more disruptive methods. For example if both LiveMigrate and Shutdown\n                      methods are listed, only VMs which are not live migratable will be restarted/shutdown\n\n                      An empty list defaults to no automated workload updating\n                    items:\n                      type: string\n                    type: array\n                    x-kubernetes-list-type: atomic\n                type: object\n              workloads:\n                description: selectors and tolerations that should apply to KubeVirt\n                  workloads\n                properties:\n                  nodePlacement:\n                    description: |-\n                      nodePlacement describes scheduling configuration for specific\n                      KubeVirt components\n                    properties:\n                      affinity:\n                        description: |-\n                          affinity enables pod affinity/anti-affinity placement expanding the types of constraints\n                          that can be expressed with nodeSelector.\n                          affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector\n                          See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity\n                        properties:\n                          nodeAffinity:\n                            description: Describes node affinity scheduling rules\n                              for the pod.\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node matches the corresponding matchExpressions; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: |-\n                                    An empty preferred scheduling term matches all objects with implicit weight 0\n                                    (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).\n                                  properties:\n                                    preference:\n                                      description: A node selector term, associated\n                                        with the corresponding weight.\n                                      properties:\n                                        matchExpressions:\n                                          description: A list of node selector requirements\n                                            by node's labels.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchFields:\n                                          description: A list of node selector requirements\n                                            by node's fields.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    weight:\n                                      description: Weight associated with matching\n                                        the corresponding nodeSelectorTerm, in the\n                                        range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - preference\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to an update), the system\n                                  may or may not try to eventually evict the pod from its node.\n                                properties:\n                                  nodeSelectorTerms:\n                                    description: Required. A list of node selector\n                                      terms. The terms are ORed.\n                                    items:\n                                      description: |-\n                                        A null or empty node selector term matches no objects. The requirements of\n                                        them are ANDed.\n                                        The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.\n                                      properties:\n                                        matchExpressions:\n                                          description: A list of node selector requirements\n                                            by node's labels.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchFields:\n                                          description: A list of node selector requirements\n                                            by node's fields.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                required:\n                                - nodeSelectorTerms\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                          podAffinity:\n                            description: Describes pod affinity scheduling rules (e.g.\n                              co-locate this pod in the same node, zone, etc. as some\n                              other pod(s)).\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: The weights of all of the matched WeightedPodAffinityTerm\n                                    fields are added per-node to find the most preferred\n                                    node(s)\n                                  properties:\n                                    podAffinityTerm:\n                                      description: Required. A pod affinity term,\n                                        associated with the corresponding weight.\n                                      properties:\n                                        labelSelector:\n                                          description: |-\n                                            A label query over a set of resources, in this case pods.\n                                            If it's null, this PodAffinityTerm matches with no Pods.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        matchLabelKeys:\n                                          description: |-\n                                            MatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                            Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        mismatchLabelKeys:\n                                          description: |-\n                                            MismatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                            Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        namespaceSelector:\n                                          description: |-\n                                            A label query over the set of namespaces that the term applies to.\n                                            The term is applied to the union of the namespaces selected by this field\n                                            and the ones listed in the namespaces field.\n                                            null selector and null or empty namespaces list means \"this pod's namespace\".\n                                            An empty selector ({}) matches all namespaces.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        namespaces:\n                                          description: |-\n                                            namespaces specifies a static list of namespace names that the term applies to.\n                                            The term is applied to the union of the namespaces listed in this field\n                                            and the ones selected by namespaceSelector.\n                                            null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        topologyKey:\n                                          description: |-\n                                            This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                            the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                            whose value of the label with key topologyKey matches that of any node on which any of the\n                                            selected pods is running.\n                                            Empty topologyKey is not allowed.\n                                          type: string\n                                      required:\n                                      - topologyKey\n                                      type: object\n                                    weight:\n                                      description: |-\n                                        weight associated with matching the corresponding podAffinityTerm,\n                                        in the range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - podAffinityTerm\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to a pod label update), the\n                                  system may or may not try to eventually evict the pod from its node.\n                                  When there are multiple elements, the lists of nodes corresponding to each\n                                  podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                                items:\n                                  description: |-\n                                    Defines a set of pods (namely those matching the labelSelector\n                                    relative to the given namespace(s)) that this pod should be\n                                    co-located (affinity) or not co-located (anti-affinity) with,\n                                    where co-located is defined as running on a node whose value of\n                                    the label with key <topologyKey> matches that of any node on which\n                                    a pod of the set of pods is running\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            type: object\n                          podAntiAffinity:\n                            description: Describes pod anti-affinity scheduling rules\n                              (e.g. avoid putting this pod in the same node, zone,\n                              etc. as some other pod(s)).\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the anti-affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling anti-affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: The weights of all of the matched WeightedPodAffinityTerm\n                                    fields are added per-node to find the most preferred\n                                    node(s)\n                                  properties:\n                                    podAffinityTerm:\n                                      description: Required. A pod affinity term,\n                                        associated with the corresponding weight.\n                                      properties:\n                                        labelSelector:\n                                          description: |-\n                                            A label query over a set of resources, in this case pods.\n                                            If it's null, this PodAffinityTerm matches with no Pods.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        matchLabelKeys:\n                                          description: |-\n                                            MatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                            Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        mismatchLabelKeys:\n                                          description: |-\n                                            MismatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                            Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        namespaceSelector:\n                                          description: |-\n                                            A label query over the set of namespaces that the term applies to.\n                                            The term is applied to the union of the namespaces selected by this field\n                                            and the ones listed in the namespaces field.\n                                            null selector and null or empty namespaces list means \"this pod's namespace\".\n                                            An empty selector ({}) matches all namespaces.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        namespaces:\n                                          description: |-\n                                            namespaces specifies a static list of namespace names that the term applies to.\n                                            The term is applied to the union of the namespaces listed in this field\n                                            and the ones selected by namespaceSelector.\n                                            null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        topologyKey:\n                                          description: |-\n                                            This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                            the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                            whose value of the label with key topologyKey matches that of any node on which any of the\n                                            selected pods is running.\n                                            Empty topologyKey is not allowed.\n                                          type: string\n                                      required:\n                                      - topologyKey\n                                      type: object\n                                    weight:\n                                      description: |-\n                                        weight associated with matching the corresponding podAffinityTerm,\n                                        in the range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - podAffinityTerm\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the anti-affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the anti-affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to a pod label update), the\n                                  system may or may not try to eventually evict the pod from its node.\n                                  When there are multiple elements, the lists of nodes corresponding to each\n                                  podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                                items:\n                                  description: |-\n                                    Defines a set of pods (namely those matching the labelSelector\n                                    relative to the given namespace(s)) that this pod should be\n                                    co-located (affinity) or not co-located (anti-affinity) with,\n                                    where co-located is defined as running on a node whose value of\n                                    the label with key <topologyKey> matches that of any node on which\n                                    a pod of the set of pods is running\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            type: object\n                        type: object\n                      nodeSelector:\n                        additionalProperties:\n                          type: string\n                        description: |-\n                          nodeSelector is the node selector applied to the relevant kind of pods\n                          It specifies a map of key-value pairs: for the pod to be eligible to run on a node,\n                          the node must have each of the indicated key-value pairs as labels\n                          (it can have additional labels as well).\n                          See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector\n                        type: object\n                      tolerations:\n                        description: |-\n                          tolerations is a list of tolerations applied to the relevant kind of pods\n                          See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info.\n                          These are additional tolerations other than default ones.\n                        items:\n                          description: |-\n                            The pod this Toleration is attached to tolerates any taint that matches\n                            the triple <key,value,effect> using the matching operator <operator>.\n                          properties:\n                            effect:\n                              description: |-\n                                Effect indicates the taint effect to match. Empty means match all taint effects.\n                                When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.\n                              type: string\n                            key:\n                              description: |-\n                                Key is the taint key that the toleration applies to. Empty means match all taint keys.\n                                If the key is empty, operator must be Exists; this combination means to match all values and all keys.\n                              type: string\n                            operator:\n                              description: |-\n                                Operator represents a key's relationship to the value.\n                                Valid operators are Exists and Equal. Defaults to Equal.\n                                Exists is equivalent to wildcard for value, so that a pod can\n                                tolerate all taints of a particular category.\n                              type: string\n                            tolerationSeconds:\n                              description: |-\n                                TolerationSeconds represents the period of time the toleration (which must be\n                                of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,\n                                it is not set, which means tolerate the taint forever (do not evict). Zero and\n                                negative values will be treated as 0 (evict immediately) by the system.\n                              format: int64\n                              type: integer\n                            value:\n                              description: |-\n                                Value is the taint value the toleration matches to.\n                                If the operator is Exists, the value should be empty, otherwise just a regular string.\n                              type: string\n                          type: object\n                        type: array\n                    type: object\n                  replicas:\n                    description: |-\n                      replicas indicates how many replicas should be created for each KubeVirt infrastructure\n                      component (like virt-api or virt-controller). Defaults to 2.\n                      WARNING: this is an advanced feature that prevents auto-scaling for core kubevirt components. Please use with caution!\n                    type: integer\n                type: object\n            type: object\n          status:\n            description: KubeVirtStatus represents information pertaining to a KubeVirt\n              deployment.\n            properties:\n              conditions:\n                items:\n                  description: KubeVirtCondition represents a condition of a KubeVirt\n                    deployment\n                  properties:\n                    lastProbeTime:\n                      format: date-time\n                      nullable: true\n                      type: string\n                    lastTransitionTime:\n                      format: date-time\n                      nullable: true\n                      type: string\n                    message:\n                      type: string\n                    reason:\n                      type: string\n                    status:\n                      type: string\n                    type:\n                      type: string\n                  required:\n                  - status\n                  - type\n                  type: object\n                type: array\n              defaultArchitecture:\n                type: string\n              generations:\n                items:\n                  description: GenerationStatus keeps track of the generation for\n                    a given resource so that decisions about forced updates can be\n                    made.\n                  properties:\n                    group:\n                      description: group is the group of the thing you're tracking\n                      type: string\n                    hash:\n                      description: hash is an optional field set for resources without\n                        generation that are content sensitive like secrets and configmaps\n                      type: string\n                    lastGeneration:\n                      description: lastGeneration is the last generation of the workload\n                        controller involved\n                      format: int64\n                      type: integer\n                    name:\n                      description: name is the name of the thing you're tracking\n                      type: string\n                    namespace:\n                      description: namespace is where the thing you're tracking is\n                      type: string\n                    resource:\n                      description: resource is the resource type of the thing you're\n                        tracking\n                      type: string\n                  required:\n                  - group\n                  - lastGeneration\n                  - name\n                  - resource\n                  type: object\n                type: array\n                x-kubernetes-list-type: atomic\n              observedDeploymentConfig:\n                type: string\n              observedDeploymentID:\n                type: string\n              observedGeneration:\n                format: int64\n                type: integer\n              observedKubeVirtRegistry:\n                type: string\n              observedKubeVirtVersion:\n                type: string\n              operatorVersion:\n                type: string\n              outdatedVirtualMachineInstanceWorkloads:\n                type: integer\n              phase:\n                description: KubeVirtPhase is a label for the phase of a KubeVirt\n                  deployment at the current time.\n                type: string\n              targetDeploymentConfig:\n                type: string\n              targetDeploymentID:\n                type: string\n              targetKubeVirtRegistry:\n                type: string\n              targetKubeVirtVersion:\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n  - additionalPrinterColumns:\n    - jsonPath: .metadata.creationTimestamp\n      name: Age\n      type: date\n    - jsonPath: .status.phase\n      name: Phase\n      type: string\n    deprecated: true\n    deprecationWarning: kubevirt.io/v1alpha3 is now deprecated and will be removed\n      in a future release.\n    name: v1alpha3\n    schema:\n      openAPIV3Schema:\n        description: KubeVirt represents the object deploying all KubeVirt resources\n        properties:\n          apiVersion:\n            description: |-\n              APIVersion defines the versioned schema of this representation of an object.\n              Servers should convert recognized schemas to the latest internal value, and\n              may reject unrecognized values.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources\n            type: string\n          kind:\n            description: |-\n              Kind is a string value representing the REST resource this object represents.\n              Servers may infer this from the endpoint the client submits requests to.\n              Cannot be updated.\n              In CamelCase.\n              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n            type: string\n          metadata:\n            type: object\n          spec:\n            properties:\n              certificateRotateStrategy:\n                properties:\n                  selfSigned:\n                    properties:\n                      ca:\n                        description: |-\n                          CA configuration\n                          CA certs are kept in the CA bundle as long as they are valid\n                        properties:\n                          duration:\n                            description: The requested 'duration' (i.e. lifetime)\n                              of the Certificate.\n                            type: string\n                          renewBefore:\n                            description: |-\n                              The amount of time before the currently issued certificate's \"notAfter\"\n                              time that we will begin to attempt to renew the certificate.\n                            type: string\n                        type: object\n                      caOverlapInterval:\n                        description: Deprecated. Use CA.Duration and CA.RenewBefore\n                          instead\n                        type: string\n                      caRotateInterval:\n                        description: Deprecated. Use CA.Duration instead\n                        type: string\n                      certRotateInterval:\n                        description: Deprecated. Use Server.Duration instead\n                        type: string\n                      server:\n                        description: |-\n                          Server configuration\n                          Certs are rotated and discarded\n                        properties:\n                          duration:\n                            description: The requested 'duration' (i.e. lifetime)\n                              of the Certificate.\n                            type: string\n                          renewBefore:\n                            description: |-\n                              The amount of time before the currently issued certificate's \"notAfter\"\n                              time that we will begin to attempt to renew the certificate.\n                            type: string\n                        type: object\n                    type: object\n                type: object\n              configuration:\n                description: |-\n                  holds kubevirt configurations.\n                  same as the virt-configMap\n                properties:\n                  additionalGuestMemoryOverheadRatio:\n                    description: |-\n                      AdditionalGuestMemoryOverheadRatio can be used to increase the virtualization infrastructure\n                      overhead. This is useful, since the calculation of this overhead is not accurate and cannot\n                      be entirely known in advance. The ratio that is being set determines by which factor to increase\n                      the overhead calculated by Kubevirt. A higher ratio means that the VMs would be less compromised\n                      by node pressures, but would mean that fewer VMs could be scheduled to a node.\n                      If not set, the default is 1.\n                    type: string\n                  apiConfiguration:\n                    description: |-\n                      ReloadableComponentConfiguration holds all generic k8s configuration options which can\n                      be reloaded by components without requiring a restart.\n                    properties:\n                      restClient:\n                        description: RestClient can be used to tune certain aspects\n                          of the k8s client in use.\n                        properties:\n                          rateLimiter:\n                            description: RateLimiter allows selecting and configuring\n                              different rate limiters for the k8s client.\n                            properties:\n                              tokenBucketRateLimiter:\n                                properties:\n                                  burst:\n                                    description: |-\n                                      Maximum burst for throttle.\n                                      If it's zero, the component default will be used\n                                    type: integer\n                                  qps:\n                                    description: |-\n                                      QPS indicates the maximum QPS to the apiserver from this client.\n                                      If it's zero, the component default will be used\n                                    type: number\n                                required:\n                                - burst\n                                - qps\n                                type: object\n                            type: object\n                        type: object\n                    type: object\n                  architectureConfiguration:\n                    properties:\n                      amd64:\n                        properties:\n                          emulatedMachines:\n                            items:\n                              type: string\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          machineType:\n                            type: string\n                          ovmfPath:\n                            type: string\n                        type: object\n                      arm64:\n                        properties:\n                          emulatedMachines:\n                            items:\n                              type: string\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          machineType:\n                            type: string\n                          ovmfPath:\n                            type: string\n                        type: object\n                      defaultArchitecture:\n                        type: string\n                      ppc64le:\n                        properties:\n                          emulatedMachines:\n                            items:\n                              type: string\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          machineType:\n                            type: string\n                          ovmfPath:\n                            type: string\n                        type: object\n                    type: object\n                  autoCPULimitNamespaceLabelSelector:\n                    description: |-\n                      When set, AutoCPULimitNamespaceLabelSelector will set a CPU limit on virt-launcher for VMIs running inside\n                      namespaces that match the label selector.\n                      The CPU limit will equal the number of requested vCPUs.\n                      This setting does not apply to VMIs with dedicated CPUs.\n                    properties:\n                      matchExpressions:\n                        description: matchExpressions is a list of label selector\n                          requirements. The requirements are ANDed.\n                        items:\n                          description: |-\n                            A label selector requirement is a selector that contains values, a key, and an operator that\n                            relates the key and values.\n                          properties:\n                            key:\n                              description: key is the label key that the selector\n                                applies to.\n                              type: string\n                            operator:\n                              description: |-\n                                operator represents a key's relationship to a set of values.\n                                Valid operators are In, NotIn, Exists and DoesNotExist.\n                              type: string\n                            values:\n                              description: |-\n                                values is an array of string values. If the operator is In or NotIn,\n                                the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                the values array must be empty. This array is replaced during a strategic\n                                merge patch.\n                              items:\n                                type: string\n                              type: array\n                              x-kubernetes-list-type: atomic\n                          required:\n                          - key\n                          - operator\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      matchLabels:\n                        additionalProperties:\n                          type: string\n                        description: |-\n                          matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                          map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                          operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                        type: object\n                    type: object\n                    x-kubernetes-map-type: atomic\n                  commonInstancetypesDeployment:\n                    description: CommonInstancetypesDeployment controls the deployment\n                      of common-instancetypes resources\n                    nullable: true\n                    properties:\n                      enabled:\n                        description: Enabled controls the deployment of common-instancetypes\n                          resources, defaults to True.\n                        nullable: true\n                        type: boolean\n                    type: object\n                  controllerConfiguration:\n                    description: |-\n                      ReloadableComponentConfiguration holds all generic k8s configuration options which can\n                      be reloaded by components without requiring a restart.\n                    properties:\n                      restClient:\n                        description: RestClient can be used to tune certain aspects\n                          of the k8s client in use.\n                        properties:\n                          rateLimiter:\n                            description: RateLimiter allows selecting and configuring\n                              different rate limiters for the k8s client.\n                            properties:\n                              tokenBucketRateLimiter:\n                                properties:\n                                  burst:\n                                    description: |-\n                                      Maximum burst for throttle.\n                                      If it's zero, the component default will be used\n                                    type: integer\n                                  qps:\n                                    description: |-\n                                      QPS indicates the maximum QPS to the apiserver from this client.\n                                      If it's zero, the component default will be used\n                                    type: number\n                                required:\n                                - burst\n                                - qps\n                                type: object\n                            type: object\n                        type: object\n                    type: object\n                  cpuModel:\n                    type: string\n                  cpuRequest:\n                    anyOf:\n                    - type: integer\n                    - type: string\n                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                    x-kubernetes-int-or-string: true\n                  defaultRuntimeClass:\n                    type: string\n                  developerConfiguration:\n                    description: DeveloperConfiguration holds developer options\n                    properties:\n                      cpuAllocationRatio:\n                        description: |-\n                          For each requested virtual CPU, CPUAllocationRatio defines how much physical CPU to request per VMI\n                          from the hosting node. The value is in fraction of a CPU thread (or core on non-hyperthreaded nodes).\n                          For example, a value of 1 means 1 physical CPU thread per VMI CPU thread.\n                          A value of 100 would be 1% of a physical thread allocated for each requested VMI thread.\n                          This option has no effect on VMIs that request dedicated CPUs. More information at:\n                          https://kubevirt.io/user-guide/operations/node_overcommit/#node-cpu-allocation-ratio\n                          Defaults to 10\n                        type: integer\n                      diskVerification:\n                        description: DiskVerification holds container disks verification\n                          limits\n                        properties:\n                          memoryLimit:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                        required:\n                        - memoryLimit\n                        type: object\n                      featureGates:\n                        description: FeatureGates is the list of experimental features\n                          to enable. Defaults to none\n                        items:\n                          type: string\n                        type: array\n                      logVerbosity:\n                        description: LogVerbosity sets log verbosity level of  various\n                          components\n                        properties:\n                          nodeVerbosity:\n                            additionalProperties:\n                              type: integer\n                            description: NodeVerbosity represents a map of nodes with\n                              a specific verbosity level\n                            type: object\n                          virtAPI:\n                            type: integer\n                          virtController:\n                            type: integer\n                          virtHandler:\n                            type: integer\n                          virtLauncher:\n                            type: integer\n                          virtOperator:\n                            type: integer\n                        type: object\n                      memoryOvercommit:\n                        description: |-\n                          MemoryOvercommit is the percentage of memory we want to give VMIs compared to the amount\n                          given to its parent pod (virt-launcher). For example, a value of 102 means the VMI will\n                          \"see\" 2% more memory than its parent pod. Values under 100 are effectively \"undercommits\".\n                          Overcommits can lead to memory exhaustion, which in turn can lead to crashes. Use carefully.\n                          Defaults to 100\n                        type: integer\n                      minimumClusterTSCFrequency:\n                        description: |-\n                          Allow overriding the automatically determined minimum TSC frequency of the cluster\n                          and fixate the minimum to this frequency.\n                        format: int64\n                        type: integer\n                      minimumReservePVCBytes:\n                        description: |-\n                          MinimumReservePVCBytes is the amount of space, in bytes, to leave unused on disks.\n                          Defaults to 131072 (128KiB)\n                        format: int64\n                        type: integer\n                      nodeSelectors:\n                        additionalProperties:\n                          type: string\n                        description: |-\n                          NodeSelectors allows restricting VMI creation to nodes that match a set of labels.\n                          Defaults to none\n                        type: object\n                      pvcTolerateLessSpaceUpToPercent:\n                        description: |-\n                          LessPVCSpaceToleration determines how much smaller, in percentage, disk PVCs are\n                          allowed to be compared to the requested size (to account for various overheads).\n                          Defaults to 10\n                        type: integer\n                      useEmulation:\n                        description: |-\n                          UseEmulation can be set to true to allow fallback to software emulation\n                          in case hardware-assisted emulation is not available. Defaults to false\n                        type: boolean\n                    type: object\n                  emulatedMachines:\n                    description: Deprecated. Use architectureConfiguration instead.\n                    items:\n                      type: string\n                    type: array\n                  evictionStrategy:\n                    description: |-\n                      EvictionStrategy defines at the cluster level if the VirtualMachineInstance should be\n                      migrated instead of shut-off in case of a node drain. If the VirtualMachineInstance specific\n                      field is set it overrides the cluster level one.\n                    type: string\n                  handlerConfiguration:\n                    description: |-\n                      ReloadableComponentConfiguration holds all generic k8s configuration options which can\n                      be reloaded by components without requiring a restart.\n                    properties:\n                      restClient:\n                        description: RestClient can be used to tune certain aspects\n                          of the k8s client in use.\n                        properties:\n                          rateLimiter:\n                            description: RateLimiter allows selecting and configuring\n                              different rate limiters for the k8s client.\n                            properties:\n                              tokenBucketRateLimiter:\n                                properties:\n                                  burst:\n                                    description: |-\n                                      Maximum burst for throttle.\n                                      If it's zero, the component default will be used\n                                    type: integer\n                                  qps:\n                                    description: |-\n                                      QPS indicates the maximum QPS to the apiserver from this client.\n                                      If it's zero, the component default will be used\n                                    type: number\n                                required:\n                                - burst\n                                - qps\n                                type: object\n                            type: object\n                        type: object\n                    type: object\n                  imagePullPolicy:\n                    description: PullPolicy describes a policy for if/when to pull\n                      a container image\n                    type: string\n                  instancetype:\n                    description: Instancetype configuration\n                    nullable: true\n                    properties:\n                      referencePolicy:\n                        description: |-\n                          ReferencePolicy defines how an instance type or preference should be referenced by the VM after submission, supported values are:\n                          reference (default) - Where a copy of the original object is stashed in a ControllerRevision and referenced by the VM.\n                          expand - Where the instance type or preference are expanded into the VM if no revisionNames have been populated.\n                          expandAll - Where the instance type or preference are expanded into the VM regardless of revisionNames previously being populated.\n                        enum:\n                        - reference\n                        - expand\n                        - expandAll\n                        nullable: true\n                        type: string\n                    type: object\n                  ksmConfiguration:\n                    description: KSMConfiguration holds the information regarding\n                      the enabling the KSM in the nodes (if available).\n                    properties:\n                      nodeLabelSelector:\n                        description: |-\n                          NodeLabelSelector is a selector that filters in which nodes the KSM will be enabled.\n                          Empty NodeLabelSelector will enable ksm for every node.\n                        properties:\n                          matchExpressions:\n                            description: matchExpressions is a list of label selector\n                              requirements. The requirements are ANDed.\n                            items:\n                              description: |-\n                                A label selector requirement is a selector that contains values, a key, and an operator that\n                                relates the key and values.\n                              properties:\n                                key:\n                                  description: key is the label key that the selector\n                                    applies to.\n                                  type: string\n                                operator:\n                                  description: |-\n                                    operator represents a key's relationship to a set of values.\n                                    Valid operators are In, NotIn, Exists and DoesNotExist.\n                                  type: string\n                                values:\n                                  description: |-\n                                    values is an array of string values. If the operator is In or NotIn,\n                                    the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                    the values array must be empty. This array is replaced during a strategic\n                                    merge patch.\n                                  items:\n                                    type: string\n                                  type: array\n                                  x-kubernetes-list-type: atomic\n                              required:\n                              - key\n                              - operator\n                              type: object\n                            type: array\n                            x-kubernetes-list-type: atomic\n                          matchLabels:\n                            additionalProperties:\n                              type: string\n                            description: |-\n                              matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                              map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                              operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                            type: object\n                        type: object\n                        x-kubernetes-map-type: atomic\n                    type: object\n                  liveUpdateConfiguration:\n                    description: LiveUpdateConfiguration holds defaults for live update\n                      features\n                    properties:\n                      maxCpuSockets:\n                        description: |-\n                          MaxCpuSockets provides a MaxSockets value for VMs that do not provide their own.\n                          For VMs with more sockets than maximum the MaxSockets will be set to equal number of sockets.\n                        format: int32\n                        type: integer\n                      maxGuest:\n                        anyOf:\n                        - type: integer\n                        - type: string\n                        description: |-\n                          MaxGuest defines the maximum amount memory that can be allocated\n                          to the guest using hotplug.\n                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                        x-kubernetes-int-or-string: true\n                      maxHotplugRatio:\n                        description: |-\n                          MaxHotplugRatio is the ratio used to define the max amount\n                          of a hotplug resource that can be made available to a VM\n                          when the specific Max* setting is not defined (MaxCpuSockets, MaxGuest)\n                          Example: VM is configured with 512Mi of guest memory, if MaxGuest is not\n                          defined and MaxHotplugRatio is 2 then MaxGuest = 1Gi\n                          defaults to 4\n                        format: int32\n                        type: integer\n                    type: object\n                  machineType:\n                    description: Deprecated. Use architectureConfiguration instead.\n                    type: string\n                  mediatedDevicesConfiguration:\n                    description: MediatedDevicesConfiguration holds information about\n                      MDEV types to be defined, if available\n                    properties:\n                      mediatedDeviceTypes:\n                        items:\n                          type: string\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      mediatedDevicesTypes:\n                        description: Deprecated. Use mediatedDeviceTypes instead.\n                        items:\n                          type: string\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      nodeMediatedDeviceTypes:\n                        items:\n                          description: NodeMediatedDeviceTypesConfig holds information\n                            about MDEV types to be defined in a specific node that\n                            matches the NodeSelector field.\n                          properties:\n                            mediatedDeviceTypes:\n                              items:\n                                type: string\n                              type: array\n                              x-kubernetes-list-type: atomic\n                            mediatedDevicesTypes:\n                              description: Deprecated. Use mediatedDeviceTypes instead.\n                              items:\n                                type: string\n                              type: array\n                              x-kubernetes-list-type: atomic\n                            nodeSelector:\n                              additionalProperties:\n                                type: string\n                              description: |-\n                                NodeSelector is a selector which must be true for the vmi to fit on a node.\n                                Selector which must match a node's labels for the vmi to be scheduled on that node.\n                                More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/\n                              type: object\n                          required:\n                          - nodeSelector\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                    type: object\n                  memBalloonStatsPeriod:\n                    format: int32\n                    type: integer\n                  migrations:\n                    description: |-\n                      MigrationConfiguration holds migration options.\n                      Can be overridden for specific groups of VMs though migration policies.\n                      Visit https://kubevirt.io/user-guide/operations/migration_policies/ for more information.\n                    properties:\n                      allowAutoConverge:\n                        description: |-\n                          AllowAutoConverge allows the platform to compromise performance/availability of VMIs to\n                          guarantee successful VMI live migrations. Defaults to false\n                        type: boolean\n                      allowPostCopy:\n                        description: |-\n                          AllowPostCopy enables post-copy live migrations. Such migrations allow even the busiest VMIs\n                          to successfully live-migrate. However, events like a network failure can cause a VMI crash.\n                          If set to true, migrations will still start in pre-copy, but switch to post-copy when\n                          CompletionTimeoutPerGiB triggers. Defaults to false\n                        type: boolean\n                      allowWorkloadDisruption:\n                        description: |-\n                          AllowWorkloadDisruption indicates that the migration shouldn't be\n                          canceled after acceptableCompletionTime is exceeded. Instead, if\n                          permitted, migration will be switched to post-copy or the VMI will be\n                          paused to allow the migration to complete\n                        type: boolean\n                      bandwidthPerMigration:\n                        anyOf:\n                        - type: integer\n                        - type: string\n                        description: |-\n                          BandwidthPerMigration limits the amount of network bandwidth live migrations are allowed to use.\n                          The value is in quantity per second. Defaults to 0 (no limit)\n                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                        x-kubernetes-int-or-string: true\n                      completionTimeoutPerGiB:\n                        description: |-\n                          CompletionTimeoutPerGiB is the maximum number of seconds per GiB a migration is allowed to take.\n                          If the timeout is reached, the migration will be either paused, switched\n                          to post-copy or cancelled depending on other settings. Defaults to 150\n                        format: int64\n                        type: integer\n                      disableTLS:\n                        description: |-\n                          When set to true, DisableTLS will disable the additional layer of live migration encryption\n                          provided by KubeVirt. This is usually a bad idea. Defaults to false\n                        type: boolean\n                      matchSELinuxLevelOnMigration:\n                        description: |-\n                          By default, the SELinux level of target virt-launcher pods is forced to the level of the source virt-launcher.\n                          When set to true, MatchSELinuxLevelOnMigration lets the CRI auto-assign a random level to the target.\n                          That will ensure the target virt-launcher doesn't share categories with another pod on the node.\n                          However, migrations will fail when using RWX volumes that don't automatically deal with SELinux levels.\n                        type: boolean\n                      network:\n                        description: |-\n                          Network is the name of the CNI network to use for live migrations. By default, migrations go\n                          through the pod network.\n                        type: string\n                      nodeDrainTaintKey:\n                        description: |-\n                          NodeDrainTaintKey defines the taint key that indicates a node should be drained.\n                          Note: this option relies on the deprecated node taint feature. Default: kubevirt.io/drain\n                        type: string\n                      parallelMigrationsPerCluster:\n                        description: |-\n                          ParallelMigrationsPerCluster is the total number of concurrent live migrations\n                          allowed cluster-wide. Defaults to 5\n                        format: int32\n                        type: integer\n                      parallelOutboundMigrationsPerNode:\n                        description: |-\n                          ParallelOutboundMigrationsPerNode is the maximum number of concurrent outgoing live migrations\n                          allowed per node. Defaults to 2\n                        format: int32\n                        type: integer\n                      progressTimeout:\n                        description: |-\n                          ProgressTimeout is the maximum number of seconds a live migration is allowed to make no progress.\n                          Hitting this timeout means a migration transferred 0 data for that many seconds. The migration is\n                          then considered stuck and therefore cancelled. Defaults to 150\n                        format: int64\n                        type: integer\n                      unsafeMigrationOverride:\n                        description: |-\n                          UnsafeMigrationOverride allows live migrations to occur even if the compatibility check\n                          indicates the migration will be unsafe to the guest. Defaults to false\n                        type: boolean\n                    type: object\n                  minCPUModel:\n                    type: string\n                  network:\n                    description: NetworkConfiguration holds network options\n                    properties:\n                      binding:\n                        additionalProperties:\n                          properties:\n                            computeResourceOverhead:\n                              description: |-\n                                ComputeResourceOverhead specifies the resource overhead that should be added to the compute container when using the binding.\n                                version: v1alphav1\n                              properties:\n                                limits:\n                                  additionalProperties:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  description: |-\n                                    Limits describes the maximum amount of compute resources allowed.\n                                    More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                                  type: object\n                                requests:\n                                  additionalProperties:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                  description: |-\n                                    Requests describes the minimum amount of compute resources required.\n                                    If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,\n                                    otherwise to an implementation-defined value. Requests cannot exceed Limits.\n                                    More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                                  type: object\n                              type: object\n                            domainAttachmentType:\n                              description: |-\n                                DomainAttachmentType is a standard domain network attachment method kubevirt supports.\n                                Supported values: \"tap\", \"managedTap\" (since v1.4).\n                                The standard domain attachment can be used instead or in addition to the sidecarImage.\n                                version: 1alphav1\n                              type: string\n                            downwardAPI:\n                              description: |-\n                                DownwardAPI specifies what kind of data should be exposed to the binding plugin sidecar.\n                                Supported values: \"device-info\"\n                                version: v1alphav1\n                              type: string\n                            migration:\n                              description: |-\n                                Migration means the VM using the plugin can be safely migrated\n                                version: 1alphav1\n                              properties:\n                                method:\n                                  description: |-\n                                    Method defines a pre-defined migration methodology\n                                    version: 1alphav1\n                                  type: string\n                              type: object\n                            networkAttachmentDefinition:\n                              description: |-\n                                NetworkAttachmentDefinition references to a NetworkAttachmentDefinition CR object.\n                                Format: <name>, <namespace>/<name>.\n                                If namespace is not specified, VMI namespace is assumed.\n                                version: 1alphav1\n                              type: string\n                            sidecarImage:\n                              description: |-\n                                SidecarImage references a container image that runs in the virt-launcher pod.\n                                The sidecar handles (libvirt) domain configuration and optional services.\n                                version: 1alphav1\n                              type: string\n                          type: object\n                        type: object\n                      defaultNetworkInterface:\n                        type: string\n                      permitBridgeInterfaceOnPodNetwork:\n                        type: boolean\n                      permitSlirpInterface:\n                        description: |-\n                          DeprecatedPermitSlirpInterface is an alias for the deprecated PermitSlirpInterface.\n                          Deprecated: Removed in v1.3.\n                        type: boolean\n                    type: object\n                  obsoleteCPUModels:\n                    additionalProperties:\n                      type: boolean\n                    type: object\n                  ovmfPath:\n                    description: Deprecated. Use architectureConfiguration instead.\n                    type: string\n                  permittedHostDevices:\n                    description: PermittedHostDevices holds information about devices\n                      allowed for passthrough\n                    properties:\n                      mediatedDevices:\n                        items:\n                          description: MediatedHostDevice represents a host mediated\n                            device allowed for passthrough\n                          properties:\n                            externalResourceProvider:\n                              type: boolean\n                            mdevNameSelector:\n                              type: string\n                            resourceName:\n                              type: string\n                          required:\n                          - mdevNameSelector\n                          - resourceName\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      pciHostDevices:\n                        items:\n                          description: PciHostDevice represents a host PCI device\n                            allowed for passthrough\n                          properties:\n                            externalResourceProvider:\n                              description: |-\n                                If true, KubeVirt will leave the allocation and monitoring to an\n                                external device plugin\n                              type: boolean\n                            pciVendorSelector:\n                              description: The vendor_id:product_id tuple of the PCI\n                                device\n                              type: string\n                            resourceName:\n                              description: |-\n                                The name of the resource that is representing the device. Exposed by\n                                a device plugin and requested by VMs. Typically of the form\n                                vendor.com/product_name\n                              type: string\n                          required:\n                          - pciVendorSelector\n                          - resourceName\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                      usb:\n                        items:\n                          properties:\n                            externalResourceProvider:\n                              description: |-\n                                If true, KubeVirt will leave the allocation and monitoring to an\n                                external device plugin\n                              type: boolean\n                            resourceName:\n                              description: |-\n                                Identifies the list of USB host devices.\n                                e.g: kubevirt.io/storage, kubevirt.io/bootable-usb, etc\n                              type: string\n                            selectors:\n                              items:\n                                properties:\n                                  product:\n                                    type: string\n                                  vendor:\n                                    type: string\n                                required:\n                                - product\n                                - vendor\n                                type: object\n                              type: array\n                              x-kubernetes-list-type: atomic\n                          required:\n                          - resourceName\n                          type: object\n                        type: array\n                        x-kubernetes-list-type: atomic\n                    type: object\n                  seccompConfiguration:\n                    description: SeccompConfiguration holds Seccomp configuration\n                      for Kubevirt components\n                    properties:\n                      virtualMachineInstanceProfile:\n                        description: VirtualMachineInstanceProfile defines what profile\n                          should be used with virt-launcher. Defaults to none\n                        properties:\n                          customProfile:\n                            description: CustomProfile allows to request arbitrary\n                              profile for virt-launcher\n                            properties:\n                              localhostProfile:\n                                type: string\n                              runtimeDefaultProfile:\n                                type: boolean\n                            type: object\n                        type: object\n                    type: object\n                  selinuxLauncherType:\n                    type: string\n                  smbios:\n                    properties:\n                      family:\n                        type: string\n                      manufacturer:\n                        type: string\n                      product:\n                        type: string\n                      sku:\n                        type: string\n                      version:\n                        type: string\n                    type: object\n                  supportContainerResources:\n                    description: SupportContainerResources specifies the resource\n                      requirements for various types of supporting containers such\n                      as container disks/virtiofs/sidecars and hotplug attachment\n                      pods. If omitted a sensible default will be supplied.\n                    items:\n                      description: SupportContainerResources are used to specify the\n                        cpu/memory request and limits for the containers that support\n                        various features of Virtual Machines. These containers are\n                        usually idle and don't require a lot of memory or cpu.\n                      properties:\n                        resources:\n                          description: |-\n                            ResourceRequirementsWithoutClaims describes the compute resource requirements.\n                            This struct was taken from the k8s.ResourceRequirements and cleaned up the 'Claims' field.\n                          properties:\n                            limits:\n                              additionalProperties:\n                                anyOf:\n                                - type: integer\n                                - type: string\n                                pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                x-kubernetes-int-or-string: true\n                              description: |-\n                                Limits describes the maximum amount of compute resources allowed.\n                                More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                              type: object\n                            requests:\n                              additionalProperties:\n                                anyOf:\n                                - type: integer\n                                - type: string\n                                pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                x-kubernetes-int-or-string: true\n                              description: |-\n                                Requests describes the minimum amount of compute resources required.\n                                If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,\n                                otherwise to an implementation-defined value. Requests cannot exceed Limits.\n                                More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\n                              type: object\n                          type: object\n                        type:\n                          type: string\n                      required:\n                      - resources\n                      - type\n                      type: object\n                    type: array\n                    x-kubernetes-list-map-keys:\n                    - type\n                    x-kubernetes-list-type: map\n                  supportedGuestAgentVersions:\n                    description: deprecated\n                    items:\n                      type: string\n                    type: array\n                  tlsConfiguration:\n                    description: TLSConfiguration holds TLS options\n                    properties:\n                      ciphers:\n                        items:\n                          type: string\n                        type: array\n                        x-kubernetes-list-type: set\n                      minTLSVersion:\n                        description: |-\n                          MinTLSVersion is a way to specify the minimum protocol version that is acceptable for TLS connections.\n                          Protocol versions are based on the following most common TLS configurations:\n\n                            https://ssl-config.mozilla.org/\n\n                          Note that SSLv3.0 is not a supported protocol version due to well known\n                          vulnerabilities such as POODLE: https://en.wikipedia.org/wiki/POODLE\n                        enum:\n                        - VersionTLS10\n                        - VersionTLS11\n                        - VersionTLS12\n                        - VersionTLS13\n                        type: string\n                    type: object\n                  virtualMachineInstancesPerNode:\n                    type: integer\n                  virtualMachineOptions:\n                    description: VirtualMachineOptions holds the cluster level information\n                      regarding the virtual machine.\n                    properties:\n                      disableFreePageReporting:\n                        description: |-\n                          DisableFreePageReporting disable the free page reporting of\n                          memory balloon device https://libvirt.org/formatdomain.html#memory-balloon-device.\n                          This will have effect only if AutoattachMemBalloon is not false and the vmi is not\n                          requesting any high performance feature (dedicatedCPU/realtime/hugePages), in which free page reporting is always disabled.\n                        type: object\n                      disableSerialConsoleLog:\n                        description: |-\n                          DisableSerialConsoleLog disables logging the auto-attached default serial console.\n                          If not set, serial console logs will be written to a file and then streamed from a container named 'guest-console-log'.\n                          The value can be individually overridden for each VM, not relevant if AutoattachSerialConsole is disabled.\n                        type: object\n                    type: object\n                  vmRolloutStrategy:\n                    description: |-\n                      VMRolloutStrategy defines how live-updatable fields, like CPU sockets, memory,\n                      tolerations, and affinity, are propagated from a VM to its VMI.\n                    enum:\n                    - Stage\n                    - LiveUpdate\n                    nullable: true\n                    type: string\n                  vmStateStorageClass:\n                    description: VMStateStorageClass is the name of the storage class\n                      to use for the PVCs created to preserve VM state, like TPM.\n                    type: string\n                  webhookConfiguration:\n                    description: |-\n                      ReloadableComponentConfiguration holds all generic k8s configuration options which can\n                      be reloaded by components without requiring a restart.\n                    properties:\n                      restClient:\n                        description: RestClient can be used to tune certain aspects\n                          of the k8s client in use.\n                        properties:\n                          rateLimiter:\n                            description: RateLimiter allows selecting and configuring\n                              different rate limiters for the k8s client.\n                            properties:\n                              tokenBucketRateLimiter:\n                                properties:\n                                  burst:\n                                    description: |-\n                                      Maximum burst for throttle.\n                                      If it's zero, the component default will be used\n                                    type: integer\n                                  qps:\n                                    description: |-\n                                      QPS indicates the maximum QPS to the apiserver from this client.\n                                      If it's zero, the component default will be used\n                                    type: number\n                                required:\n                                - burst\n                                - qps\n                                type: object\n                            type: object\n                        type: object\n                    type: object\n                type: object\n              customizeComponents:\n                properties:\n                  flags:\n                    description: Configure the value used for deployment and daemonset\n                      resources\n                    properties:\n                      api:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      controller:\n                        additionalProperties:\n                          type: string\n                        type: object\n                      handler:\n                        additionalProperties:\n                          type: string\n                        type: object\n                    type: object\n                  patches:\n                    items:\n                      properties:\n                        patch:\n                          type: string\n                        resourceName:\n                          minLength: 1\n                          type: string\n                        resourceType:\n                          minLength: 1\n                          type: string\n                        type:\n                          type: string\n                      required:\n                      - patch\n                      - resourceName\n                      - resourceType\n                      - type\n                      type: object\n                    type: array\n                    x-kubernetes-list-type: atomic\n                type: object\n              imagePullPolicy:\n                description: The ImagePullPolicy to use.\n                type: string\n              imagePullSecrets:\n                description: |-\n                  The imagePullSecrets to pull the container images from\n                  Defaults to none\n                items:\n                  description: |-\n                    LocalObjectReference contains enough information to let you locate the\n                    referenced object inside the same namespace.\n                  properties:\n                    name:\n                      default: \"\"\n                      description: |-\n                        Name of the referent.\n                        This field is effectively required, but due to backwards compatibility is\n                        allowed to be empty. Instances of this type with an empty value here are\n                        almost certainly wrong.\n                        More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                      type: string\n                  type: object\n                  x-kubernetes-map-type: atomic\n                type: array\n                x-kubernetes-list-type: atomic\n              imageRegistry:\n                description: |-\n                  The image registry to pull the container images from\n                  Defaults to the same registry the operator's container image is pulled from.\n                type: string\n              imageTag:\n                description: |-\n                  The image tag to use for the continer images installed.\n                  Defaults to the same tag as the operator's container image.\n                type: string\n              infra:\n                description: selectors and tolerations that should apply to KubeVirt\n                  infrastructure components\n                properties:\n                  nodePlacement:\n                    description: |-\n                      nodePlacement describes scheduling configuration for specific\n                      KubeVirt components\n                    properties:\n                      affinity:\n                        description: |-\n                          affinity enables pod affinity/anti-affinity placement expanding the types of constraints\n                          that can be expressed with nodeSelector.\n                          affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector\n                          See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity\n                        properties:\n                          nodeAffinity:\n                            description: Describes node affinity scheduling rules\n                              for the pod.\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node matches the corresponding matchExpressions; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: |-\n                                    An empty preferred scheduling term matches all objects with implicit weight 0\n                                    (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).\n                                  properties:\n                                    preference:\n                                      description: A node selector term, associated\n                                        with the corresponding weight.\n                                      properties:\n                                        matchExpressions:\n                                          description: A list of node selector requirements\n                                            by node's labels.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchFields:\n                                          description: A list of node selector requirements\n                                            by node's fields.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    weight:\n                                      description: Weight associated with matching\n                                        the corresponding nodeSelectorTerm, in the\n                                        range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - preference\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to an update), the system\n                                  may or may not try to eventually evict the pod from its node.\n                                properties:\n                                  nodeSelectorTerms:\n                                    description: Required. A list of node selector\n                                      terms. The terms are ORed.\n                                    items:\n                                      description: |-\n                                        A null or empty node selector term matches no objects. The requirements of\n                                        them are ANDed.\n                                        The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.\n                                      properties:\n                                        matchExpressions:\n                                          description: A list of node selector requirements\n                                            by node's labels.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchFields:\n                                          description: A list of node selector requirements\n                                            by node's fields.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                required:\n                                - nodeSelectorTerms\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                          podAffinity:\n                            description: Describes pod affinity scheduling rules (e.g.\n                              co-locate this pod in the same node, zone, etc. as some\n                              other pod(s)).\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: The weights of all of the matched WeightedPodAffinityTerm\n                                    fields are added per-node to find the most preferred\n                                    node(s)\n                                  properties:\n                                    podAffinityTerm:\n                                      description: Required. A pod affinity term,\n                                        associated with the corresponding weight.\n                                      properties:\n                                        labelSelector:\n                                          description: |-\n                                            A label query over a set of resources, in this case pods.\n                                            If it's null, this PodAffinityTerm matches with no Pods.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        matchLabelKeys:\n                                          description: |-\n                                            MatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                            Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        mismatchLabelKeys:\n                                          description: |-\n                                            MismatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                            Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        namespaceSelector:\n                                          description: |-\n                                            A label query over the set of namespaces that the term applies to.\n                                            The term is applied to the union of the namespaces selected by this field\n                                            and the ones listed in the namespaces field.\n                                            null selector and null or empty namespaces list means \"this pod's namespace\".\n                                            An empty selector ({}) matches all namespaces.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        namespaces:\n                                          description: |-\n                                            namespaces specifies a static list of namespace names that the term applies to.\n                                            The term is applied to the union of the namespaces listed in this field\n                                            and the ones selected by namespaceSelector.\n                                            null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        topologyKey:\n                                          description: |-\n                                            This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                            the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                            whose value of the label with key topologyKey matches that of any node on which any of the\n                                            selected pods is running.\n                                            Empty topologyKey is not allowed.\n                                          type: string\n                                      required:\n                                      - topologyKey\n                                      type: object\n                                    weight:\n                                      description: |-\n                                        weight associated with matching the corresponding podAffinityTerm,\n                                        in the range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - podAffinityTerm\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to a pod label update), the\n                                  system may or may not try to eventually evict the pod from its node.\n                                  When there are multiple elements, the lists of nodes corresponding to each\n                                  podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                                items:\n                                  description: |-\n                                    Defines a set of pods (namely those matching the labelSelector\n                                    relative to the given namespace(s)) that this pod should be\n                                    co-located (affinity) or not co-located (anti-affinity) with,\n                                    where co-located is defined as running on a node whose value of\n                                    the label with key <topologyKey> matches that of any node on which\n                                    a pod of the set of pods is running\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            type: object\n                          podAntiAffinity:\n                            description: Describes pod anti-affinity scheduling rules\n                              (e.g. avoid putting this pod in the same node, zone,\n                              etc. as some other pod(s)).\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the anti-affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling anti-affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: The weights of all of the matched WeightedPodAffinityTerm\n                                    fields are added per-node to find the most preferred\n                                    node(s)\n                                  properties:\n                                    podAffinityTerm:\n                                      description: Required. A pod affinity term,\n                                        associated with the corresponding weight.\n                                      properties:\n                                        labelSelector:\n                                          description: |-\n                                            A label query over a set of resources, in this case pods.\n                                            If it's null, this PodAffinityTerm matches with no Pods.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        matchLabelKeys:\n                                          description: |-\n                                            MatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                            Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        mismatchLabelKeys:\n                                          description: |-\n                                            MismatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                            Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        namespaceSelector:\n                                          description: |-\n                                            A label query over the set of namespaces that the term applies to.\n                                            The term is applied to the union of the namespaces selected by this field\n                                            and the ones listed in the namespaces field.\n                                            null selector and null or empty namespaces list means \"this pod's namespace\".\n                                            An empty selector ({}) matches all namespaces.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        namespaces:\n                                          description: |-\n                                            namespaces specifies a static list of namespace names that the term applies to.\n                                            The term is applied to the union of the namespaces listed in this field\n                                            and the ones selected by namespaceSelector.\n                                            null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        topologyKey:\n                                          description: |-\n                                            This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                            the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                            whose value of the label with key topologyKey matches that of any node on which any of the\n                                            selected pods is running.\n                                            Empty topologyKey is not allowed.\n                                          type: string\n                                      required:\n                                      - topologyKey\n                                      type: object\n                                    weight:\n                                      description: |-\n                                        weight associated with matching the corresponding podAffinityTerm,\n                                        in the range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - podAffinityTerm\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the anti-affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the anti-affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to a pod label update), the\n                                  system may or may not try to eventually evict the pod from its node.\n                                  When there are multiple elements, the lists of nodes corresponding to each\n                                  podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                                items:\n                                  description: |-\n                                    Defines a set of pods (namely those matching the labelSelector\n                                    relative to the given namespace(s)) that this pod should be\n                                    co-located (affinity) or not co-located (anti-affinity) with,\n                                    where co-located is defined as running on a node whose value of\n                                    the label with key <topologyKey> matches that of any node on which\n                                    a pod of the set of pods is running\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            type: object\n                        type: object\n                      nodeSelector:\n                        additionalProperties:\n                          type: string\n                        description: |-\n                          nodeSelector is the node selector applied to the relevant kind of pods\n                          It specifies a map of key-value pairs: for the pod to be eligible to run on a node,\n                          the node must have each of the indicated key-value pairs as labels\n                          (it can have additional labels as well).\n                          See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector\n                        type: object\n                      tolerations:\n                        description: |-\n                          tolerations is a list of tolerations applied to the relevant kind of pods\n                          See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info.\n                          These are additional tolerations other than default ones.\n                        items:\n                          description: |-\n                            The pod this Toleration is attached to tolerates any taint that matches\n                            the triple <key,value,effect> using the matching operator <operator>.\n                          properties:\n                            effect:\n                              description: |-\n                                Effect indicates the taint effect to match. Empty means match all taint effects.\n                                When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.\n                              type: string\n                            key:\n                              description: |-\n                                Key is the taint key that the toleration applies to. Empty means match all taint keys.\n                                If the key is empty, operator must be Exists; this combination means to match all values and all keys.\n                              type: string\n                            operator:\n                              description: |-\n                                Operator represents a key's relationship to the value.\n                                Valid operators are Exists and Equal. Defaults to Equal.\n                                Exists is equivalent to wildcard for value, so that a pod can\n                                tolerate all taints of a particular category.\n                              type: string\n                            tolerationSeconds:\n                              description: |-\n                                TolerationSeconds represents the period of time the toleration (which must be\n                                of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,\n                                it is not set, which means tolerate the taint forever (do not evict). Zero and\n                                negative values will be treated as 0 (evict immediately) by the system.\n                              format: int64\n                              type: integer\n                            value:\n                              description: |-\n                                Value is the taint value the toleration matches to.\n                                If the operator is Exists, the value should be empty, otherwise just a regular string.\n                              type: string\n                          type: object\n                        type: array\n                    type: object\n                  replicas:\n                    description: |-\n                      replicas indicates how many replicas should be created for each KubeVirt infrastructure\n                      component (like virt-api or virt-controller). Defaults to 2.\n                      WARNING: this is an advanced feature that prevents auto-scaling for core kubevirt components. Please use with caution!\n                    type: integer\n                type: object\n              monitorAccount:\n                description: |-\n                  The name of the Prometheus service account that needs read-access to KubeVirt endpoints\n                  Defaults to prometheus-k8s\n                type: string\n              monitorNamespace:\n                description: |-\n                  The namespace Prometheus is deployed in\n                  Defaults to openshift-monitor\n                type: string\n              productComponent:\n                description: |-\n                  Designate the apps.kubevirt.io/component label for KubeVirt components.\n                  Useful if KubeVirt is included as part of a product.\n                  If ProductComponent is not specified, the component label default value is kubevirt.\n                type: string\n              productName:\n                description: |-\n                  Designate the apps.kubevirt.io/part-of label for KubeVirt components.\n                  Useful if KubeVirt is included as part of a product.\n                  If ProductName is not specified, the part-of label will be omitted.\n                type: string\n              productVersion:\n                description: |-\n                  Designate the apps.kubevirt.io/version label for KubeVirt components.\n                  Useful if KubeVirt is included as part of a product.\n                  If ProductVersion is not specified, KubeVirt's version will be used.\n                type: string\n              serviceMonitorNamespace:\n                description: |-\n                  The namespace the service monitor will be deployed\n                   When ServiceMonitorNamespace is set, then we'll install the service monitor object in that namespace\n                  otherwise we will use the monitoring namespace.\n                type: string\n              uninstallStrategy:\n                description: |-\n                  Specifies if kubevirt can be deleted if workloads are still present.\n                  This is mainly a precaution to avoid accidental data loss\n                type: string\n              workloadUpdateStrategy:\n                description: |-\n                  WorkloadUpdateStrategy defines at the cluster level how to handle\n                  automated workload updates\n                properties:\n                  batchEvictionInterval:\n                    description: |-\n                      BatchEvictionInterval Represents the interval to wait before issuing the next\n                      batch of shutdowns\n\n                      Defaults to 1 minute\n                    type: string\n                  batchEvictionSize:\n                    description: |-\n                      BatchEvictionSize Represents the number of VMIs that can be forced updated per\n                      the BatchShutdownInteral interval\n\n                      Defaults to 10\n                    type: integer\n                  workloadUpdateMethods:\n                    description: |-\n                      WorkloadUpdateMethods defines the methods that can be used to disrupt workloads\n                      during automated workload updates.\n                      When multiple methods are present, the least disruptive method takes\n                      precedence over more disruptive methods. For example if both LiveMigrate and Shutdown\n                      methods are listed, only VMs which are not live migratable will be restarted/shutdown\n\n                      An empty list defaults to no automated workload updating\n                    items:\n                      type: string\n                    type: array\n                    x-kubernetes-list-type: atomic\n                type: object\n              workloads:\n                description: selectors and tolerations that should apply to KubeVirt\n                  workloads\n                properties:\n                  nodePlacement:\n                    description: |-\n                      nodePlacement describes scheduling configuration for specific\n                      KubeVirt components\n                    properties:\n                      affinity:\n                        description: |-\n                          affinity enables pod affinity/anti-affinity placement expanding the types of constraints\n                          that can be expressed with nodeSelector.\n                          affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector\n                          See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity\n                        properties:\n                          nodeAffinity:\n                            description: Describes node affinity scheduling rules\n                              for the pod.\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node matches the corresponding matchExpressions; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: |-\n                                    An empty preferred scheduling term matches all objects with implicit weight 0\n                                    (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).\n                                  properties:\n                                    preference:\n                                      description: A node selector term, associated\n                                        with the corresponding weight.\n                                      properties:\n                                        matchExpressions:\n                                          description: A list of node selector requirements\n                                            by node's labels.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchFields:\n                                          description: A list of node selector requirements\n                                            by node's fields.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    weight:\n                                      description: Weight associated with matching\n                                        the corresponding nodeSelectorTerm, in the\n                                        range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - preference\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to an update), the system\n                                  may or may not try to eventually evict the pod from its node.\n                                properties:\n                                  nodeSelectorTerms:\n                                    description: Required. A list of node selector\n                                      terms. The terms are ORed.\n                                    items:\n                                      description: |-\n                                        A null or empty node selector term matches no objects. The requirements of\n                                        them are ANDed.\n                                        The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.\n                                      properties:\n                                        matchExpressions:\n                                          description: A list of node selector requirements\n                                            by node's labels.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchFields:\n                                          description: A list of node selector requirements\n                                            by node's fields.\n                                          items:\n                                            description: |-\n                                              A node selector requirement is a selector that contains values, a key, and an operator\n                                              that relates the key and values.\n                                            properties:\n                                              key:\n                                                description: The label key that the\n                                                  selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  Represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  An array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. If the operator is Gt or Lt, the values\n                                                  array must have a single element, which will be interpreted as an integer.\n                                                  This array is replaced during a strategic merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    type: array\n                                    x-kubernetes-list-type: atomic\n                                required:\n                                - nodeSelectorTerms\n                                type: object\n                                x-kubernetes-map-type: atomic\n                            type: object\n                          podAffinity:\n                            description: Describes pod affinity scheduling rules (e.g.\n                              co-locate this pod in the same node, zone, etc. as some\n                              other pod(s)).\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: The weights of all of the matched WeightedPodAffinityTerm\n                                    fields are added per-node to find the most preferred\n                                    node(s)\n                                  properties:\n                                    podAffinityTerm:\n                                      description: Required. A pod affinity term,\n                                        associated with the corresponding weight.\n                                      properties:\n                                        labelSelector:\n                                          description: |-\n                                            A label query over a set of resources, in this case pods.\n                                            If it's null, this PodAffinityTerm matches with no Pods.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        matchLabelKeys:\n                                          description: |-\n                                            MatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                            Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        mismatchLabelKeys:\n                                          description: |-\n                                            MismatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                            Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        namespaceSelector:\n                                          description: |-\n                                            A label query over the set of namespaces that the term applies to.\n                                            The term is applied to the union of the namespaces selected by this field\n                                            and the ones listed in the namespaces field.\n                                            null selector and null or empty namespaces list means \"this pod's namespace\".\n                                            An empty selector ({}) matches all namespaces.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        namespaces:\n                                          description: |-\n                                            namespaces specifies a static list of namespace names that the term applies to.\n                                            The term is applied to the union of the namespaces listed in this field\n                                            and the ones selected by namespaceSelector.\n                                            null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        topologyKey:\n                                          description: |-\n                                            This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                            the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                            whose value of the label with key topologyKey matches that of any node on which any of the\n                                            selected pods is running.\n                                            Empty topologyKey is not allowed.\n                                          type: string\n                                      required:\n                                      - topologyKey\n                                      type: object\n                                    weight:\n                                      description: |-\n                                        weight associated with matching the corresponding podAffinityTerm,\n                                        in the range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - podAffinityTerm\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to a pod label update), the\n                                  system may or may not try to eventually evict the pod from its node.\n                                  When there are multiple elements, the lists of nodes corresponding to each\n                                  podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                                items:\n                                  description: |-\n                                    Defines a set of pods (namely those matching the labelSelector\n                                    relative to the given namespace(s)) that this pod should be\n                                    co-located (affinity) or not co-located (anti-affinity) with,\n                                    where co-located is defined as running on a node whose value of\n                                    the label with key <topologyKey> matches that of any node on which\n                                    a pod of the set of pods is running\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            type: object\n                          podAntiAffinity:\n                            description: Describes pod anti-affinity scheduling rules\n                              (e.g. avoid putting this pod in the same node, zone,\n                              etc. as some other pod(s)).\n                            properties:\n                              preferredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  The scheduler will prefer to schedule pods to nodes that satisfy\n                                  the anti-affinity expressions specified by this field, but it may choose\n                                  a node that violates one or more of the expressions. The node that is\n                                  most preferred is the one with the greatest sum of weights, i.e.\n                                  for each node that meets all of the scheduling requirements (resource\n                                  request, requiredDuringScheduling anti-affinity expressions, etc.),\n                                  compute a sum by iterating through the elements of this field and adding\n                                  \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the\n                                  node(s) with the highest sum are the most preferred.\n                                items:\n                                  description: The weights of all of the matched WeightedPodAffinityTerm\n                                    fields are added per-node to find the most preferred\n                                    node(s)\n                                  properties:\n                                    podAffinityTerm:\n                                      description: Required. A pod affinity term,\n                                        associated with the corresponding weight.\n                                      properties:\n                                        labelSelector:\n                                          description: |-\n                                            A label query over a set of resources, in this case pods.\n                                            If it's null, this PodAffinityTerm matches with no Pods.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        matchLabelKeys:\n                                          description: |-\n                                            MatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                            Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        mismatchLabelKeys:\n                                          description: |-\n                                            MismatchLabelKeys is a set of pod label keys to select which pods will\n                                            be taken into consideration. The keys are used to lookup values from the\n                                            incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                            to select the group of existing pods which pods will be taken into consideration\n                                            for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                            pod labels will be ignored. The default value is empty.\n                                            The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                            Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                            This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        namespaceSelector:\n                                          description: |-\n                                            A label query over the set of namespaces that the term applies to.\n                                            The term is applied to the union of the namespaces selected by this field\n                                            and the ones listed in the namespaces field.\n                                            null selector and null or empty namespaces list means \"this pod's namespace\".\n                                            An empty selector ({}) matches all namespaces.\n                                          properties:\n                                            matchExpressions:\n                                              description: matchExpressions is a list\n                                                of label selector requirements. The\n                                                requirements are ANDed.\n                                              items:\n                                                description: |-\n                                                  A label selector requirement is a selector that contains values, a key, and an operator that\n                                                  relates the key and values.\n                                                properties:\n                                                  key:\n                                                    description: key is the label\n                                                      key that the selector applies\n                                                      to.\n                                                    type: string\n                                                  operator:\n                                                    description: |-\n                                                      operator represents a key's relationship to a set of values.\n                                                      Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                    type: string\n                                                  values:\n                                                    description: |-\n                                                      values is an array of string values. If the operator is In or NotIn,\n                                                      the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                      the values array must be empty. This array is replaced during a strategic\n                                                      merge patch.\n                                                    items:\n                                                      type: string\n                                                    type: array\n                                                    x-kubernetes-list-type: atomic\n                                                required:\n                                                - key\n                                                - operator\n                                                type: object\n                                              type: array\n                                              x-kubernetes-list-type: atomic\n                                            matchLabels:\n                                              additionalProperties:\n                                                type: string\n                                              description: |-\n                                                matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                                map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                                operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                              type: object\n                                          type: object\n                                          x-kubernetes-map-type: atomic\n                                        namespaces:\n                                          description: |-\n                                            namespaces specifies a static list of namespace names that the term applies to.\n                                            The term is applied to the union of the namespaces listed in this field\n                                            and the ones selected by namespaceSelector.\n                                            null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                          items:\n                                            type: string\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        topologyKey:\n                                          description: |-\n                                            This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                            the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                            whose value of the label with key topologyKey matches that of any node on which any of the\n                                            selected pods is running.\n                                            Empty topologyKey is not allowed.\n                                          type: string\n                                      required:\n                                      - topologyKey\n                                      type: object\n                                    weight:\n                                      description: |-\n                                        weight associated with matching the corresponding podAffinityTerm,\n                                        in the range 1-100.\n                                      format: int32\n                                      type: integer\n                                  required:\n                                  - podAffinityTerm\n                                  - weight\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                              requiredDuringSchedulingIgnoredDuringExecution:\n                                description: |-\n                                  If the anti-affinity requirements specified by this field are not met at\n                                  scheduling time, the pod will not be scheduled onto the node.\n                                  If the anti-affinity requirements specified by this field cease to be met\n                                  at some point during pod execution (e.g. due to a pod label update), the\n                                  system may or may not try to eventually evict the pod from its node.\n                                  When there are multiple elements, the lists of nodes corresponding to each\n                                  podAffinityTerm are intersected, i.e. all terms must be satisfied.\n                                items:\n                                  description: |-\n                                    Defines a set of pods (namely those matching the labelSelector\n                                    relative to the given namespace(s)) that this pod should be\n                                    co-located (affinity) or not co-located (anti-affinity) with,\n                                    where co-located is defined as running on a node whose value of\n                                    the label with key <topologyKey> matches that of any node on which\n                                    a pod of the set of pods is running\n                                  properties:\n                                    labelSelector:\n                                      description: |-\n                                        A label query over a set of resources, in this case pods.\n                                        If it's null, this PodAffinityTerm matches with no Pods.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    matchLabelKeys:\n                                      description: |-\n                                        MatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key in (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both matchLabelKeys and labelSelector.\n                                        Also, matchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    mismatchLabelKeys:\n                                      description: |-\n                                        MismatchLabelKeys is a set of pod label keys to select which pods will\n                                        be taken into consideration. The keys are used to lookup values from the\n                                        incoming pod labels, those key-value labels are merged with 'labelSelector' as 'key notin (value)'\n                                        to select the group of existing pods which pods will be taken into consideration\n                                        for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming\n                                        pod labels will be ignored. The default value is empty.\n                                        The same key is forbidden to exist in both mismatchLabelKeys and labelSelector.\n                                        Also, mismatchLabelKeys cannot be set when labelSelector isn't set.\n                                        This is a beta field and requires enabling MatchLabelKeysInPodAffinity feature gate (enabled by default).\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    namespaceSelector:\n                                      description: |-\n                                        A label query over the set of namespaces that the term applies to.\n                                        The term is applied to the union of the namespaces selected by this field\n                                        and the ones listed in the namespaces field.\n                                        null selector and null or empty namespaces list means \"this pod's namespace\".\n                                        An empty selector ({}) matches all namespaces.\n                                      properties:\n                                        matchExpressions:\n                                          description: matchExpressions is a list\n                                            of label selector requirements. The requirements\n                                            are ANDed.\n                                          items:\n                                            description: |-\n                                              A label selector requirement is a selector that contains values, a key, and an operator that\n                                              relates the key and values.\n                                            properties:\n                                              key:\n                                                description: key is the label key\n                                                  that the selector applies to.\n                                                type: string\n                                              operator:\n                                                description: |-\n                                                  operator represents a key's relationship to a set of values.\n                                                  Valid operators are In, NotIn, Exists and DoesNotExist.\n                                                type: string\n                                              values:\n                                                description: |-\n                                                  values is an array of string values. If the operator is In or NotIn,\n                                                  the values array must be non-empty. If the operator is Exists or DoesNotExist,\n                                                  the values array must be empty. This array is replaced during a strategic\n                                                  merge patch.\n                                                items:\n                                                  type: string\n                                                type: array\n                                                x-kubernetes-list-type: atomic\n                                            required:\n                                            - key\n                                            - operator\n                                            type: object\n                                          type: array\n                                          x-kubernetes-list-type: atomic\n                                        matchLabels:\n                                          additionalProperties:\n                                            type: string\n                                          description: |-\n                                            matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\n                                            map is equivalent to an element of matchExpressions, whose key field is \"key\", the\n                                            operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\n                                          type: object\n                                      type: object\n                                      x-kubernetes-map-type: atomic\n                                    namespaces:\n                                      description: |-\n                                        namespaces specifies a static list of namespace names that the term applies to.\n                                        The term is applied to the union of the namespaces listed in this field\n                                        and the ones selected by namespaceSelector.\n                                        null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n                                      items:\n                                        type: string\n                                      type: array\n                                      x-kubernetes-list-type: atomic\n                                    topologyKey:\n                                      description: |-\n                                        This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching\n                                        the labelSelector in the specified namespaces, where co-located is defined as running on a node\n                                        whose value of the label with key topologyKey matches that of any node on which any of the\n                                        selected pods is running.\n                                        Empty topologyKey is not allowed.\n                                      type: string\n                                  required:\n                                  - topologyKey\n                                  type: object\n                                type: array\n                                x-kubernetes-list-type: atomic\n                            type: object\n                        type: object\n                      nodeSelector:\n                        additionalProperties:\n                          type: string\n                        description: |-\n                          nodeSelector is the node selector applied to the relevant kind of pods\n                          It specifies a map of key-value pairs: for the pod to be eligible to run on a node,\n                          the node must have each of the indicated key-value pairs as labels\n                          (it can have additional labels as well).\n                          See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector\n                        type: object\n                      tolerations:\n                        description: |-\n                          tolerations is a list of tolerations applied to the relevant kind of pods\n                          See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info.\n                          These are additional tolerations other than default ones.\n                        items:\n                          description: |-\n                            The pod this Toleration is attached to tolerates any taint that matches\n                            the triple <key,value,effect> using the matching operator <operator>.\n                          properties:\n                            effect:\n                              description: |-\n                                Effect indicates the taint effect to match. Empty means match all taint effects.\n                                When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.\n                              type: string\n                            key:\n                              description: |-\n                                Key is the taint key that the toleration applies to. Empty means match all taint keys.\n                                If the key is empty, operator must be Exists; this combination means to match all values and all keys.\n                              type: string\n                            operator:\n                              description: |-\n                                Operator represents a key's relationship to the value.\n                                Valid operators are Exists and Equal. Defaults to Equal.\n                                Exists is equivalent to wildcard for value, so that a pod can\n                                tolerate all taints of a particular category.\n                              type: string\n                            tolerationSeconds:\n                              description: |-\n                                TolerationSeconds represents the period of time the toleration (which must be\n                                of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default,\n                                it is not set, which means tolerate the taint forever (do not evict). Zero and\n                                negative values will be treated as 0 (evict immediately) by the system.\n                              format: int64\n                              type: integer\n                            value:\n                              description: |-\n                                Value is the taint value the toleration matches to.\n                                If the operator is Exists, the value should be empty, otherwise just a regular string.\n                              type: string\n                          type: object\n                        type: array\n                    type: object\n                  replicas:\n                    description: |-\n                      replicas indicates how many replicas should be created for each KubeVirt infrastructure\n                      component (like virt-api or virt-controller). Defaults to 2.\n                      WARNING: this is an advanced feature that prevents auto-scaling for core kubevirt components. Please use with caution!\n                    type: integer\n                type: object\n            type: object\n          status:\n            description: KubeVirtStatus represents information pertaining to a KubeVirt\n              deployment.\n            properties:\n              conditions:\n                items:\n                  description: KubeVirtCondition represents a condition of a KubeVirt\n                    deployment\n                  properties:\n                    lastProbeTime:\n                      format: date-time\n                      nullable: true\n                      type: string\n                    lastTransitionTime:\n                      format: date-time\n                      nullable: true\n                      type: string\n                    message:\n                      type: string\n                    reason:\n                      type: string\n                    status:\n                      type: string\n                    type:\n                      type: string\n                  required:\n                  - status\n                  - type\n                  type: object\n                type: array\n              defaultArchitecture:\n                type: string\n              generations:\n                items:\n                  description: GenerationStatus keeps track of the generation for\n                    a given resource so that decisions about forced updates can be\n                    made.\n                  properties:\n                    group:\n                      description: group is the group of the thing you're tracking\n                      type: string\n                    hash:\n                      description: hash is an optional field set for resources without\n                        generation that are content sensitive like secrets and configmaps\n                      type: string\n                    lastGeneration:\n                      description: lastGeneration is the last generation of the workload\n                        controller involved\n                      format: int64\n                      type: integer\n                    name:\n                      description: name is the name of the thing you're tracking\n                      type: string\n                    namespace:\n                      description: namespace is where the thing you're tracking is\n                      type: string\n                    resource:\n                      description: resource is the resource type of the thing you're\n                        tracking\n                      type: string\n                  required:\n                  - group\n                  - lastGeneration\n                  - name\n                  - resource\n                  type: object\n                type: array\n                x-kubernetes-list-type: atomic\n              observedDeploymentConfig:\n                type: string\n              observedDeploymentID:\n                type: string\n              observedGeneration:\n                format: int64\n                type: integer\n              observedKubeVirtRegistry:\n                type: string\n              observedKubeVirtVersion:\n                type: string\n              operatorVersion:\n                type: string\n              outdatedVirtualMachineInstanceWorkloads:\n                type: integer\n              phase:\n                description: KubeVirtPhase is a label for the phase of a KubeVirt\n                  deployment at the current time.\n                type: string\n              targetDeploymentConfig:\n                type: string\n              targetDeploymentID:\n                type: string\n              targetKubeVirtRegistry:\n                type: string\n              targetKubeVirtVersion:\n                type: string\n            type: object\n        required:\n        - spec\n        type: object\n    served: true\n    storage: false\n    subresources:\n      status: {}\n\n---\napiVersion: scheduling.k8s.io/v1\nkind: PriorityClass\nmetadata:\n  name: kubevirt-cluster-critical\nvalue: 1000000000\nglobalDefault: false\ndescription: \"This priority class should be used for core kubevirt components only.\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: kubevirt.io:operator\n  labels:\n    operator.kubevirt.io: \"\"\n    rbac.authorization.k8s.io/aggregate-to-admin: \"true\"\nrules:\n  - apiGroups:\n      - kubevirt.io\n    resources:\n      - kubevirts\n    verbs:\n      - get\n      - delete\n      - create\n      - update\n      - patch\n      - list\n      - watch\n      - deletecollection\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    kubevirt.io: \"\"\n  name: kubevirt-operator\n  namespace: kubevirt\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    kubevirt.io: \"\"\n  name: kubevirt-operator\n  namespace: kubevirt\nrules:\n- apiGroups:\n  - \"\"\n  resourceNames:\n  - kubevirt-ca\n  - kubevirt-export-ca\n  - kubevirt-virt-handler-certs\n  - kubevirt-virt-handler-server-certs\n  - kubevirt-operator-certs\n  - kubevirt-virt-api-certs\n  - kubevirt-controller-certs\n  - kubevirt-exportproxy-certs\n  resources:\n  - secrets\n  verbs:\n  - create\n  - get\n  - list\n  - watch\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - create\n  - get\n  - list\n  - watch\n  - patch\n  - delete\n- apiGroups:\n  - route.openshift.io\n  resources:\n  - routes\n  verbs:\n  - create\n  - get\n  - list\n  - watch\n  - patch\n  - delete\n- apiGroups:\n  - route.openshift.io\n  resources:\n  - routes/custom-host\n  verbs:\n  - create\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - delete\n  - update\n  - create\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - route.openshift.io\n  resources:\n  - routes\n  verbs:\n  - list\n  - get\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  verbs:\n  - list\n  - get\n  - watch\n- apiGroups:\n  - networking.k8s.io\n  resources:\n  - ingresses\n  verbs:\n  - list\n  - get\n  - watch\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - delete\n  - update\n  - create\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resourceNames:\n  - kubevirt-export-ca\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    kubevirt.io: \"\"\n  name: kubevirt-operator-rolebinding\n  namespace: kubevirt\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: kubevirt-operator\nsubjects:\n- kind: ServiceAccount\n  name: kubevirt-operator\n  namespace: kubevirt\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    kubevirt.io: \"\"\n  name: kubevirt-operator\nrules:\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - kubevirts\n  verbs:\n  - get\n  - list\n  - watch\n  - patch\n  - update\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - serviceaccounts\n  - services\n  - endpoints\n  - pods/exec\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - patch\n  - delete\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n  - patch\n- apiGroups:\n  - apps\n  resources:\n  - controllerrevisions\n  verbs:\n  - watch\n  - list\n  - create\n  - delete\n  - patch\n- apiGroups:\n  - apps\n  resources:\n  - deployments\n  - daemonsets\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n  - patch\n- apiGroups:\n  - rbac.authorization.k8s.io\n  resources:\n  - clusterroles\n  - clusterrolebindings\n  - roles\n  - rolebindings\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n  - patch\n  - update\n- apiGroups:\n  - apiextensions.k8s.io\n  resources:\n  - customresourcedefinitions\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n  - patch\n- apiGroups:\n  - security.openshift.io\n  resources:\n  - securitycontextconstraints\n  verbs:\n  - create\n  - get\n  - list\n  - watch\n- apiGroups:\n  - security.openshift.io\n  resourceNames:\n  - privileged\n  resources:\n  - securitycontextconstraints\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - security.openshift.io\n  resourceNames:\n  - kubevirt-handler\n  - kubevirt-controller\n  resources:\n  - securitycontextconstraints\n  verbs:\n  - get\n  - list\n  - watch\n  - update\n  - delete\n- apiGroups:\n  - admissionregistration.k8s.io\n  resources:\n  - validatingwebhookconfigurations\n  - mutatingwebhookconfigurations\n  - validatingadmissionpolicybindings\n  - validatingadmissionpolicies\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n  - update\n  - patch\n- apiGroups:\n  - apiregistration.k8s.io\n  resources:\n  - apiservices\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n  - update\n  - patch\n- apiGroups:\n  - monitoring.coreos.com\n  resources:\n  - servicemonitors\n  - prometheusrules\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - delete\n  - update\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  verbs:\n  - get\n  - list\n  - watch\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  verbs:\n  - get\n  - list\n  - delete\n  - patch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachines\n  - virtualmachineinstances\n  verbs:\n  - get\n  - list\n  - watch\n  - patch\n  - update\n- apiGroups:\n  - \"\"\n  resources:\n  - persistentvolumeclaims\n  verbs:\n  - get\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachines/status\n  verbs:\n  - patch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachineinstancemigrations\n  verbs:\n  - create\n  - get\n  - list\n  - watch\n  - patch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachineinstancepresets\n  verbs:\n  - watch\n  - list\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - limitranges\n  verbs:\n  - watch\n  - list\n- apiGroups:\n  - apiextensions.k8s.io\n  resources:\n  - customresourcedefinitions\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - kubevirts\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - snapshot.kubevirt.io\n  resources:\n  - virtualmachinesnapshots\n  - virtualmachinerestores\n  - virtualmachinesnapshotcontents\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - datasources\n  - datavolumes\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - instancetype.kubevirt.io\n  resources:\n  - virtualmachineinstancetypes\n  - virtualmachineclusterinstancetypes\n  - virtualmachinepreferences\n  - virtualmachineclusterpreferences\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - migrations.kubevirt.io\n  resources:\n  - migrationpolicies\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - apps\n  resources:\n  - controllerrevisions\n  verbs:\n  - create\n  - list\n  - get\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  verbs:\n  - get\n  - list\n  - watch\n  - patch\n- apiGroups:\n  - policy\n  resources:\n  - poddisruptionbudgets\n  verbs:\n  - get\n  - list\n  - watch\n  - delete\n  - create\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  - configmaps\n  - endpoints\n  - services\n  verbs:\n  - get\n  - list\n  - watch\n  - delete\n  - update\n  - create\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - update\n  - create\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  verbs:\n  - create\n- apiGroups:\n  - \"\"\n  resources:\n  - pods/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - \"\"\n  resources:\n  - pods/eviction\n  verbs:\n  - create\n- apiGroups:\n  - \"\"\n  resources:\n  - pods/status\n  verbs:\n  - patch\n- apiGroups:\n  - \"\"\n  resources:\n  - nodes\n  verbs:\n  - get\n  - list\n  - watch\n  - update\n  - patch\n- apiGroups:\n  - apps\n  resources:\n  - daemonsets\n  verbs:\n  - list\n- apiGroups:\n  - apps\n  resources:\n  - controllerrevisions\n  verbs:\n  - watch\n  - list\n  - create\n  - delete\n  - get\n  - update\n- apiGroups:\n  - \"\"\n  resources:\n  - persistentvolumeclaims\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n  - patch\n- apiGroups:\n  - snapshot.kubevirt.io\n  resources:\n  - virtualmachinesnapshots\n  - virtualmachinesnapshots/status\n  - virtualmachinesnapshots/finalizers\n  - virtualmachinesnapshotcontents\n  - virtualmachinesnapshotcontents/status\n  - virtualmachinesnapshotcontents/finalizers\n  - virtualmachinerestores\n  - virtualmachinerestores/status\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n  - patch\n- apiGroups:\n  - export.kubevirt.io\n  resources:\n  - virtualmachineexports\n  - virtualmachineexports/status\n  - virtualmachineexports/finalizers\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n  - patch\n- apiGroups:\n  - pool.kubevirt.io\n  resources:\n  - virtualmachinepools\n  - virtualmachinepools/finalizers\n  - virtualmachinepools/status\n  - virtualmachinepools/scale\n  verbs:\n  - watch\n  - list\n  - create\n  - delete\n  - update\n  - patch\n  - get\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - '*'\n  verbs:\n  - '*'\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachines/finalizers\n  - virtualmachineinstances/finalizers\n  verbs:\n  - update\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachines/stop\n  - virtualmachineinstances/addvolume\n  - virtualmachineinstances/removevolume\n  - virtualmachineinstances/freeze\n  - virtualmachineinstances/unfreeze\n  - virtualmachineinstances/reset\n  - virtualmachineinstances/softreboot\n  - virtualmachineinstances/sev/setupsession\n  - virtualmachineinstances/sev/injectlaunchsecret\n  verbs:\n  - update\n- apiGroups:\n  - cdi.kubevirt.io\n  resources:\n  - '*'\n  verbs:\n  - '*'\n- apiGroups:\n  - k8s.cni.cncf.io\n  resources:\n  - network-attachment-definitions\n  verbs:\n  - get\n- apiGroups:\n  - apiextensions.k8s.io\n  resources:\n  - customresourcedefinitions\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n- apiGroups:\n  - snapshot.storage.k8s.io\n  resources:\n  - volumesnapshotclasses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - snapshot.storage.k8s.io\n  resources:\n  - volumesnapshots\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - delete\n- apiGroups:\n  - storage.k8s.io\n  resources:\n  - storageclasses\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - instancetype.kubevirt.io\n  resources:\n  - virtualmachineinstancetypes\n  - virtualmachineclusterinstancetypes\n  - virtualmachinepreferences\n  - virtualmachineclusterpreferences\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - migrations.kubevirt.io\n  resources:\n  - migrationpolicies\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - clone.kubevirt.io\n  resources:\n  - virtualmachineclones\n  - virtualmachineclones/status\n  - virtualmachineclones/finalizers\n  verbs:\n  - get\n  - list\n  - watch\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  verbs:\n  - get\n- apiGroups:\n  - \"\"\n  resources:\n  - resourcequotas\n  verbs:\n  - list\n  - watch\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - create\n  - get\n  - delete\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachineinstances\n  verbs:\n  - update\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - nodes\n  verbs:\n  - patch\n  - list\n  - watch\n  - get\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n- apiGroups:\n  - apiextensions.k8s.io\n  resources:\n  - customresourcedefinitions\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - kubevirts\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - migrations.kubevirt.io\n  resources:\n  - migrationpolicies\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - export.kubevirt.io\n  resources:\n  - virtualmachineexports\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - kubevirts\n  verbs:\n  - list\n  - watch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - kubevirts\n  verbs:\n  - get\n  - list\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - version\n  - guestfs\n  verbs:\n  - get\n  - list\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachineinstances/console\n  - virtualmachineinstances/vnc\n  - virtualmachineinstances/vnc/screenshot\n  - virtualmachineinstances/portforward\n  - virtualmachineinstances/guestosinfo\n  - virtualmachineinstances/filesystemlist\n  - virtualmachineinstances/userlist\n  - virtualmachineinstances/sev/fetchcertchain\n  - virtualmachineinstances/sev/querylaunchmeasurement\n  - virtualmachineinstances/usbredir\n  verbs:\n  - get\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachineinstances/pause\n  - virtualmachineinstances/unpause\n  - virtualmachineinstances/addvolume\n  - virtualmachineinstances/removevolume\n  - virtualmachineinstances/freeze\n  - virtualmachineinstances/unfreeze\n  - virtualmachineinstances/softreboot\n  - virtualmachineinstances/reset\n  - virtualmachineinstances/sev/setupsession\n  - virtualmachineinstances/sev/injectlaunchsecret\n  verbs:\n  - update\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachines/expand-spec\n  - virtualmachines/portforward\n  verbs:\n  - get\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachines/start\n  - virtualmachines/stop\n  - virtualmachines/restart\n  - virtualmachines/addvolume\n  - virtualmachines/removevolume\n  - virtualmachines/memorydump\n  verbs:\n  - update\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - expand-vm-spec\n  verbs:\n  - update\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachines\n  - virtualmachineinstances\n  - virtualmachineinstancepresets\n  - virtualmachineinstancereplicasets\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n  - deletecollection\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachineinstancemigrations\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - snapshot.kubevirt.io\n  resources:\n  - virtualmachinesnapshots\n  - virtualmachinesnapshotcontents\n  - virtualmachinerestores\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n  - deletecollection\n- apiGroups:\n  - export.kubevirt.io\n  resources:\n  - virtualmachineexports\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n  - deletecollection\n- apiGroups:\n  - clone.kubevirt.io\n  resources:\n  - virtualmachineclones\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n  - deletecollection\n- apiGroups:\n  - instancetype.kubevirt.io\n  resources:\n  - virtualmachineinstancetypes\n  - virtualmachineclusterinstancetypes\n  - virtualmachinepreferences\n  - virtualmachineclusterpreferences\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n  - deletecollection\n- apiGroups:\n  - pool.kubevirt.io\n  resources:\n  - virtualmachinepools\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n  - deletecollection\n- apiGroups:\n  - migrations.kubevirt.io\n  resources:\n  - migrationpolicies\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachineinstances/console\n  - virtualmachineinstances/vnc\n  - virtualmachineinstances/vnc/screenshot\n  - virtualmachineinstances/portforward\n  - virtualmachineinstances/guestosinfo\n  - virtualmachineinstances/filesystemlist\n  - virtualmachineinstances/userlist\n  - virtualmachineinstances/sev/fetchcertchain\n  - virtualmachineinstances/sev/querylaunchmeasurement\n  - virtualmachineinstances/usbredir\n  verbs:\n  - get\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachineinstances/pause\n  - virtualmachineinstances/unpause\n  - virtualmachineinstances/addvolume\n  - virtualmachineinstances/removevolume\n  - virtualmachineinstances/freeze\n  - virtualmachineinstances/unfreeze\n  - virtualmachineinstances/softreboot\n  - virtualmachineinstances/reset\n  - virtualmachineinstances/sev/setupsession\n  - virtualmachineinstances/sev/injectlaunchsecret\n  verbs:\n  - update\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachines/expand-spec\n  - virtualmachines/portforward\n  verbs:\n  - get\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachines/start\n  - virtualmachines/stop\n  - virtualmachines/restart\n  - virtualmachines/addvolume\n  - virtualmachines/removevolume\n  - virtualmachines/memorydump\n  verbs:\n  - update\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - expand-vm-spec\n  verbs:\n  - update\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachines\n  - virtualmachineinstances\n  - virtualmachineinstancepresets\n  - virtualmachineinstancereplicasets\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachineinstancemigrations\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - snapshot.kubevirt.io\n  resources:\n  - virtualmachinesnapshots\n  - virtualmachinesnapshotcontents\n  - virtualmachinerestores\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n- apiGroups:\n  - export.kubevirt.io\n  resources:\n  - virtualmachineexports\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n- apiGroups:\n  - clone.kubevirt.io\n  resources:\n  - virtualmachineclones\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n- apiGroups:\n  - instancetype.kubevirt.io\n  resources:\n  - virtualmachineinstancetypes\n  - virtualmachineclusterinstancetypes\n  - virtualmachinepreferences\n  - virtualmachineclusterpreferences\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n- apiGroups:\n  - pool.kubevirt.io\n  resources:\n  - virtualmachinepools\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - kubevirts\n  verbs:\n  - get\n  - list\n- apiGroups:\n  - migrations.kubevirt.io\n  resources:\n  - migrationpolicies\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - kubevirts\n  verbs:\n  - get\n  - list\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachines/expand-spec\n  - virtualmachineinstances/guestosinfo\n  - virtualmachineinstances/filesystemlist\n  - virtualmachineinstances/userlist\n  - virtualmachineinstances/sev/fetchcertchain\n  - virtualmachineinstances/sev/querylaunchmeasurement\n  verbs:\n  - get\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - expand-vm-spec\n  verbs:\n  - update\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachines\n  - virtualmachineinstances\n  - virtualmachineinstancepresets\n  - virtualmachineinstancereplicasets\n  - virtualmachineinstancemigrations\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - snapshot.kubevirt.io\n  resources:\n  - virtualmachinesnapshots\n  - virtualmachinesnapshotcontents\n  - virtualmachinerestores\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - export.kubevirt.io\n  resources:\n  - virtualmachineexports\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - clone.kubevirt.io\n  resources:\n  - virtualmachineclones\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - instancetype.kubevirt.io\n  resources:\n  - virtualmachineinstancetypes\n  - virtualmachineclusterinstancetypes\n  - virtualmachinepreferences\n  - virtualmachineclusterpreferences\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - pool.kubevirt.io\n  resources:\n  - virtualmachinepools\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - migrations.kubevirt.io\n  resources:\n  - migrationpolicies\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - instancetype.kubevirt.io\n  resources:\n  - virtualmachineclusterinstancetypes\n  - virtualmachineclusterpreferences\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - subresources.kubevirt.io\n  resources:\n  - virtualmachines/migrate\n  verbs:\n  - update\n- apiGroups:\n  - kubevirt.io\n  resources:\n  - virtualmachineinstancemigrations\n  verbs:\n  - get\n  - delete\n  - create\n  - update\n  - patch\n  - list\n  - watch\n  - deletecollection\n- apiGroups:\n  - authentication.k8s.io\n  resources:\n  - tokenreviews\n  verbs:\n  - create\n- apiGroups:\n  - authorization.k8s.io\n  resources:\n  - subjectaccessreviews\n  verbs:\n  - create\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    kubevirt.io: \"\"\n  name: kubevirt-operator\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: kubevirt-operator\nsubjects:\n- kind: ServiceAccount\n  name: kubevirt-operator\n  namespace: kubevirt\n\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    kubevirt.io: virt-operator\n  name: virt-operator\n  namespace: kubevirt\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      kubevirt.io: virt-operator\n  strategy:\n    type: RollingUpdate\n  template:\n    metadata:\n      annotations:\n        openshift.io/required-scc: restricted-v2\n      labels:\n        kubevirt.io: virt-operator\n        name: virt-operator\n        prometheus.kubevirt.io: \"true\"\n      name: virt-operator\n    spec:\n      affinity:\n        podAntiAffinity:\n          preferredDuringSchedulingIgnoredDuringExecution:\n          - podAffinityTerm:\n              labelSelector:\n                matchExpressions:\n                - key: kubevirt.io\n                  operator: In\n                  values:\n                  - virt-operator\n              topologyKey: kubernetes.io/hostname\n            weight: 1\n      containers:\n      - args:\n        - --port\n        - \"8443\"\n        - -v\n        - \"2\"\n        command:\n        - virt-operator\n        env:\n        - name: VIRT_OPERATOR_IMAGE\n          value: quay.io/kubevirt/virt-operator:v1.5.0\n        - name: WATCH_NAMESPACE\n          valueFrom:\n            fieldRef:\n              fieldPath: metadata.annotations['olm.targetNamespaces']\n        - name: KUBEVIRT_VERSION\n          value: v1.5.0\n        image: quay.io/kubevirt/virt-operator:v1.5.0\n        imagePullPolicy: IfNotPresent\n        name: virt-operator\n        ports:\n        - containerPort: 8443\n          name: metrics\n          protocol: TCP\n        - containerPort: 8444\n          name: webhooks\n          protocol: TCP\n        readinessProbe:\n          httpGet:\n            path: /metrics\n            port: 8443\n            scheme: HTTPS\n          initialDelaySeconds: 5\n          timeoutSeconds: 10\n        resources:\n          requests:\n            cpu: 10m\n            memory: 450Mi\n        securityContext:\n          allowPrivilegeEscalation: false\n          capabilities:\n            drop:\n            - ALL\n          seccompProfile:\n            type: RuntimeDefault\n        volumeMounts:\n        - mountPath: /etc/virt-operator/certificates\n          name: kubevirt-operator-certs\n          readOnly: true\n        - mountPath: /profile-data\n          name: profile-data\n      nodeSelector:\n        kubernetes.io/os: linux\n      priorityClassName: kubevirt-cluster-critical\n      securityContext:\n        runAsNonRoot: true\n        seccompProfile:\n          type: RuntimeDefault\n      serviceAccountName: kubevirt-operator\n      tolerations:\n      - key: CriticalAddonsOnly\n        operator: Exists\n      volumes:\n      - name: kubevirt-operator-certs\n        secret:\n          optional: true\n          secretName: kubevirt-operator-certs\n      - emptyDir: {}\n        name: profile-data\n"
  },
  {
    "path": "infra/kubernetes/readme-todo.txt",
    "content": ""
  },
  {
    "path": "infra/kubernetes/start-cyberdesk-operator-cr.yaml",
    "content": "apiVersion: cyberdesk.io/v1alpha1\nkind: StartCyberdeskOperator\nmetadata:\n  name: bootstrap-cyberdesk-setup \n  namespace: cyberdesk-system \nspec:\n  {} "
  },
  {
    "path": "infra/kubernetes/warm-pool.yaml",
    "content": "apiVersion: pool.kubevirt.io/v1alpha1\nkind: VirtualMachinePool\nmetadata:\n  name: cyberdesk-warm-pool\n  namespace: kubevirt\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      pool.kubevirt.io/warm: ready           # <- selector\n  virtualMachineTemplate:\n    metadata:\n      labels:\n        pool.kubevirt.io/warm: ready         # <- matches selector while warm\n    spec:\n      runStrategy: Always                    # already running\n      dataVolumeTemplates:\n      - metadata:\n          name: rootdisk\n        spec:\n          source:\n            snapshot:\n              name: vmsnapshot-fa8369cc-fcb9-4bb2-babf-e19baca0b227-volume-rootdisk # Make sure this is updated to the latest snapshot\n              namespace: kubevirt\n          pvc:\n            accessModes: [ReadWriteOnce]\n            resources:\n              requests:\n                storage: 20Gi\n            volumeMode: Filesystem\n      template:\n        metadata:\n          labels:\n            pool.kubevirt.io/warm: ready\n            app: cyberdesk\n        spec:\n          domain:\n            cpu:\n              cores: 1\n            resources:\n              requests:\n                memory: 2Gi\n            devices:\n              disks:\n              - name: rootdisk\n                disk:\n                  bus: virtio\n              - name: cloudinit\n                disk:\n                  bus: virtio\n              interfaces:\n              - name: default\n                masquerade: {}\n          networks:\n          - name: default\n            pod: {}\n          volumes:\n          - name: rootdisk\n            dataVolume:\n              name: rootdisk\n          - name: cloudinit\n            cloudInitNoCloud:\n              secretRef:\n                name: cloud-init-golden-vm\n"
  },
  {
    "path": "infra/terraform/.terraform.lock.hcl",
    "content": "# This file is maintained automatically by \"terraform init\".\n# Manual edits may be lost in future updates.\n\nprovider \"registry.terraform.io/hashicorp/azurerm\" {\n  version     = \"4.27.0\"\n  constraints = \"~> 4.27.0\"\n  hashes = [\n    \"h1:hmAzHk4XVbrGQ5iJJj1QdFx0aWNW9Hjh+GIE6S8G5I8=\",\n    \"h1:nvIa9Z9GdI3/J7U4oMR2w/h/JZ66M1ozUPh/jHn5pXU=\",\n    \"zh:0c69edea1995bd3bd9e61980757169c35bf22281b660b5c755b6cb13d08d29d2\",\n    \"zh:25b86bf7b9678371d8573983954c571696f3e64a3967133be3b835da36307106\",\n    \"zh:49921cff4f26a49bafada60cd07dabb52c5eb35231059ed928a4f4722e269c82\",\n    \"zh:4b986166531f9fd1289f01d8220519443e74888a21da512c1b841b006dad6215\",\n    \"zh:53fb65b2ca4df637f03e4748a100a7d7fc77249e307c03e294d6259cec0310f6\",\n    \"zh:5c0d021a387ca4e2a5a01da009746a08c45f08e971c10d9bda54539d7264d671\",\n    \"zh:600043f2b20dc5a45275e43f175c19fe8b6e8e9557a0c884aef018f1f63de90e\",\n    \"zh:a0284f6f38912f67bb4cb7829fda3fa75be81fea6a9b21119965c2a839430092\",\n    \"zh:a7ac0576e2069ef77557042c6b5157ded364fbd355b2f9bf7f5441622424086e\",\n    \"zh:c5db0bcafe986868e28cc6225b68b2d1cf4bf631939d260ca845f17a9aa1677d\",\n    \"zh:ce620c0eb71b1fdd925828b30cf232a869abccf1c459180f2f991c4166315251\",\n    \"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c\",\n  ]\n}\n"
  },
  {
    "path": "infra/terraform/main.tf",
    "content": "#########################################\n# main.tf\n#########################################\n\nterraform {\n  required_version = \">= 1.11.3\"\n\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp/azurerm\"\n      version = \"~> 4.27.0\"\n    }\n  }\n}\n\n#############################\n# Providers Configuration\n#############################\n\nprovider \"azurerm\" {\n  features {}\n  subscription_id = var.subscription_id\n}\n\n#############################\n# Networking & IAM Setup\n#############################\n\n# Resource Group\nresource \"azurerm_resource_group\" \"rg\" {\n  name     = var.resource_group_name\n  location = var.location\n}\n\n# Virtual Network\nresource \"azurerm_virtual_network\" \"vnet\" {\n  name                = \"vnet-kubevirt-demo\"\n  address_space       = [\"10.0.0.0/16\"]\n  location            = azurerm_resource_group.rg.location\n  resource_group_name = azurerm_resource_group.rg.name\n  depends_on          = [azurerm_resource_group.rg]\n}\n\n# AKS Subnet\nresource \"azurerm_subnet\" \"aks\" {\n  name                 = \"subnet-aks\"\n  resource_group_name  = azurerm_resource_group.rg.name\n  virtual_network_name = azurerm_virtual_network.vnet.name\n  address_prefixes     = [\"10.0.1.0/24\"]\n  depends_on           = [azurerm_resource_group.rg]\n}\n\n# NSG for AKS Subnet\nresource \"azurerm_network_security_group\" \"aks_nsg\" {\n  name                = \"nsg-aks-subnet\"\n  location            = azurerm_resource_group.rg.location\n  resource_group_name = azurerm_resource_group.rg.name\n  depends_on          = [azurerm_resource_group.rg]\n}\n\n# Outbound Internet (always allowed)\nresource \"azurerm_network_security_rule\" \"allow_outbound\" {\n  name                        = \"AllowOutboundInternet\"\n  priority                    = 300\n  direction                   = \"Outbound\"\n  access                      = \"Allow\"\n  protocol                    = \"*\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"*\"\n  source_address_prefix       = \"VirtualNetwork\"\n  destination_address_prefix  = \"Internet\"\n  resource_group_name         = azurerm_resource_group.rg.name\n  network_security_group_name = azurerm_network_security_group.aks_nsg.name\n  depends_on                  = [azurerm_resource_group.rg]\n}\n\n# Inbound HTTP from your developers (0.0.0.0/0)\nresource \"azurerm_network_security_rule\" \"allow_inbound_http\" {\n  name                        = \"AllowInboundHttpFromTrusted\"\n  priority                    = 200\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"80\"\n  source_address_prefixes     = concat(var.developer_api_ips, var.developer_vpn_ips)\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.rg.name\n  network_security_group_name = azurerm_network_security_group.aks_nsg.name\n  depends_on                  = [azurerm_resource_group.rg]\n}\n\n# Inbound HTTPS from your developers (0.0.0.0/0)\nresource \"azurerm_network_security_rule\" \"allow_inbound_https\" {\n  name                        = \"AllowInboundHttpsFromTrusted\"\n  priority                    = 210\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"443\"\n  source_address_prefixes     = concat(var.developer_api_ips, var.developer_vpn_ips)\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.rg.name\n  network_security_group_name = azurerm_network_security_group.aks_nsg.name\n  depends_on                  = [azurerm_resource_group.rg]\n}\n\n# ─────────── NEW RULES ───────────\n\n# 1) Allow Azure Load Balancer health probes on 80 & 443\nresource \"azurerm_network_security_rule\" \"allow_lb_probes_http_https\" {\n  name                        = \"AllowAzureLBProbes\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  source_address_prefix       = \"AzureLoadBalancer\"\n  destination_port_ranges     = [\"80\", \"443\"]\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.rg.name\n  network_security_group_name = azurerm_network_security_group.aks_nsg.name\n  depends_on                  = [azurerm_resource_group.rg]\n}\n\n# 2) Allow Azure Load Balancer to probe Kubernetes NodePorts\nresource \"azurerm_network_security_rule\" \"allow_lb_probes_nodeport\" {\n  name                        = \"AllowAzureLBNodePorts\"\n  priority                    = 110\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  source_address_prefix       = \"AzureLoadBalancer\"\n  destination_port_range      = \"30000-32767\"\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.rg.name\n  network_security_group_name = azurerm_network_security_group.aks_nsg.name\n  depends_on                  = [azurerm_resource_group.rg]\n}\n\n# Associate NSG with the subnet\nresource \"azurerm_subnet_network_security_group_association\" \"aks_nsg_assoc\" {\n  subnet_id                 = azurerm_subnet.aks.id\n  network_security_group_id = azurerm_network_security_group.aks_nsg.id\n  depends_on                = [azurerm_resource_group.rg]\n}\n\n# AKS Cluster\nresource \"azurerm_kubernetes_cluster\" \"aks\" {\n  name                = var.aks_cluster_name\n  location            = azurerm_resource_group.rg.location\n  resource_group_name = azurerm_resource_group.rg.name\n  dns_prefix          = var.aks_cluster_name\n\n  default_node_pool {\n    name                          = \"default\"\n    vm_size                       = var.aks_default_node_pool_vm_size\n    temporary_name_for_rotation   = \"tempnodepool\"\n    auto_scaling_enabled          = true\n    min_count                     = var.aks_default_node_pool_min_count\n    max_count                     = var.aks_default_node_pool_max_count\n    vnet_subnet_id                = azurerm_subnet.aks.id\n  }\n\n  identity {\n    type = \"SystemAssigned\"\n  }\n\n  network_profile {\n    network_plugin     = \"azure\"\n    network_policy     = \"azure\"\n    service_cidr       = \"10.2.0.0/16\"\n    dns_service_ip     = \"10.2.0.10\"\n  }\n  depends_on = [azurerm_resource_group.rg]\n}\n\n# Assign AKS identity rights\nresource \"azurerm_role_assignment\" \"aks_subnet_role\" {\n  scope                = azurerm_subnet.aks.id\n  role_definition_name = \"Network Contributor\"\n  principal_id         = azurerm_kubernetes_cluster.aks.identity[0].principal_id\n  depends_on           = [azurerm_resource_group.rg]\n}\n\nresource \"azurerm_role_assignment\" \"aks_routetable_role\" {\n  scope                = azurerm_resource_group.rg.id\n  role_definition_name = \"Network Contributor\"\n  principal_id         = azurerm_kubernetes_cluster.aks.identity[0].principal_id\n  depends_on           = [azurerm_resource_group.rg]\n}\n"
  },
  {
    "path": "infra/terraform/variables.tf",
    "content": "variable \"subscription_id\" {\n  description = \"The Azure Subscription ID where resources will be created.\"\n  type        = string\n}\n\nvariable \"resource_group_name\" {\n  description = \"The name of the resource group in which to create resources.\"\n  type        = string\n}\n\nvariable \"location\" {\n  description = \"Azure region for the resources.\"\n  type        = string\n}\n\nvariable \"aks_cluster_name\" {\n  description = \"The name to use for the AKS cluster.\"\n  type        = string\n}\n\nvariable \"aks_default_node_pool_vm_size\" {\n  description = \"The VM size for the default AKS node pool.\"\n  type        = string\n  default     = \"Standard_D8ds_v5\" # Keeping the original as default\n}\n\nvariable \"aks_default_node_pool_min_count\" {\n  description = \"The minimum number of nodes for the default AKS node pool autoscaler.\"\n  type        = number\n  default     = 1\n}\n\nvariable \"aks_default_node_pool_max_count\" {\n  description = \"The maximum number of nodes for the default AKS node pool autoscaler.\"\n  type        = number\n  default     = 2\n}\n\nvariable \"developer_api_ips\" {\n  type        = list(string)\n  description = \"List of dedicated public IP addresses/CIDRs for the Developer-facing API app.\"\n  default     = [\"0.0.0.0/0\"]\n}\n\nvariable \"developer_vpn_ips\" {\n  type        = list(string)\n  description = \"List of public IP addresses/CIDRs for the developer VPN (for secure local access to the cluster)\"\n  default     = [\"0.0.0.0/0\"]\n}"
  },
  {
    "path": "sdks/openapi.json",
    "content": "{\n    \"openapi\": \"3.1.0\",\n    \"info\": {\n        \"title\": \"API Reference\",\n        \"version\": \"1.2.1\",\n        \"description\": \"API for Cyberdesk, to create, control, and manage virtual desktop instances.\"\n    },\n    \"servers\": [\n        {\n            \"url\": \"https://api.cyberdesk.io\",\n            \"description\": \"Production server\"\n        }\n    ],\n    \"components\": {\n        \"securitySchemes\": {\n            \"apiKeyAuth\": {\n                \"type\": \"apiKey\",\n                \"in\": \"header\",\n                \"name\": \"x-api-key\"\n            }\n        },\n        \"schemas\": {},\n        \"parameters\": {}\n    },\n    \"paths\": {\n        \"/v1/desktop/{id}\": {\n            \"get\": {\n                \"tags\": [\n                    \"Desktop\"\n                ],\n                \"summary\": \"Get details of a specific desktop instance\",\n                \"description\": \"Returns the ID, status, creation timestamp, and timeout timestamp for a given desktop instance.\",\n                \"parameters\": [\n                    {\n                        \"schema\": {\n                            \"type\": \"string\",\n                            \"format\": \"uuid\",\n                            \"description\": \"The UUID of the desktop instance to retrieve\",\n                            \"example\": \"a1b2c3d4-e5f6-7890-1234-567890abcdef\"\n                        },\n                        \"required\": true,\n                        \"name\": \"id\",\n                        \"in\": \"path\"\n                    },\n                    {\n                        \"schema\": {\n                            \"type\": \"string\",\n                            \"description\": \"API key for authentication\",\n                            \"example\": \"api_12345\"\n                        },\n                        \"required\": true,\n                        \"name\": \"x-api-key\",\n                        \"in\": \"header\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"Desktop instance details retrieved successfully\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"id\": {\n                                            \"type\": \"string\",\n                                            \"format\": \"uuid\",\n                                            \"description\": \"Unique identifier for the desktop instance\",\n                                            \"example\": \"a1b2c3d4-e5f6-7890-1234-567890abcdef\"\n                                        },\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"pending\",\n                                                \"running\",\n                                                \"terminated\",\n                                                \"error\"\n                                            ],\n                                            \"description\": \"Current status of the desktop instance\",\n                                            \"example\": \"running\"\n                                        },\n                                        \"stream_url\": {\n                                            \"type\": \"string\",\n                                            \"nullable\": true,\n                                            \"description\": \"URL for the desktop stream (null if the desktop is not running)\",\n                                            \"example\": \"https://cyberdesk.com/vnc/a1b2c3d4-e5f6-7890-1234-567890abcdef\"\n                                        },\n                                        \"created_at\": {\n                                            \"type\": \"string\",\n                                            \"format\": \"date-time\",\n                                            \"description\": \"Timestamp when the instance was created\",\n                                            \"example\": \"2023-10-27T10:00:00Z\"\n                                        },\n                                        \"timeout_at\": {\n                                            \"type\": \"string\",\n                                            \"format\": \"date-time\",\n                                            \"description\": \"Timestamp when the instance will automatically time out\",\n                                            \"example\": \"2023-10-28T10:00:00Z\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"id\",\n                                        \"status\",\n                                        \"stream_url\",\n                                        \"created_at\",\n                                        \"timeout_at\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Although the HTTP standard specifies \\\"unauthorized\\\", semantically this response means \\\"unauthenticated\\\". That is, the client must authenticate itself to get the requested response.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"403\": {\n                        \"description\": \"The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"409\": {\n                        \"description\": \"This response is sent when a request conflicts with the current state of the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"429\": {\n                        \"description\": \"The user has sent too many requests in a given amount of time (\\\"rate limiting\\\")\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"The server has encountered a situation it does not know how to handle.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"502\": {\n                        \"description\": \"The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"/v1/desktop\": {\n            \"post\": {\n                \"tags\": [\n                    \"Desktop\"\n                ],\n                \"summary\": \"Create a new virtual desktop instance\",\n                \"description\": \"Creates a new virtual desktop instance and returns its ID and stream URL\",\n                \"parameters\": [\n                    {\n                        \"schema\": {\n                            \"type\": \"string\",\n                            \"description\": \"API key for authentication\",\n                            \"example\": \"api_12345\"\n                        },\n                        \"required\": true,\n                        \"name\": \"x-api-key\",\n                        \"in\": \"header\"\n                    }\n                ],\n                \"requestBody\": {\n                    \"content\": {\n                        \"application/json\": {\n                            \"schema\": {\n                                \"type\": \"object\",\n                                \"properties\": {\n                                    \"timeout_ms\": {\n                                        \"type\": \"integer\",\n                                        \"description\": \"Timeout in milliseconds for the desktop session\",\n                                        \"example\": 3600000\n                                    }\n                                }\n                            }\n                        }\n                    }\n                },\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"Desktop creation initiated successfully\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"id\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Unique identifier for the desktop instance\",\n                                            \"example\": \"desktop_12345\"\n                                        },\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"pending\",\n                                                \"running\",\n                                                \"terminated\",\n                                                \"error\"\n                                            ],\n                                            \"description\": \"Initial status of the desktop instance after creation request\",\n                                            \"example\": \"pending\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"id\",\n                                        \"status\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Although the HTTP standard specifies \\\"unauthorized\\\", semantically this response means \\\"unauthenticated\\\". That is, the client must authenticate itself to get the requested response.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"403\": {\n                        \"description\": \"The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"409\": {\n                        \"description\": \"This response is sent when a request conflicts with the current state of the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"429\": {\n                        \"description\": \"The user has sent too many requests in a given amount of time (\\\"rate limiting\\\")\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"The server has encountered a situation it does not know how to handle.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"502\": {\n                        \"description\": \"The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"/v1/desktop/{id}/stop\": {\n            \"post\": {\n                \"tags\": [\n                    \"Desktop\"\n                ],\n                \"summary\": \"Stop a running desktop instance\",\n                \"description\": \"Stops a running desktop instance and cleans up resources\",\n                \"parameters\": [\n                    {\n                        \"schema\": {\n                            \"type\": \"string\",\n                            \"description\": \"Desktop instance ID to stop\",\n                            \"example\": \"desktop_12345\"\n                        },\n                        \"required\": true,\n                        \"name\": \"id\",\n                        \"in\": \"path\"\n                    },\n                    {\n                        \"schema\": {\n                            \"type\": \"string\",\n                            \"description\": \"API key for authentication\",\n                            \"example\": \"api_12345\"\n                        },\n                        \"required\": true,\n                        \"name\": \"x-api-key\",\n                        \"in\": \"header\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"Desktop stopped successfully\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"pending\",\n                                                \"running\",\n                                                \"terminated\",\n                                                \"error\"\n                                            ],\n                                            \"description\": \"Status of the desktop instance after stopping\",\n                                            \"example\": \"terminated\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Although the HTTP standard specifies \\\"unauthorized\\\", semantically this response means \\\"unauthenticated\\\". That is, the client must authenticate itself to get the requested response.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"403\": {\n                        \"description\": \"The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"409\": {\n                        \"description\": \"This response is sent when a request conflicts with the current state of the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"429\": {\n                        \"description\": \"The user has sent too many requests in a given amount of time (\\\"rate limiting\\\")\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"The server has encountered a situation it does not know how to handle.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"502\": {\n                        \"description\": \"The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"/v1/desktop/{id}/computer-action\": {\n            \"post\": {\n                \"tags\": [\n                    \"Desktop\"\n                ],\n                \"summary\": \"Perform an action on the desktop\",\n                \"description\": \"Executes a computer action such as mouse clicks, keyboard input, or screenshots on the desktop\",\n                \"parameters\": [\n                    {\n                        \"schema\": {\n                            \"type\": \"string\",\n                            \"description\": \"Desktop instance ID to perform the action on\",\n                            \"example\": \"desktop_12345\"\n                        },\n                        \"required\": true,\n                        \"name\": \"id\",\n                        \"in\": \"path\"\n                    },\n                    {\n                        \"schema\": {\n                            \"type\": \"string\",\n                            \"description\": \"API key for authentication\",\n                            \"example\": \"api_12345\"\n                        },\n                        \"required\": true,\n                        \"name\": \"x-api-key\",\n                        \"in\": \"header\"\n                    }\n                ],\n                \"requestBody\": {\n                    \"content\": {\n                        \"application/json\": {\n                            \"schema\": {\n                                \"oneOf\": [\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"click_mouse\"\n                                                ],\n                                                \"description\": \"Perform a mouse action: click, press (down), or release (up). Defaults to a single left click at the current position.\",\n                                                \"example\": \"click_mouse\"\n                                            },\n                                            \"x\": {\n                                                \"type\": \"integer\",\n                                                \"description\": \"X coordinate for the action (optional, uses current position if omitted)\",\n                                                \"example\": 500\n                                            },\n                                            \"y\": {\n                                                \"type\": \"integer\",\n                                                \"description\": \"Y coordinate for the action (optional, uses current position if omitted)\",\n                                                \"example\": 300\n                                            },\n                                            \"button\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"left\",\n                                                    \"right\",\n                                                    \"middle\"\n                                                ],\n                                                \"description\": \"Mouse button to use (optional, defaults to 'left')\",\n                                                \"example\": \"left\"\n                                            },\n                                            \"num_of_clicks\": {\n                                                \"type\": \"integer\",\n                                                \"minimum\": 0,\n                                                \"description\": \"Number of clicks to perform (optional, defaults to 1, only applicable for 'click' type)\",\n                                                \"example\": 1\n                                            },\n                                            \"click_type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"click\",\n                                                    \"down\",\n                                                    \"up\"\n                                                ],\n                                                \"description\": \"Type of mouse action (optional, defaults to 'click')\",\n                                                \"example\": \"click\"\n                                            }\n                                        },\n                                        \"required\": [\n                                            \"type\"\n                                        ],\n                                        \"title\": \"Click Mouse Action\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"scroll\"\n                                                ],\n                                                \"description\": \"Scroll the mouse wheel in the specified direction\",\n                                                \"example\": \"scroll\"\n                                            },\n                                            \"direction\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"up\",\n                                                    \"down\",\n                                                    \"left\",\n                                                    \"right\"\n                                                ],\n                                                \"description\": \"Direction to scroll\",\n                                                \"example\": \"down\"\n                                            },\n                                            \"amount\": {\n                                                \"type\": \"integer\",\n                                                \"description\": \"Amount to scroll in pixels\",\n                                                \"example\": 100\n                                            }\n                                        },\n                                        \"required\": [\n                                            \"type\",\n                                            \"direction\",\n                                            \"amount\"\n                                        ],\n                                        \"title\": \"Scroll Action\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"move_mouse\"\n                                                ],\n                                                \"description\": \"Move the mouse cursor to the specified coordinates\",\n                                                \"example\": \"move_mouse\"\n                                            },\n                                            \"x\": {\n                                                \"type\": \"integer\",\n                                                \"description\": \"X coordinate to move to\",\n                                                \"example\": 500\n                                            },\n                                            \"y\": {\n                                                \"type\": \"integer\",\n                                                \"description\": \"Y coordinate to move to\",\n                                                \"example\": 300\n                                            }\n                                        },\n                                        \"required\": [\n                                            \"type\",\n                                            \"x\",\n                                            \"y\"\n                                        ],\n                                        \"title\": \"Move Mouse Action\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"drag_mouse\"\n                                                ],\n                                                \"description\": \"Drag the mouse from start to end coordinates\",\n                                                \"example\": \"drag_mouse\"\n                                            },\n                                            \"start\": {\n                                                \"type\": \"object\",\n                                                \"properties\": {\n                                                    \"x\": {\n                                                        \"type\": \"integer\",\n                                                        \"description\": \"X coordinate on the screen\",\n                                                        \"example\": 500\n                                                    },\n                                                    \"y\": {\n                                                        \"type\": \"integer\",\n                                                        \"description\": \"Y coordinate on the screen\",\n                                                        \"example\": 300\n                                                    }\n                                                },\n                                                \"required\": [\n                                                    \"x\",\n                                                    \"y\"\n                                                ],\n                                                \"description\": \"Starting coordinates for the drag operation\",\n                                                \"example\": {\n                                                    \"x\": 100,\n                                                    \"y\": 100\n                                                }\n                                            },\n                                            \"end\": {\n                                                \"type\": \"object\",\n                                                \"properties\": {\n                                                    \"x\": {\n                                                        \"type\": \"integer\",\n                                                        \"description\": \"X coordinate on the screen\",\n                                                        \"example\": 500\n                                                    },\n                                                    \"y\": {\n                                                        \"type\": \"integer\",\n                                                        \"description\": \"Y coordinate on the screen\",\n                                                        \"example\": 300\n                                                    }\n                                                },\n                                                \"required\": [\n                                                    \"x\",\n                                                    \"y\"\n                                                ],\n                                                \"description\": \"Ending coordinates for the drag operation\",\n                                                \"example\": {\n                                                    \"x\": 300,\n                                                    \"y\": 300\n                                                }\n                                            }\n                                        },\n                                        \"required\": [\n                                            \"type\",\n                                            \"start\",\n                                            \"end\"\n                                        ],\n                                        \"title\": \"Drag Mouse Action\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"type\"\n                                                ],\n                                                \"description\": \"Type text at the current cursor position\",\n                                                \"example\": \"type\"\n                                            },\n                                            \"text\": {\n                                                \"type\": \"string\",\n                                                \"description\": \"Text to type\",\n                                                \"example\": \"Hello, World!\"\n                                            }\n                                        },\n                                        \"required\": [\n                                            \"type\",\n                                            \"text\"\n                                        ],\n                                        \"title\": \"Type Text Action\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"press_keys\"\n                                                ],\n                                                \"description\": \"Press, hold down, or release one or more keyboard keys. Defaults to a single press and release.\",\n                                                \"example\": \"press_keys\"\n                                            },\n                                            \"keys\": {\n                                                \"anyOf\": [\n                                                    {\n                                                        \"type\": \"string\",\n                                                        \"description\": \"Single key to press\",\n                                                        \"example\": \"Enter\"\n                                                    },\n                                                    {\n                                                        \"type\": \"array\",\n                                                        \"items\": {\n                                                            \"type\": \"string\"\n                                                        },\n                                                        \"description\": \"Multiple keys to press simultaneously\",\n                                                        \"example\": [\n                                                            \"Control\",\n                                                            \"c\"\n                                                        ]\n                                                    }\n                                                ]\n                                            },\n                                            \"key_action_type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"press\",\n                                                    \"down\",\n                                                    \"up\"\n                                                ],\n                                                \"description\": \"Type of key action (optional, defaults to 'press' which is a down and up action)\",\n                                                \"example\": \"press\"\n                                            }\n                                        },\n                                        \"required\": [\n                                            \"type\",\n                                            \"keys\"\n                                        ],\n                                        \"title\": \"Press Keys Action\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"wait\"\n                                                ],\n                                                \"description\": \"Wait for the specified number of milliseconds\",\n                                                \"example\": \"wait\"\n                                            },\n                                            \"ms\": {\n                                                \"type\": \"integer\",\n                                                \"description\": \"Time to wait in milliseconds\",\n                                                \"example\": 1000\n                                            }\n                                        },\n                                        \"required\": [\n                                            \"type\",\n                                            \"ms\"\n                                        ],\n                                        \"title\": \"Wait Action\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"screenshot\"\n                                                ],\n                                                \"description\": \"Take a screenshot of the desktop\",\n                                                \"example\": \"screenshot\"\n                                            }\n                                        },\n                                        \"required\": [\n                                            \"type\"\n                                        ],\n                                        \"title\": \"Screenshot Action\"\n                                    },\n                                    {\n                                        \"type\": \"object\",\n                                        \"properties\": {\n                                            \"type\": {\n                                                \"type\": \"string\",\n                                                \"enum\": [\n                                                    \"get_cursor_position\"\n                                                ],\n                                                \"description\": \"Get the current mouse cursor position\",\n                                                \"example\": \"get_cursor_position\"\n                                            }\n                                        },\n                                        \"required\": [\n                                            \"type\"\n                                        ],\n                                        \"title\": \"Get Cursor Position Action\"\n                                    }\n                                ]\n                            }\n                        }\n                    }\n                },\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"Action executed successfully. Response may contain output or image data depending on the action.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"output\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Raw string output from the executed command (if any)\",\n                                            \"example\": \"X=500 Y=300\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message if the operation failed (also indicated by non-2xx HTTP status)\",\n                                            \"example\": \"Command failed with code 1: xdotool: command not found\"\n                                        },\n                                        \"base64_image\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Base64 encoded JPEG image data (only returned for screenshot actions)\",\n                                            \"example\": \"/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQ...\"\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Although the HTTP standard specifies \\\"unauthorized\\\", semantically this response means \\\"unauthenticated\\\". That is, the client must authenticate itself to get the requested response.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"403\": {\n                        \"description\": \"The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"409\": {\n                        \"description\": \"This response is sent when a request conflicts with the current state of the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"429\": {\n                        \"description\": \"The user has sent too many requests in a given amount of time (\\\"rate limiting\\\")\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"The server has encountered a situation it does not know how to handle.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"502\": {\n                        \"description\": \"The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"/v1/desktop/{id}/bash-action\": {\n            \"post\": {\n                \"tags\": [\n                    \"Desktop\"\n                ],\n                \"summary\": \"Execute a bash command on the desktop\",\n                \"description\": \"Runs a bash command on the desktop and returns the command output\",\n                \"parameters\": [\n                    {\n                        \"schema\": {\n                            \"type\": \"string\",\n                            \"description\": \"Desktop instance ID to run the command on\",\n                            \"example\": \"desktop_12345\"\n                        },\n                        \"required\": true,\n                        \"name\": \"id\",\n                        \"in\": \"path\"\n                    },\n                    {\n                        \"schema\": {\n                            \"type\": \"string\",\n                            \"description\": \"API key for authentication\",\n                            \"example\": \"api_12345\"\n                        },\n                        \"required\": true,\n                        \"name\": \"x-api-key\",\n                        \"in\": \"header\"\n                    }\n                ],\n                \"requestBody\": {\n                    \"content\": {\n                        \"application/json\": {\n                            \"schema\": {\n                                \"type\": \"object\",\n                                \"properties\": {\n                                    \"command\": {\n                                        \"type\": \"string\",\n                                        \"description\": \"Bash command to execute\",\n                                        \"example\": \"echo 'Hello, World!'\"\n                                    }\n                                },\n                                \"required\": [\n                                    \"command\"\n                                ]\n                            }\n                        }\n                    }\n                },\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"Command executed successfully. Response contains command output.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"output\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Raw string output from the executed command (if any)\",\n                                            \"example\": \"X=500 Y=300\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message if the operation failed (also indicated by non-2xx HTTP status)\",\n                                            \"example\": \"Command failed with code 1: xdotool: command not found\"\n                                        },\n                                        \"base64_image\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Base64 encoded JPEG image data (only returned for screenshot actions)\",\n                                            \"example\": \"/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQ...\"\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Although the HTTP standard specifies \\\"unauthorized\\\", semantically this response means \\\"unauthenticated\\\". That is, the client must authenticate itself to get the requested response.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"403\": {\n                        \"description\": \"The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"404\": {\n                        \"description\": \"The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"409\": {\n                        \"description\": \"This response is sent when a request conflicts with the current state of the server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"429\": {\n                        \"description\": \"The user has sent too many requests in a given amount of time (\\\"rate limiting\\\")\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"The server has encountered a situation it does not know how to handle.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    },\n                    \"502\": {\n                        \"description\": \"The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\",\n                        \"content\": {\n                            \"application/json\": {\n                                \"schema\": {\n                                    \"type\": \"object\",\n                                    \"properties\": {\n                                        \"status\": {\n                                            \"type\": \"string\",\n                                            \"enum\": [\n                                                \"error\"\n                                            ],\n                                            \"example\": \"error\"\n                                        },\n                                        \"error\": {\n                                            \"type\": \"string\",\n                                            \"description\": \"Error message detailing what went wrong\",\n                                            \"example\": \"Instance not found or unauthorized\"\n                                        }\n                                    },\n                                    \"required\": [\n                                        \"status\",\n                                        \"error\"\n                                    ]\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "sdks/py-sdk/.gitignore",
    "content": "# Python cache\n__pycache__/\n*.pyc\n\n# Build artifacts\ndist/\nbuild/\n*.egg-info/ "
  },
  {
    "path": "sdks/py-sdk/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Cyberdesk Team\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE. "
  },
  {
    "path": "sdks/py-sdk/README.md",
    "content": "# cyberdesk\n\n[![PyPI version](https://badge.fury.io/py/cyberdesk.svg)](https://badge.fury.io/py/cyberdesk)\n\nThe official Python SDK for Cyberdesk.\n\n## Installation\n\n```bash\npip install cyberdesk\n```\n\n## Usage\n\nFirst, create a Cyberdesk client instance with your API key:\n\n```python\nfrom cyberdesk import CyberdeskClient\n\nclient = CyberdeskClient(api_key=\"YOUR_API_KEY\")\n```\n\n---\n\n### Launch a Desktop\n\n```python\nresult = client.launch_desktop(timeout_ms=10000)  # Optional: set a timeout for the desktop session\n\n# Error handling example\nif hasattr(result, 'error') and result.error:\n    raise Exception('Failed to launch desktop: ' + str(result.error))\n\n# Success\nif hasattr(result, 'id'):\n    desktop_id = result.id\n    print('Launched desktop with ID:', desktop_id)\n```\n\n---\n\n### Get Desktop Info\n\n```python\ninfo = client.get_desktop(\"your-desktop-id\")\n\nif hasattr(info, 'error') and info.error:\n    raise Exception('Failed to get desktop info: ' + str(info.error))\n\nprint('Desktop info:', info)\n```\n\n---\n\n### Perform a Computer Action (e.g., Mouse Click)\n\n```python\nfrom cyberdesk.actions import click_mouse, ClickMouseButton\n\naction = click_mouse(x=100, y=150, button=ClickMouseButton.LEFT)\naction_result = client.execute_computer_action(\"your-desktop-id\", action)\n\nif hasattr(action_result, 'error') and action_result.error:\n    raise Exception('Action failed: ' + str(action_result.error))\n\nprint('Action result:', action_result)\n```\n\n---\n\n### Run a Bash Command\n\n```python\nbash_result = client.execute_bash_action(\n    \"your-desktop-id\",\n    \"echo Hello, world!\"\n)\n\nif hasattr(bash_result, 'error') and bash_result.error:\n    raise Exception('Bash command failed: ' + str(bash_result.error))\n\nprint('Bash output:', getattr(bash_result, 'output', bash_result))\n```\n\n---\n\n## Ergonomic, Type-Safe Actions\n\nTo create computer actions (mouse, keyboard, etc.), use the factory functions in `cyberdesk.actions`. These provide full type hints and IDE autocompletion for all required and optional fields.\n\n**Example:**\n```python\nfrom cyberdesk.actions import click_mouse, type_text, ClickMouseButton\n\naction1 = click_mouse(x=100, y=200, button=ClickMouseButton.LEFT)\naction2 = type_text(text=\"Hello, world!\")\n\nclient.execute_computer_action(\"your-desktop-id\", action1)\nclient.execute_computer_action(\"your-desktop-id\", action2)\n```\n\n| Action         | Factory Function         | Description                |\n|----------------|-------------------------|----------------------------|\n| Click Mouse    | `click_mouse`           | Mouse click at (x, y)      |\n| Drag Mouse     | `drag_mouse`            | Mouse drag from/to (x, y)  |\n| Move Mouse     | `move_mouse`            | Move mouse to (x, y)       |\n| Scroll         | `scroll`                | Scroll by dx, dy           |\n| Type Text      | `type_text`             | Type text                  |\n| Press Keys     | `press_keys`            | Press keyboard keys        |\n| Screenshot     | `screenshot`            | Take a screenshot          |\n| Wait           | `wait`                  | Wait for ms milliseconds   |\n| Get Cursor Pos | `get_cursor_position`   | Get mouse cursor position  |\n\n---\n\n## Async Usage\n\nAll methods are also available as async variants (prefixed with `async_`). Example:\n\n```python\nimport asyncio\nfrom cyberdesk import CyberdeskClient\nfrom cyberdesk.actions import click_mouse, ClickMouseButton\n\nasync def main():\n    client = CyberdeskClient(api_key=\"YOUR_API_KEY\")\n    result = await client.async_launch_desktop(timeout_ms=10000)\n    print(result)\n    # Example async computer action\n    action = click_mouse(x=100, y=200, button=ClickMouseButton.LEFT)\n    await client.async_execute_computer_action(\"your-desktop-id\", action)\n    # ... use other async_ methods as needed\n\nasyncio.run(main())\n```\n\n---\n\n## Type Hints and Models\n\nAll request/response types are available from the generated models, and all computer actions are available as factory functions in `cyberdesk.actions` for ergonomic, type-safe usage.\n\n```python\nfrom cyberdesk.actions import click_mouse, drag_mouse, type_text, wait, scroll, move_mouse, press_keys, screenshot, get_cursor_position, ClickMouseButton, ClickMouseActionType, PressKeyActionType, ScrollDirection\n```\n\n# Note: All action enums (e.g., ClickMouseButton, ClickMouseActionType, PressKeyActionType, ScrollDirection, etc.) are available from cyberdesk.actions for type-safe usage.\n\n---\n\n## For Cyberdesk Team: Publishing to PyPI\n\n**Recommended:** Always use a [virtual environment](https://docs.python.org/3/library/venv.html) (venv) for building and publishing to avoid dependency conflicts.\n\nTo build and publish this package to [PyPI](https://pypi.org/project/cyberdesk/):\n\n1. **Log into PyPI** (get credentials from the Cyberdesk team).\n\n2. **Install dev dependencies** (in a clean venv):\n   ```bash\n   pip install .[dev]\n   # or\n   uv pip install .[dev]\n   ```\n\n3. **Bump the version number** in `pyproject.toml` (e.g., `version = \"0.2.4\"`).\n\n4. **Clean your `dist/` directory** before building to avoid 'File already exists' errors:\n   ```bash\n   rm -rf dist/*\n   # On Windows PowerShell:\n   Remove-Item dist\\* -Force\n   ```\n\n5. **Build the package:**\n   ```bash\n   python -m build\n   ```\n   This creates a `dist/` directory with `.whl` and `.tar.gz` files.\n\n6. **(Recommended) Set up a `.pypirc` file for easy publishing:**\n   - Create a file named `.pypirc` in your home directory (e.g., `C:\\Users\\yourname\\.pypirc` on Windows or `~/.pypirc` on Linux/macOS).\n   - Add:\n     ```ini\n     [distutils]\n     index-servers =\n         pypi\n\n     [pypi]\n     username = __token__\n     password = pypi-AgEIcH...   # <-- paste your API token here\n     ```\n\n7. **Publish to PyPI:**\n   ```bash\n   twine upload dist/*\n   ```\n   - If you set up `.pypirc`, you won't be prompted for credentials.\n   - If not, enter `__token__` as the username and paste your API token as the password.\n\n8. **Verify:**\n   - Visit https://pypi.org/project/cyberdesk/ to see your published package.\n   - Try installing it in a fresh environment:\n     ```bash\n     pip install cyberdesk\n     ```\n\n---\n\n## License\n\n[MIT](LICENSE) "
  },
  {
    "path": "sdks/py-sdk/cyberdesk/__init__.py",
    "content": "from .client import CyberdeskClient\nfrom .types import (\n    GetDesktopParams,\n    LaunchDesktopParams,\n    TerminateDesktopParams,\n    ExecuteBashActionParams,\n) "
  },
  {
    "path": "sdks/py-sdk/cyberdesk/actions.py",
    "content": "from typing import Union, List, Optional\n\nfrom openapi_client.api_reference_client.types import UNSET\nfrom openapi_client.api_reference_client.models import (\n    PostV1DesktopIdComputerActionClickMouseAction,\n    PostV1DesktopIdComputerActionClickMouseActionType,\n    PostV1DesktopIdComputerActionClickMouseActionButton as ClickMouseButton,\n    PostV1DesktopIdComputerActionClickMouseActionClickType as ClickMouseActionType,\n    PostV1DesktopIdComputerActionDragMouseAction,\n    PostV1DesktopIdComputerActionDragMouseActionType,\n    PostV1DesktopIdComputerActionDragMouseActionStart,\n    PostV1DesktopIdComputerActionDragMouseActionEnd,\n    PostV1DesktopIdComputerActionGetCursorPositionAction,\n    PostV1DesktopIdComputerActionGetCursorPositionActionType,\n    PostV1DesktopIdComputerActionMoveMouseAction,\n    PostV1DesktopIdComputerActionMoveMouseActionType,\n    PostV1DesktopIdComputerActionPressKeysAction,\n    PostV1DesktopIdComputerActionPressKeysActionType,\n    PostV1DesktopIdComputerActionPressKeysActionKeyActionType as PressKeyActionType,\n    PostV1DesktopIdComputerActionScreenshotAction,\n    PostV1DesktopIdComputerActionScreenshotActionType,\n    PostV1DesktopIdComputerActionScrollAction,\n    PostV1DesktopIdComputerActionScrollActionType,\n    PostV1DesktopIdComputerActionScrollActionDirection as ScrollDirection,\n    PostV1DesktopIdComputerActionTypeTextAction,\n    PostV1DesktopIdComputerActionTypeTextActionType,\n    PostV1DesktopIdComputerActionWaitAction,\n    PostV1DesktopIdComputerActionWaitActionType,\n)\n\n# Re-export the original Enum types under their aliased names for user convenience if they need to import them\n# This makes `from cyberdesk.actions import ClickMouseButton` possible.\n\ndef click_mouse(\n    x: Optional[int] = None,\n    y: Optional[int] = None,\n    button: Optional[ClickMouseButton] = None,\n    num_of_clicks: Optional[int] = None,\n    click_type: Optional[ClickMouseActionType] = None,\n) -> PostV1DesktopIdComputerActionClickMouseAction:\n    return PostV1DesktopIdComputerActionClickMouseAction(\n        type_=PostV1DesktopIdComputerActionClickMouseActionType.CLICK_MOUSE,\n        x=x if x is not None else UNSET,\n        y=y if y is not None else UNSET,\n        button=button if button is not None else UNSET,\n        num_of_clicks=num_of_clicks if num_of_clicks is not None else UNSET,\n        click_type=click_type if click_type is not None else UNSET\n    )\n\ndef drag_mouse(\n    start_x: int,\n    start_y: int,\n    end_x: int,\n    end_y: int\n) -> PostV1DesktopIdComputerActionDragMouseAction:\n    start_model = PostV1DesktopIdComputerActionDragMouseActionStart(x=start_x, y=start_y)\n    end_model = PostV1DesktopIdComputerActionDragMouseActionEnd(x=end_x, y=end_y)\n    return PostV1DesktopIdComputerActionDragMouseAction(\n        type_=PostV1DesktopIdComputerActionDragMouseActionType.DRAG_MOUSE,\n        start=start_model,\n        end=end_model\n    )\n\ndef get_cursor_position() -> PostV1DesktopIdComputerActionGetCursorPositionAction:\n    return PostV1DesktopIdComputerActionGetCursorPositionAction(\n        type_=PostV1DesktopIdComputerActionGetCursorPositionActionType.GET_CURSOR_POSITION\n    )\n\ndef move_mouse(\n    x: int,\n    y: int\n) -> PostV1DesktopIdComputerActionMoveMouseAction:\n    return PostV1DesktopIdComputerActionMoveMouseAction(\n        type_=PostV1DesktopIdComputerActionMoveMouseActionType.MOVE_MOUSE,\n        x=x,\n        y=y\n    )\n\ndef press_keys(\n    keys: Optional[Union[str, List[str]]] = None,\n    key_action_type: Optional[PressKeyActionType] = None\n) -> PostV1DesktopIdComputerActionPressKeysAction:\n    return PostV1DesktopIdComputerActionPressKeysAction(\n        type_=PostV1DesktopIdComputerActionPressKeysActionType.PRESS_KEYS,\n        keys=keys if keys is not None else UNSET,\n        key_action_type=key_action_type if key_action_type is not None else UNSET\n    )\n\ndef screenshot() -> PostV1DesktopIdComputerActionScreenshotAction:\n    return PostV1DesktopIdComputerActionScreenshotAction(\n        type_=PostV1DesktopIdComputerActionScreenshotActionType.SCREENSHOT\n    )\n\ndef scroll(\n    direction: ScrollDirection,\n    amount: int\n) -> PostV1DesktopIdComputerActionScrollAction:\n    return PostV1DesktopIdComputerActionScrollAction(\n        type_=PostV1DesktopIdComputerActionScrollActionType.SCROLL,\n        direction=direction,\n        amount=amount\n    )\n\ndef type_text(\n    text: str\n) -> PostV1DesktopIdComputerActionTypeTextAction:\n    return PostV1DesktopIdComputerActionTypeTextAction(\n        type_=PostV1DesktopIdComputerActionTypeTextActionType.TYPE,\n        text=text\n    )\n\ndef wait(\n    ms: int\n) -> PostV1DesktopIdComputerActionWaitAction:\n    return PostV1DesktopIdComputerActionWaitAction(\n        type_=PostV1DesktopIdComputerActionWaitActionType.WAIT,\n        ms=ms\n    ) "
  },
  {
    "path": "sdks/py-sdk/cyberdesk/client.py",
    "content": "\"\"\"\nCyberdesk Python SDK wrapper client.\n\"\"\"\n\nfrom .types import (\n    GetDesktopParams,\n    TerminateDesktopParams,\n    ExecuteBashActionParams,\n    ComputerActionModel,\n)\nfrom openapi_client.api_reference_client.client import Client\nfrom openapi_client.api_reference_client.api.desktop import (\n    get_v1_desktop_id,\n    post_v1_desktop,\n    post_v1_desktop_id_stop,\n    post_v1_desktop_id_computer_action,\n    post_v1_desktop_id_bash_action,\n)\nfrom openapi_client.api_reference_client.models import (\n    PostV1DesktopBody,\n    PostV1DesktopIdBashActionBody,\n)\n\nclass CyberdeskClient:\n    \"\"\"\n    Wrapper client for the Cyberdesk API.\n    Provides both synchronous and asynchronous methods.\n    \"\"\"\n    def __init__(self, api_key: str, base_url: str = \"https://api.cyberdesk.io\"):\n        self.api_key = api_key\n        self.client = Client(base_url=base_url, headers={\"x-api-key\": api_key})\n\n    def get_desktop(self, id: GetDesktopParams):\n        \"\"\"Synchronous: Get details of a specific desktop instance.\"\"\"\n        return get_v1_desktop_id.sync(id=id, client=self.client, x_api_key=self.api_key)\n\n    async def async_get_desktop(self, id: GetDesktopParams):\n        \"\"\"Async: Get details of a specific desktop instance. Use with 'await'.\"\"\"\n        return await get_v1_desktop_id.asyncio(id=id, client=self.client, x_api_key=self.api_key)\n\n    def launch_desktop(self, timeout_ms: int = None):\n        \"\"\"Synchronous: Create a new virtual desktop instance.\"\"\"\n        body = PostV1DesktopBody(timeout_ms=timeout_ms) if timeout_ms is not None else PostV1DesktopBody()\n        return post_v1_desktop.sync(client=self.client, body=body, x_api_key=self.api_key)\n\n    async def async_launch_desktop(self, timeout_ms: int = None):\n        \"\"\"Async: Create a new virtual desktop instance. Use with 'await'.\"\"\"\n        body = PostV1DesktopBody(timeout_ms=timeout_ms) if timeout_ms is not None else PostV1DesktopBody()\n        return await post_v1_desktop.asyncio(client=self.client, body=body, x_api_key=self.api_key)\n\n    def terminate_desktop(self, id: TerminateDesktopParams):\n        \"\"\"Synchronous: Stop a running desktop instance.\"\"\"\n        return post_v1_desktop_id_stop.sync(id=id, client=self.client, x_api_key=self.api_key)\n\n    async def async_terminate_desktop(self, id: TerminateDesktopParams):\n        \"\"\"Async: Stop a running desktop instance. Use with 'await'.\"\"\"\n        return await post_v1_desktop_id_stop.asyncio(id=id, client=self.client, x_api_key=self.api_key)\n\n    def execute_computer_action(self, id: GetDesktopParams, action: ComputerActionModel):\n        \"\"\"Synchronous: Perform an action on the desktop (mouse, keyboard, etc).\"\"\"\n        return post_v1_desktop_id_computer_action.sync(id=id, client=self.client, body=action, x_api_key=self.api_key)\n\n    async def async_execute_computer_action(self, id: GetDesktopParams, action: ComputerActionModel):\n        \"\"\"Async: Perform an action on the desktop (mouse, keyboard, etc). Use with 'await'.\"\"\"\n        return await post_v1_desktop_id_computer_action.asyncio(id=id, client=self.client, body=action, x_api_key=self.api_key)\n\n    def execute_bash_action(self, id: GetDesktopParams, command: ExecuteBashActionParams):\n        \"\"\"Synchronous: Execute a bash command on the desktop.\"\"\"\n        body = PostV1DesktopIdBashActionBody(command=command)\n        return post_v1_desktop_id_bash_action.sync(id=id, client=self.client, body=body, x_api_key=self.api_key)\n\n    async def async_execute_bash_action(self, id: GetDesktopParams, command: ExecuteBashActionParams):\n        \"\"\"Async: Execute a bash command on the desktop. Use with 'await'.\"\"\"\n        body = PostV1DesktopIdBashActionBody(command=command)\n        return await post_v1_desktop_id_bash_action.asyncio(id=id, client=self.client, body=body, x_api_key=self.api_key) "
  },
  {
    "path": "sdks/py-sdk/cyberdesk/types.py",
    "content": "from openapi_client.api_reference_client.models import (\n    PostV1DesktopBody,\n    PostV1DesktopIdComputerActionClickMouseAction,\n    PostV1DesktopIdComputerActionDragMouseAction,\n    PostV1DesktopIdComputerActionGetCursorPositionAction,\n    PostV1DesktopIdComputerActionMoveMouseAction,\n    PostV1DesktopIdComputerActionPressKeysAction,\n    PostV1DesktopIdComputerActionScreenshotAction,\n    PostV1DesktopIdComputerActionScrollAction,\n    PostV1DesktopIdComputerActionTypeTextAction,\n    PostV1DesktopIdComputerActionWaitAction,\n)\nfrom typing import Union\n\n# Named parameter types for SDK methods\nGetDesktopParams = str  # Desktop ID\nLaunchDesktopParams = PostV1DesktopBody\nTerminateDesktopParams = str  # Desktop ID\nExecuteBashActionParams = str  # Command string\n\n# Strongly-typed union for all computer action models\nComputerActionModel = Union[\n    PostV1DesktopIdComputerActionClickMouseAction,\n    PostV1DesktopIdComputerActionDragMouseAction,\n    PostV1DesktopIdComputerActionGetCursorPositionAction,\n    PostV1DesktopIdComputerActionMoveMouseAction,\n    PostV1DesktopIdComputerActionPressKeysAction,\n    PostV1DesktopIdComputerActionScreenshotAction,\n    PostV1DesktopIdComputerActionScrollAction,\n    PostV1DesktopIdComputerActionTypeTextAction,\n    PostV1DesktopIdComputerActionWaitAction,\n]\n\n# Re-export action models for ergonomic imports\n__all__ = [\n    \"GetDesktopParams\",\n    \"LaunchDesktopParams\",\n    \"TerminateDesktopParams\",\n    \"ExecuteBashActionParams\",\n    \"ComputerActionModel\",\n    \"PostV1DesktopIdComputerActionClickMouseAction\",\n    \"PostV1DesktopIdComputerActionDragMouseAction\",\n    \"PostV1DesktopIdComputerActionGetCursorPositionAction\",\n    \"PostV1DesktopIdComputerActionMoveMouseAction\",\n    \"PostV1DesktopIdComputerActionPressKeysAction\",\n    \"PostV1DesktopIdComputerActionScreenshotAction\",\n    \"PostV1DesktopIdComputerActionScrollAction\",\n    \"PostV1DesktopIdComputerActionTypeTextAction\",\n    \"PostV1DesktopIdComputerActionWaitAction\",\n] "
  },
  {
    "path": "sdks/py-sdk/openapi_client/.gitignore",
    "content": "__pycache__/\nbuild/\ndist/\n*.egg-info/\n.pytest_cache/\n\n# pyenv\n.python-version\n\n# Environments\n.env\n.venv\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# JetBrains\n.idea/\n\n/coverage.xml\n/.coverage\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/README.md",
    "content": "# api-reference-client\nA client library for accessing API Reference\n\n## Usage\nFirst, create a client:\n\n```python\nfrom api_reference_client import Client\n\nclient = Client(base_url=\"https://api.example.com\")\n```\n\nIf the endpoints you're going to hit require authentication, use `AuthenticatedClient` instead:\n\n```python\nfrom api_reference_client import AuthenticatedClient\n\nclient = AuthenticatedClient(base_url=\"https://api.example.com\", token=\"SuperSecretToken\")\n```\n\nNow call your endpoint and use your models:\n\n```python\nfrom api_reference_client.models import MyDataModel\nfrom api_reference_client.api.my_tag import get_my_data_model\nfrom api_reference_client.types import Response\n\nwith client as client:\n    my_data: MyDataModel = get_my_data_model.sync(client=client)\n    # or if you need more info (e.g. status_code)\n    response: Response[MyDataModel] = get_my_data_model.sync_detailed(client=client)\n```\n\nOr do the same thing with an async version:\n\n```python\nfrom api_reference_client.models import MyDataModel\nfrom api_reference_client.api.my_tag import get_my_data_model\nfrom api_reference_client.types import Response\n\nasync with client as client:\n    my_data: MyDataModel = await get_my_data_model.asyncio(client=client)\n    response: Response[MyDataModel] = await get_my_data_model.asyncio_detailed(client=client)\n```\n\nBy default, when you're calling an HTTPS API it will attempt to verify that SSL is working correctly. Using certificate verification is highly recommended most of the time, but sometimes you may need to authenticate to a server (especially an internal server) using a custom certificate bundle.\n\n```python\nclient = AuthenticatedClient(\n    base_url=\"https://internal_api.example.com\", \n    token=\"SuperSecretToken\",\n    verify_ssl=\"/path/to/certificate_bundle.pem\",\n)\n```\n\nYou can also disable certificate validation altogether, but beware that **this is a security risk**.\n\n```python\nclient = AuthenticatedClient(\n    base_url=\"https://internal_api.example.com\", \n    token=\"SuperSecretToken\", \n    verify_ssl=False\n)\n```\n\nThings to know:\n1. Every path/method combo becomes a Python module with four functions:\n    1. `sync`: Blocking request that returns parsed data (if successful) or `None`\n    1. `sync_detailed`: Blocking request that always returns a `Request`, optionally with `parsed` set if the request was successful.\n    1. `asyncio`: Like `sync` but async instead of blocking\n    1. `asyncio_detailed`: Like `sync_detailed` but async instead of blocking\n\n1. All path/query params, and bodies become method arguments.\n1. If your endpoint had any tags on it, the first tag will be used as a module name for the function (my_tag above)\n1. Any endpoint which did not have a tag will be in `api_reference_client.api.default`\n\n## Advanced customizations\n\nThere are more settings on the generated `Client` class which let you control more runtime behavior, check out the docstring on that class for more info. You can also customize the underlying `httpx.Client` or `httpx.AsyncClient` (depending on your use-case):\n\n```python\nfrom api_reference_client import Client\n\ndef log_request(request):\n    print(f\"Request event hook: {request.method} {request.url} - Waiting for response\")\n\ndef log_response(response):\n    request = response.request\n    print(f\"Response event hook: {request.method} {request.url} - Status {response.status_code}\")\n\nclient = Client(\n    base_url=\"https://api.example.com\",\n    httpx_args={\"event_hooks\": {\"request\": [log_request], \"response\": [log_response]}},\n)\n\n# Or get the underlying httpx client to modify directly with client.get_httpx_client() or client.get_async_httpx_client()\n```\n\nYou can even set the httpx client directly, but beware that this will override any existing settings (e.g., base_url):\n\n```python\nimport httpx\nfrom api_reference_client import Client\n\nclient = Client(\n    base_url=\"https://api.example.com\",\n)\n# Note that base_url needs to be re-set, as would any shared cookies, headers, etc.\nclient.set_httpx_client(httpx.Client(base_url=\"https://api.example.com\", proxies=\"http://localhost:8030\"))\n```\n\n## Building / publishing this package\nThis project uses [Poetry](https://python-poetry.org/) to manage dependencies  and packaging.  Here are the basics:\n1. Update the metadata in pyproject.toml (e.g. authors, version)\n1. If you're using a private repository, configure it with Poetry\n    1. `poetry config repositories.<your-repository-name> <url-to-your-repository>`\n    1. `poetry config http-basic.<your-repository-name> <username> <password>`\n1. Publish the client with `poetry publish --build -r <your-repository-name>` or, if for public PyPI, just `poetry publish --build`\n\nIf you want to install this client into another project without publishing it (e.g. for development) then:\n1. If that project **is using Poetry**, you can simply do `poetry add <path-to-this-client>` from that project\n1. If that project is not using Poetry:\n    1. Build a wheel with `poetry build -f wheel`\n    1. Install that wheel from the other project `pip install <path-to-wheel>`\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/__init__.py",
    "content": "\"\"\"A client library for accessing API Reference\"\"\"\n\nfrom .client import AuthenticatedClient, Client\n\n__all__ = (\n    \"AuthenticatedClient\",\n    \"Client\",\n)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/api/__init__.py",
    "content": "\"\"\"Contains methods for accessing the API\"\"\"\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/api/desktop/__init__.py",
    "content": "\"\"\"Contains endpoint functions for accessing the API\"\"\"\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/api/desktop/get_v1_desktop_id.py",
    "content": "from http import HTTPStatus\nfrom typing import Any, Optional, Union\nfrom uuid import UUID\n\nimport httpx\n\nfrom ... import errors\nfrom ...client import AuthenticatedClient, Client\nfrom ...models.get_v1_desktop_id_response_200 import GetV1DesktopIdResponse200\nfrom ...models.get_v1_desktop_id_response_400 import GetV1DesktopIdResponse400\nfrom ...models.get_v1_desktop_id_response_401 import GetV1DesktopIdResponse401\nfrom ...models.get_v1_desktop_id_response_403 import GetV1DesktopIdResponse403\nfrom ...models.get_v1_desktop_id_response_404 import GetV1DesktopIdResponse404\nfrom ...models.get_v1_desktop_id_response_409 import GetV1DesktopIdResponse409\nfrom ...models.get_v1_desktop_id_response_429 import GetV1DesktopIdResponse429\nfrom ...models.get_v1_desktop_id_response_500 import GetV1DesktopIdResponse500\nfrom ...models.get_v1_desktop_id_response_502 import GetV1DesktopIdResponse502\nfrom ...types import Response\n\n\ndef _get_kwargs(\n    id: UUID,\n    *,\n    x_api_key: str,\n) -> dict[str, Any]:\n    headers: dict[str, Any] = {}\n    headers[\"x-api-key\"] = x_api_key\n\n    _kwargs: dict[str, Any] = {\n        \"method\": \"get\",\n        \"url\": f\"/v1/desktop/{id}\",\n    }\n\n    _kwargs[\"headers\"] = headers\n    return _kwargs\n\n\ndef _parse_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Optional[\n    Union[\n        GetV1DesktopIdResponse200,\n        GetV1DesktopIdResponse400,\n        GetV1DesktopIdResponse401,\n        GetV1DesktopIdResponse403,\n        GetV1DesktopIdResponse404,\n        GetV1DesktopIdResponse409,\n        GetV1DesktopIdResponse429,\n        GetV1DesktopIdResponse500,\n        GetV1DesktopIdResponse502,\n    ]\n]:\n    if response.status_code == 200:\n        response_200 = GetV1DesktopIdResponse200.from_dict(response.json())\n\n        return response_200\n    if response.status_code == 400:\n        response_400 = GetV1DesktopIdResponse400.from_dict(response.json())\n\n        return response_400\n    if response.status_code == 401:\n        response_401 = GetV1DesktopIdResponse401.from_dict(response.json())\n\n        return response_401\n    if response.status_code == 403:\n        response_403 = GetV1DesktopIdResponse403.from_dict(response.json())\n\n        return response_403\n    if response.status_code == 404:\n        response_404 = GetV1DesktopIdResponse404.from_dict(response.json())\n\n        return response_404\n    if response.status_code == 409:\n        response_409 = GetV1DesktopIdResponse409.from_dict(response.json())\n\n        return response_409\n    if response.status_code == 429:\n        response_429 = GetV1DesktopIdResponse429.from_dict(response.json())\n\n        return response_429\n    if response.status_code == 500:\n        response_500 = GetV1DesktopIdResponse500.from_dict(response.json())\n\n        return response_500\n    if response.status_code == 502:\n        response_502 = GetV1DesktopIdResponse502.from_dict(response.json())\n\n        return response_502\n    if client.raise_on_unexpected_status:\n        raise errors.UnexpectedStatus(response.status_code, response.content)\n    else:\n        return None\n\n\ndef _build_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Response[\n    Union[\n        GetV1DesktopIdResponse200,\n        GetV1DesktopIdResponse400,\n        GetV1DesktopIdResponse401,\n        GetV1DesktopIdResponse403,\n        GetV1DesktopIdResponse404,\n        GetV1DesktopIdResponse409,\n        GetV1DesktopIdResponse429,\n        GetV1DesktopIdResponse500,\n        GetV1DesktopIdResponse502,\n    ]\n]:\n    return Response(\n        status_code=HTTPStatus(response.status_code),\n        content=response.content,\n        headers=response.headers,\n        parsed=_parse_response(client=client, response=response),\n    )\n\n\ndef sync_detailed(\n    id: UUID,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    x_api_key: str,\n) -> Response[\n    Union[\n        GetV1DesktopIdResponse200,\n        GetV1DesktopIdResponse400,\n        GetV1DesktopIdResponse401,\n        GetV1DesktopIdResponse403,\n        GetV1DesktopIdResponse404,\n        GetV1DesktopIdResponse409,\n        GetV1DesktopIdResponse429,\n        GetV1DesktopIdResponse500,\n        GetV1DesktopIdResponse502,\n    ]\n]:\n    \"\"\"Get details of a specific desktop instance\n\n     Returns the ID, status, creation timestamp, and timeout timestamp for a given desktop instance.\n\n    Args:\n        id (UUID): The UUID of the desktop instance to retrieve Example:\n            a1b2c3d4-e5f6-7890-1234-567890abcdef.\n        x_api_key (str): API key for authentication Example: api_12345.\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[GetV1DesktopIdResponse200, GetV1DesktopIdResponse400, GetV1DesktopIdResponse401, GetV1DesktopIdResponse403, GetV1DesktopIdResponse404, GetV1DesktopIdResponse409, GetV1DesktopIdResponse429, GetV1DesktopIdResponse500, GetV1DesktopIdResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        id=id,\n        x_api_key=x_api_key,\n    )\n\n    response = client.get_httpx_client().request(\n        **kwargs,\n    )\n\n    return _build_response(client=client, response=response)\n\n\ndef sync(\n    id: UUID,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    x_api_key: str,\n) -> Optional[\n    Union[\n        GetV1DesktopIdResponse200,\n        GetV1DesktopIdResponse400,\n        GetV1DesktopIdResponse401,\n        GetV1DesktopIdResponse403,\n        GetV1DesktopIdResponse404,\n        GetV1DesktopIdResponse409,\n        GetV1DesktopIdResponse429,\n        GetV1DesktopIdResponse500,\n        GetV1DesktopIdResponse502,\n    ]\n]:\n    \"\"\"Get details of a specific desktop instance\n\n     Returns the ID, status, creation timestamp, and timeout timestamp for a given desktop instance.\n\n    Args:\n        id (UUID): The UUID of the desktop instance to retrieve Example:\n            a1b2c3d4-e5f6-7890-1234-567890abcdef.\n        x_api_key (str): API key for authentication Example: api_12345.\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[GetV1DesktopIdResponse200, GetV1DesktopIdResponse400, GetV1DesktopIdResponse401, GetV1DesktopIdResponse403, GetV1DesktopIdResponse404, GetV1DesktopIdResponse409, GetV1DesktopIdResponse429, GetV1DesktopIdResponse500, GetV1DesktopIdResponse502]\n    \"\"\"\n\n    return sync_detailed(\n        id=id,\n        client=client,\n        x_api_key=x_api_key,\n    ).parsed\n\n\nasync def asyncio_detailed(\n    id: UUID,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    x_api_key: str,\n) -> Response[\n    Union[\n        GetV1DesktopIdResponse200,\n        GetV1DesktopIdResponse400,\n        GetV1DesktopIdResponse401,\n        GetV1DesktopIdResponse403,\n        GetV1DesktopIdResponse404,\n        GetV1DesktopIdResponse409,\n        GetV1DesktopIdResponse429,\n        GetV1DesktopIdResponse500,\n        GetV1DesktopIdResponse502,\n    ]\n]:\n    \"\"\"Get details of a specific desktop instance\n\n     Returns the ID, status, creation timestamp, and timeout timestamp for a given desktop instance.\n\n    Args:\n        id (UUID): The UUID of the desktop instance to retrieve Example:\n            a1b2c3d4-e5f6-7890-1234-567890abcdef.\n        x_api_key (str): API key for authentication Example: api_12345.\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[GetV1DesktopIdResponse200, GetV1DesktopIdResponse400, GetV1DesktopIdResponse401, GetV1DesktopIdResponse403, GetV1DesktopIdResponse404, GetV1DesktopIdResponse409, GetV1DesktopIdResponse429, GetV1DesktopIdResponse500, GetV1DesktopIdResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        id=id,\n        x_api_key=x_api_key,\n    )\n\n    response = await client.get_async_httpx_client().request(**kwargs)\n\n    return _build_response(client=client, response=response)\n\n\nasync def asyncio(\n    id: UUID,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    x_api_key: str,\n) -> Optional[\n    Union[\n        GetV1DesktopIdResponse200,\n        GetV1DesktopIdResponse400,\n        GetV1DesktopIdResponse401,\n        GetV1DesktopIdResponse403,\n        GetV1DesktopIdResponse404,\n        GetV1DesktopIdResponse409,\n        GetV1DesktopIdResponse429,\n        GetV1DesktopIdResponse500,\n        GetV1DesktopIdResponse502,\n    ]\n]:\n    \"\"\"Get details of a specific desktop instance\n\n     Returns the ID, status, creation timestamp, and timeout timestamp for a given desktop instance.\n\n    Args:\n        id (UUID): The UUID of the desktop instance to retrieve Example:\n            a1b2c3d4-e5f6-7890-1234-567890abcdef.\n        x_api_key (str): API key for authentication Example: api_12345.\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[GetV1DesktopIdResponse200, GetV1DesktopIdResponse400, GetV1DesktopIdResponse401, GetV1DesktopIdResponse403, GetV1DesktopIdResponse404, GetV1DesktopIdResponse409, GetV1DesktopIdResponse429, GetV1DesktopIdResponse500, GetV1DesktopIdResponse502]\n    \"\"\"\n\n    return (\n        await asyncio_detailed(\n            id=id,\n            client=client,\n            x_api_key=x_api_key,\n        )\n    ).parsed\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/api/desktop/post_v1_desktop.py",
    "content": "from http import HTTPStatus\nfrom typing import Any, Optional, Union\n\nimport httpx\n\nfrom ... import errors\nfrom ...client import AuthenticatedClient, Client\nfrom ...models.post_v1_desktop_body import PostV1DesktopBody\nfrom ...models.post_v1_desktop_response_200 import PostV1DesktopResponse200\nfrom ...models.post_v1_desktop_response_400 import PostV1DesktopResponse400\nfrom ...models.post_v1_desktop_response_401 import PostV1DesktopResponse401\nfrom ...models.post_v1_desktop_response_403 import PostV1DesktopResponse403\nfrom ...models.post_v1_desktop_response_404 import PostV1DesktopResponse404\nfrom ...models.post_v1_desktop_response_409 import PostV1DesktopResponse409\nfrom ...models.post_v1_desktop_response_429 import PostV1DesktopResponse429\nfrom ...models.post_v1_desktop_response_500 import PostV1DesktopResponse500\nfrom ...models.post_v1_desktop_response_502 import PostV1DesktopResponse502\nfrom ...types import Response\n\n\ndef _get_kwargs(\n    *,\n    body: PostV1DesktopBody,\n    x_api_key: str,\n) -> dict[str, Any]:\n    headers: dict[str, Any] = {}\n    headers[\"x-api-key\"] = x_api_key\n\n    _kwargs: dict[str, Any] = {\n        \"method\": \"post\",\n        \"url\": \"/v1/desktop\",\n    }\n\n    _body = body.to_dict()\n\n    _kwargs[\"json\"] = _body\n    headers[\"Content-Type\"] = \"application/json\"\n\n    _kwargs[\"headers\"] = headers\n    return _kwargs\n\n\ndef _parse_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Optional[\n    Union[\n        PostV1DesktopResponse200,\n        PostV1DesktopResponse400,\n        PostV1DesktopResponse401,\n        PostV1DesktopResponse403,\n        PostV1DesktopResponse404,\n        PostV1DesktopResponse409,\n        PostV1DesktopResponse429,\n        PostV1DesktopResponse500,\n        PostV1DesktopResponse502,\n    ]\n]:\n    if response.status_code == 200:\n        response_200 = PostV1DesktopResponse200.from_dict(response.json())\n\n        return response_200\n    if response.status_code == 400:\n        response_400 = PostV1DesktopResponse400.from_dict(response.json())\n\n        return response_400\n    if response.status_code == 401:\n        response_401 = PostV1DesktopResponse401.from_dict(response.json())\n\n        return response_401\n    if response.status_code == 403:\n        response_403 = PostV1DesktopResponse403.from_dict(response.json())\n\n        return response_403\n    if response.status_code == 404:\n        response_404 = PostV1DesktopResponse404.from_dict(response.json())\n\n        return response_404\n    if response.status_code == 409:\n        response_409 = PostV1DesktopResponse409.from_dict(response.json())\n\n        return response_409\n    if response.status_code == 429:\n        response_429 = PostV1DesktopResponse429.from_dict(response.json())\n\n        return response_429\n    if response.status_code == 500:\n        response_500 = PostV1DesktopResponse500.from_dict(response.json())\n\n        return response_500\n    if response.status_code == 502:\n        response_502 = PostV1DesktopResponse502.from_dict(response.json())\n\n        return response_502\n    if client.raise_on_unexpected_status:\n        raise errors.UnexpectedStatus(response.status_code, response.content)\n    else:\n        return None\n\n\ndef _build_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Response[\n    Union[\n        PostV1DesktopResponse200,\n        PostV1DesktopResponse400,\n        PostV1DesktopResponse401,\n        PostV1DesktopResponse403,\n        PostV1DesktopResponse404,\n        PostV1DesktopResponse409,\n        PostV1DesktopResponse429,\n        PostV1DesktopResponse500,\n        PostV1DesktopResponse502,\n    ]\n]:\n    return Response(\n        status_code=HTTPStatus(response.status_code),\n        content=response.content,\n        headers=response.headers,\n        parsed=_parse_response(client=client, response=response),\n    )\n\n\ndef sync_detailed(\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: PostV1DesktopBody,\n    x_api_key: str,\n) -> Response[\n    Union[\n        PostV1DesktopResponse200,\n        PostV1DesktopResponse400,\n        PostV1DesktopResponse401,\n        PostV1DesktopResponse403,\n        PostV1DesktopResponse404,\n        PostV1DesktopResponse409,\n        PostV1DesktopResponse429,\n        PostV1DesktopResponse500,\n        PostV1DesktopResponse502,\n    ]\n]:\n    \"\"\"Create a new virtual desktop instance\n\n     Creates a new virtual desktop instance and returns its ID and stream URL\n\n    Args:\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (PostV1DesktopBody):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[PostV1DesktopResponse200, PostV1DesktopResponse400, PostV1DesktopResponse401, PostV1DesktopResponse403, PostV1DesktopResponse404, PostV1DesktopResponse409, PostV1DesktopResponse429, PostV1DesktopResponse500, PostV1DesktopResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        body=body,\n        x_api_key=x_api_key,\n    )\n\n    response = client.get_httpx_client().request(\n        **kwargs,\n    )\n\n    return _build_response(client=client, response=response)\n\n\ndef sync(\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: PostV1DesktopBody,\n    x_api_key: str,\n) -> Optional[\n    Union[\n        PostV1DesktopResponse200,\n        PostV1DesktopResponse400,\n        PostV1DesktopResponse401,\n        PostV1DesktopResponse403,\n        PostV1DesktopResponse404,\n        PostV1DesktopResponse409,\n        PostV1DesktopResponse429,\n        PostV1DesktopResponse500,\n        PostV1DesktopResponse502,\n    ]\n]:\n    \"\"\"Create a new virtual desktop instance\n\n     Creates a new virtual desktop instance and returns its ID and stream URL\n\n    Args:\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (PostV1DesktopBody):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[PostV1DesktopResponse200, PostV1DesktopResponse400, PostV1DesktopResponse401, PostV1DesktopResponse403, PostV1DesktopResponse404, PostV1DesktopResponse409, PostV1DesktopResponse429, PostV1DesktopResponse500, PostV1DesktopResponse502]\n    \"\"\"\n\n    return sync_detailed(\n        client=client,\n        body=body,\n        x_api_key=x_api_key,\n    ).parsed\n\n\nasync def asyncio_detailed(\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: PostV1DesktopBody,\n    x_api_key: str,\n) -> Response[\n    Union[\n        PostV1DesktopResponse200,\n        PostV1DesktopResponse400,\n        PostV1DesktopResponse401,\n        PostV1DesktopResponse403,\n        PostV1DesktopResponse404,\n        PostV1DesktopResponse409,\n        PostV1DesktopResponse429,\n        PostV1DesktopResponse500,\n        PostV1DesktopResponse502,\n    ]\n]:\n    \"\"\"Create a new virtual desktop instance\n\n     Creates a new virtual desktop instance and returns its ID and stream URL\n\n    Args:\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (PostV1DesktopBody):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[PostV1DesktopResponse200, PostV1DesktopResponse400, PostV1DesktopResponse401, PostV1DesktopResponse403, PostV1DesktopResponse404, PostV1DesktopResponse409, PostV1DesktopResponse429, PostV1DesktopResponse500, PostV1DesktopResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        body=body,\n        x_api_key=x_api_key,\n    )\n\n    response = await client.get_async_httpx_client().request(**kwargs)\n\n    return _build_response(client=client, response=response)\n\n\nasync def asyncio(\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: PostV1DesktopBody,\n    x_api_key: str,\n) -> Optional[\n    Union[\n        PostV1DesktopResponse200,\n        PostV1DesktopResponse400,\n        PostV1DesktopResponse401,\n        PostV1DesktopResponse403,\n        PostV1DesktopResponse404,\n        PostV1DesktopResponse409,\n        PostV1DesktopResponse429,\n        PostV1DesktopResponse500,\n        PostV1DesktopResponse502,\n    ]\n]:\n    \"\"\"Create a new virtual desktop instance\n\n     Creates a new virtual desktop instance and returns its ID and stream URL\n\n    Args:\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (PostV1DesktopBody):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[PostV1DesktopResponse200, PostV1DesktopResponse400, PostV1DesktopResponse401, PostV1DesktopResponse403, PostV1DesktopResponse404, PostV1DesktopResponse409, PostV1DesktopResponse429, PostV1DesktopResponse500, PostV1DesktopResponse502]\n    \"\"\"\n\n    return (\n        await asyncio_detailed(\n            client=client,\n            body=body,\n            x_api_key=x_api_key,\n        )\n    ).parsed\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/api/desktop/post_v1_desktop_id_bash_action.py",
    "content": "from http import HTTPStatus\nfrom typing import Any, Optional, Union\n\nimport httpx\n\nfrom ... import errors\nfrom ...client import AuthenticatedClient, Client\nfrom ...models.post_v1_desktop_id_bash_action_body import PostV1DesktopIdBashActionBody\nfrom ...models.post_v1_desktop_id_bash_action_response_200 import PostV1DesktopIdBashActionResponse200\nfrom ...models.post_v1_desktop_id_bash_action_response_400 import PostV1DesktopIdBashActionResponse400\nfrom ...models.post_v1_desktop_id_bash_action_response_401 import PostV1DesktopIdBashActionResponse401\nfrom ...models.post_v1_desktop_id_bash_action_response_403 import PostV1DesktopIdBashActionResponse403\nfrom ...models.post_v1_desktop_id_bash_action_response_404 import PostV1DesktopIdBashActionResponse404\nfrom ...models.post_v1_desktop_id_bash_action_response_409 import PostV1DesktopIdBashActionResponse409\nfrom ...models.post_v1_desktop_id_bash_action_response_429 import PostV1DesktopIdBashActionResponse429\nfrom ...models.post_v1_desktop_id_bash_action_response_500 import PostV1DesktopIdBashActionResponse500\nfrom ...models.post_v1_desktop_id_bash_action_response_502 import PostV1DesktopIdBashActionResponse502\nfrom ...types import Response\n\n\ndef _get_kwargs(\n    id: str,\n    *,\n    body: PostV1DesktopIdBashActionBody,\n    x_api_key: str,\n) -> dict[str, Any]:\n    headers: dict[str, Any] = {}\n    headers[\"x-api-key\"] = x_api_key\n\n    _kwargs: dict[str, Any] = {\n        \"method\": \"post\",\n        \"url\": f\"/v1/desktop/{id}/bash-action\",\n    }\n\n    _body = body.to_dict()\n\n    _kwargs[\"json\"] = _body\n    headers[\"Content-Type\"] = \"application/json\"\n\n    _kwargs[\"headers\"] = headers\n    return _kwargs\n\n\ndef _parse_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Optional[\n    Union[\n        PostV1DesktopIdBashActionResponse200,\n        PostV1DesktopIdBashActionResponse400,\n        PostV1DesktopIdBashActionResponse401,\n        PostV1DesktopIdBashActionResponse403,\n        PostV1DesktopIdBashActionResponse404,\n        PostV1DesktopIdBashActionResponse409,\n        PostV1DesktopIdBashActionResponse429,\n        PostV1DesktopIdBashActionResponse500,\n        PostV1DesktopIdBashActionResponse502,\n    ]\n]:\n    if response.status_code == 200:\n        response_200 = PostV1DesktopIdBashActionResponse200.from_dict(response.json())\n\n        return response_200\n    if response.status_code == 400:\n        response_400 = PostV1DesktopIdBashActionResponse400.from_dict(response.json())\n\n        return response_400\n    if response.status_code == 401:\n        response_401 = PostV1DesktopIdBashActionResponse401.from_dict(response.json())\n\n        return response_401\n    if response.status_code == 403:\n        response_403 = PostV1DesktopIdBashActionResponse403.from_dict(response.json())\n\n        return response_403\n    if response.status_code == 404:\n        response_404 = PostV1DesktopIdBashActionResponse404.from_dict(response.json())\n\n        return response_404\n    if response.status_code == 409:\n        response_409 = PostV1DesktopIdBashActionResponse409.from_dict(response.json())\n\n        return response_409\n    if response.status_code == 429:\n        response_429 = PostV1DesktopIdBashActionResponse429.from_dict(response.json())\n\n        return response_429\n    if response.status_code == 500:\n        response_500 = PostV1DesktopIdBashActionResponse500.from_dict(response.json())\n\n        return response_500\n    if response.status_code == 502:\n        response_502 = PostV1DesktopIdBashActionResponse502.from_dict(response.json())\n\n        return response_502\n    if client.raise_on_unexpected_status:\n        raise errors.UnexpectedStatus(response.status_code, response.content)\n    else:\n        return None\n\n\ndef _build_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Response[\n    Union[\n        PostV1DesktopIdBashActionResponse200,\n        PostV1DesktopIdBashActionResponse400,\n        PostV1DesktopIdBashActionResponse401,\n        PostV1DesktopIdBashActionResponse403,\n        PostV1DesktopIdBashActionResponse404,\n        PostV1DesktopIdBashActionResponse409,\n        PostV1DesktopIdBashActionResponse429,\n        PostV1DesktopIdBashActionResponse500,\n        PostV1DesktopIdBashActionResponse502,\n    ]\n]:\n    return Response(\n        status_code=HTTPStatus(response.status_code),\n        content=response.content,\n        headers=response.headers,\n        parsed=_parse_response(client=client, response=response),\n    )\n\n\ndef sync_detailed(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: PostV1DesktopIdBashActionBody,\n    x_api_key: str,\n) -> Response[\n    Union[\n        PostV1DesktopIdBashActionResponse200,\n        PostV1DesktopIdBashActionResponse400,\n        PostV1DesktopIdBashActionResponse401,\n        PostV1DesktopIdBashActionResponse403,\n        PostV1DesktopIdBashActionResponse404,\n        PostV1DesktopIdBashActionResponse409,\n        PostV1DesktopIdBashActionResponse429,\n        PostV1DesktopIdBashActionResponse500,\n        PostV1DesktopIdBashActionResponse502,\n    ]\n]:\n    \"\"\"Execute a bash command on the desktop\n\n     Runs a bash command on the desktop and returns the command output\n\n    Args:\n        id (str): Desktop instance ID to run the command on Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (PostV1DesktopIdBashActionBody):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[PostV1DesktopIdBashActionResponse200, PostV1DesktopIdBashActionResponse400, PostV1DesktopIdBashActionResponse401, PostV1DesktopIdBashActionResponse403, PostV1DesktopIdBashActionResponse404, PostV1DesktopIdBashActionResponse409, PostV1DesktopIdBashActionResponse429, PostV1DesktopIdBashActionResponse500, PostV1DesktopIdBashActionResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        id=id,\n        body=body,\n        x_api_key=x_api_key,\n    )\n\n    response = client.get_httpx_client().request(\n        **kwargs,\n    )\n\n    return _build_response(client=client, response=response)\n\n\ndef sync(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: PostV1DesktopIdBashActionBody,\n    x_api_key: str,\n) -> Optional[\n    Union[\n        PostV1DesktopIdBashActionResponse200,\n        PostV1DesktopIdBashActionResponse400,\n        PostV1DesktopIdBashActionResponse401,\n        PostV1DesktopIdBashActionResponse403,\n        PostV1DesktopIdBashActionResponse404,\n        PostV1DesktopIdBashActionResponse409,\n        PostV1DesktopIdBashActionResponse429,\n        PostV1DesktopIdBashActionResponse500,\n        PostV1DesktopIdBashActionResponse502,\n    ]\n]:\n    \"\"\"Execute a bash command on the desktop\n\n     Runs a bash command on the desktop and returns the command output\n\n    Args:\n        id (str): Desktop instance ID to run the command on Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (PostV1DesktopIdBashActionBody):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[PostV1DesktopIdBashActionResponse200, PostV1DesktopIdBashActionResponse400, PostV1DesktopIdBashActionResponse401, PostV1DesktopIdBashActionResponse403, PostV1DesktopIdBashActionResponse404, PostV1DesktopIdBashActionResponse409, PostV1DesktopIdBashActionResponse429, PostV1DesktopIdBashActionResponse500, PostV1DesktopIdBashActionResponse502]\n    \"\"\"\n\n    return sync_detailed(\n        id=id,\n        client=client,\n        body=body,\n        x_api_key=x_api_key,\n    ).parsed\n\n\nasync def asyncio_detailed(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: PostV1DesktopIdBashActionBody,\n    x_api_key: str,\n) -> Response[\n    Union[\n        PostV1DesktopIdBashActionResponse200,\n        PostV1DesktopIdBashActionResponse400,\n        PostV1DesktopIdBashActionResponse401,\n        PostV1DesktopIdBashActionResponse403,\n        PostV1DesktopIdBashActionResponse404,\n        PostV1DesktopIdBashActionResponse409,\n        PostV1DesktopIdBashActionResponse429,\n        PostV1DesktopIdBashActionResponse500,\n        PostV1DesktopIdBashActionResponse502,\n    ]\n]:\n    \"\"\"Execute a bash command on the desktop\n\n     Runs a bash command on the desktop and returns the command output\n\n    Args:\n        id (str): Desktop instance ID to run the command on Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (PostV1DesktopIdBashActionBody):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[PostV1DesktopIdBashActionResponse200, PostV1DesktopIdBashActionResponse400, PostV1DesktopIdBashActionResponse401, PostV1DesktopIdBashActionResponse403, PostV1DesktopIdBashActionResponse404, PostV1DesktopIdBashActionResponse409, PostV1DesktopIdBashActionResponse429, PostV1DesktopIdBashActionResponse500, PostV1DesktopIdBashActionResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        id=id,\n        body=body,\n        x_api_key=x_api_key,\n    )\n\n    response = await client.get_async_httpx_client().request(**kwargs)\n\n    return _build_response(client=client, response=response)\n\n\nasync def asyncio(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: PostV1DesktopIdBashActionBody,\n    x_api_key: str,\n) -> Optional[\n    Union[\n        PostV1DesktopIdBashActionResponse200,\n        PostV1DesktopIdBashActionResponse400,\n        PostV1DesktopIdBashActionResponse401,\n        PostV1DesktopIdBashActionResponse403,\n        PostV1DesktopIdBashActionResponse404,\n        PostV1DesktopIdBashActionResponse409,\n        PostV1DesktopIdBashActionResponse429,\n        PostV1DesktopIdBashActionResponse500,\n        PostV1DesktopIdBashActionResponse502,\n    ]\n]:\n    \"\"\"Execute a bash command on the desktop\n\n     Runs a bash command on the desktop and returns the command output\n\n    Args:\n        id (str): Desktop instance ID to run the command on Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (PostV1DesktopIdBashActionBody):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[PostV1DesktopIdBashActionResponse200, PostV1DesktopIdBashActionResponse400, PostV1DesktopIdBashActionResponse401, PostV1DesktopIdBashActionResponse403, PostV1DesktopIdBashActionResponse404, PostV1DesktopIdBashActionResponse409, PostV1DesktopIdBashActionResponse429, PostV1DesktopIdBashActionResponse500, PostV1DesktopIdBashActionResponse502]\n    \"\"\"\n\n    return (\n        await asyncio_detailed(\n            id=id,\n            client=client,\n            body=body,\n            x_api_key=x_api_key,\n        )\n    ).parsed\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/api/desktop/post_v1_desktop_id_computer_action.py",
    "content": "from http import HTTPStatus\nfrom typing import Any, Optional, Union\n\nimport httpx\n\nfrom ... import errors\nfrom ...client import AuthenticatedClient, Client\nfrom ...models.post_v1_desktop_id_computer_action_click_mouse_action import (\n    PostV1DesktopIdComputerActionClickMouseAction,\n)\nfrom ...models.post_v1_desktop_id_computer_action_drag_mouse_action import PostV1DesktopIdComputerActionDragMouseAction\nfrom ...models.post_v1_desktop_id_computer_action_get_cursor_position_action import (\n    PostV1DesktopIdComputerActionGetCursorPositionAction,\n)\nfrom ...models.post_v1_desktop_id_computer_action_move_mouse_action import PostV1DesktopIdComputerActionMoveMouseAction\nfrom ...models.post_v1_desktop_id_computer_action_press_keys_action import PostV1DesktopIdComputerActionPressKeysAction\nfrom ...models.post_v1_desktop_id_computer_action_response_200 import PostV1DesktopIdComputerActionResponse200\nfrom ...models.post_v1_desktop_id_computer_action_response_400 import PostV1DesktopIdComputerActionResponse400\nfrom ...models.post_v1_desktop_id_computer_action_response_401 import PostV1DesktopIdComputerActionResponse401\nfrom ...models.post_v1_desktop_id_computer_action_response_403 import PostV1DesktopIdComputerActionResponse403\nfrom ...models.post_v1_desktop_id_computer_action_response_404 import PostV1DesktopIdComputerActionResponse404\nfrom ...models.post_v1_desktop_id_computer_action_response_409 import PostV1DesktopIdComputerActionResponse409\nfrom ...models.post_v1_desktop_id_computer_action_response_429 import PostV1DesktopIdComputerActionResponse429\nfrom ...models.post_v1_desktop_id_computer_action_response_500 import PostV1DesktopIdComputerActionResponse500\nfrom ...models.post_v1_desktop_id_computer_action_response_502 import PostV1DesktopIdComputerActionResponse502\nfrom ...models.post_v1_desktop_id_computer_action_screenshot_action import PostV1DesktopIdComputerActionScreenshotAction\nfrom ...models.post_v1_desktop_id_computer_action_scroll_action import PostV1DesktopIdComputerActionScrollAction\nfrom ...models.post_v1_desktop_id_computer_action_type_text_action import PostV1DesktopIdComputerActionTypeTextAction\nfrom ...models.post_v1_desktop_id_computer_action_wait_action import PostV1DesktopIdComputerActionWaitAction\nfrom ...types import Response\n\n\ndef _get_kwargs(\n    id: str,\n    *,\n    body: Union[\n        \"PostV1DesktopIdComputerActionClickMouseAction\",\n        \"PostV1DesktopIdComputerActionDragMouseAction\",\n        \"PostV1DesktopIdComputerActionGetCursorPositionAction\",\n        \"PostV1DesktopIdComputerActionMoveMouseAction\",\n        \"PostV1DesktopIdComputerActionPressKeysAction\",\n        \"PostV1DesktopIdComputerActionScreenshotAction\",\n        \"PostV1DesktopIdComputerActionScrollAction\",\n        \"PostV1DesktopIdComputerActionTypeTextAction\",\n        \"PostV1DesktopIdComputerActionWaitAction\",\n    ],\n    x_api_key: str,\n) -> dict[str, Any]:\n    headers: dict[str, Any] = {}\n    headers[\"x-api-key\"] = x_api_key\n\n    _kwargs: dict[str, Any] = {\n        \"method\": \"post\",\n        \"url\": f\"/v1/desktop/{id}/computer-action\",\n    }\n\n    _body: dict[str, Any]\n    if isinstance(body, PostV1DesktopIdComputerActionClickMouseAction):\n        _body = body.to_dict()\n    elif isinstance(body, PostV1DesktopIdComputerActionScrollAction):\n        _body = body.to_dict()\n    elif isinstance(body, PostV1DesktopIdComputerActionMoveMouseAction):\n        _body = body.to_dict()\n    elif isinstance(body, PostV1DesktopIdComputerActionDragMouseAction):\n        _body = body.to_dict()\n    elif isinstance(body, PostV1DesktopIdComputerActionTypeTextAction):\n        _body = body.to_dict()\n    elif isinstance(body, PostV1DesktopIdComputerActionPressKeysAction):\n        _body = body.to_dict()\n    elif isinstance(body, PostV1DesktopIdComputerActionWaitAction):\n        _body = body.to_dict()\n    elif isinstance(body, PostV1DesktopIdComputerActionScreenshotAction):\n        _body = body.to_dict()\n    else:\n        _body = body.to_dict()\n\n    _kwargs[\"json\"] = _body\n    headers[\"Content-Type\"] = \"application/json\"\n\n    _kwargs[\"headers\"] = headers\n    return _kwargs\n\n\ndef _parse_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Optional[\n    Union[\n        PostV1DesktopIdComputerActionResponse200,\n        PostV1DesktopIdComputerActionResponse400,\n        PostV1DesktopIdComputerActionResponse401,\n        PostV1DesktopIdComputerActionResponse403,\n        PostV1DesktopIdComputerActionResponse404,\n        PostV1DesktopIdComputerActionResponse409,\n        PostV1DesktopIdComputerActionResponse429,\n        PostV1DesktopIdComputerActionResponse500,\n        PostV1DesktopIdComputerActionResponse502,\n    ]\n]:\n    if response.status_code == 200:\n        response_200 = PostV1DesktopIdComputerActionResponse200.from_dict(response.json())\n\n        return response_200\n    if response.status_code == 400:\n        response_400 = PostV1DesktopIdComputerActionResponse400.from_dict(response.json())\n\n        return response_400\n    if response.status_code == 401:\n        response_401 = PostV1DesktopIdComputerActionResponse401.from_dict(response.json())\n\n        return response_401\n    if response.status_code == 403:\n        response_403 = PostV1DesktopIdComputerActionResponse403.from_dict(response.json())\n\n        return response_403\n    if response.status_code == 404:\n        response_404 = PostV1DesktopIdComputerActionResponse404.from_dict(response.json())\n\n        return response_404\n    if response.status_code == 409:\n        response_409 = PostV1DesktopIdComputerActionResponse409.from_dict(response.json())\n\n        return response_409\n    if response.status_code == 429:\n        response_429 = PostV1DesktopIdComputerActionResponse429.from_dict(response.json())\n\n        return response_429\n    if response.status_code == 500:\n        response_500 = PostV1DesktopIdComputerActionResponse500.from_dict(response.json())\n\n        return response_500\n    if response.status_code == 502:\n        response_502 = PostV1DesktopIdComputerActionResponse502.from_dict(response.json())\n\n        return response_502\n    if client.raise_on_unexpected_status:\n        raise errors.UnexpectedStatus(response.status_code, response.content)\n    else:\n        return None\n\n\ndef _build_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Response[\n    Union[\n        PostV1DesktopIdComputerActionResponse200,\n        PostV1DesktopIdComputerActionResponse400,\n        PostV1DesktopIdComputerActionResponse401,\n        PostV1DesktopIdComputerActionResponse403,\n        PostV1DesktopIdComputerActionResponse404,\n        PostV1DesktopIdComputerActionResponse409,\n        PostV1DesktopIdComputerActionResponse429,\n        PostV1DesktopIdComputerActionResponse500,\n        PostV1DesktopIdComputerActionResponse502,\n    ]\n]:\n    return Response(\n        status_code=HTTPStatus(response.status_code),\n        content=response.content,\n        headers=response.headers,\n        parsed=_parse_response(client=client, response=response),\n    )\n\n\ndef sync_detailed(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: Union[\n        \"PostV1DesktopIdComputerActionClickMouseAction\",\n        \"PostV1DesktopIdComputerActionDragMouseAction\",\n        \"PostV1DesktopIdComputerActionGetCursorPositionAction\",\n        \"PostV1DesktopIdComputerActionMoveMouseAction\",\n        \"PostV1DesktopIdComputerActionPressKeysAction\",\n        \"PostV1DesktopIdComputerActionScreenshotAction\",\n        \"PostV1DesktopIdComputerActionScrollAction\",\n        \"PostV1DesktopIdComputerActionTypeTextAction\",\n        \"PostV1DesktopIdComputerActionWaitAction\",\n    ],\n    x_api_key: str,\n) -> Response[\n    Union[\n        PostV1DesktopIdComputerActionResponse200,\n        PostV1DesktopIdComputerActionResponse400,\n        PostV1DesktopIdComputerActionResponse401,\n        PostV1DesktopIdComputerActionResponse403,\n        PostV1DesktopIdComputerActionResponse404,\n        PostV1DesktopIdComputerActionResponse409,\n        PostV1DesktopIdComputerActionResponse429,\n        PostV1DesktopIdComputerActionResponse500,\n        PostV1DesktopIdComputerActionResponse502,\n    ]\n]:\n    \"\"\"Perform an action on the desktop\n\n     Executes a computer action such as mouse clicks, keyboard input, or screenshots on the desktop\n\n    Args:\n        id (str): Desktop instance ID to perform the action on Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (Union['PostV1DesktopIdComputerActionClickMouseAction',\n            'PostV1DesktopIdComputerActionDragMouseAction',\n            'PostV1DesktopIdComputerActionGetCursorPositionAction',\n            'PostV1DesktopIdComputerActionMoveMouseAction',\n            'PostV1DesktopIdComputerActionPressKeysAction',\n            'PostV1DesktopIdComputerActionScreenshotAction',\n            'PostV1DesktopIdComputerActionScrollAction',\n            'PostV1DesktopIdComputerActionTypeTextAction',\n            'PostV1DesktopIdComputerActionWaitAction']):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[PostV1DesktopIdComputerActionResponse200, PostV1DesktopIdComputerActionResponse400, PostV1DesktopIdComputerActionResponse401, PostV1DesktopIdComputerActionResponse403, PostV1DesktopIdComputerActionResponse404, PostV1DesktopIdComputerActionResponse409, PostV1DesktopIdComputerActionResponse429, PostV1DesktopIdComputerActionResponse500, PostV1DesktopIdComputerActionResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        id=id,\n        body=body,\n        x_api_key=x_api_key,\n    )\n\n    response = client.get_httpx_client().request(\n        **kwargs,\n    )\n\n    return _build_response(client=client, response=response)\n\n\ndef sync(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: Union[\n        \"PostV1DesktopIdComputerActionClickMouseAction\",\n        \"PostV1DesktopIdComputerActionDragMouseAction\",\n        \"PostV1DesktopIdComputerActionGetCursorPositionAction\",\n        \"PostV1DesktopIdComputerActionMoveMouseAction\",\n        \"PostV1DesktopIdComputerActionPressKeysAction\",\n        \"PostV1DesktopIdComputerActionScreenshotAction\",\n        \"PostV1DesktopIdComputerActionScrollAction\",\n        \"PostV1DesktopIdComputerActionTypeTextAction\",\n        \"PostV1DesktopIdComputerActionWaitAction\",\n    ],\n    x_api_key: str,\n) -> Optional[\n    Union[\n        PostV1DesktopIdComputerActionResponse200,\n        PostV1DesktopIdComputerActionResponse400,\n        PostV1DesktopIdComputerActionResponse401,\n        PostV1DesktopIdComputerActionResponse403,\n        PostV1DesktopIdComputerActionResponse404,\n        PostV1DesktopIdComputerActionResponse409,\n        PostV1DesktopIdComputerActionResponse429,\n        PostV1DesktopIdComputerActionResponse500,\n        PostV1DesktopIdComputerActionResponse502,\n    ]\n]:\n    \"\"\"Perform an action on the desktop\n\n     Executes a computer action such as mouse clicks, keyboard input, or screenshots on the desktop\n\n    Args:\n        id (str): Desktop instance ID to perform the action on Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (Union['PostV1DesktopIdComputerActionClickMouseAction',\n            'PostV1DesktopIdComputerActionDragMouseAction',\n            'PostV1DesktopIdComputerActionGetCursorPositionAction',\n            'PostV1DesktopIdComputerActionMoveMouseAction',\n            'PostV1DesktopIdComputerActionPressKeysAction',\n            'PostV1DesktopIdComputerActionScreenshotAction',\n            'PostV1DesktopIdComputerActionScrollAction',\n            'PostV1DesktopIdComputerActionTypeTextAction',\n            'PostV1DesktopIdComputerActionWaitAction']):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[PostV1DesktopIdComputerActionResponse200, PostV1DesktopIdComputerActionResponse400, PostV1DesktopIdComputerActionResponse401, PostV1DesktopIdComputerActionResponse403, PostV1DesktopIdComputerActionResponse404, PostV1DesktopIdComputerActionResponse409, PostV1DesktopIdComputerActionResponse429, PostV1DesktopIdComputerActionResponse500, PostV1DesktopIdComputerActionResponse502]\n    \"\"\"\n\n    return sync_detailed(\n        id=id,\n        client=client,\n        body=body,\n        x_api_key=x_api_key,\n    ).parsed\n\n\nasync def asyncio_detailed(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: Union[\n        \"PostV1DesktopIdComputerActionClickMouseAction\",\n        \"PostV1DesktopIdComputerActionDragMouseAction\",\n        \"PostV1DesktopIdComputerActionGetCursorPositionAction\",\n        \"PostV1DesktopIdComputerActionMoveMouseAction\",\n        \"PostV1DesktopIdComputerActionPressKeysAction\",\n        \"PostV1DesktopIdComputerActionScreenshotAction\",\n        \"PostV1DesktopIdComputerActionScrollAction\",\n        \"PostV1DesktopIdComputerActionTypeTextAction\",\n        \"PostV1DesktopIdComputerActionWaitAction\",\n    ],\n    x_api_key: str,\n) -> Response[\n    Union[\n        PostV1DesktopIdComputerActionResponse200,\n        PostV1DesktopIdComputerActionResponse400,\n        PostV1DesktopIdComputerActionResponse401,\n        PostV1DesktopIdComputerActionResponse403,\n        PostV1DesktopIdComputerActionResponse404,\n        PostV1DesktopIdComputerActionResponse409,\n        PostV1DesktopIdComputerActionResponse429,\n        PostV1DesktopIdComputerActionResponse500,\n        PostV1DesktopIdComputerActionResponse502,\n    ]\n]:\n    \"\"\"Perform an action on the desktop\n\n     Executes a computer action such as mouse clicks, keyboard input, or screenshots on the desktop\n\n    Args:\n        id (str): Desktop instance ID to perform the action on Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (Union['PostV1DesktopIdComputerActionClickMouseAction',\n            'PostV1DesktopIdComputerActionDragMouseAction',\n            'PostV1DesktopIdComputerActionGetCursorPositionAction',\n            'PostV1DesktopIdComputerActionMoveMouseAction',\n            'PostV1DesktopIdComputerActionPressKeysAction',\n            'PostV1DesktopIdComputerActionScreenshotAction',\n            'PostV1DesktopIdComputerActionScrollAction',\n            'PostV1DesktopIdComputerActionTypeTextAction',\n            'PostV1DesktopIdComputerActionWaitAction']):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[PostV1DesktopIdComputerActionResponse200, PostV1DesktopIdComputerActionResponse400, PostV1DesktopIdComputerActionResponse401, PostV1DesktopIdComputerActionResponse403, PostV1DesktopIdComputerActionResponse404, PostV1DesktopIdComputerActionResponse409, PostV1DesktopIdComputerActionResponse429, PostV1DesktopIdComputerActionResponse500, PostV1DesktopIdComputerActionResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        id=id,\n        body=body,\n        x_api_key=x_api_key,\n    )\n\n    response = await client.get_async_httpx_client().request(**kwargs)\n\n    return _build_response(client=client, response=response)\n\n\nasync def asyncio(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    body: Union[\n        \"PostV1DesktopIdComputerActionClickMouseAction\",\n        \"PostV1DesktopIdComputerActionDragMouseAction\",\n        \"PostV1DesktopIdComputerActionGetCursorPositionAction\",\n        \"PostV1DesktopIdComputerActionMoveMouseAction\",\n        \"PostV1DesktopIdComputerActionPressKeysAction\",\n        \"PostV1DesktopIdComputerActionScreenshotAction\",\n        \"PostV1DesktopIdComputerActionScrollAction\",\n        \"PostV1DesktopIdComputerActionTypeTextAction\",\n        \"PostV1DesktopIdComputerActionWaitAction\",\n    ],\n    x_api_key: str,\n) -> Optional[\n    Union[\n        PostV1DesktopIdComputerActionResponse200,\n        PostV1DesktopIdComputerActionResponse400,\n        PostV1DesktopIdComputerActionResponse401,\n        PostV1DesktopIdComputerActionResponse403,\n        PostV1DesktopIdComputerActionResponse404,\n        PostV1DesktopIdComputerActionResponse409,\n        PostV1DesktopIdComputerActionResponse429,\n        PostV1DesktopIdComputerActionResponse500,\n        PostV1DesktopIdComputerActionResponse502,\n    ]\n]:\n    \"\"\"Perform an action on the desktop\n\n     Executes a computer action such as mouse clicks, keyboard input, or screenshots on the desktop\n\n    Args:\n        id (str): Desktop instance ID to perform the action on Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n        body (Union['PostV1DesktopIdComputerActionClickMouseAction',\n            'PostV1DesktopIdComputerActionDragMouseAction',\n            'PostV1DesktopIdComputerActionGetCursorPositionAction',\n            'PostV1DesktopIdComputerActionMoveMouseAction',\n            'PostV1DesktopIdComputerActionPressKeysAction',\n            'PostV1DesktopIdComputerActionScreenshotAction',\n            'PostV1DesktopIdComputerActionScrollAction',\n            'PostV1DesktopIdComputerActionTypeTextAction',\n            'PostV1DesktopIdComputerActionWaitAction']):\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[PostV1DesktopIdComputerActionResponse200, PostV1DesktopIdComputerActionResponse400, PostV1DesktopIdComputerActionResponse401, PostV1DesktopIdComputerActionResponse403, PostV1DesktopIdComputerActionResponse404, PostV1DesktopIdComputerActionResponse409, PostV1DesktopIdComputerActionResponse429, PostV1DesktopIdComputerActionResponse500, PostV1DesktopIdComputerActionResponse502]\n    \"\"\"\n\n    return (\n        await asyncio_detailed(\n            id=id,\n            client=client,\n            body=body,\n            x_api_key=x_api_key,\n        )\n    ).parsed\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/api/desktop/post_v1_desktop_id_stop.py",
    "content": "from http import HTTPStatus\nfrom typing import Any, Optional, Union\n\nimport httpx\n\nfrom ... import errors\nfrom ...client import AuthenticatedClient, Client\nfrom ...models.post_v1_desktop_id_stop_response_200 import PostV1DesktopIdStopResponse200\nfrom ...models.post_v1_desktop_id_stop_response_400 import PostV1DesktopIdStopResponse400\nfrom ...models.post_v1_desktop_id_stop_response_401 import PostV1DesktopIdStopResponse401\nfrom ...models.post_v1_desktop_id_stop_response_403 import PostV1DesktopIdStopResponse403\nfrom ...models.post_v1_desktop_id_stop_response_404 import PostV1DesktopIdStopResponse404\nfrom ...models.post_v1_desktop_id_stop_response_409 import PostV1DesktopIdStopResponse409\nfrom ...models.post_v1_desktop_id_stop_response_429 import PostV1DesktopIdStopResponse429\nfrom ...models.post_v1_desktop_id_stop_response_500 import PostV1DesktopIdStopResponse500\nfrom ...models.post_v1_desktop_id_stop_response_502 import PostV1DesktopIdStopResponse502\nfrom ...types import Response\n\n\ndef _get_kwargs(\n    id: str,\n    *,\n    x_api_key: str,\n) -> dict[str, Any]:\n    headers: dict[str, Any] = {}\n    headers[\"x-api-key\"] = x_api_key\n\n    _kwargs: dict[str, Any] = {\n        \"method\": \"post\",\n        \"url\": f\"/v1/desktop/{id}/stop\",\n    }\n\n    _kwargs[\"headers\"] = headers\n    return _kwargs\n\n\ndef _parse_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Optional[\n    Union[\n        PostV1DesktopIdStopResponse200,\n        PostV1DesktopIdStopResponse400,\n        PostV1DesktopIdStopResponse401,\n        PostV1DesktopIdStopResponse403,\n        PostV1DesktopIdStopResponse404,\n        PostV1DesktopIdStopResponse409,\n        PostV1DesktopIdStopResponse429,\n        PostV1DesktopIdStopResponse500,\n        PostV1DesktopIdStopResponse502,\n    ]\n]:\n    if response.status_code == 200:\n        response_200 = PostV1DesktopIdStopResponse200.from_dict(response.json())\n\n        return response_200\n    if response.status_code == 400:\n        response_400 = PostV1DesktopIdStopResponse400.from_dict(response.json())\n\n        return response_400\n    if response.status_code == 401:\n        response_401 = PostV1DesktopIdStopResponse401.from_dict(response.json())\n\n        return response_401\n    if response.status_code == 403:\n        response_403 = PostV1DesktopIdStopResponse403.from_dict(response.json())\n\n        return response_403\n    if response.status_code == 404:\n        response_404 = PostV1DesktopIdStopResponse404.from_dict(response.json())\n\n        return response_404\n    if response.status_code == 409:\n        response_409 = PostV1DesktopIdStopResponse409.from_dict(response.json())\n\n        return response_409\n    if response.status_code == 429:\n        response_429 = PostV1DesktopIdStopResponse429.from_dict(response.json())\n\n        return response_429\n    if response.status_code == 500:\n        response_500 = PostV1DesktopIdStopResponse500.from_dict(response.json())\n\n        return response_500\n    if response.status_code == 502:\n        response_502 = PostV1DesktopIdStopResponse502.from_dict(response.json())\n\n        return response_502\n    if client.raise_on_unexpected_status:\n        raise errors.UnexpectedStatus(response.status_code, response.content)\n    else:\n        return None\n\n\ndef _build_response(\n    *, client: Union[AuthenticatedClient, Client], response: httpx.Response\n) -> Response[\n    Union[\n        PostV1DesktopIdStopResponse200,\n        PostV1DesktopIdStopResponse400,\n        PostV1DesktopIdStopResponse401,\n        PostV1DesktopIdStopResponse403,\n        PostV1DesktopIdStopResponse404,\n        PostV1DesktopIdStopResponse409,\n        PostV1DesktopIdStopResponse429,\n        PostV1DesktopIdStopResponse500,\n        PostV1DesktopIdStopResponse502,\n    ]\n]:\n    return Response(\n        status_code=HTTPStatus(response.status_code),\n        content=response.content,\n        headers=response.headers,\n        parsed=_parse_response(client=client, response=response),\n    )\n\n\ndef sync_detailed(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    x_api_key: str,\n) -> Response[\n    Union[\n        PostV1DesktopIdStopResponse200,\n        PostV1DesktopIdStopResponse400,\n        PostV1DesktopIdStopResponse401,\n        PostV1DesktopIdStopResponse403,\n        PostV1DesktopIdStopResponse404,\n        PostV1DesktopIdStopResponse409,\n        PostV1DesktopIdStopResponse429,\n        PostV1DesktopIdStopResponse500,\n        PostV1DesktopIdStopResponse502,\n    ]\n]:\n    \"\"\"Stop a running desktop instance\n\n     Stops a running desktop instance and cleans up resources\n\n    Args:\n        id (str): Desktop instance ID to stop Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[PostV1DesktopIdStopResponse200, PostV1DesktopIdStopResponse400, PostV1DesktopIdStopResponse401, PostV1DesktopIdStopResponse403, PostV1DesktopIdStopResponse404, PostV1DesktopIdStopResponse409, PostV1DesktopIdStopResponse429, PostV1DesktopIdStopResponse500, PostV1DesktopIdStopResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        id=id,\n        x_api_key=x_api_key,\n    )\n\n    response = client.get_httpx_client().request(\n        **kwargs,\n    )\n\n    return _build_response(client=client, response=response)\n\n\ndef sync(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    x_api_key: str,\n) -> Optional[\n    Union[\n        PostV1DesktopIdStopResponse200,\n        PostV1DesktopIdStopResponse400,\n        PostV1DesktopIdStopResponse401,\n        PostV1DesktopIdStopResponse403,\n        PostV1DesktopIdStopResponse404,\n        PostV1DesktopIdStopResponse409,\n        PostV1DesktopIdStopResponse429,\n        PostV1DesktopIdStopResponse500,\n        PostV1DesktopIdStopResponse502,\n    ]\n]:\n    \"\"\"Stop a running desktop instance\n\n     Stops a running desktop instance and cleans up resources\n\n    Args:\n        id (str): Desktop instance ID to stop Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[PostV1DesktopIdStopResponse200, PostV1DesktopIdStopResponse400, PostV1DesktopIdStopResponse401, PostV1DesktopIdStopResponse403, PostV1DesktopIdStopResponse404, PostV1DesktopIdStopResponse409, PostV1DesktopIdStopResponse429, PostV1DesktopIdStopResponse500, PostV1DesktopIdStopResponse502]\n    \"\"\"\n\n    return sync_detailed(\n        id=id,\n        client=client,\n        x_api_key=x_api_key,\n    ).parsed\n\n\nasync def asyncio_detailed(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    x_api_key: str,\n) -> Response[\n    Union[\n        PostV1DesktopIdStopResponse200,\n        PostV1DesktopIdStopResponse400,\n        PostV1DesktopIdStopResponse401,\n        PostV1DesktopIdStopResponse403,\n        PostV1DesktopIdStopResponse404,\n        PostV1DesktopIdStopResponse409,\n        PostV1DesktopIdStopResponse429,\n        PostV1DesktopIdStopResponse500,\n        PostV1DesktopIdStopResponse502,\n    ]\n]:\n    \"\"\"Stop a running desktop instance\n\n     Stops a running desktop instance and cleans up resources\n\n    Args:\n        id (str): Desktop instance ID to stop Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Response[Union[PostV1DesktopIdStopResponse200, PostV1DesktopIdStopResponse400, PostV1DesktopIdStopResponse401, PostV1DesktopIdStopResponse403, PostV1DesktopIdStopResponse404, PostV1DesktopIdStopResponse409, PostV1DesktopIdStopResponse429, PostV1DesktopIdStopResponse500, PostV1DesktopIdStopResponse502]]\n    \"\"\"\n\n    kwargs = _get_kwargs(\n        id=id,\n        x_api_key=x_api_key,\n    )\n\n    response = await client.get_async_httpx_client().request(**kwargs)\n\n    return _build_response(client=client, response=response)\n\n\nasync def asyncio(\n    id: str,\n    *,\n    client: Union[AuthenticatedClient, Client],\n    x_api_key: str,\n) -> Optional[\n    Union[\n        PostV1DesktopIdStopResponse200,\n        PostV1DesktopIdStopResponse400,\n        PostV1DesktopIdStopResponse401,\n        PostV1DesktopIdStopResponse403,\n        PostV1DesktopIdStopResponse404,\n        PostV1DesktopIdStopResponse409,\n        PostV1DesktopIdStopResponse429,\n        PostV1DesktopIdStopResponse500,\n        PostV1DesktopIdStopResponse502,\n    ]\n]:\n    \"\"\"Stop a running desktop instance\n\n     Stops a running desktop instance and cleans up resources\n\n    Args:\n        id (str): Desktop instance ID to stop Example: desktop_12345.\n        x_api_key (str): API key for authentication Example: api_12345.\n\n    Raises:\n        errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.\n        httpx.TimeoutException: If the request takes longer than Client.timeout.\n\n    Returns:\n        Union[PostV1DesktopIdStopResponse200, PostV1DesktopIdStopResponse400, PostV1DesktopIdStopResponse401, PostV1DesktopIdStopResponse403, PostV1DesktopIdStopResponse404, PostV1DesktopIdStopResponse409, PostV1DesktopIdStopResponse429, PostV1DesktopIdStopResponse500, PostV1DesktopIdStopResponse502]\n    \"\"\"\n\n    return (\n        await asyncio_detailed(\n            id=id,\n            client=client,\n            x_api_key=x_api_key,\n        )\n    ).parsed\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/client.py",
    "content": "import ssl\nfrom typing import Any, Optional, Union\n\nimport httpx\nfrom attrs import define, evolve, field\n\n\n@define\nclass Client:\n    \"\"\"A class for keeping track of data related to the API\n\n    The following are accepted as keyword arguments and will be used to construct httpx Clients internally:\n\n        ``base_url``: The base URL for the API, all requests are made to a relative path to this URL\n\n        ``cookies``: A dictionary of cookies to be sent with every request\n\n        ``headers``: A dictionary of headers to be sent with every request\n\n        ``timeout``: The maximum amount of a time a request can take. API functions will raise\n        httpx.TimeoutException if this is exceeded.\n\n        ``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,\n        but can be set to False for testing purposes.\n\n        ``follow_redirects``: Whether or not to follow redirects. Default value is False.\n\n        ``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.\n\n\n    Attributes:\n        raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a\n            status code that was not documented in the source OpenAPI document. Can also be provided as a keyword\n            argument to the constructor.\n    \"\"\"\n\n    raise_on_unexpected_status: bool = field(default=False, kw_only=True)\n    _base_url: str = field(alias=\"base_url\")\n    _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias=\"cookies\")\n    _headers: dict[str, str] = field(factory=dict, kw_only=True, alias=\"headers\")\n    _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias=\"timeout\")\n    _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias=\"verify_ssl\")\n    _follow_redirects: bool = field(default=False, kw_only=True, alias=\"follow_redirects\")\n    _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias=\"httpx_args\")\n    _client: Optional[httpx.Client] = field(default=None, init=False)\n    _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)\n\n    def with_headers(self, headers: dict[str, str]) -> \"Client\":\n        \"\"\"Get a new client matching this one with additional headers\"\"\"\n        if self._client is not None:\n            self._client.headers.update(headers)\n        if self._async_client is not None:\n            self._async_client.headers.update(headers)\n        return evolve(self, headers={**self._headers, **headers})\n\n    def with_cookies(self, cookies: dict[str, str]) -> \"Client\":\n        \"\"\"Get a new client matching this one with additional cookies\"\"\"\n        if self._client is not None:\n            self._client.cookies.update(cookies)\n        if self._async_client is not None:\n            self._async_client.cookies.update(cookies)\n        return evolve(self, cookies={**self._cookies, **cookies})\n\n    def with_timeout(self, timeout: httpx.Timeout) -> \"Client\":\n        \"\"\"Get a new client matching this one with a new timeout (in seconds)\"\"\"\n        if self._client is not None:\n            self._client.timeout = timeout\n        if self._async_client is not None:\n            self._async_client.timeout = timeout\n        return evolve(self, timeout=timeout)\n\n    def set_httpx_client(self, client: httpx.Client) -> \"Client\":\n        \"\"\"Manually set the underlying httpx.Client\n\n        **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.\n        \"\"\"\n        self._client = client\n        return self\n\n    def get_httpx_client(self) -> httpx.Client:\n        \"\"\"Get the underlying httpx.Client, constructing a new one if not previously set\"\"\"\n        if self._client is None:\n            self._client = httpx.Client(\n                base_url=self._base_url,\n                cookies=self._cookies,\n                headers=self._headers,\n                timeout=self._timeout,\n                verify=self._verify_ssl,\n                follow_redirects=self._follow_redirects,\n                **self._httpx_args,\n            )\n        return self._client\n\n    def __enter__(self) -> \"Client\":\n        \"\"\"Enter a context manager for self.client—you cannot enter twice (see httpx docs)\"\"\"\n        self.get_httpx_client().__enter__()\n        return self\n\n    def __exit__(self, *args: Any, **kwargs: Any) -> None:\n        \"\"\"Exit a context manager for internal httpx.Client (see httpx docs)\"\"\"\n        self.get_httpx_client().__exit__(*args, **kwargs)\n\n    def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> \"Client\":\n        \"\"\"Manually the underlying httpx.AsyncClient\n\n        **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.\n        \"\"\"\n        self._async_client = async_client\n        return self\n\n    def get_async_httpx_client(self) -> httpx.AsyncClient:\n        \"\"\"Get the underlying httpx.AsyncClient, constructing a new one if not previously set\"\"\"\n        if self._async_client is None:\n            self._async_client = httpx.AsyncClient(\n                base_url=self._base_url,\n                cookies=self._cookies,\n                headers=self._headers,\n                timeout=self._timeout,\n                verify=self._verify_ssl,\n                follow_redirects=self._follow_redirects,\n                **self._httpx_args,\n            )\n        return self._async_client\n\n    async def __aenter__(self) -> \"Client\":\n        \"\"\"Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)\"\"\"\n        await self.get_async_httpx_client().__aenter__()\n        return self\n\n    async def __aexit__(self, *args: Any, **kwargs: Any) -> None:\n        \"\"\"Exit a context manager for underlying httpx.AsyncClient (see httpx docs)\"\"\"\n        await self.get_async_httpx_client().__aexit__(*args, **kwargs)\n\n\n@define\nclass AuthenticatedClient:\n    \"\"\"A Client which has been authenticated for use on secured endpoints\n\n    The following are accepted as keyword arguments and will be used to construct httpx Clients internally:\n\n        ``base_url``: The base URL for the API, all requests are made to a relative path to this URL\n\n        ``cookies``: A dictionary of cookies to be sent with every request\n\n        ``headers``: A dictionary of headers to be sent with every request\n\n        ``timeout``: The maximum amount of a time a request can take. API functions will raise\n        httpx.TimeoutException if this is exceeded.\n\n        ``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,\n        but can be set to False for testing purposes.\n\n        ``follow_redirects``: Whether or not to follow redirects. Default value is False.\n\n        ``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.\n\n\n    Attributes:\n        raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a\n            status code that was not documented in the source OpenAPI document. Can also be provided as a keyword\n            argument to the constructor.\n        token: The token to use for authentication\n        prefix: The prefix to use for the Authorization header\n        auth_header_name: The name of the Authorization header\n    \"\"\"\n\n    raise_on_unexpected_status: bool = field(default=False, kw_only=True)\n    _base_url: str = field(alias=\"base_url\")\n    _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias=\"cookies\")\n    _headers: dict[str, str] = field(factory=dict, kw_only=True, alias=\"headers\")\n    _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias=\"timeout\")\n    _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias=\"verify_ssl\")\n    _follow_redirects: bool = field(default=False, kw_only=True, alias=\"follow_redirects\")\n    _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias=\"httpx_args\")\n    _client: Optional[httpx.Client] = field(default=None, init=False)\n    _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)\n\n    token: str\n    prefix: str = \"Bearer\"\n    auth_header_name: str = \"Authorization\"\n\n    def with_headers(self, headers: dict[str, str]) -> \"AuthenticatedClient\":\n        \"\"\"Get a new client matching this one with additional headers\"\"\"\n        if self._client is not None:\n            self._client.headers.update(headers)\n        if self._async_client is not None:\n            self._async_client.headers.update(headers)\n        return evolve(self, headers={**self._headers, **headers})\n\n    def with_cookies(self, cookies: dict[str, str]) -> \"AuthenticatedClient\":\n        \"\"\"Get a new client matching this one with additional cookies\"\"\"\n        if self._client is not None:\n            self._client.cookies.update(cookies)\n        if self._async_client is not None:\n            self._async_client.cookies.update(cookies)\n        return evolve(self, cookies={**self._cookies, **cookies})\n\n    def with_timeout(self, timeout: httpx.Timeout) -> \"AuthenticatedClient\":\n        \"\"\"Get a new client matching this one with a new timeout (in seconds)\"\"\"\n        if self._client is not None:\n            self._client.timeout = timeout\n        if self._async_client is not None:\n            self._async_client.timeout = timeout\n        return evolve(self, timeout=timeout)\n\n    def set_httpx_client(self, client: httpx.Client) -> \"AuthenticatedClient\":\n        \"\"\"Manually set the underlying httpx.Client\n\n        **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.\n        \"\"\"\n        self._client = client\n        return self\n\n    def get_httpx_client(self) -> httpx.Client:\n        \"\"\"Get the underlying httpx.Client, constructing a new one if not previously set\"\"\"\n        if self._client is None:\n            self._headers[self.auth_header_name] = f\"{self.prefix} {self.token}\" if self.prefix else self.token\n            self._client = httpx.Client(\n                base_url=self._base_url,\n                cookies=self._cookies,\n                headers=self._headers,\n                timeout=self._timeout,\n                verify=self._verify_ssl,\n                follow_redirects=self._follow_redirects,\n                **self._httpx_args,\n            )\n        return self._client\n\n    def __enter__(self) -> \"AuthenticatedClient\":\n        \"\"\"Enter a context manager for self.client—you cannot enter twice (see httpx docs)\"\"\"\n        self.get_httpx_client().__enter__()\n        return self\n\n    def __exit__(self, *args: Any, **kwargs: Any) -> None:\n        \"\"\"Exit a context manager for internal httpx.Client (see httpx docs)\"\"\"\n        self.get_httpx_client().__exit__(*args, **kwargs)\n\n    def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> \"AuthenticatedClient\":\n        \"\"\"Manually the underlying httpx.AsyncClient\n\n        **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.\n        \"\"\"\n        self._async_client = async_client\n        return self\n\n    def get_async_httpx_client(self) -> httpx.AsyncClient:\n        \"\"\"Get the underlying httpx.AsyncClient, constructing a new one if not previously set\"\"\"\n        if self._async_client is None:\n            self._headers[self.auth_header_name] = f\"{self.prefix} {self.token}\" if self.prefix else self.token\n            self._async_client = httpx.AsyncClient(\n                base_url=self._base_url,\n                cookies=self._cookies,\n                headers=self._headers,\n                timeout=self._timeout,\n                verify=self._verify_ssl,\n                follow_redirects=self._follow_redirects,\n                **self._httpx_args,\n            )\n        return self._async_client\n\n    async def __aenter__(self) -> \"AuthenticatedClient\":\n        \"\"\"Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)\"\"\"\n        await self.get_async_httpx_client().__aenter__()\n        return self\n\n    async def __aexit__(self, *args: Any, **kwargs: Any) -> None:\n        \"\"\"Exit a context manager for underlying httpx.AsyncClient (see httpx docs)\"\"\"\n        await self.get_async_httpx_client().__aexit__(*args, **kwargs)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/errors.py",
    "content": "\"\"\"Contains shared errors types that can be raised from API functions\"\"\"\n\n\nclass UnexpectedStatus(Exception):\n    \"\"\"Raised by api functions when the response status an undocumented status and Client.raise_on_unexpected_status is True\"\"\"\n\n    def __init__(self, status_code: int, content: bytes):\n        self.status_code = status_code\n        self.content = content\n\n        super().__init__(\n            f\"Unexpected status code: {status_code}\\n\\nResponse content:\\n{content.decode(errors='ignore')}\"\n        )\n\n\n__all__ = [\"UnexpectedStatus\"]\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/__init__.py",
    "content": "\"\"\"Contains all the data models used in inputs/outputs\"\"\"\n\nfrom .get_v1_desktop_id_response_200 import GetV1DesktopIdResponse200\nfrom .get_v1_desktop_id_response_200_status import GetV1DesktopIdResponse200Status\nfrom .get_v1_desktop_id_response_400 import GetV1DesktopIdResponse400\nfrom .get_v1_desktop_id_response_400_status import GetV1DesktopIdResponse400Status\nfrom .get_v1_desktop_id_response_401 import GetV1DesktopIdResponse401\nfrom .get_v1_desktop_id_response_401_status import GetV1DesktopIdResponse401Status\nfrom .get_v1_desktop_id_response_403 import GetV1DesktopIdResponse403\nfrom .get_v1_desktop_id_response_403_status import GetV1DesktopIdResponse403Status\nfrom .get_v1_desktop_id_response_404 import GetV1DesktopIdResponse404\nfrom .get_v1_desktop_id_response_404_status import GetV1DesktopIdResponse404Status\nfrom .get_v1_desktop_id_response_409 import GetV1DesktopIdResponse409\nfrom .get_v1_desktop_id_response_409_status import GetV1DesktopIdResponse409Status\nfrom .get_v1_desktop_id_response_429 import GetV1DesktopIdResponse429\nfrom .get_v1_desktop_id_response_429_status import GetV1DesktopIdResponse429Status\nfrom .get_v1_desktop_id_response_500 import GetV1DesktopIdResponse500\nfrom .get_v1_desktop_id_response_500_status import GetV1DesktopIdResponse500Status\nfrom .get_v1_desktop_id_response_502 import GetV1DesktopIdResponse502\nfrom .get_v1_desktop_id_response_502_status import GetV1DesktopIdResponse502Status\nfrom .post_v1_desktop_body import PostV1DesktopBody\nfrom .post_v1_desktop_id_bash_action_body import PostV1DesktopIdBashActionBody\nfrom .post_v1_desktop_id_bash_action_response_200 import PostV1DesktopIdBashActionResponse200\nfrom .post_v1_desktop_id_bash_action_response_400 import PostV1DesktopIdBashActionResponse400\nfrom .post_v1_desktop_id_bash_action_response_400_status import PostV1DesktopIdBashActionResponse400Status\nfrom .post_v1_desktop_id_bash_action_response_401 import PostV1DesktopIdBashActionResponse401\nfrom .post_v1_desktop_id_bash_action_response_401_status import PostV1DesktopIdBashActionResponse401Status\nfrom .post_v1_desktop_id_bash_action_response_403 import PostV1DesktopIdBashActionResponse403\nfrom .post_v1_desktop_id_bash_action_response_403_status import PostV1DesktopIdBashActionResponse403Status\nfrom .post_v1_desktop_id_bash_action_response_404 import PostV1DesktopIdBashActionResponse404\nfrom .post_v1_desktop_id_bash_action_response_404_status import PostV1DesktopIdBashActionResponse404Status\nfrom .post_v1_desktop_id_bash_action_response_409 import PostV1DesktopIdBashActionResponse409\nfrom .post_v1_desktop_id_bash_action_response_409_status import PostV1DesktopIdBashActionResponse409Status\nfrom .post_v1_desktop_id_bash_action_response_429 import PostV1DesktopIdBashActionResponse429\nfrom .post_v1_desktop_id_bash_action_response_429_status import PostV1DesktopIdBashActionResponse429Status\nfrom .post_v1_desktop_id_bash_action_response_500 import PostV1DesktopIdBashActionResponse500\nfrom .post_v1_desktop_id_bash_action_response_500_status import PostV1DesktopIdBashActionResponse500Status\nfrom .post_v1_desktop_id_bash_action_response_502 import PostV1DesktopIdBashActionResponse502\nfrom .post_v1_desktop_id_bash_action_response_502_status import PostV1DesktopIdBashActionResponse502Status\nfrom .post_v1_desktop_id_computer_action_click_mouse_action import PostV1DesktopIdComputerActionClickMouseAction\nfrom .post_v1_desktop_id_computer_action_click_mouse_action_button import (\n    PostV1DesktopIdComputerActionClickMouseActionButton,\n)\nfrom .post_v1_desktop_id_computer_action_click_mouse_action_click_type import (\n    PostV1DesktopIdComputerActionClickMouseActionClickType,\n)\nfrom .post_v1_desktop_id_computer_action_click_mouse_action_type import (\n    PostV1DesktopIdComputerActionClickMouseActionType,\n)\nfrom .post_v1_desktop_id_computer_action_drag_mouse_action import PostV1DesktopIdComputerActionDragMouseAction\nfrom .post_v1_desktop_id_computer_action_drag_mouse_action_end import PostV1DesktopIdComputerActionDragMouseActionEnd\nfrom .post_v1_desktop_id_computer_action_drag_mouse_action_start import (\n    PostV1DesktopIdComputerActionDragMouseActionStart,\n)\nfrom .post_v1_desktop_id_computer_action_drag_mouse_action_type import PostV1DesktopIdComputerActionDragMouseActionType\nfrom .post_v1_desktop_id_computer_action_get_cursor_position_action import (\n    PostV1DesktopIdComputerActionGetCursorPositionAction,\n)\nfrom .post_v1_desktop_id_computer_action_get_cursor_position_action_type import (\n    PostV1DesktopIdComputerActionGetCursorPositionActionType,\n)\nfrom .post_v1_desktop_id_computer_action_move_mouse_action import PostV1DesktopIdComputerActionMoveMouseAction\nfrom .post_v1_desktop_id_computer_action_move_mouse_action_type import PostV1DesktopIdComputerActionMoveMouseActionType\nfrom .post_v1_desktop_id_computer_action_press_keys_action import PostV1DesktopIdComputerActionPressKeysAction\nfrom .post_v1_desktop_id_computer_action_press_keys_action_key_action_type import (\n    PostV1DesktopIdComputerActionPressKeysActionKeyActionType,\n)\nfrom .post_v1_desktop_id_computer_action_press_keys_action_type import PostV1DesktopIdComputerActionPressKeysActionType\nfrom .post_v1_desktop_id_computer_action_response_200 import PostV1DesktopIdComputerActionResponse200\nfrom .post_v1_desktop_id_computer_action_response_400 import PostV1DesktopIdComputerActionResponse400\nfrom .post_v1_desktop_id_computer_action_response_400_status import PostV1DesktopIdComputerActionResponse400Status\nfrom .post_v1_desktop_id_computer_action_response_401 import PostV1DesktopIdComputerActionResponse401\nfrom .post_v1_desktop_id_computer_action_response_401_status import PostV1DesktopIdComputerActionResponse401Status\nfrom .post_v1_desktop_id_computer_action_response_403 import PostV1DesktopIdComputerActionResponse403\nfrom .post_v1_desktop_id_computer_action_response_403_status import PostV1DesktopIdComputerActionResponse403Status\nfrom .post_v1_desktop_id_computer_action_response_404 import PostV1DesktopIdComputerActionResponse404\nfrom .post_v1_desktop_id_computer_action_response_404_status import PostV1DesktopIdComputerActionResponse404Status\nfrom .post_v1_desktop_id_computer_action_response_409 import PostV1DesktopIdComputerActionResponse409\nfrom .post_v1_desktop_id_computer_action_response_409_status import PostV1DesktopIdComputerActionResponse409Status\nfrom .post_v1_desktop_id_computer_action_response_429 import PostV1DesktopIdComputerActionResponse429\nfrom .post_v1_desktop_id_computer_action_response_429_status import PostV1DesktopIdComputerActionResponse429Status\nfrom .post_v1_desktop_id_computer_action_response_500 import PostV1DesktopIdComputerActionResponse500\nfrom .post_v1_desktop_id_computer_action_response_500_status import PostV1DesktopIdComputerActionResponse500Status\nfrom .post_v1_desktop_id_computer_action_response_502 import PostV1DesktopIdComputerActionResponse502\nfrom .post_v1_desktop_id_computer_action_response_502_status import PostV1DesktopIdComputerActionResponse502Status\nfrom .post_v1_desktop_id_computer_action_screenshot_action import PostV1DesktopIdComputerActionScreenshotAction\nfrom .post_v1_desktop_id_computer_action_screenshot_action_type import PostV1DesktopIdComputerActionScreenshotActionType\nfrom .post_v1_desktop_id_computer_action_scroll_action import PostV1DesktopIdComputerActionScrollAction\nfrom .post_v1_desktop_id_computer_action_scroll_action_direction import (\n    PostV1DesktopIdComputerActionScrollActionDirection,\n)\nfrom .post_v1_desktop_id_computer_action_scroll_action_type import PostV1DesktopIdComputerActionScrollActionType\nfrom .post_v1_desktop_id_computer_action_type_text_action import PostV1DesktopIdComputerActionTypeTextAction\nfrom .post_v1_desktop_id_computer_action_type_text_action_type import PostV1DesktopIdComputerActionTypeTextActionType\nfrom .post_v1_desktop_id_computer_action_wait_action import PostV1DesktopIdComputerActionWaitAction\nfrom .post_v1_desktop_id_computer_action_wait_action_type import PostV1DesktopIdComputerActionWaitActionType\nfrom .post_v1_desktop_id_stop_response_200 import PostV1DesktopIdStopResponse200\nfrom .post_v1_desktop_id_stop_response_200_status import PostV1DesktopIdStopResponse200Status\nfrom .post_v1_desktop_id_stop_response_400 import PostV1DesktopIdStopResponse400\nfrom .post_v1_desktop_id_stop_response_400_status import PostV1DesktopIdStopResponse400Status\nfrom .post_v1_desktop_id_stop_response_401 import PostV1DesktopIdStopResponse401\nfrom .post_v1_desktop_id_stop_response_401_status import PostV1DesktopIdStopResponse401Status\nfrom .post_v1_desktop_id_stop_response_403 import PostV1DesktopIdStopResponse403\nfrom .post_v1_desktop_id_stop_response_403_status import PostV1DesktopIdStopResponse403Status\nfrom .post_v1_desktop_id_stop_response_404 import PostV1DesktopIdStopResponse404\nfrom .post_v1_desktop_id_stop_response_404_status import PostV1DesktopIdStopResponse404Status\nfrom .post_v1_desktop_id_stop_response_409 import PostV1DesktopIdStopResponse409\nfrom .post_v1_desktop_id_stop_response_409_status import PostV1DesktopIdStopResponse409Status\nfrom .post_v1_desktop_id_stop_response_429 import PostV1DesktopIdStopResponse429\nfrom .post_v1_desktop_id_stop_response_429_status import PostV1DesktopIdStopResponse429Status\nfrom .post_v1_desktop_id_stop_response_500 import PostV1DesktopIdStopResponse500\nfrom .post_v1_desktop_id_stop_response_500_status import PostV1DesktopIdStopResponse500Status\nfrom .post_v1_desktop_id_stop_response_502 import PostV1DesktopIdStopResponse502\nfrom .post_v1_desktop_id_stop_response_502_status import PostV1DesktopIdStopResponse502Status\nfrom .post_v1_desktop_response_200 import PostV1DesktopResponse200\nfrom .post_v1_desktop_response_200_status import PostV1DesktopResponse200Status\nfrom .post_v1_desktop_response_400 import PostV1DesktopResponse400\nfrom .post_v1_desktop_response_400_status import PostV1DesktopResponse400Status\nfrom .post_v1_desktop_response_401 import PostV1DesktopResponse401\nfrom .post_v1_desktop_response_401_status import PostV1DesktopResponse401Status\nfrom .post_v1_desktop_response_403 import PostV1DesktopResponse403\nfrom .post_v1_desktop_response_403_status import PostV1DesktopResponse403Status\nfrom .post_v1_desktop_response_404 import PostV1DesktopResponse404\nfrom .post_v1_desktop_response_404_status import PostV1DesktopResponse404Status\nfrom .post_v1_desktop_response_409 import PostV1DesktopResponse409\nfrom .post_v1_desktop_response_409_status import PostV1DesktopResponse409Status\nfrom .post_v1_desktop_response_429 import PostV1DesktopResponse429\nfrom .post_v1_desktop_response_429_status import PostV1DesktopResponse429Status\nfrom .post_v1_desktop_response_500 import PostV1DesktopResponse500\nfrom .post_v1_desktop_response_500_status import PostV1DesktopResponse500Status\nfrom .post_v1_desktop_response_502 import PostV1DesktopResponse502\nfrom .post_v1_desktop_response_502_status import PostV1DesktopResponse502Status\n\n__all__ = (\n    \"GetV1DesktopIdResponse200\",\n    \"GetV1DesktopIdResponse200Status\",\n    \"GetV1DesktopIdResponse400\",\n    \"GetV1DesktopIdResponse400Status\",\n    \"GetV1DesktopIdResponse401\",\n    \"GetV1DesktopIdResponse401Status\",\n    \"GetV1DesktopIdResponse403\",\n    \"GetV1DesktopIdResponse403Status\",\n    \"GetV1DesktopIdResponse404\",\n    \"GetV1DesktopIdResponse404Status\",\n    \"GetV1DesktopIdResponse409\",\n    \"GetV1DesktopIdResponse409Status\",\n    \"GetV1DesktopIdResponse429\",\n    \"GetV1DesktopIdResponse429Status\",\n    \"GetV1DesktopIdResponse500\",\n    \"GetV1DesktopIdResponse500Status\",\n    \"GetV1DesktopIdResponse502\",\n    \"GetV1DesktopIdResponse502Status\",\n    \"PostV1DesktopBody\",\n    \"PostV1DesktopIdBashActionBody\",\n    \"PostV1DesktopIdBashActionResponse200\",\n    \"PostV1DesktopIdBashActionResponse400\",\n    \"PostV1DesktopIdBashActionResponse400Status\",\n    \"PostV1DesktopIdBashActionResponse401\",\n    \"PostV1DesktopIdBashActionResponse401Status\",\n    \"PostV1DesktopIdBashActionResponse403\",\n    \"PostV1DesktopIdBashActionResponse403Status\",\n    \"PostV1DesktopIdBashActionResponse404\",\n    \"PostV1DesktopIdBashActionResponse404Status\",\n    \"PostV1DesktopIdBashActionResponse409\",\n    \"PostV1DesktopIdBashActionResponse409Status\",\n    \"PostV1DesktopIdBashActionResponse429\",\n    \"PostV1DesktopIdBashActionResponse429Status\",\n    \"PostV1DesktopIdBashActionResponse500\",\n    \"PostV1DesktopIdBashActionResponse500Status\",\n    \"PostV1DesktopIdBashActionResponse502\",\n    \"PostV1DesktopIdBashActionResponse502Status\",\n    \"PostV1DesktopIdComputerActionClickMouseAction\",\n    \"PostV1DesktopIdComputerActionClickMouseActionButton\",\n    \"PostV1DesktopIdComputerActionClickMouseActionClickType\",\n    \"PostV1DesktopIdComputerActionClickMouseActionType\",\n    \"PostV1DesktopIdComputerActionDragMouseAction\",\n    \"PostV1DesktopIdComputerActionDragMouseActionEnd\",\n    \"PostV1DesktopIdComputerActionDragMouseActionStart\",\n    \"PostV1DesktopIdComputerActionDragMouseActionType\",\n    \"PostV1DesktopIdComputerActionGetCursorPositionAction\",\n    \"PostV1DesktopIdComputerActionGetCursorPositionActionType\",\n    \"PostV1DesktopIdComputerActionMoveMouseAction\",\n    \"PostV1DesktopIdComputerActionMoveMouseActionType\",\n    \"PostV1DesktopIdComputerActionPressKeysAction\",\n    \"PostV1DesktopIdComputerActionPressKeysActionKeyActionType\",\n    \"PostV1DesktopIdComputerActionPressKeysActionType\",\n    \"PostV1DesktopIdComputerActionResponse200\",\n    \"PostV1DesktopIdComputerActionResponse400\",\n    \"PostV1DesktopIdComputerActionResponse400Status\",\n    \"PostV1DesktopIdComputerActionResponse401\",\n    \"PostV1DesktopIdComputerActionResponse401Status\",\n    \"PostV1DesktopIdComputerActionResponse403\",\n    \"PostV1DesktopIdComputerActionResponse403Status\",\n    \"PostV1DesktopIdComputerActionResponse404\",\n    \"PostV1DesktopIdComputerActionResponse404Status\",\n    \"PostV1DesktopIdComputerActionResponse409\",\n    \"PostV1DesktopIdComputerActionResponse409Status\",\n    \"PostV1DesktopIdComputerActionResponse429\",\n    \"PostV1DesktopIdComputerActionResponse429Status\",\n    \"PostV1DesktopIdComputerActionResponse500\",\n    \"PostV1DesktopIdComputerActionResponse500Status\",\n    \"PostV1DesktopIdComputerActionResponse502\",\n    \"PostV1DesktopIdComputerActionResponse502Status\",\n    \"PostV1DesktopIdComputerActionScreenshotAction\",\n    \"PostV1DesktopIdComputerActionScreenshotActionType\",\n    \"PostV1DesktopIdComputerActionScrollAction\",\n    \"PostV1DesktopIdComputerActionScrollActionDirection\",\n    \"PostV1DesktopIdComputerActionScrollActionType\",\n    \"PostV1DesktopIdComputerActionTypeTextAction\",\n    \"PostV1DesktopIdComputerActionTypeTextActionType\",\n    \"PostV1DesktopIdComputerActionWaitAction\",\n    \"PostV1DesktopIdComputerActionWaitActionType\",\n    \"PostV1DesktopIdStopResponse200\",\n    \"PostV1DesktopIdStopResponse200Status\",\n    \"PostV1DesktopIdStopResponse400\",\n    \"PostV1DesktopIdStopResponse400Status\",\n    \"PostV1DesktopIdStopResponse401\",\n    \"PostV1DesktopIdStopResponse401Status\",\n    \"PostV1DesktopIdStopResponse403\",\n    \"PostV1DesktopIdStopResponse403Status\",\n    \"PostV1DesktopIdStopResponse404\",\n    \"PostV1DesktopIdStopResponse404Status\",\n    \"PostV1DesktopIdStopResponse409\",\n    \"PostV1DesktopIdStopResponse409Status\",\n    \"PostV1DesktopIdStopResponse429\",\n    \"PostV1DesktopIdStopResponse429Status\",\n    \"PostV1DesktopIdStopResponse500\",\n    \"PostV1DesktopIdStopResponse500Status\",\n    \"PostV1DesktopIdStopResponse502\",\n    \"PostV1DesktopIdStopResponse502Status\",\n    \"PostV1DesktopResponse200\",\n    \"PostV1DesktopResponse200Status\",\n    \"PostV1DesktopResponse400\",\n    \"PostV1DesktopResponse400Status\",\n    \"PostV1DesktopResponse401\",\n    \"PostV1DesktopResponse401Status\",\n    \"PostV1DesktopResponse403\",\n    \"PostV1DesktopResponse403Status\",\n    \"PostV1DesktopResponse404\",\n    \"PostV1DesktopResponse404Status\",\n    \"PostV1DesktopResponse409\",\n    \"PostV1DesktopResponse409Status\",\n    \"PostV1DesktopResponse429\",\n    \"PostV1DesktopResponse429Status\",\n    \"PostV1DesktopResponse500\",\n    \"PostV1DesktopResponse500Status\",\n    \"PostV1DesktopResponse502\",\n    \"PostV1DesktopResponse502Status\",\n)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_200.py",
    "content": "import datetime\nfrom collections.abc import Mapping\nfrom typing import Any, TypeVar, Union, cast\nfrom uuid import UUID\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\nfrom dateutil.parser import isoparse\n\nfrom ..models.get_v1_desktop_id_response_200_status import GetV1DesktopIdResponse200Status\n\nT = TypeVar(\"T\", bound=\"GetV1DesktopIdResponse200\")\n\n\n@_attrs_define\nclass GetV1DesktopIdResponse200:\n    \"\"\"\n    Attributes:\n        id (UUID): Unique identifier for the desktop instance Example: a1b2c3d4-e5f6-7890-1234-567890abcdef.\n        status (GetV1DesktopIdResponse200Status): Current status of the desktop instance Example: running.\n        stream_url (Union[None, str]): URL for the desktop stream (null if the desktop is not running) Example:\n            https://cyberdesk.com/vnc/a1b2c3d4-e5f6-7890-1234-567890abcdef.\n        created_at (datetime.datetime): Timestamp when the instance was created Example: 2023-10-27T10:00:00Z.\n        timeout_at (datetime.datetime): Timestamp when the instance will automatically time out Example:\n            2023-10-28T10:00:00Z.\n    \"\"\"\n\n    id: UUID\n    status: GetV1DesktopIdResponse200Status\n    stream_url: Union[None, str]\n    created_at: datetime.datetime\n    timeout_at: datetime.datetime\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        id = str(self.id)\n\n        status = self.status.value\n\n        stream_url: Union[None, str]\n        stream_url = self.stream_url\n\n        created_at = self.created_at.isoformat()\n\n        timeout_at = self.timeout_at.isoformat()\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"id\": id,\n                \"status\": status,\n                \"stream_url\": stream_url,\n                \"created_at\": created_at,\n                \"timeout_at\": timeout_at,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        id = UUID(d.pop(\"id\"))\n\n        status = GetV1DesktopIdResponse200Status(d.pop(\"status\"))\n\n        def _parse_stream_url(data: object) -> Union[None, str]:\n            if data is None:\n                return data\n            return cast(Union[None, str], data)\n\n        stream_url = _parse_stream_url(d.pop(\"stream_url\"))\n\n        created_at = isoparse(d.pop(\"created_at\"))\n\n        timeout_at = isoparse(d.pop(\"timeout_at\"))\n\n        get_v1_desktop_id_response_200 = cls(\n            id=id,\n            status=status,\n            stream_url=stream_url,\n            created_at=created_at,\n            timeout_at=timeout_at,\n        )\n\n        get_v1_desktop_id_response_200.additional_properties = d\n        return get_v1_desktop_id_response_200\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_200_status.py",
    "content": "from enum import Enum\n\n\nclass GetV1DesktopIdResponse200Status(str, Enum):\n    ERROR = \"error\"\n    PENDING = \"pending\"\n    RUNNING = \"running\"\n    TERMINATED = \"terminated\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_400.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.get_v1_desktop_id_response_400_status import GetV1DesktopIdResponse400Status\n\nT = TypeVar(\"T\", bound=\"GetV1DesktopIdResponse400\")\n\n\n@_attrs_define\nclass GetV1DesktopIdResponse400:\n    \"\"\"\n    Attributes:\n        status (GetV1DesktopIdResponse400Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: GetV1DesktopIdResponse400Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = GetV1DesktopIdResponse400Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        get_v1_desktop_id_response_400 = cls(\n            status=status,\n            error=error,\n        )\n\n        get_v1_desktop_id_response_400.additional_properties = d\n        return get_v1_desktop_id_response_400\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_400_status.py",
    "content": "from enum import Enum\n\n\nclass GetV1DesktopIdResponse400Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_401.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.get_v1_desktop_id_response_401_status import GetV1DesktopIdResponse401Status\n\nT = TypeVar(\"T\", bound=\"GetV1DesktopIdResponse401\")\n\n\n@_attrs_define\nclass GetV1DesktopIdResponse401:\n    \"\"\"\n    Attributes:\n        status (GetV1DesktopIdResponse401Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: GetV1DesktopIdResponse401Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = GetV1DesktopIdResponse401Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        get_v1_desktop_id_response_401 = cls(\n            status=status,\n            error=error,\n        )\n\n        get_v1_desktop_id_response_401.additional_properties = d\n        return get_v1_desktop_id_response_401\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_401_status.py",
    "content": "from enum import Enum\n\n\nclass GetV1DesktopIdResponse401Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_403.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.get_v1_desktop_id_response_403_status import GetV1DesktopIdResponse403Status\n\nT = TypeVar(\"T\", bound=\"GetV1DesktopIdResponse403\")\n\n\n@_attrs_define\nclass GetV1DesktopIdResponse403:\n    \"\"\"\n    Attributes:\n        status (GetV1DesktopIdResponse403Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: GetV1DesktopIdResponse403Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = GetV1DesktopIdResponse403Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        get_v1_desktop_id_response_403 = cls(\n            status=status,\n            error=error,\n        )\n\n        get_v1_desktop_id_response_403.additional_properties = d\n        return get_v1_desktop_id_response_403\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_403_status.py",
    "content": "from enum import Enum\n\n\nclass GetV1DesktopIdResponse403Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_404.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.get_v1_desktop_id_response_404_status import GetV1DesktopIdResponse404Status\n\nT = TypeVar(\"T\", bound=\"GetV1DesktopIdResponse404\")\n\n\n@_attrs_define\nclass GetV1DesktopIdResponse404:\n    \"\"\"\n    Attributes:\n        status (GetV1DesktopIdResponse404Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: GetV1DesktopIdResponse404Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = GetV1DesktopIdResponse404Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        get_v1_desktop_id_response_404 = cls(\n            status=status,\n            error=error,\n        )\n\n        get_v1_desktop_id_response_404.additional_properties = d\n        return get_v1_desktop_id_response_404\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_404_status.py",
    "content": "from enum import Enum\n\n\nclass GetV1DesktopIdResponse404Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_409.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.get_v1_desktop_id_response_409_status import GetV1DesktopIdResponse409Status\n\nT = TypeVar(\"T\", bound=\"GetV1DesktopIdResponse409\")\n\n\n@_attrs_define\nclass GetV1DesktopIdResponse409:\n    \"\"\"\n    Attributes:\n        status (GetV1DesktopIdResponse409Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: GetV1DesktopIdResponse409Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = GetV1DesktopIdResponse409Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        get_v1_desktop_id_response_409 = cls(\n            status=status,\n            error=error,\n        )\n\n        get_v1_desktop_id_response_409.additional_properties = d\n        return get_v1_desktop_id_response_409\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_409_status.py",
    "content": "from enum import Enum\n\n\nclass GetV1DesktopIdResponse409Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_429.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.get_v1_desktop_id_response_429_status import GetV1DesktopIdResponse429Status\n\nT = TypeVar(\"T\", bound=\"GetV1DesktopIdResponse429\")\n\n\n@_attrs_define\nclass GetV1DesktopIdResponse429:\n    \"\"\"\n    Attributes:\n        status (GetV1DesktopIdResponse429Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: GetV1DesktopIdResponse429Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = GetV1DesktopIdResponse429Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        get_v1_desktop_id_response_429 = cls(\n            status=status,\n            error=error,\n        )\n\n        get_v1_desktop_id_response_429.additional_properties = d\n        return get_v1_desktop_id_response_429\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_429_status.py",
    "content": "from enum import Enum\n\n\nclass GetV1DesktopIdResponse429Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_500.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.get_v1_desktop_id_response_500_status import GetV1DesktopIdResponse500Status\n\nT = TypeVar(\"T\", bound=\"GetV1DesktopIdResponse500\")\n\n\n@_attrs_define\nclass GetV1DesktopIdResponse500:\n    \"\"\"\n    Attributes:\n        status (GetV1DesktopIdResponse500Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: GetV1DesktopIdResponse500Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = GetV1DesktopIdResponse500Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        get_v1_desktop_id_response_500 = cls(\n            status=status,\n            error=error,\n        )\n\n        get_v1_desktop_id_response_500.additional_properties = d\n        return get_v1_desktop_id_response_500\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_500_status.py",
    "content": "from enum import Enum\n\n\nclass GetV1DesktopIdResponse500Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_502.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.get_v1_desktop_id_response_502_status import GetV1DesktopIdResponse502Status\n\nT = TypeVar(\"T\", bound=\"GetV1DesktopIdResponse502\")\n\n\n@_attrs_define\nclass GetV1DesktopIdResponse502:\n    \"\"\"\n    Attributes:\n        status (GetV1DesktopIdResponse502Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: GetV1DesktopIdResponse502Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = GetV1DesktopIdResponse502Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        get_v1_desktop_id_response_502 = cls(\n            status=status,\n            error=error,\n        )\n\n        get_v1_desktop_id_response_502.additional_properties = d\n        return get_v1_desktop_id_response_502\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/get_v1_desktop_id_response_502_status.py",
    "content": "from enum import Enum\n\n\nclass GetV1DesktopIdResponse502Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_body.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar, Union\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..types import UNSET, Unset\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopBody\")\n\n\n@_attrs_define\nclass PostV1DesktopBody:\n    \"\"\"\n    Attributes:\n        timeout_ms (Union[Unset, int]): Timeout in milliseconds for the desktop session Example: 3600000.\n    \"\"\"\n\n    timeout_ms: Union[Unset, int] = UNSET\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        timeout_ms = self.timeout_ms\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update({})\n        if timeout_ms is not UNSET:\n            field_dict[\"timeout_ms\"] = timeout_ms\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        timeout_ms = d.pop(\"timeout_ms\", UNSET)\n\n        post_v1_desktop_body = cls(\n            timeout_ms=timeout_ms,\n        )\n\n        post_v1_desktop_body.additional_properties = d\n        return post_v1_desktop_body\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_body.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionBody\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionBody:\n    \"\"\"\n    Attributes:\n        command (str): Bash command to execute Example: echo 'Hello, World!'.\n    \"\"\"\n\n    command: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        command = self.command\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"command\": command,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        command = d.pop(\"command\")\n\n        post_v1_desktop_id_bash_action_body = cls(\n            command=command,\n        )\n\n        post_v1_desktop_id_bash_action_body.additional_properties = d\n        return post_v1_desktop_id_bash_action_body\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_200.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar, Union\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..types import UNSET, Unset\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionResponse200\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionResponse200:\n    \"\"\"\n    Attributes:\n        output (Union[Unset, str]): Raw string output from the executed command (if any) Example: X=500 Y=300.\n        error (Union[Unset, str]): Error message if the operation failed (also indicated by non-2xx HTTP status)\n            Example: Command failed with code 1: xdotool: command not found.\n        base64_image (Union[Unset, str]): Base64 encoded JPEG image data (only returned for screenshot actions) Example:\n            /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQ....\n    \"\"\"\n\n    output: Union[Unset, str] = UNSET\n    error: Union[Unset, str] = UNSET\n    base64_image: Union[Unset, str] = UNSET\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        output = self.output\n\n        error = self.error\n\n        base64_image = self.base64_image\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update({})\n        if output is not UNSET:\n            field_dict[\"output\"] = output\n        if error is not UNSET:\n            field_dict[\"error\"] = error\n        if base64_image is not UNSET:\n            field_dict[\"base64_image\"] = base64_image\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        output = d.pop(\"output\", UNSET)\n\n        error = d.pop(\"error\", UNSET)\n\n        base64_image = d.pop(\"base64_image\", UNSET)\n\n        post_v1_desktop_id_bash_action_response_200 = cls(\n            output=output,\n            error=error,\n            base64_image=base64_image,\n        )\n\n        post_v1_desktop_id_bash_action_response_200.additional_properties = d\n        return post_v1_desktop_id_bash_action_response_200\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_400.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_bash_action_response_400_status import PostV1DesktopIdBashActionResponse400Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionResponse400\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionResponse400:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdBashActionResponse400Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdBashActionResponse400Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdBashActionResponse400Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_bash_action_response_400 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_bash_action_response_400.additional_properties = d\n        return post_v1_desktop_id_bash_action_response_400\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_400_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdBashActionResponse400Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_401.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_bash_action_response_401_status import PostV1DesktopIdBashActionResponse401Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionResponse401\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionResponse401:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdBashActionResponse401Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdBashActionResponse401Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdBashActionResponse401Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_bash_action_response_401 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_bash_action_response_401.additional_properties = d\n        return post_v1_desktop_id_bash_action_response_401\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_401_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdBashActionResponse401Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_403.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_bash_action_response_403_status import PostV1DesktopIdBashActionResponse403Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionResponse403\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionResponse403:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdBashActionResponse403Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdBashActionResponse403Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdBashActionResponse403Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_bash_action_response_403 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_bash_action_response_403.additional_properties = d\n        return post_v1_desktop_id_bash_action_response_403\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_403_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdBashActionResponse403Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_404.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_bash_action_response_404_status import PostV1DesktopIdBashActionResponse404Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionResponse404\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionResponse404:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdBashActionResponse404Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdBashActionResponse404Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdBashActionResponse404Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_bash_action_response_404 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_bash_action_response_404.additional_properties = d\n        return post_v1_desktop_id_bash_action_response_404\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_404_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdBashActionResponse404Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_409.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_bash_action_response_409_status import PostV1DesktopIdBashActionResponse409Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionResponse409\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionResponse409:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdBashActionResponse409Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdBashActionResponse409Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdBashActionResponse409Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_bash_action_response_409 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_bash_action_response_409.additional_properties = d\n        return post_v1_desktop_id_bash_action_response_409\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_409_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdBashActionResponse409Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_429.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_bash_action_response_429_status import PostV1DesktopIdBashActionResponse429Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionResponse429\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionResponse429:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdBashActionResponse429Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdBashActionResponse429Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdBashActionResponse429Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_bash_action_response_429 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_bash_action_response_429.additional_properties = d\n        return post_v1_desktop_id_bash_action_response_429\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_429_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdBashActionResponse429Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_500.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_bash_action_response_500_status import PostV1DesktopIdBashActionResponse500Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionResponse500\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionResponse500:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdBashActionResponse500Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdBashActionResponse500Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdBashActionResponse500Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_bash_action_response_500 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_bash_action_response_500.additional_properties = d\n        return post_v1_desktop_id_bash_action_response_500\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_500_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdBashActionResponse500Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_502.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_bash_action_response_502_status import PostV1DesktopIdBashActionResponse502Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdBashActionResponse502\")\n\n\n@_attrs_define\nclass PostV1DesktopIdBashActionResponse502:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdBashActionResponse502Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdBashActionResponse502Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdBashActionResponse502Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_bash_action_response_502 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_bash_action_response_502.additional_properties = d\n        return post_v1_desktop_id_bash_action_response_502\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_bash_action_response_502_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdBashActionResponse502Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_click_mouse_action.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar, Union\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_click_mouse_action_button import (\n    PostV1DesktopIdComputerActionClickMouseActionButton,\n)\nfrom ..models.post_v1_desktop_id_computer_action_click_mouse_action_click_type import (\n    PostV1DesktopIdComputerActionClickMouseActionClickType,\n)\nfrom ..models.post_v1_desktop_id_computer_action_click_mouse_action_type import (\n    PostV1DesktopIdComputerActionClickMouseActionType,\n)\nfrom ..types import UNSET, Unset\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionClickMouseAction\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionClickMouseAction:\n    \"\"\"\n    Attributes:\n        type_ (PostV1DesktopIdComputerActionClickMouseActionType): Perform a mouse action: click, press (down), or\n            release (up). Defaults to a single left click at the current position. Example: click_mouse.\n        x (Union[Unset, int]): X coordinate for the action (optional, uses current position if omitted) Example: 500.\n        y (Union[Unset, int]): Y coordinate for the action (optional, uses current position if omitted) Example: 300.\n        button (Union[Unset, PostV1DesktopIdComputerActionClickMouseActionButton]): Mouse button to use (optional,\n            defaults to 'left') Example: left.\n        num_of_clicks (Union[Unset, int]): Number of clicks to perform (optional, defaults to 1, only applicable for\n            'click' type) Example: 1.\n        click_type (Union[Unset, PostV1DesktopIdComputerActionClickMouseActionClickType]): Type of mouse action\n            (optional, defaults to 'click') Example: click.\n    \"\"\"\n\n    type_: PostV1DesktopIdComputerActionClickMouseActionType\n    x: Union[Unset, int] = UNSET\n    y: Union[Unset, int] = UNSET\n    button: Union[Unset, PostV1DesktopIdComputerActionClickMouseActionButton] = UNSET\n    num_of_clicks: Union[Unset, int] = UNSET\n    click_type: Union[Unset, PostV1DesktopIdComputerActionClickMouseActionClickType] = UNSET\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        type_ = self.type_.value\n\n        x = self.x\n\n        y = self.y\n\n        button: Union[Unset, str] = UNSET\n        if not isinstance(self.button, Unset):\n            button = self.button.value\n\n        num_of_clicks = self.num_of_clicks\n\n        click_type: Union[Unset, str] = UNSET\n        if not isinstance(self.click_type, Unset):\n            click_type = self.click_type.value\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"type\": type_,\n            }\n        )\n        if x is not UNSET:\n            field_dict[\"x\"] = x\n        if y is not UNSET:\n            field_dict[\"y\"] = y\n        if button is not UNSET:\n            field_dict[\"button\"] = button\n        if num_of_clicks is not UNSET:\n            field_dict[\"num_of_clicks\"] = num_of_clicks\n        if click_type is not UNSET:\n            field_dict[\"click_type\"] = click_type\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        type_ = PostV1DesktopIdComputerActionClickMouseActionType(d.pop(\"type\"))\n\n        x = d.pop(\"x\", UNSET)\n\n        y = d.pop(\"y\", UNSET)\n\n        _button = d.pop(\"button\", UNSET)\n        button: Union[Unset, PostV1DesktopIdComputerActionClickMouseActionButton]\n        if isinstance(_button, Unset):\n            button = UNSET\n        else:\n            button = PostV1DesktopIdComputerActionClickMouseActionButton(_button)\n\n        num_of_clicks = d.pop(\"num_of_clicks\", UNSET)\n\n        _click_type = d.pop(\"click_type\", UNSET)\n        click_type: Union[Unset, PostV1DesktopIdComputerActionClickMouseActionClickType]\n        if isinstance(_click_type, Unset):\n            click_type = UNSET\n        else:\n            click_type = PostV1DesktopIdComputerActionClickMouseActionClickType(_click_type)\n\n        post_v1_desktop_id_computer_action_click_mouse_action = cls(\n            type_=type_,\n            x=x,\n            y=y,\n            button=button,\n            num_of_clicks=num_of_clicks,\n            click_type=click_type,\n        )\n\n        post_v1_desktop_id_computer_action_click_mouse_action.additional_properties = d\n        return post_v1_desktop_id_computer_action_click_mouse_action\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_click_mouse_action_button.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionClickMouseActionButton(str, Enum):\n    LEFT = \"left\"\n    MIDDLE = \"middle\"\n    RIGHT = \"right\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_click_mouse_action_click_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionClickMouseActionClickType(str, Enum):\n    CLICK = \"click\"\n    DOWN = \"down\"\n    UP = \"up\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_click_mouse_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionClickMouseActionType(str, Enum):\n    CLICK_MOUSE = \"click_mouse\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_drag_mouse_action.py",
    "content": "from collections.abc import Mapping\nfrom typing import TYPE_CHECKING, Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_drag_mouse_action_type import (\n    PostV1DesktopIdComputerActionDragMouseActionType,\n)\n\nif TYPE_CHECKING:\n    from ..models.post_v1_desktop_id_computer_action_drag_mouse_action_end import (\n        PostV1DesktopIdComputerActionDragMouseActionEnd,\n    )\n    from ..models.post_v1_desktop_id_computer_action_drag_mouse_action_start import (\n        PostV1DesktopIdComputerActionDragMouseActionStart,\n    )\n\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionDragMouseAction\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionDragMouseAction:\n    \"\"\"\n    Attributes:\n        type_ (PostV1DesktopIdComputerActionDragMouseActionType): Drag the mouse from start to end coordinates Example:\n            drag_mouse.\n        start (PostV1DesktopIdComputerActionDragMouseActionStart): Starting coordinates for the drag operation Example:\n            {'x': 100, 'y': 100}.\n        end (PostV1DesktopIdComputerActionDragMouseActionEnd): Ending coordinates for the drag operation Example: {'x':\n            300, 'y': 300}.\n    \"\"\"\n\n    type_: PostV1DesktopIdComputerActionDragMouseActionType\n    start: \"PostV1DesktopIdComputerActionDragMouseActionStart\"\n    end: \"PostV1DesktopIdComputerActionDragMouseActionEnd\"\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        type_ = self.type_.value\n\n        start = self.start.to_dict()\n\n        end = self.end.to_dict()\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"type\": type_,\n                \"start\": start,\n                \"end\": end,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        from ..models.post_v1_desktop_id_computer_action_drag_mouse_action_end import (\n            PostV1DesktopIdComputerActionDragMouseActionEnd,\n        )\n        from ..models.post_v1_desktop_id_computer_action_drag_mouse_action_start import (\n            PostV1DesktopIdComputerActionDragMouseActionStart,\n        )\n\n        d = dict(src_dict)\n        type_ = PostV1DesktopIdComputerActionDragMouseActionType(d.pop(\"type\"))\n\n        start = PostV1DesktopIdComputerActionDragMouseActionStart.from_dict(d.pop(\"start\"))\n\n        end = PostV1DesktopIdComputerActionDragMouseActionEnd.from_dict(d.pop(\"end\"))\n\n        post_v1_desktop_id_computer_action_drag_mouse_action = cls(\n            type_=type_,\n            start=start,\n            end=end,\n        )\n\n        post_v1_desktop_id_computer_action_drag_mouse_action.additional_properties = d\n        return post_v1_desktop_id_computer_action_drag_mouse_action\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_drag_mouse_action_end.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionDragMouseActionEnd\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionDragMouseActionEnd:\n    \"\"\"Ending coordinates for the drag operation\n\n    Example:\n        {'x': 300, 'y': 300}\n\n    Attributes:\n        x (int): X coordinate on the screen Example: 500.\n        y (int): Y coordinate on the screen Example: 300.\n    \"\"\"\n\n    x: int\n    y: int\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        x = self.x\n\n        y = self.y\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"x\": x,\n                \"y\": y,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        x = d.pop(\"x\")\n\n        y = d.pop(\"y\")\n\n        post_v1_desktop_id_computer_action_drag_mouse_action_end = cls(\n            x=x,\n            y=y,\n        )\n\n        post_v1_desktop_id_computer_action_drag_mouse_action_end.additional_properties = d\n        return post_v1_desktop_id_computer_action_drag_mouse_action_end\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_drag_mouse_action_start.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionDragMouseActionStart\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionDragMouseActionStart:\n    \"\"\"Starting coordinates for the drag operation\n\n    Example:\n        {'x': 100, 'y': 100}\n\n    Attributes:\n        x (int): X coordinate on the screen Example: 500.\n        y (int): Y coordinate on the screen Example: 300.\n    \"\"\"\n\n    x: int\n    y: int\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        x = self.x\n\n        y = self.y\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"x\": x,\n                \"y\": y,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        x = d.pop(\"x\")\n\n        y = d.pop(\"y\")\n\n        post_v1_desktop_id_computer_action_drag_mouse_action_start = cls(\n            x=x,\n            y=y,\n        )\n\n        post_v1_desktop_id_computer_action_drag_mouse_action_start.additional_properties = d\n        return post_v1_desktop_id_computer_action_drag_mouse_action_start\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_drag_mouse_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionDragMouseActionType(str, Enum):\n    DRAG_MOUSE = \"drag_mouse\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_get_cursor_position_action.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_get_cursor_position_action_type import (\n    PostV1DesktopIdComputerActionGetCursorPositionActionType,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionGetCursorPositionAction\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionGetCursorPositionAction:\n    \"\"\"\n    Attributes:\n        type_ (PostV1DesktopIdComputerActionGetCursorPositionActionType): Get the current mouse cursor position Example:\n            get_cursor_position.\n    \"\"\"\n\n    type_: PostV1DesktopIdComputerActionGetCursorPositionActionType\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        type_ = self.type_.value\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"type\": type_,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        type_ = PostV1DesktopIdComputerActionGetCursorPositionActionType(d.pop(\"type\"))\n\n        post_v1_desktop_id_computer_action_get_cursor_position_action = cls(\n            type_=type_,\n        )\n\n        post_v1_desktop_id_computer_action_get_cursor_position_action.additional_properties = d\n        return post_v1_desktop_id_computer_action_get_cursor_position_action\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_get_cursor_position_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionGetCursorPositionActionType(str, Enum):\n    GET_CURSOR_POSITION = \"get_cursor_position\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_move_mouse_action.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_move_mouse_action_type import (\n    PostV1DesktopIdComputerActionMoveMouseActionType,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionMoveMouseAction\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionMoveMouseAction:\n    \"\"\"\n    Attributes:\n        type_ (PostV1DesktopIdComputerActionMoveMouseActionType): Move the mouse cursor to the specified coordinates\n            Example: move_mouse.\n        x (int): X coordinate to move to Example: 500.\n        y (int): Y coordinate to move to Example: 300.\n    \"\"\"\n\n    type_: PostV1DesktopIdComputerActionMoveMouseActionType\n    x: int\n    y: int\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        type_ = self.type_.value\n\n        x = self.x\n\n        y = self.y\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"type\": type_,\n                \"x\": x,\n                \"y\": y,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        type_ = PostV1DesktopIdComputerActionMoveMouseActionType(d.pop(\"type\"))\n\n        x = d.pop(\"x\")\n\n        y = d.pop(\"y\")\n\n        post_v1_desktop_id_computer_action_move_mouse_action = cls(\n            type_=type_,\n            x=x,\n            y=y,\n        )\n\n        post_v1_desktop_id_computer_action_move_mouse_action.additional_properties = d\n        return post_v1_desktop_id_computer_action_move_mouse_action\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_move_mouse_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionMoveMouseActionType(str, Enum):\n    MOVE_MOUSE = \"move_mouse\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_press_keys_action.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar, Union, cast\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_press_keys_action_key_action_type import (\n    PostV1DesktopIdComputerActionPressKeysActionKeyActionType,\n)\nfrom ..models.post_v1_desktop_id_computer_action_press_keys_action_type import (\n    PostV1DesktopIdComputerActionPressKeysActionType,\n)\nfrom ..types import UNSET, Unset\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionPressKeysAction\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionPressKeysAction:\n    \"\"\"\n    Attributes:\n        type_ (PostV1DesktopIdComputerActionPressKeysActionType): Press, hold down, or release one or more keyboard\n            keys. Defaults to a single press and release. Example: press_keys.\n        keys (Union[list[str], str]):\n        key_action_type (Union[Unset, PostV1DesktopIdComputerActionPressKeysActionKeyActionType]): Type of key action\n            (optional, defaults to 'press' which is a down and up action) Example: press.\n    \"\"\"\n\n    type_: PostV1DesktopIdComputerActionPressKeysActionType\n    keys: Union[list[str], str]\n    key_action_type: Union[Unset, PostV1DesktopIdComputerActionPressKeysActionKeyActionType] = UNSET\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        type_ = self.type_.value\n\n        keys: Union[list[str], str]\n        if isinstance(self.keys, list):\n            keys = self.keys\n\n        else:\n            keys = self.keys\n\n        key_action_type: Union[Unset, str] = UNSET\n        if not isinstance(self.key_action_type, Unset):\n            key_action_type = self.key_action_type.value\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"type\": type_,\n                \"keys\": keys,\n            }\n        )\n        if key_action_type is not UNSET:\n            field_dict[\"key_action_type\"] = key_action_type\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        type_ = PostV1DesktopIdComputerActionPressKeysActionType(d.pop(\"type\"))\n\n        def _parse_keys(data: object) -> Union[list[str], str]:\n            try:\n                if not isinstance(data, list):\n                    raise TypeError()\n                keys_type_1 = cast(list[str], data)\n\n                return keys_type_1\n            except:  # noqa: E722\n                pass\n            return cast(Union[list[str], str], data)\n\n        keys = _parse_keys(d.pop(\"keys\"))\n\n        _key_action_type = d.pop(\"key_action_type\", UNSET)\n        key_action_type: Union[Unset, PostV1DesktopIdComputerActionPressKeysActionKeyActionType]\n        if isinstance(_key_action_type, Unset):\n            key_action_type = UNSET\n        else:\n            key_action_type = PostV1DesktopIdComputerActionPressKeysActionKeyActionType(_key_action_type)\n\n        post_v1_desktop_id_computer_action_press_keys_action = cls(\n            type_=type_,\n            keys=keys,\n            key_action_type=key_action_type,\n        )\n\n        post_v1_desktop_id_computer_action_press_keys_action.additional_properties = d\n        return post_v1_desktop_id_computer_action_press_keys_action\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_press_keys_action_key_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionPressKeysActionKeyActionType(str, Enum):\n    DOWN = \"down\"\n    PRESS = \"press\"\n    UP = \"up\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_press_keys_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionPressKeysActionType(str, Enum):\n    PRESS_KEYS = \"press_keys\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_200.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar, Union\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..types import UNSET, Unset\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionResponse200\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionResponse200:\n    \"\"\"\n    Attributes:\n        output (Union[Unset, str]): Raw string output from the executed command (if any) Example: X=500 Y=300.\n        error (Union[Unset, str]): Error message if the operation failed (also indicated by non-2xx HTTP status)\n            Example: Command failed with code 1: xdotool: command not found.\n        base64_image (Union[Unset, str]): Base64 encoded JPEG image data (only returned for screenshot actions) Example:\n            /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQ....\n    \"\"\"\n\n    output: Union[Unset, str] = UNSET\n    error: Union[Unset, str] = UNSET\n    base64_image: Union[Unset, str] = UNSET\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        output = self.output\n\n        error = self.error\n\n        base64_image = self.base64_image\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update({})\n        if output is not UNSET:\n            field_dict[\"output\"] = output\n        if error is not UNSET:\n            field_dict[\"error\"] = error\n        if base64_image is not UNSET:\n            field_dict[\"base64_image\"] = base64_image\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        output = d.pop(\"output\", UNSET)\n\n        error = d.pop(\"error\", UNSET)\n\n        base64_image = d.pop(\"base64_image\", UNSET)\n\n        post_v1_desktop_id_computer_action_response_200 = cls(\n            output=output,\n            error=error,\n            base64_image=base64_image,\n        )\n\n        post_v1_desktop_id_computer_action_response_200.additional_properties = d\n        return post_v1_desktop_id_computer_action_response_200\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_400.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_response_400_status import (\n    PostV1DesktopIdComputerActionResponse400Status,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionResponse400\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionResponse400:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdComputerActionResponse400Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdComputerActionResponse400Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdComputerActionResponse400Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_computer_action_response_400 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_computer_action_response_400.additional_properties = d\n        return post_v1_desktop_id_computer_action_response_400\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_400_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionResponse400Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_401.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_response_401_status import (\n    PostV1DesktopIdComputerActionResponse401Status,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionResponse401\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionResponse401:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdComputerActionResponse401Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdComputerActionResponse401Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdComputerActionResponse401Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_computer_action_response_401 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_computer_action_response_401.additional_properties = d\n        return post_v1_desktop_id_computer_action_response_401\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_401_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionResponse401Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_403.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_response_403_status import (\n    PostV1DesktopIdComputerActionResponse403Status,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionResponse403\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionResponse403:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdComputerActionResponse403Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdComputerActionResponse403Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdComputerActionResponse403Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_computer_action_response_403 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_computer_action_response_403.additional_properties = d\n        return post_v1_desktop_id_computer_action_response_403\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_403_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionResponse403Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_404.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_response_404_status import (\n    PostV1DesktopIdComputerActionResponse404Status,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionResponse404\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionResponse404:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdComputerActionResponse404Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdComputerActionResponse404Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdComputerActionResponse404Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_computer_action_response_404 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_computer_action_response_404.additional_properties = d\n        return post_v1_desktop_id_computer_action_response_404\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_404_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionResponse404Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_409.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_response_409_status import (\n    PostV1DesktopIdComputerActionResponse409Status,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionResponse409\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionResponse409:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdComputerActionResponse409Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdComputerActionResponse409Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdComputerActionResponse409Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_computer_action_response_409 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_computer_action_response_409.additional_properties = d\n        return post_v1_desktop_id_computer_action_response_409\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_409_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionResponse409Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_429.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_response_429_status import (\n    PostV1DesktopIdComputerActionResponse429Status,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionResponse429\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionResponse429:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdComputerActionResponse429Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdComputerActionResponse429Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdComputerActionResponse429Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_computer_action_response_429 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_computer_action_response_429.additional_properties = d\n        return post_v1_desktop_id_computer_action_response_429\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_429_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionResponse429Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_500.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_response_500_status import (\n    PostV1DesktopIdComputerActionResponse500Status,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionResponse500\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionResponse500:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdComputerActionResponse500Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdComputerActionResponse500Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdComputerActionResponse500Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_computer_action_response_500 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_computer_action_response_500.additional_properties = d\n        return post_v1_desktop_id_computer_action_response_500\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_500_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionResponse500Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_502.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_response_502_status import (\n    PostV1DesktopIdComputerActionResponse502Status,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionResponse502\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionResponse502:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdComputerActionResponse502Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdComputerActionResponse502Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdComputerActionResponse502Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_computer_action_response_502 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_computer_action_response_502.additional_properties = d\n        return post_v1_desktop_id_computer_action_response_502\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_response_502_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionResponse502Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_screenshot_action.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_screenshot_action_type import (\n    PostV1DesktopIdComputerActionScreenshotActionType,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionScreenshotAction\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionScreenshotAction:\n    \"\"\"\n    Attributes:\n        type_ (PostV1DesktopIdComputerActionScreenshotActionType): Take a screenshot of the desktop Example: screenshot.\n    \"\"\"\n\n    type_: PostV1DesktopIdComputerActionScreenshotActionType\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        type_ = self.type_.value\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"type\": type_,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        type_ = PostV1DesktopIdComputerActionScreenshotActionType(d.pop(\"type\"))\n\n        post_v1_desktop_id_computer_action_screenshot_action = cls(\n            type_=type_,\n        )\n\n        post_v1_desktop_id_computer_action_screenshot_action.additional_properties = d\n        return post_v1_desktop_id_computer_action_screenshot_action\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_screenshot_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionScreenshotActionType(str, Enum):\n    SCREENSHOT = \"screenshot\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_scroll_action.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_scroll_action_direction import (\n    PostV1DesktopIdComputerActionScrollActionDirection,\n)\nfrom ..models.post_v1_desktop_id_computer_action_scroll_action_type import PostV1DesktopIdComputerActionScrollActionType\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionScrollAction\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionScrollAction:\n    \"\"\"\n    Attributes:\n        type_ (PostV1DesktopIdComputerActionScrollActionType): Scroll the mouse wheel in the specified direction\n            Example: scroll.\n        direction (PostV1DesktopIdComputerActionScrollActionDirection): Direction to scroll Example: down.\n        amount (int): Amount to scroll in pixels Example: 100.\n    \"\"\"\n\n    type_: PostV1DesktopIdComputerActionScrollActionType\n    direction: PostV1DesktopIdComputerActionScrollActionDirection\n    amount: int\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        type_ = self.type_.value\n\n        direction = self.direction.value\n\n        amount = self.amount\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"type\": type_,\n                \"direction\": direction,\n                \"amount\": amount,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        type_ = PostV1DesktopIdComputerActionScrollActionType(d.pop(\"type\"))\n\n        direction = PostV1DesktopIdComputerActionScrollActionDirection(d.pop(\"direction\"))\n\n        amount = d.pop(\"amount\")\n\n        post_v1_desktop_id_computer_action_scroll_action = cls(\n            type_=type_,\n            direction=direction,\n            amount=amount,\n        )\n\n        post_v1_desktop_id_computer_action_scroll_action.additional_properties = d\n        return post_v1_desktop_id_computer_action_scroll_action\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_scroll_action_direction.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionScrollActionDirection(str, Enum):\n    DOWN = \"down\"\n    LEFT = \"left\"\n    RIGHT = \"right\"\n    UP = \"up\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_scroll_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionScrollActionType(str, Enum):\n    SCROLL = \"scroll\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_type_text_action.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_type_text_action_type import (\n    PostV1DesktopIdComputerActionTypeTextActionType,\n)\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionTypeTextAction\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionTypeTextAction:\n    \"\"\"\n    Attributes:\n        type_ (PostV1DesktopIdComputerActionTypeTextActionType): Type text at the current cursor position Example: type.\n        text (str): Text to type Example: Hello, World!.\n    \"\"\"\n\n    type_: PostV1DesktopIdComputerActionTypeTextActionType\n    text: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        type_ = self.type_.value\n\n        text = self.text\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"type\": type_,\n                \"text\": text,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        type_ = PostV1DesktopIdComputerActionTypeTextActionType(d.pop(\"type\"))\n\n        text = d.pop(\"text\")\n\n        post_v1_desktop_id_computer_action_type_text_action = cls(\n            type_=type_,\n            text=text,\n        )\n\n        post_v1_desktop_id_computer_action_type_text_action.additional_properties = d\n        return post_v1_desktop_id_computer_action_type_text_action\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_type_text_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionTypeTextActionType(str, Enum):\n    TYPE = \"type\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_wait_action.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_computer_action_wait_action_type import PostV1DesktopIdComputerActionWaitActionType\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdComputerActionWaitAction\")\n\n\n@_attrs_define\nclass PostV1DesktopIdComputerActionWaitAction:\n    \"\"\"\n    Attributes:\n        type_ (PostV1DesktopIdComputerActionWaitActionType): Wait for the specified number of milliseconds Example:\n            wait.\n        ms (int): Time to wait in milliseconds Example: 1000.\n    \"\"\"\n\n    type_: PostV1DesktopIdComputerActionWaitActionType\n    ms: int\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        type_ = self.type_.value\n\n        ms = self.ms\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"type\": type_,\n                \"ms\": ms,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        type_ = PostV1DesktopIdComputerActionWaitActionType(d.pop(\"type\"))\n\n        ms = d.pop(\"ms\")\n\n        post_v1_desktop_id_computer_action_wait_action = cls(\n            type_=type_,\n            ms=ms,\n        )\n\n        post_v1_desktop_id_computer_action_wait_action.additional_properties = d\n        return post_v1_desktop_id_computer_action_wait_action\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_computer_action_wait_action_type.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdComputerActionWaitActionType(str, Enum):\n    WAIT = \"wait\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_200.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_stop_response_200_status import PostV1DesktopIdStopResponse200Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdStopResponse200\")\n\n\n@_attrs_define\nclass PostV1DesktopIdStopResponse200:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdStopResponse200Status): Status of the desktop instance after stopping Example:\n            terminated.\n    \"\"\"\n\n    status: PostV1DesktopIdStopResponse200Status\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdStopResponse200Status(d.pop(\"status\"))\n\n        post_v1_desktop_id_stop_response_200 = cls(\n            status=status,\n        )\n\n        post_v1_desktop_id_stop_response_200.additional_properties = d\n        return post_v1_desktop_id_stop_response_200\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_200_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdStopResponse200Status(str, Enum):\n    ERROR = \"error\"\n    PENDING = \"pending\"\n    RUNNING = \"running\"\n    TERMINATED = \"terminated\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_400.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_stop_response_400_status import PostV1DesktopIdStopResponse400Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdStopResponse400\")\n\n\n@_attrs_define\nclass PostV1DesktopIdStopResponse400:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdStopResponse400Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdStopResponse400Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdStopResponse400Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_stop_response_400 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_stop_response_400.additional_properties = d\n        return post_v1_desktop_id_stop_response_400\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_400_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdStopResponse400Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_401.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_stop_response_401_status import PostV1DesktopIdStopResponse401Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdStopResponse401\")\n\n\n@_attrs_define\nclass PostV1DesktopIdStopResponse401:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdStopResponse401Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdStopResponse401Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdStopResponse401Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_stop_response_401 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_stop_response_401.additional_properties = d\n        return post_v1_desktop_id_stop_response_401\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_401_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdStopResponse401Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_403.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_stop_response_403_status import PostV1DesktopIdStopResponse403Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdStopResponse403\")\n\n\n@_attrs_define\nclass PostV1DesktopIdStopResponse403:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdStopResponse403Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdStopResponse403Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdStopResponse403Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_stop_response_403 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_stop_response_403.additional_properties = d\n        return post_v1_desktop_id_stop_response_403\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_403_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdStopResponse403Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_404.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_stop_response_404_status import PostV1DesktopIdStopResponse404Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdStopResponse404\")\n\n\n@_attrs_define\nclass PostV1DesktopIdStopResponse404:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdStopResponse404Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdStopResponse404Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdStopResponse404Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_stop_response_404 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_stop_response_404.additional_properties = d\n        return post_v1_desktop_id_stop_response_404\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_404_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdStopResponse404Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_409.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_stop_response_409_status import PostV1DesktopIdStopResponse409Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdStopResponse409\")\n\n\n@_attrs_define\nclass PostV1DesktopIdStopResponse409:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdStopResponse409Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdStopResponse409Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdStopResponse409Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_stop_response_409 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_stop_response_409.additional_properties = d\n        return post_v1_desktop_id_stop_response_409\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_409_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdStopResponse409Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_429.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_stop_response_429_status import PostV1DesktopIdStopResponse429Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdStopResponse429\")\n\n\n@_attrs_define\nclass PostV1DesktopIdStopResponse429:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdStopResponse429Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdStopResponse429Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdStopResponse429Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_stop_response_429 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_stop_response_429.additional_properties = d\n        return post_v1_desktop_id_stop_response_429\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_429_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdStopResponse429Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_500.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_stop_response_500_status import PostV1DesktopIdStopResponse500Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdStopResponse500\")\n\n\n@_attrs_define\nclass PostV1DesktopIdStopResponse500:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdStopResponse500Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdStopResponse500Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdStopResponse500Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_stop_response_500 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_stop_response_500.additional_properties = d\n        return post_v1_desktop_id_stop_response_500\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_500_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdStopResponse500Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_502.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_id_stop_response_502_status import PostV1DesktopIdStopResponse502Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopIdStopResponse502\")\n\n\n@_attrs_define\nclass PostV1DesktopIdStopResponse502:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopIdStopResponse502Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopIdStopResponse502Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopIdStopResponse502Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_id_stop_response_502 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_id_stop_response_502.additional_properties = d\n        return post_v1_desktop_id_stop_response_502\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_id_stop_response_502_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopIdStopResponse502Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_200.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_response_200_status import PostV1DesktopResponse200Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopResponse200\")\n\n\n@_attrs_define\nclass PostV1DesktopResponse200:\n    \"\"\"\n    Attributes:\n        id (str): Unique identifier for the desktop instance Example: desktop_12345.\n        status (PostV1DesktopResponse200Status): Initial status of the desktop instance after creation request Example:\n            pending.\n    \"\"\"\n\n    id: str\n    status: PostV1DesktopResponse200Status\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        id = self.id\n\n        status = self.status.value\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"id\": id,\n                \"status\": status,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        id = d.pop(\"id\")\n\n        status = PostV1DesktopResponse200Status(d.pop(\"status\"))\n\n        post_v1_desktop_response_200 = cls(\n            id=id,\n            status=status,\n        )\n\n        post_v1_desktop_response_200.additional_properties = d\n        return post_v1_desktop_response_200\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_200_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopResponse200Status(str, Enum):\n    ERROR = \"error\"\n    PENDING = \"pending\"\n    RUNNING = \"running\"\n    TERMINATED = \"terminated\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_400.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_response_400_status import PostV1DesktopResponse400Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopResponse400\")\n\n\n@_attrs_define\nclass PostV1DesktopResponse400:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopResponse400Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopResponse400Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopResponse400Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_response_400 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_response_400.additional_properties = d\n        return post_v1_desktop_response_400\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_400_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopResponse400Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_401.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_response_401_status import PostV1DesktopResponse401Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopResponse401\")\n\n\n@_attrs_define\nclass PostV1DesktopResponse401:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopResponse401Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopResponse401Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopResponse401Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_response_401 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_response_401.additional_properties = d\n        return post_v1_desktop_response_401\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_401_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopResponse401Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_403.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_response_403_status import PostV1DesktopResponse403Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopResponse403\")\n\n\n@_attrs_define\nclass PostV1DesktopResponse403:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopResponse403Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopResponse403Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopResponse403Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_response_403 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_response_403.additional_properties = d\n        return post_v1_desktop_response_403\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_403_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopResponse403Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_404.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_response_404_status import PostV1DesktopResponse404Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopResponse404\")\n\n\n@_attrs_define\nclass PostV1DesktopResponse404:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopResponse404Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopResponse404Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopResponse404Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_response_404 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_response_404.additional_properties = d\n        return post_v1_desktop_response_404\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_404_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopResponse404Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_409.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_response_409_status import PostV1DesktopResponse409Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopResponse409\")\n\n\n@_attrs_define\nclass PostV1DesktopResponse409:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopResponse409Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopResponse409Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopResponse409Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_response_409 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_response_409.additional_properties = d\n        return post_v1_desktop_response_409\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_409_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopResponse409Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_429.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_response_429_status import PostV1DesktopResponse429Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopResponse429\")\n\n\n@_attrs_define\nclass PostV1DesktopResponse429:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopResponse429Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopResponse429Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopResponse429Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_response_429 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_response_429.additional_properties = d\n        return post_v1_desktop_response_429\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_429_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopResponse429Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_500.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_response_500_status import PostV1DesktopResponse500Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopResponse500\")\n\n\n@_attrs_define\nclass PostV1DesktopResponse500:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopResponse500Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopResponse500Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopResponse500Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_response_500 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_response_500.additional_properties = d\n        return post_v1_desktop_response_500\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_500_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopResponse500Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_502.py",
    "content": "from collections.abc import Mapping\nfrom typing import Any, TypeVar\n\nfrom attrs import define as _attrs_define\nfrom attrs import field as _attrs_field\n\nfrom ..models.post_v1_desktop_response_502_status import PostV1DesktopResponse502Status\n\nT = TypeVar(\"T\", bound=\"PostV1DesktopResponse502\")\n\n\n@_attrs_define\nclass PostV1DesktopResponse502:\n    \"\"\"\n    Attributes:\n        status (PostV1DesktopResponse502Status):  Example: error.\n        error (str): Error message detailing what went wrong Example: Instance not found or unauthorized.\n    \"\"\"\n\n    status: PostV1DesktopResponse502Status\n    error: str\n    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n\n    def to_dict(self) -> dict[str, Any]:\n        status = self.status.value\n\n        error = self.error\n\n        field_dict: dict[str, Any] = {}\n        field_dict.update(self.additional_properties)\n        field_dict.update(\n            {\n                \"status\": status,\n                \"error\": error,\n            }\n        )\n\n        return field_dict\n\n    @classmethod\n    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:\n        d = dict(src_dict)\n        status = PostV1DesktopResponse502Status(d.pop(\"status\"))\n\n        error = d.pop(\"error\")\n\n        post_v1_desktop_response_502 = cls(\n            status=status,\n            error=error,\n        )\n\n        post_v1_desktop_response_502.additional_properties = d\n        return post_v1_desktop_response_502\n\n    @property\n    def additional_keys(self) -> list[str]:\n        return list(self.additional_properties.keys())\n\n    def __getitem__(self, key: str) -> Any:\n        return self.additional_properties[key]\n\n    def __setitem__(self, key: str, value: Any) -> None:\n        self.additional_properties[key] = value\n\n    def __delitem__(self, key: str) -> None:\n        del self.additional_properties[key]\n\n    def __contains__(self, key: str) -> bool:\n        return key in self.additional_properties\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/models/post_v1_desktop_response_502_status.py",
    "content": "from enum import Enum\n\n\nclass PostV1DesktopResponse502Status(str, Enum):\n    ERROR = \"error\"\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/py.typed",
    "content": "# Marker file for PEP 561"
  },
  {
    "path": "sdks/py-sdk/openapi_client/api_reference_client/types.py",
    "content": "\"\"\"Contains some shared types for properties\"\"\"\n\nfrom collections.abc import MutableMapping\nfrom http import HTTPStatus\nfrom typing import BinaryIO, Generic, Literal, Optional, TypeVar\n\nfrom attrs import define\n\n\nclass Unset:\n    def __bool__(self) -> Literal[False]:\n        return False\n\n\nUNSET: Unset = Unset()\n\nFileJsonType = tuple[Optional[str], BinaryIO, Optional[str]]\n\n\n@define\nclass File:\n    \"\"\"Contains information for file uploads\"\"\"\n\n    payload: BinaryIO\n    file_name: Optional[str] = None\n    mime_type: Optional[str] = None\n\n    def to_tuple(self) -> FileJsonType:\n        \"\"\"Return a tuple representation that httpx will accept for multipart/form-data\"\"\"\n        return self.file_name, self.payload, self.mime_type\n\n\nT = TypeVar(\"T\")\n\n\n@define\nclass Response(Generic[T]):\n    \"\"\"A response from an endpoint\"\"\"\n\n    status_code: HTTPStatus\n    content: bytes\n    headers: MutableMapping[str, str]\n    parsed: Optional[T]\n\n\n__all__ = [\"UNSET\", \"File\", \"FileJsonType\", \"Response\", \"Unset\"]\n"
  },
  {
    "path": "sdks/py-sdk/openapi_client/pyproject.toml",
    "content": "[tool.poetry]\nname = \"api-reference-client\"\nversion = \"1.2.1\"\ndescription = \"A client library for accessing API Reference\"\nauthors = []\nreadme = \"README.md\"\npackages = [\n    {include = \"api_reference_client\"},\n]\ninclude = [\"CHANGELOG.md\", \"api_reference_client/py.typed\"]\n\n\n[tool.poetry.dependencies]\npython = \"^3.9\"\nhttpx = \">=0.20.0,<0.29.0\"\nattrs = \">=22.2.0\"\npython-dateutil = \"^2.8.0\"\n\n[build-system]\nrequires = [\"poetry-core>=1.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\n[tool.ruff]\nline-length = 120\n\n[tool.ruff.lint]\nselect = [\"F\", \"I\", \"UP\"]\n"
  },
  {
    "path": "sdks/py-sdk/pyproject.toml",
    "content": "[project]\nname = \"cyberdesk\"\nversion = \"0.2.7\"\ndescription = \"The official Python SDK for Cyberdesk\"\nauthors = [{name = \"Cyberdesk Team\", email = \"dev@cyberdesk.io\"}]\nreadme = \"README.md\"\nlicense = \"MIT\"\ndependencies = [\n    \"httpx\",\n    \"attrs\"\n]\n\n[build-system]\nrequires = [\"setuptools\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project.optional-dependencies]\ndev = [\n    \"openapi-python-client\",\n    \"build\",\n    \"twine\"\n]\n\n[tool.setuptools.packages.find]\ninclude = [\"cyberdesk*\", \"openapi_client*\"] "
  },
  {
    "path": "sdks/py-sdk/scripts/generate.py",
    "content": "import subprocess\nimport sys\nfrom pathlib import Path\n\n# Path to the OpenAPI spec\nOPENAPI_PATH = Path(__file__).parent.parent.parent / \"openapi.json\"\nOUTPUT_DIR = Path(__file__).parent.parent / \"openapi_client\"\n\ncmd = [\n    sys.executable, \"-m\", \"openapi_python_client\", \"generate\",\n    \"--path\", str(OPENAPI_PATH),\n    \"--output-path\", str(OUTPUT_DIR),\n    \"--overwrite\"\n]\n\nprint(f\"Running: {' '.join(cmd)}\")\nprint(f\"OpenAPI path: {OPENAPI_PATH}\")\nprint(f\"Output dir: {OUTPUT_DIR}\")\nsubprocess.run(cmd, check=True) "
  },
  {
    "path": "sdks/sandbox/py-sdk/.gitignore",
    "content": "config.py "
  },
  {
    "path": "sdks/sandbox/py-sdk/README.md",
    "content": "# Cyberdesk PyPI Package Sandbox Test\n\nThis folder provides a clean environment to test the published `cyberdesk` Python SDK from PyPI. In the future, we'll add proper tests for all of the SDK's, but for now this will have to do in terms of quick manual test scripts.\n\n## Setup Instructions\n\n1. **Create and activate a virtual environment:**\n\n   ```bash\n   python -m venv venv\n   # On Windows:\n   venv\\Scripts\\activate\n   # On Mac/Linux:\n   source venv/bin/activate\n   ```\n\n2. **Install the published package from PyPI:**\n\n   ```bash\n   pip install cyberdesk\n   ```\n\n3. **Set your API key:**\n\n   Edit `config.py` and replace `\"your-api-key-here\"` with your actual Cyberdesk API key.\n\n4. **Run the test script:**\n\n   ```bash\n   python test_sdk.py\n   ```\n\n## What This Does\n- Launches a desktop via the SDK\n- Fetches its details\n- Terminates the desktop\n\nIf you encounter errors, check your API key and ensure the package is published and available on PyPI. "
  },
  {
    "path": "sdks/sandbox/py-sdk/test_sdk.py",
    "content": "# test_sdk.py\nfrom cyberdesk import CyberdeskClient\nfrom cyberdesk.actions import click_mouse, ClickMouseButton, type_text\nfrom config import API_KEY\nimport time\n\ndef main():\n    client = CyberdeskClient(api_key=API_KEY)\n    desktop_id = None\n    try:\n        desktop = client.launch_desktop()\n        print(\"Launched desktop:\", desktop)\n        desktop_id = desktop.id\n        # Wait for desktop to be running\n        print(\"Waiting for desktop to be running...\")\n        while True:\n            details = client.get_desktop(desktop_id)\n            status_str = details.status.value  # status is always an Enum\n            print(f\"Current status: {status_str}\")\n            if status_str == \"running\":\n                break\n            time.sleep(2)\n        print(\"Desktop is running!\")\n        # Perform a click_mouse action\n        action = click_mouse(x=100, y=100, button=ClickMouseButton.RIGHT)\n        action_result = client.execute_computer_action(desktop_id, action)\n        print(\"Click mouse result:\", action_result)\n        # Perform a type_text action\n        action = type_text(text=\"Hello, World!\")\n        action_result = client.execute_computer_action(desktop_id, action)\n        print(\"Type text result:\", action_result)\n    except Exception as e:\n        print(\"Error during SDK usage:\", e)\n    finally:\n        if desktop_id:\n            try:\n                result = client.terminate_desktop(desktop_id)\n                print(\"Terminated desktop:\", result)\n                if result.status == \"terminated\":\n                    print(\"Desktop terminated successfully\")\n                else:\n                    print(\"Desktop termination failed\")\n            except Exception as term_e:\n                print(\"Error during desktop termination:\", term_e)\n\nif __name__ == \"__main__\":\n    main() "
  },
  {
    "path": "sdks/ts-sdk/.gitignore",
    "content": "# Logs\n*.log\n\n# Dependency directories\nnode_modules/\n\n\n# Compiled TypeScript output\ndist/\n\n# Optional: IDE directories\n.vscode/\n.idea/\n\n# Optional: OS generated files\n.DS_Store\nThumbs.db\n\n# Optional: Environment variables file\n.env*\n!/.env.example\n\n# Optional: Test coverage\ncoverage/\n"
  },
  {
    "path": "sdks/ts-sdk/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Cyberdesk\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE. "
  },
  {
    "path": "sdks/ts-sdk/README.md",
    "content": "# cyberdesk\n\n[![npm version](https://badge.fury.io/js/cyberdesk.svg)](https://badge.fury.io/js/cyberdesk)\n\nThe official TypeScript SDK for Cyberdesk.\n\n## Installation\n\n```bash\nnpm install cyberdesk\n# or\nyarn add cyberdesk\n# or\npnpm add cyberdesk\n```\n\n## Usage\n\nFirst, create a Cyberdesk client instance with your API key:\n\n```typescript\nimport { createCyberdeskClient } from 'cyberdesk';\n\nconst cyberdesk = createCyberdeskClient({\n  apiKey: 'YOUR_API_KEY',\n  // Optionally, you can override the baseUrl or provide a custom fetch implementation\n});\n```\n\n### Launch a Desktop\n\n```typescript\nconst launchResult = await cyberdesk.launchDesktop({\n  body: { timeout_ms: 10000 } // Optional: set a timeout for the desktop session\n});\n\nif (launchResult.error) {\n  throw new Error('Failed to launch desktop: ' + launchResult.error.error);\n}\n\nconst desktopId = launchResult.id;\nconsole.log('Launched desktop with ID:', desktopId);\n```\n\n### Get Desktop Info\n\n```typescript\nconst info = await cyberdesk.getDesktop({\n  path: { id: desktopId }\n});\n\nif ('error' in info) {\n  throw new Error('Failed to get desktop info: ' + info.error);\n}\n\nconsole.log('Desktop info:', info);\n```\n\n### Perform a Computer Action (e.g., Mouse Click)\n\n```typescript\nconst actionResult = await cyberdesk.executeComputerAction({\n  path: { id: desktopId },\n  body: {\n    type: 'click_mouse',\n    x: 100,\n    y: 150\n  }\n});\n\nif (actionResult.error) {\n  throw new Error('Action failed: ' + actionResult.error);\n}\n\nconsole.log('Action result:', actionResult);\n```\n\n### Run a Bash Command\n\n```typescript\nconst bashResult = await cyberdesk.executeBashAction({\n  path: { id: desktopId },\n  body: {\n    command: 'echo Hello, world!'\n  }\n});\n\nif (bashResult.error) {\n  throw new Error('Bash command failed: ' + bashResult.error);\n}\n\nconsole.log('Bash output:', bashResult.output);\n```\n\n## TypeScript Support\n\nAll request parameter types and the `CyberdeskSDK` type are exported for convenience:\n\n```typescript\nimport type { LaunchDesktopParams, CyberdeskSDK } from 'cyberdesk';\n```\n\n## License\n\n[MIT](LICENSE) "
  },
  {
    "path": "sdks/ts-sdk/openapi-ts.config.ts",
    "content": "import { defineConfig } from '@hey-api/openapi-ts';\n\nexport default defineConfig({\n  input: '../openapi.json',\n  output: 'src/client',\n  plugins: ['@hey-api/client-fetch'],\n});"
  },
  {
    "path": "sdks/ts-sdk/package.json",
    "content": "{\n  \"name\": \"cyberdesk\",\n  \"version\": \"0.2.1\",\n  \"description\": \"The official TypeScript SDK for Cyberdesk\",\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"LICENSE\",\n    \"README.md\"\n  ],\n  \"scripts\": {\n    \"generate\": \"openapi-ts\",\n    \"build\": \"npm run generate && tsc\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/cyberdesk-hq/cyberdesk.git\"\n  },\n  \"license\": \"MIT\",\n  \"author\": \"Cyberdesk Team <dev@cyberdesk.io>\",\n  \"bugs\": {\n    \"url\": \"https://github.com/cyberdesk-hq/cyberdesk/issues\"\n  },\n  \"homepage\": \"https://github.com/cyberdesk-hq/cyberdesk#readme\",\n  \"dependencies\": {\n    \"@hey-api/client-fetch\": \"^0.10.0\"\n  },\n  \"devDependencies\": {\n    \"@hey-api/openapi-ts\": \"^0.66.6\",\n    \"typescript\": \"^5.0.0\"\n  }\n}\n"
  },
  {
    "path": "sdks/ts-sdk/src/client/client.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { ClientOptions } from './types.gen';\nimport { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from '@hey-api/client-fetch';\n\n/**\n * The `createClientConfig()` function will be called on client initialization\n * and the returned object will become the client's initial configuration.\n *\n * You may want to initialize your client this way instead of calling\n * `setConfig()`. This is useful for example if you're using Next.js\n * to ensure your client always has the correct values.\n */\nexport type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>;\n\nexport const client = createClient(createConfig<ClientOptions>({\n    baseUrl: 'https://api.cyberdesk.io'\n}));"
  },
  {
    "path": "sdks/ts-sdk/src/client/index.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\nexport * from './types.gen';\nexport * from './sdk.gen';"
  },
  {
    "path": "sdks/ts-sdk/src/client/sdk.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch';\nimport type { GetV1DesktopByIdData, GetV1DesktopByIdResponse, GetV1DesktopByIdError, PostV1DesktopData, PostV1DesktopResponse, PostV1DesktopError, PostV1DesktopByIdStopData, PostV1DesktopByIdStopResponse, PostV1DesktopByIdStopError, PostV1DesktopByIdComputerActionData, PostV1DesktopByIdComputerActionResponse, PostV1DesktopByIdComputerActionError, PostV1DesktopByIdBashActionData, PostV1DesktopByIdBashActionResponse, PostV1DesktopByIdBashActionError } from './types.gen';\nimport { client as _heyApiClient } from './client.gen';\n\nexport type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {\n    /**\n     * You can provide a client instance returned by `createClient()` instead of\n     * individual options. This might be also useful if you want to implement a\n     * custom client.\n     */\n    client?: Client;\n    /**\n     * You can pass arbitrary values through the `meta` object. This can be\n     * used to access values that aren't defined as part of the SDK function.\n     */\n    meta?: Record<string, unknown>;\n};\n\n/**\n * Get details of a specific desktop instance\n * Returns the ID, status, creation timestamp, and timeout timestamp for a given desktop instance.\n */\nexport const getV1DesktopById = <ThrowOnError extends boolean = false>(options: Options<GetV1DesktopByIdData, ThrowOnError>) => {\n    return (options.client ?? _heyApiClient).get<GetV1DesktopByIdResponse, GetV1DesktopByIdError, ThrowOnError>({\n        url: '/v1/desktop/{id}',\n        ...options\n    });\n};\n\n/**\n * Create a new virtual desktop instance\n * Creates a new virtual desktop instance and returns its ID and stream URL\n */\nexport const postV1Desktop = <ThrowOnError extends boolean = false>(options: Options<PostV1DesktopData, ThrowOnError>) => {\n    return (options.client ?? _heyApiClient).post<PostV1DesktopResponse, PostV1DesktopError, ThrowOnError>({\n        url: '/v1/desktop',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options?.headers\n        }\n    });\n};\n\n/**\n * Stop a running desktop instance\n * Stops a running desktop instance and cleans up resources\n */\nexport const postV1DesktopByIdStop = <ThrowOnError extends boolean = false>(options: Options<PostV1DesktopByIdStopData, ThrowOnError>) => {\n    return (options.client ?? _heyApiClient).post<PostV1DesktopByIdStopResponse, PostV1DesktopByIdStopError, ThrowOnError>({\n        url: '/v1/desktop/{id}/stop',\n        ...options\n    });\n};\n\n/**\n * Perform an action on the desktop\n * Executes a computer action such as mouse clicks, keyboard input, or screenshots on the desktop\n */\nexport const postV1DesktopByIdComputerAction = <ThrowOnError extends boolean = false>(options: Options<PostV1DesktopByIdComputerActionData, ThrowOnError>) => {\n    return (options.client ?? _heyApiClient).post<PostV1DesktopByIdComputerActionResponse, PostV1DesktopByIdComputerActionError, ThrowOnError>({\n        url: '/v1/desktop/{id}/computer-action',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options?.headers\n        }\n    });\n};\n\n/**\n * Execute a bash command on the desktop\n * Runs a bash command on the desktop and returns the command output\n */\nexport const postV1DesktopByIdBashAction = <ThrowOnError extends boolean = false>(options: Options<PostV1DesktopByIdBashActionData, ThrowOnError>) => {\n    return (options.client ?? _heyApiClient).post<PostV1DesktopByIdBashActionResponse, PostV1DesktopByIdBashActionError, ThrowOnError>({\n        url: '/v1/desktop/{id}/bash-action',\n        ...options,\n        headers: {\n            'Content-Type': 'application/json',\n            ...options?.headers\n        }\n    });\n};"
  },
  {
    "path": "sdks/ts-sdk/src/client/types.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nexport type GetV1DesktopByIdData = {\n    body?: never;\n    headers: {\n        /**\n         * API key for authentication\n         */\n        'x-api-key': string;\n    };\n    path: {\n        /**\n         * The UUID of the desktop instance to retrieve\n         */\n        id: string;\n    };\n    query?: never;\n    url: '/v1/desktop/{id}';\n};\n\nexport type GetV1DesktopByIdErrors = {\n    /**\n     * The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\n     */\n    400: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response.\n     */\n    401: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\n     */\n    403: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\n     */\n    404: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * This response is sent when a request conflicts with the current state of the server.\n     */\n    409: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The user has sent too many requests in a given amount of time (\"rate limiting\")\n     */\n    429: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server has encountered a situation it does not know how to handle.\n     */\n    500: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\n     */\n    502: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n};\n\nexport type GetV1DesktopByIdError = GetV1DesktopByIdErrors[keyof GetV1DesktopByIdErrors];\n\nexport type GetV1DesktopByIdResponses = {\n    /**\n     * Desktop instance details retrieved successfully\n     */\n    200: {\n        /**\n         * Unique identifier for the desktop instance\n         */\n        id: string;\n        /**\n         * Current status of the desktop instance\n         */\n        status: 'pending' | 'running' | 'terminated' | 'error';\n        /**\n         * URL for the desktop stream (null if the desktop is not running)\n         */\n        stream_url: string;\n        /**\n         * Timestamp when the instance was created\n         */\n        created_at: string;\n        /**\n         * Timestamp when the instance will automatically time out\n         */\n        timeout_at: string;\n    };\n};\n\nexport type GetV1DesktopByIdResponse = GetV1DesktopByIdResponses[keyof GetV1DesktopByIdResponses];\n\nexport type PostV1DesktopData = {\n    body?: {\n        /**\n         * Timeout in milliseconds for the desktop session\n         */\n        timeout_ms?: number;\n    };\n    headers: {\n        /**\n         * API key for authentication\n         */\n        'x-api-key': string;\n    };\n    path?: never;\n    query?: never;\n    url: '/v1/desktop';\n};\n\nexport type PostV1DesktopErrors = {\n    /**\n     * The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\n     */\n    400: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response.\n     */\n    401: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\n     */\n    403: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\n     */\n    404: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * This response is sent when a request conflicts with the current state of the server.\n     */\n    409: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The user has sent too many requests in a given amount of time (\"rate limiting\")\n     */\n    429: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server has encountered a situation it does not know how to handle.\n     */\n    500: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\n     */\n    502: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n};\n\nexport type PostV1DesktopError = PostV1DesktopErrors[keyof PostV1DesktopErrors];\n\nexport type PostV1DesktopResponses = {\n    /**\n     * Desktop creation initiated successfully\n     */\n    200: {\n        /**\n         * Unique identifier for the desktop instance\n         */\n        id: string;\n        /**\n         * Initial status of the desktop instance after creation request\n         */\n        status: 'pending' | 'running' | 'terminated' | 'error';\n    };\n};\n\nexport type PostV1DesktopResponse = PostV1DesktopResponses[keyof PostV1DesktopResponses];\n\nexport type PostV1DesktopByIdStopData = {\n    body?: never;\n    headers: {\n        /**\n         * API key for authentication\n         */\n        'x-api-key': string;\n    };\n    path: {\n        /**\n         * Desktop instance ID to stop\n         */\n        id: string;\n    };\n    query?: never;\n    url: '/v1/desktop/{id}/stop';\n};\n\nexport type PostV1DesktopByIdStopErrors = {\n    /**\n     * The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\n     */\n    400: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response.\n     */\n    401: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\n     */\n    403: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\n     */\n    404: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * This response is sent when a request conflicts with the current state of the server.\n     */\n    409: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The user has sent too many requests in a given amount of time (\"rate limiting\")\n     */\n    429: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server has encountered a situation it does not know how to handle.\n     */\n    500: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\n     */\n    502: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n};\n\nexport type PostV1DesktopByIdStopError = PostV1DesktopByIdStopErrors[keyof PostV1DesktopByIdStopErrors];\n\nexport type PostV1DesktopByIdStopResponses = {\n    /**\n     * Desktop stopped successfully\n     */\n    200: {\n        /**\n         * Status of the desktop instance after stopping\n         */\n        status: 'pending' | 'running' | 'terminated' | 'error';\n    };\n};\n\nexport type PostV1DesktopByIdStopResponse = PostV1DesktopByIdStopResponses[keyof PostV1DesktopByIdStopResponses];\n\nexport type PostV1DesktopByIdComputerActionData = {\n    body?: {\n        /**\n         * Perform a mouse action: click, press (down), or release (up). Defaults to a single left click at the current position.\n         */\n        type: 'click_mouse';\n        /**\n         * X coordinate for the action (optional, uses current position if omitted)\n         */\n        x?: number;\n        /**\n         * Y coordinate for the action (optional, uses current position if omitted)\n         */\n        y?: number;\n        /**\n         * Mouse button to use (optional, defaults to 'left')\n         */\n        button?: 'left' | 'right' | 'middle';\n        /**\n         * Number of clicks to perform (optional, defaults to 1, only applicable for 'click' type)\n         */\n        num_of_clicks?: number;\n        /**\n         * Type of mouse action (optional, defaults to 'click')\n         */\n        click_type?: 'click' | 'down' | 'up';\n    } | {\n        /**\n         * Scroll the mouse wheel in the specified direction\n         */\n        type: 'scroll';\n        /**\n         * Direction to scroll\n         */\n        direction: 'up' | 'down' | 'left' | 'right';\n        /**\n         * Amount to scroll in pixels\n         */\n        amount: number;\n    } | {\n        /**\n         * Move the mouse cursor to the specified coordinates\n         */\n        type: 'move_mouse';\n        /**\n         * X coordinate to move to\n         */\n        x: number;\n        /**\n         * Y coordinate to move to\n         */\n        y: number;\n    } | {\n        /**\n         * Drag the mouse from start to end coordinates\n         */\n        type: 'drag_mouse';\n        /**\n         * Starting coordinates for the drag operation\n         */\n        start: {\n            /**\n             * X coordinate on the screen\n             */\n            x: number;\n            /**\n             * Y coordinate on the screen\n             */\n            y: number;\n        };\n        /**\n         * Ending coordinates for the drag operation\n         */\n        end: {\n            /**\n             * X coordinate on the screen\n             */\n            x: number;\n            /**\n             * Y coordinate on the screen\n             */\n            y: number;\n        };\n    } | {\n        /**\n         * Type text at the current cursor position\n         */\n        type: 'type';\n        /**\n         * Text to type\n         */\n        text: string;\n    } | {\n        /**\n         * Press, hold down, or release one or more keyboard keys. Defaults to a single press and release.\n         */\n        type: 'press_keys';\n        keys: string | Array<string>;\n        /**\n         * Type of key action (optional, defaults to 'press' which is a down and up action)\n         */\n        key_action_type?: 'press' | 'down' | 'up';\n    } | {\n        /**\n         * Wait for the specified number of milliseconds\n         */\n        type: 'wait';\n        /**\n         * Time to wait in milliseconds\n         */\n        ms: number;\n    } | {\n        /**\n         * Take a screenshot of the desktop\n         */\n        type: 'screenshot';\n    } | {\n        /**\n         * Get the current mouse cursor position\n         */\n        type: 'get_cursor_position';\n    };\n    headers: {\n        /**\n         * API key for authentication\n         */\n        'x-api-key': string;\n    };\n    path: {\n        /**\n         * Desktop instance ID to perform the action on\n         */\n        id: string;\n    };\n    query?: never;\n    url: '/v1/desktop/{id}/computer-action';\n};\n\nexport type PostV1DesktopByIdComputerActionErrors = {\n    /**\n     * The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\n     */\n    400: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response.\n     */\n    401: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\n     */\n    403: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\n     */\n    404: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * This response is sent when a request conflicts with the current state of the server.\n     */\n    409: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The user has sent too many requests in a given amount of time (\"rate limiting\")\n     */\n    429: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server has encountered a situation it does not know how to handle.\n     */\n    500: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\n     */\n    502: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n};\n\nexport type PostV1DesktopByIdComputerActionError = PostV1DesktopByIdComputerActionErrors[keyof PostV1DesktopByIdComputerActionErrors];\n\nexport type PostV1DesktopByIdComputerActionResponses = {\n    /**\n     * Action executed successfully. Response may contain output or image data depending on the action.\n     */\n    200: {\n        /**\n         * Raw string output from the executed command (if any)\n         */\n        output?: string;\n        /**\n         * Error message if the operation failed (also indicated by non-2xx HTTP status)\n         */\n        error?: string;\n        /**\n         * Base64 encoded JPEG image data (only returned for screenshot actions)\n         */\n        base64_image?: string;\n    };\n};\n\nexport type PostV1DesktopByIdComputerActionResponse = PostV1DesktopByIdComputerActionResponses[keyof PostV1DesktopByIdComputerActionResponses];\n\nexport type PostV1DesktopByIdBashActionData = {\n    body?: {\n        /**\n         * Bash command to execute\n         */\n        command: string;\n    };\n    headers: {\n        /**\n         * API key for authentication\n         */\n        'x-api-key': string;\n    };\n    path: {\n        /**\n         * Desktop instance ID to run the command on\n         */\n        id: string;\n    };\n    query?: never;\n    url: '/v1/desktop/{id}/bash-action';\n};\n\nexport type PostV1DesktopByIdBashActionErrors = {\n    /**\n     * The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).\n     */\n    400: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * Although the HTTP standard specifies \"unauthorized\", semantically this response means \"unauthenticated\". That is, the client must authenticate itself to get the requested response.\n     */\n    401: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server.\n     */\n    403: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.\n     */\n    404: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * This response is sent when a request conflicts with the current state of the server.\n     */\n    409: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The user has sent too many requests in a given amount of time (\"rate limiting\")\n     */\n    429: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server has encountered a situation it does not know how to handle.\n     */\n    500: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n    /**\n     * The server, while acting as a gateway or proxy, received an invalid response from the upstream server.\n     */\n    502: {\n        status: 'error';\n        /**\n         * Error message detailing what went wrong\n         */\n        error: string;\n    };\n};\n\nexport type PostV1DesktopByIdBashActionError = PostV1DesktopByIdBashActionErrors[keyof PostV1DesktopByIdBashActionErrors];\n\nexport type PostV1DesktopByIdBashActionResponses = {\n    /**\n     * Command executed successfully. Response contains command output.\n     */\n    200: {\n        /**\n         * Raw string output from the executed command (if any)\n         */\n        output?: string;\n        /**\n         * Error message if the operation failed (also indicated by non-2xx HTTP status)\n         */\n        error?: string;\n        /**\n         * Base64 encoded JPEG image data (only returned for screenshot actions)\n         */\n        base64_image?: string;\n    };\n};\n\nexport type PostV1DesktopByIdBashActionResponse = PostV1DesktopByIdBashActionResponses[keyof PostV1DesktopByIdBashActionResponses];\n\nexport type ClientOptions = {\n    baseUrl: 'https://api.cyberdesk.io' | (string & {});\n};"
  },
  {
    "path": "sdks/ts-sdk/src/index.ts",
    "content": "/// <reference lib=\"dom\" />\n\nimport { createClient, type Client, type Options as ClientOptions } from '@hey-api/client-fetch';\nimport * as apiMethods from './client/sdk.gen';\nimport { \n    GetV1DesktopByIdData,\n    PostV1DesktopData,\n    PostV1DesktopByIdStopData,\n    PostV1DesktopByIdComputerActionData,\n    PostV1DesktopByIdBashActionData\n} from './client/types.gen';\n\nconst DEFAULT_BASE_URL = 'https://api.cyberdesk.io';\n\ntype FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * Configuration options for the Cyberdesk SDK client.\n */\nexport interface CyberdeskClientOptions {\n    /** Your Cyberdesk API Key */\n    apiKey: string;\n    /** Optional: Override the base URL for the API. Defaults to Cyberdesk production API. */\n    baseUrl?: string;\n    /** Optional: Provide a custom fetch implementation. */\n    fetch?: FetchFn;\n    /** Optional: Provide additional default options for the underlying client (e.g., timeout, keepalive). */\n    // Using Partial<ClientOptions> allows any subset of options but is less strict than Omit.\n    clientOptions?: Partial<ClientOptions>;\n}\n\n// Named parameter types for SDK methods\nexport type GetDesktopParams = Omit<GetV1DesktopByIdData, 'headers' | 'url'>;\nexport type LaunchDesktopParams = Omit<PostV1DesktopData, 'headers' | 'url'>;\nexport type TerminateDesktopParams = Omit<PostV1DesktopByIdStopData, 'headers' | 'url'>;\nexport type ExecuteComputerActionParams = Omit<PostV1DesktopByIdComputerActionData, 'headers' | 'url'>;\nexport type ExecuteBashActionParams = Omit<PostV1DesktopByIdBashActionData, 'headers' | 'url'>;\n\nexport type CyberdeskSDK = {\n    getDesktop: (opts: GetDesktopParams) => ReturnType<typeof apiMethods.getV1DesktopById>;\n    launchDesktop: (opts: LaunchDesktopParams) => ReturnType<typeof apiMethods.postV1Desktop>;\n    terminateDesktop: (opts: TerminateDesktopParams) => ReturnType<typeof apiMethods.postV1DesktopByIdStop>;\n    executeComputerAction: (opts: ExecuteComputerActionParams) => ReturnType<typeof apiMethods.postV1DesktopByIdComputerAction>;\n    executeBashAction: (opts: ExecuteBashActionParams) => ReturnType<typeof apiMethods.postV1DesktopByIdBashAction>;\n};\n\n/**\n * Creates a Cyberdesk SDK instance configured with your API key.\n *\n * @param options - Configuration options including the API key.\n * @returns An SDK instance with methods ready to be called.\n */\n\nexport function createCyberdeskClient(options: CyberdeskClientOptions): CyberdeskSDK {\n    const { apiKey, baseUrl = DEFAULT_BASE_URL, fetch: customFetch, clientOptions = {} } = options;\n\n    if (!apiKey) {\n        throw new Error('Cyberdesk SDK requires an `apiKey` to be provided.');\n    }\n\n    const finalBaseUrl: string | undefined = baseUrl;\n\n    const mergedHeaders = {\n        'x-api-key': apiKey,\n        'Content-Type': 'application/json',\n        ...(clientOptions.headers || {}),\n    };\n\n    const finalClientOptions = {\n        baseUrl: finalBaseUrl,\n        headers: mergedHeaders,\n        ...(customFetch && { fetch: customFetch })\n    };\n\n    const configuredClient: Client = createClient(finalClientOptions);\n\n    // Return an object where each method is pre-configured with the client instance\n    return {\n        getDesktop: (opts) => apiMethods.getV1DesktopById({\n            ...(opts as GetV1DesktopByIdData), // Cast opts to allow potential headers\n            client: configuredClient,\n            headers: { ...mergedHeaders, ...(opts as GetV1DesktopByIdData).headers }\n        }),\n        launchDesktop: (opts) => apiMethods.postV1Desktop({\n            ...(opts as PostV1DesktopData),\n            client: configuredClient,\n            headers: { ...mergedHeaders, ...(opts as PostV1DesktopData).headers }\n        }),\n        terminateDesktop: (opts) => apiMethods.postV1DesktopByIdStop({\n            ...(opts as PostV1DesktopByIdStopData),\n            path: { ...(opts as PostV1DesktopByIdStopData).path, id: (opts as PostV1DesktopByIdStopData).path.id },\n            client: configuredClient,\n            headers: { ...mergedHeaders, ...(opts as PostV1DesktopByIdStopData).headers }\n        }),\n        executeComputerAction: (opts) => apiMethods.postV1DesktopByIdComputerAction({\n            ...(opts as PostV1DesktopByIdComputerActionData),\n            client: configuredClient,\n            headers: { ...mergedHeaders, ...(opts as PostV1DesktopByIdComputerActionData).headers }\n        }),\n        executeBashAction: (opts) => apiMethods.postV1DesktopByIdBashAction({\n            ...(opts as PostV1DesktopByIdBashActionData),\n            client: configuredClient,\n            headers: { ...mergedHeaders, ...(opts as PostV1DesktopByIdBashActionData).headers }\n        }),\n    };\n}"
  },
  {
    "path": "sdks/ts-sdk/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    /* Base Options: */\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"target\": \"ES2016\", // Or a later version like ES2020, ESNext\n    \"allowJs\": true,\n    \"resolveJsonModule\": true,\n    \"moduleDetection\": \"force\",\n    \"isolatedModules\": true,\n\n    /* Strictness */\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noImplicitAny\": true,\n\n    /* If NOT transpiling code: */\n    // These lines were removed as we need to emit JS for the SDK build.\n\n    /* If your code runs in the DOM: */\n    // \"lib\": [\"es2022\", \"dom\", \"dom.iterable\"],\n\n    /* If your code DOES NOT run in the DOM: */\n    \"lib\": [\"ES2022\"], // Adjust target lib as needed (e.g., ES2016)\n\n    /* Publishing Options */\n    \"declaration\": true, // Generate .d.ts files\n    \"outDir\": \"./dist\", // Output directory for compiled files\n    \"rootDir\": \"./src\", // Root directory of source files\n\n    /* Module Resolution */\n    \"module\": \"CommonJS\", // Or ESNext, NodeNext depending on target environment\n    \"moduleResolution\": \"node\" // How modules get resolved\n  },\n  \"include\": [\"src/**/*\"], // Which files to include in compilation\n  \"exclude\": [\"node_modules\", \"dist\", \"**/*.test.ts\"] // Which files to exclude\n} "
  },
  {
    "path": "self-host.md",
    "content": "# Welcome to Self-Hosting Cyberdesk!\n\nWe're excited that you're interested in self-hosting Cyberdesk.\n\n- For a detailed, step-by-step guide on installing Cyberdesk's infrastructure components onto an AKS cluster, please see our [deployment guide](infra/README.md).\n- We're still fleshing out additional self-hosting steps and documentation. If you'd like personalized help, please [book a call with us here](https://cal.com/aland) and we'll personally guide you through every step of the self-hosting process.\n\nThank you for being an early adopter!\n"
  },
  {
    "path": "services/cyberdesk-operator/.gitignore",
    "content": "# Python\n__pycache__/\n*.py[cod]\n*$py.class\n*.so\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# Virtual environments\n.env\n.venv\nvenv/\nENV/\nenv/\n\n# IDE\n.idea/\n.vscode/\n*.swp\n*.swo\n\n# Logs\nlogs/\n*.log\n\n# Default config files (should not be committed)\ntests/test-user-data.yaml\ntests/test-secret.yaml\n\n# Local Kubernetes configuration\n.kube/\nkubeconfig\n\n# MacOS\n.DS_Store "
  },
  {
    "path": "services/cyberdesk-operator/Dockerfile",
    "content": "FROM python:3.12\n\nWORKDIR /app\n\n# Copy requirements first for layer caching\nCOPY requirements.txt /app/\nRUN pip install --no-cache-dir -r requirements.txt\n\n# Copy the application code\nCOPY handlers/controller.py /app/\n\nCMD kopf run /app/controller.py --liveness=http://0.0.0.0:8080/healthz --all-namespaces"
  },
  {
    "path": "services/cyberdesk-operator/README.md",
    "content": "Run locally:\n- Ensure you are on the developer cluster\n- Scale down the cluster operator deployment (notify team): kubectl scale deployment cyberdesk-operator --replicas=0 -n cyberdesk-system\n- docker build -t cyberdesk/cyberdesk-operator:local .\n- docker run --rm -it -v \"${env:USERPROFILE}\\.kube:/root/.kube:ro\" --env-file ./.env cyberdesk/cyberdesk-operator:local"
  },
  {
    "path": "services/cyberdesk-operator/checklist.md",
    "content": "# Cyberdesk Operator Implementation Checklist\n\n## Overview\n\nThe Cyberdesk Operator is a Kubernetes Custom Resource Operator that monitors and manages Cyberdesk resources in a Kubernetes cluster. It's responsible for automatically provisioning and lifecycle management of KubeVirt VirtualMachine resources based on Cyberdesk Custom Resource definitions. The operator handles VM creation, status tracking, timeout enforcement, and cleanup of resources, providing an abstraction layer that simplifies VM provisioning for users while ensuring proper resource management within the cluster.\n\n## Project Setup\n\n- [x] Create basic project structure\n- [x] Set up Python virtual environment\n- [x] Create requirements.txt with kopf and kubernetes dependencies\n- [x] Create a Dockerfile for the operator\n- [x] Create a deployment manifest for Kubernetes\n\n## Core Operator Logic\n\n- [x] Create handlers/controller.py for main operator logic\n- [x] Implement Kopf watches for Cyberdesk creation\n- [x] Implement Kopf watches for VirtualMachineInstance updates\n- [x] Implement VM creation logic when a new Cyberdesk is created\n- [x] Implement listener for VMI status updates, to perform various actions\n- [x] Implement Kopf watches for Cyberdesk deletion, triggering the deletion of the underlaying VirtualMachineInstance\n- [x] Implement timeout mechanism based on timeoutMs, shutting down the underlaying VirtualMachineInstance if it's running longer than the timeout\n- [x] Ensure event handler idempotency for all handlers by checking the current resource state (using resourceVersion or status fields) to guarantee that duplicate events don't trigger duplicate operations.\n\n## Leader Election and High Availability\n- [x] Configure leader election in your operator to designate one active replica for processing state-changing events, while others are available for failover.\n- [x] Document the expected behavior and failover process to ensure consistent operation in a multi-replica environment.\n\n## Secrets \n- [x] In production, create Kubernetes Secret resources and inject them into your Pods via environment variables or volumes.\n- [x] Use python-dotenv only for local development; in production rely entirely on Kubernetes-managed secrets.\n\n## Error Handling and Lifecycle Management\n\n- [x] Ensure proper error handling and retries\n- [x] Add graceful cleanup logic for terminated instances\n- [x] Implement VM deletion on Cyberdesk deletion\n- [x] Add finalizers to ensure clean resource deletion\n\n## Documentation\n\n- [x] Add README.md with operator overview and K8s installation instructions.\n- [x] Document troubleshooting steps, including:\n  - [x] How idempotency is ensured\n  - [x] How leader election is configured and behaves\n  - [x] How secrets are managed in different environments\n\n## CI/CD Pipeline\n\n- [x] Add workflow for building and pushing Docker image\n\n## Security\n\n- [x] Ensure secure container settings (non-root user, readonly filesystem where possible)\n- [x] Review RBAC policies for accessing secrets and managing resources\n\n## Monitoring & Observability\n\n- [x] Set up logging and metrics (consider integrating with Prometheus and Grafana)\n- [x] Document key operational alerts and troubleshooting tips\n"
  },
  {
    "path": "services/cyberdesk-operator/docs/troubleshooting.md",
    "content": "# Cyberdesk Operator: Troubleshooting Guide\n\nThis document provides detailed information about the Cyberdesk Operator implementation, focusing on key aspects that are important for troubleshooting and maintenance.\n\n## Idempotency\n\nThe Cyberdesk Operator ensures that all operations are idempotent, meaning that they can be safely retried without causing duplicate or inconsistent states.\n\n### How Idempotency is Implemented\n\n1. **Resource Version Tracking**: \n   - The operator tracks which resource versions it has already processed using annotations on the Cyberdesk resources.\n   - Each handler adds its identifier and the resource version it processed to these annotations.\n   - Before processing a resource, handlers check if they've already processed the current resource version.\n\n2. **Existence Checks**: \n   - Before creating resources, the operator checks if they already exist.\n   - For example, before creating a VM, the operator checks if a VM with the same name already exists.\n\n3. **Annotation-Based Tracking**:\n   - Two key annotations track handler processing:\n     - `cyberdesk.io/processed-by`: Lists handlers that have processed this resource\n     - `cyberdesk.io/processed-versions`: Lists handler:resourceVersion pairs that have been processed\n\n4. **Helper Functions**:\n   - `is_handler_already_processed()`: Checks if a handler has already processed a resource version\n   - `mark_handler_processed()`: Marks a resource as processed by a handler\n\n### Troubleshooting Idempotency Issues\n\nIf you suspect that the operator is not correctly handling idempotency:\n\n1. Check the annotations on the Cyberdesk resource:\n   ```bash\n   kubectl get cyberdesks <name> -o jsonpath='{.metadata.annotations}'\n   ```\n\n2. Verify the logs to see if handlers are skipping already processed resources:\n   ```bash\n   kubectl logs -n cyberdesk-system -l app=cyberdesk-operator | grep \"already processed\"\n   ```\n\n3. If resources are being created multiple times, check if the annotations are being properly updated.\n\n## Leader Election\n\nTo ensure that only one instance of the operator processes resources in a multi-replica deployment, the operator uses Kopf's built-in leader election mechanism.\n\n### How Leader Election is Configured\n\n1. **Kopf Peering Resource**:\n   - The operator uses a Kubernetes custom resource called a \"Peering\" for leader election.\n   - The peering resource name is set via the `KOPF_PEERING` environment variable (defaults to \"cyberdesk-operator\").\n\n2. **Leader ID**:\n   - Each operator instance has a unique leader ID, by default using the Pod name.\n   - If POD_NAME is not available, it falls back to using the hostname.\n\n3. **Priority**:\n   - All instances have the same priority (0) by default.\n   - The instance with the lowest priority number becomes the leader.\n\n4. **Startup Configuration**:\n   - Leader election is configured in `main.py` through the `kopf.run()` parameters:\n     - `peering`: Name of the peering resource\n     - `id`: Unique ID for this operator instance\n     - `priority`: Priority in leader election\n\n### Failover Process\n\n1. Kopf periodically updates a heartbeat in the peering resource.\n2. If the leader fails to update its heartbeat, Kopf automatically promotes another instance to leader.\n3. The new leader will resume processing events.\n4. During failover, there might be a brief period (typically less than 30 seconds) where no instance is processing events.\n\n### Troubleshooting Leader Election\n\n1. Check the current leader:\n   ```bash\n   kubectl get kopfpeering cyberdesk-operator -o yaml\n   ```\n\n2. Check operator logs to see leader election activity:\n   ```bash\n   kubectl logs -n cyberdesk-system -l app=cyberdesk-operator | grep \"leader\"\n   ```\n\n3. If no instance becomes the leader, ensure the operator has permissions to create and update the peering resource.\n\n## Secrets Management\n\nThe Cyberdesk Operator handles secrets differently in development and production environments.\n\n### Development Environment\n\nIn the development environment:\n\n1. **Dotenv**:\n   - The operator uses `python-dotenv` to load environment variables from a `.env` file.\n   - This is only intended for local development and testing.\n\n2. **Local Configuration**:\n   - Secrets such as API keys or credentials should be stored in a `.env` file that is not committed to version control.\n   - The `.env.example` file provides a template for required variables.\n\n### Production Environment\n\nIn the production environment:\n\n1. **Kubernetes Secrets**:\n   - All secrets are stored as Kubernetes Secret resources.\n   - Secrets are mounted as either:\n     - Environment variables using `envFrom` in the Deployment spec\n     - Volume mounts for sensitive files (like certificates)\n\n2. **No Dotenv Usage**:\n   - The `python-dotenv` library is still imported but has no effect if `.env` is not present.\n   - All configuration comes from the container environment or mounted files.\n\n3. **Secret References**:\n   - The operator's Deployment manifest references Kubernetes Secrets for sensitive data.\n   - Example:\n     ```yaml\n     envFrom:\n     - secretRef:\n         name: cyberdesk-operator-secrets\n     ```\n\n### Secrets Used by the Operator\n\nThe operator may use the following types of secrets:\n\n1. **API Credentials**: For accessing external services (if applicable)\n2. **TLS Certificates**: For secure communication (if applicable)\n3. **SSH Keys**: For VM access (if applicable)\n\n### Troubleshooting Secrets Issues\n\n1. Check if the required secrets exist:\n   ```bash\n   kubectl get secrets -n cyberdesk-system\n   ```\n\n2. Verify the operator has permissions to access secrets:\n   ```bash\n   kubectl auth can-i get secrets -n cyberdesk-system --as=system:serviceaccount:cyberdesk-system:cyberdesk-operator\n   ```\n\n3. Check if secrets are correctly mounted:\n   ```bash\n   kubectl exec -n cyberdesk-system deploy/cyberdesk-operator -- env | grep SENSITIVE_VAR\n   ```\n\n## Monitoring and Metrics\n\nThe operator exposes Prometheus metrics on port 8081:\n\n1. **Available Metrics**:\n   - `cyberdesk_vm_created_total`: Counter of VMs created\n   - `cyberdesk_vm_deleted_total`: Counter of VMs deleted\n   - `cyberdesk_vm_timeout_total`: Counter of VMs that timed out\n   - `cyberdesk_active_vm_count`: Gauge showing current active VMs\n   - `cyberdesk_operation_duration_seconds`: Histogram of operation durations\n\n2. **Accessing Metrics**:\n   ```bash\n   kubectl port-forward -n cyberdesk-system deploy/cyberdesk-operator 8081:8081\n   curl localhost:8081\n   ``` "
  },
  {
    "path": "services/cyberdesk-operator/handlers/controller.py",
    "content": "\"\"\"\ncyberdesk_operator.py\n---------------------\nKopf‑based Kubernetes operator that provisions and manages KubeVirt VMs for a custom\n`Cyberdesk` CRD. Supabase is used as an external source of truth for instance state.\n\nKey responsibilities\n~~~~~~~~~~~~~~~~~~~~\n* Bootstrap the operator (load configuration, check for golden snapshot).\n* Handle `Cyberdesk` resource creation:\n    * Check a `VirtualMachinePool` for an available, running \"warm\" VM.\n    * If found, assign the VM from the pool (remove owner refs, add labels), patch its metadata, and notify the gateway.\n    * If no warm VM is available, initiate a `VirtualMachineClone` from a golden `VirtualMachineSnapshot`.\n    * Track provisioning state (pool vs. clone) via the `Cyberdesk` status.\n* Keep Supabase in sync with KubeVirt `VirtualMachineInstance` phase changes for provisioned VMs.\n* Handle `Cyberdesk` resource deletion or expiration:\n    * Delete the associated `VirtualMachine`.\n    * Attempt cleanup of any lingering `VirtualMachineClone` operations if provisioning didn't complete.\n\nThis single file keeps a clear top‑down structure:\n    1. Standard‑library / third‑party imports\n    2. Global configuration & logging\n    3. Constants & enums\n    4. Supabase and Kubernetes client bootstrap (sets gateway URL based on environment)\n    5. Utility helpers (template loading, DB helpers, warm pool lookup, etc.)\n    6. Kopf event‑handlers (startup, create/update/delete, timers, field watchers)\n\nAll helpers are deliberately *side‑effect free* (raise on error, return data), making\nunit‑testing straightforward.\n\"\"\"\nfrom __future__ import annotations\n\nimport logging\nimport os\nimport socket\nimport urllib.request\nimport urllib.error\nfrom datetime import UTC, datetime, timedelta\nfrom enum import Enum\nfrom pathlib import Path\nfrom typing import Dict, Optional\n\nimport kopf\nimport kubernetes\nfrom dotenv import load_dotenv\nfrom kopf import OperatorSettings\nfrom kubernetes.client import (  # noqa: WPS433 — explicit import list for type checking\n    CoreV1Api,\n    CustomObjectsApi,\n    ApiextensionsV1Api,\n    ApiException,\n)\nfrom supabase import Client, create_client\n\n# ---------------------------------------------------------------------------\n# Logging & basic config -----------------------------------------------------\n# ---------------------------------------------------------------------------\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s %(levelname)s %(name)s: %(message)s\")\nlogger = logging.getLogger(__name__)\n\n# Ensure ENV is loaded *early* so everything that relies on os.getenv works.\nload_dotenv()\n\n# ---------------------------------------------------------------------------\n# Constants -----------------------------------------------------------------\n# ---------------------------------------------------------------------------\nCYBERDESK_GROUP = \"cyberdesk.io\"\nCYBERDESK_VERSION = \"v1alpha1\"\nCYBERDESK_PLURAL = \"cyberdesks\"\nSTART_OPERATOR_PLURAL = \"startcyberdeskoperators\"\n\nKUBEVIRT_GROUP = \"kubevirt.io\"\nKUBEVIRT_VERSION = \"v1\"\nKUBEVIRT_NAMESPACE = os.getenv(\"KUBEVIRT_NAMESPACE\", \"kubevirt\")\nKUBEVIRT_VM_PLURAL = \"virtualmachines\"\nKUBEVIRT_VMI_PLURAL = \"virtualmachineinstances\"\n\nMANAGED_BY = \"cyberdesk-operator\"\nCYBERDESK_NAMESPACE = os.getenv(\"CYBERDESK_NAMESPACE\", \"cyberdesk-system\")\n\n# ---------------------------------------------------------------------------\n# Enums ----------------------------------------------------------------------\n# ---------------------------------------------------------------------------\nclass KubeVirtVMIPhase(str, Enum):\n    \"\"\"Supported phases as emitted by KubeVirt.\"\"\"\n\n    PENDING = \"Pending\"\n    SCHEDULING = \"Scheduling\"\n    SCHEDULED = \"Scheduled\"\n    RUNNING = \"Running\"\n    SUCCEEDED = \"Succeeded\"\n    FAILED = \"Failed\"\n    UNKNOWN = \"Unknown\"\n\n\nclass SupabaseInstanceStatus(str, Enum):\n    \"\"\"Canonical states stored in Supabase.\"\"\"\n\n    PENDING = \"pending\"\n    RUNNING = \"running\"\n    TERMINATED = \"terminated\"\n    ERROR = \"error\"\n\n\n# Static mapping between the two state machines -----------------------------\nVMI_PHASE_TO_SUPABASE_STATUS: Dict[KubeVirtVMIPhase, SupabaseInstanceStatus] = {\n    KubeVirtVMIPhase.PENDING: SupabaseInstanceStatus.PENDING,\n    KubeVirtVMIPhase.SCHEDULING: SupabaseInstanceStatus.PENDING,\n    KubeVirtVMIPhase.SCHEDULED: SupabaseInstanceStatus.PENDING,\n    KubeVirtVMIPhase.RUNNING: SupabaseInstanceStatus.PENDING, # We now only denote running after cloud init is done\n    KubeVirtVMIPhase.SUCCEEDED: SupabaseInstanceStatus.TERMINATED,\n    KubeVirtVMIPhase.FAILED: SupabaseInstanceStatus.ERROR,\n    KubeVirtVMIPhase.UNKNOWN: SupabaseInstanceStatus.ERROR,\n}\n\nCLONE_GROUP = \"clone.kubevirt.io\"\nCLONE_VERSION = \"v1beta1\"\nCLONE_PLURAL = \"virtualmachineclones\"\nGOLDEN_SNAPSHOT_NAME = \"snapshot-golden-vm\" # Name of the golden snapshot source\nSNAPSHOT_GROUP = \"snapshot.kubevirt.io\" # Correct API group for VirtualMachineSnapshot\nSNAPSHOT_VERSION = \"v1beta1\" # Correct version for VirtualMachineSnapshot\nSNAPSHOT_PLURAL = \"virtualmachinesnapshots\"\n\n# ---------------------------------------------------------------------------\n# Bootstrap helpers ----------------------------------------------------------\n# ---------------------------------------------------------------------------\n\ndef _init_supabase() -> Client:\n    \"\"\"Create and return a Supabase client or raise ``kopf.PermanentError``.\"\"\"\n    supabase_url = os.getenv(\"SUPABASE_URL\")\n    supabase_key = os.getenv(\"SUPABASE_KEY\")\n\n    if not supabase_url or not supabase_key:\n        msg = \"SUPABASE_URL / SUPABASE_KEY env vars must be set\"\n        logger.critical(msg)\n        raise kopf.PermanentError(msg)\n\n    try:\n        client = create_client(supabase_url, supabase_key)\n        logger.info(\"Supabase client initialised\")\n        return client\n    except Exception as exc:  # noqa: BLE001 — log real error and abort\n        logger.critical(\"Failed to initialise Supabase: %s\", exc)\n        raise kopf.PermanentError(\"Supabase init failed\") from exc\n\n\ndef _init_kubernetes_clients() -> tuple[CoreV1Api, CustomObjectsApi, ApiextensionsV1Api]:\n    \"\"\"Return (core_v1, custom_objects, apiext) after loading config and set globals.\"\"\"\n    global IS_IN_CLUSTER, GATEWAY_BASE_URL # Declare modification intent\n    try:\n        kubernetes.config.load_kube_config()\n        logger.info(\"Loaded kube‑config from local file\")\n        IS_IN_CLUSTER = False\n        # Use a single env var for the full testing URL\n        testing_url = os.getenv(\"GATEWAY_TESTING_URL\")\n        if testing_url:\n            GATEWAY_BASE_URL = testing_url\n            logger.info(f\"Using configured local testing gateway URL: {GATEWAY_BASE_URL}\")\n            # Log a hint if it looks like the user might want host.docker.internal\n            if \"localhost\" in testing_url:\n                 logger.warning(\"GATEWAY_TESTING_URL contains 'localhost'. If operator and gateway run in separate local containers, consider using 'host.docker.internal' instead of 'localhost'.\")\n        else:\n            logger.warning(\"Running locally but GATEWAY_TESTING_URL env var not set. Gateway notifications will be skipped.\")\n            GATEWAY_BASE_URL = None\n\n    except kubernetes.config.config_exception.ConfigException:\n        try:\n            kubernetes.config.load_incluster_config()\n            logger.info(\"Loaded in‑cluster kube‑config\")\n            IS_IN_CLUSTER = True\n            GATEWAY_BASE_URL = \"http://gateway.cyberdesk-system.svc.cluster.local:80\"\n            logger.info(f\"Using in-cluster gateway URL: {GATEWAY_BASE_URL}\")\n        except kubernetes.config.config_exception.ConfigException as exc:\n            logger.critical(\"Failed to load Kubernetes configuration: %s\", exc)\n            raise kopf.PermanentError(\"Cannot load Kubernetes config\") from exc\n\n    return CoreV1Api(), CustomObjectsApi(), ApiextensionsV1Api()\n\n\n# Globals to store environment-dependent configuration set during init\nIS_IN_CLUSTER = False # Default, will be updated by _init_kubernetes_clients\nGATEWAY_BASE_URL: Optional[str] = None # Default, will be updated by _init_kubernetes_clients\n\n# --- Bootstrap Clients ---\nSUPABASE: Client = _init_supabase()\nCORE_V1_API, CUSTOM_OBJECTS_API, APIEXT_V1_API = _init_kubernetes_clients()\n\n# ---------------------------------------------------------------------------\n# Supabase helpers -----------------------------------------------------------\n# ---------------------------------------------------------------------------\n\ndef get_instance_status(instance_id: str) -> Optional[str]:\n    \"\"\"Return the current status for *instance_id* or ``None`` if missing/error.\"\"\"\n    try:\n        logger.debug(\"Supabase query: status for %s\", instance_id)\n        resp = SUPABASE.table(\"cyberdesk_instances\").select(\"status\").eq(\"id\", instance_id).limit(1).execute()\n        return (resp.data[0][\"status\"] if resp.data else None)\n    except Exception as exc:  # noqa: BLE001\n        logger.error(\"Supabase error: %s\", exc)\n        return None\n\n\ndef update_instance_status(instance_id: str, vmi_phase: str) -> None:\n    \"\"\"Translate *vmi_phase* → Supabase status and update row if needed.\"\"\"\n    try:\n        phase_enum = KubeVirtVMIPhase(vmi_phase)\n    except ValueError:\n        logger.error(\"Unknown VMI phase '%s' → marking ERROR\", vmi_phase)\n        target = SupabaseInstanceStatus.ERROR\n    else:\n        target = VMI_PHASE_TO_SUPABASE_STATUS.get(phase_enum, SupabaseInstanceStatus.ERROR)\n\n    try:\n        SUPABASE.table(\"cyberdesk_instances\").update({\"status\": target.value}).eq(\"id\", instance_id).execute()\n        logger.info(\"Supabase status for %s set to %s\", instance_id, target.value)\n    except Exception as exc:  # noqa: BLE001\n        logger.error(\"Supabase update failed for %s: %s\", instance_id, exc)\n\n# ---------------------------------------------------------------------------\n# Kubernetes Helpers (including Warm Pool) -----------------------------------\n# ---------------------------------------------------------------------------\n\ndef get_free_vm_from_pool(namespace: str, logger: kopf.Logger) -> Optional[str]:\n    \"\"\"\n    Find, claim, and return the name of an available warm VM, or None.\n\n    A VM is considered available if it has the 'pool.kubevirt.io/warm=ready'\n    label, is Running, and does not have 'pool.kubevirt.io/in-use=true'.\n\n    If found, the VM is \"assigned\" from the pool by:\n    1. Removing ownerReferences.\n    2. Setting 'pool.kubevirt.io/in-use=true' and 'pool.kubevirt.io/warm=claimed'.\n    \"\"\"\n    pool_label_selector = \"pool.kubevirt.io/warm=ready\"\n    logger.debug(f\"Searching for warm VMs in namespace '{namespace}' with label '{pool_label_selector}'\")\n    try:\n        vms = CUSTOM_OBJECTS_API.list_namespaced_custom_object(\n            KUBEVIRT_GROUP,\n            KUBEVIRT_VERSION,\n            namespace,\n            KUBEVIRT_VM_PLURAL,\n            label_selector=pool_label_selector,\n        )\n    except ApiException as e:\n        logger.error(f\"Error listing VMs for warm pool: {e.status} {e.reason}\")\n        # Treat as temporary, maybe API server issue\n        raise kopf.TemporaryError(\"Failed to list VMs for warm pool.\", delay=15) from e\n\n    for vm in vms.get(\"items\", []):\n        meta = vm.get(\"metadata\", {})\n        status = vm.get(\"status\", {})\n        labels = meta.get(\"labels\", {})\n\n        vm_name = meta.get(\"name\")\n        if not vm_name:\n            logger.warning(\"Found VM in pool list without a name, skipping.\")\n            continue\n\n        # Check if already marked as in-use by the pool logic itself\n        if labels.get(\"pool.kubevirt.io/in-use\") == \"true\":\n            logger.debug(f\"Warm VM '{vm_name}' found but already marked in-use, skipping.\")\n            continue\n\n        # Check if running (important!)\n        if status.get(\"printableStatus\") != \"Running\":\n            logger.debug(f\"Warm VM '{vm_name}' found but not Running (status: {status.get('printableStatus')}), skipping.\")\n            continue\n\n        # --- Assign the VM from Pool ---\n        logger.info(f\"Found available warm VM: '{vm_name}'. Attempting to assign from pool.\")\n        patch_body = {\n            \"metadata\": {\n                \"ownerReferences\": None,  # Detach from the pool controller\n                \"labels\": {\n                    **labels, # Keep existing labels\n                    \"pool.kubevirt.io/in-use\": \"true\", # Mark as used\n                    \"pool.kubevirt.io/warm\": \"claimed\", # Update pool status label\n                    # Note: We will add cyberdesk-instance label in the main handler\n                },\n            }\n        }\n        try:\n            CUSTOM_OBJECTS_API.patch_namespaced_custom_object(\n                group=KUBEVIRT_GROUP,\n                version=KUBEVIRT_VERSION,\n                namespace=namespace,\n                plural=KUBEVIRT_VM_PLURAL,\n                name=vm_name,\n                body=patch_body,\n            )\n            logger.info(f\"Successfully assigned warm VM '{vm_name}' from pool. Removed ownerReferences and added 'in-use' label.\")\n            return vm_name # Return the name of the assigned VM\n        except ApiException as e:\n            logger.error(f\"Failed to patch (assign) warm VM '{vm_name}': {e.status} {e.reason}\")\n            # If patching fails, maybe the VM was deleted concurrently? Or permissions issue.\n            # Log error and continue searching, maybe another VM will work.\n            # If it's a transient issue, the next reconciliation might succeed.\n            continue # Try the next VM in the list\n\n    logger.info(\"No available warm VMs found in the pool.\")\n    return None\n\n# ---------------------------------------------------------------------------\n# CRD definition -------------------------------------------------------------\n# ---------------------------------------------------------------------------\nCYBERDESK_CRD_MANIFEST: dict = {\n    \"apiVersion\": \"apiextensions.k8s.io/v1\",\n    \"kind\": \"CustomResourceDefinition\",\n    \"metadata\": {\"name\": f\"{CYBERDESK_PLURAL}.{CYBERDESK_GROUP}\"},\n    \"spec\": {\n        \"group\": CYBERDESK_GROUP,\n        \"scope\": \"Namespaced\",\n        \"names\": {\n            \"plural\": CYBERDESK_PLURAL,\n            \"singular\": \"cyberdesk\",\n            \"kind\": \"Cyberdesk\",\n            \"shortNames\": [\"cd\", \"cds\"],\n        },\n        \"versions\": [\n            {\n                \"name\": CYBERDESK_VERSION,\n                \"served\": True,\n                \"storage\": True,\n                \"schema\": {\n                    \"openAPIV3Schema\": {\n                        \"type\": \"object\",\n                        \"properties\": {\n                            \"spec\": {\n                                \"type\": \"object\",\n                                \"properties\": {\n                                    \"timeoutMs\": {\n                                        \"type\": \"integer\",\n                                        \"minimum\": 1000,\n                                        \"description\": \"Milliseconds until VM is terminated.\",\n                                    }\n                                },\n                                \"required\": [\"timeoutMs\"],\n                            },\n                            \"status\": {\n                                \"type\": \"object\",\n                                \"x-kubernetes-preserve-unknown-fields\": True,\n                            },\n                        },\n                    }\n                },\n                \"subresources\": {\"status\": {}},\n            }\n        ],\n    },\n}\n\n# ---------------------------------------------------------------------------\n# Kopf handlers --------------------------------------------------------------\n# ---------------------------------------------------------------------------\n\ndef ensure_golden_snapshot_exists():\n    \"\"\"Check if the required golden VirtualMachineSnapshot exists.\"\"\"\n    logger.info(f\"Checking for golden snapshot: {GOLDEN_SNAPSHOT_NAME} in {KUBEVIRT_NAMESPACE}\")\n    try:\n        CUSTOM_OBJECTS_API.get_namespaced_custom_object(\n            group=SNAPSHOT_GROUP,\n            version=SNAPSHOT_VERSION,\n            namespace=KUBEVIRT_NAMESPACE,\n            plural=SNAPSHOT_PLURAL,\n            name=GOLDEN_SNAPSHOT_NAME,\n        )\n        logger.info(f\"Golden snapshot '{GOLDEN_SNAPSHOT_NAME}' found.\")\n    except ApiException as e:\n        if e.status == 404:\n            msg = f\"Required golden snapshot '{GOLDEN_SNAPSHOT_NAME}' not found in namespace '{KUBEVIRT_NAMESPACE}'.\"\n            logger.critical(msg)\n            raise kopf.PermanentError(msg)\n        else:\n            msg = f\"Error checking for golden snapshot '{GOLDEN_SNAPSHOT_NAME}': {e.status} {e.reason}\"\n            logger.error(msg)\n            # Treat other errors as temporary to allow retries after potential cluster issues\n            raise kopf.TemporaryError(msg, delay=30) from e\n    except Exception as e:\n        msg = f\"Unexpected error checking for golden snapshot '{GOLDEN_SNAPSHOT_NAME}': {e}\"\n        logger.error(msg)\n        raise kopf.TemporaryError(msg, delay=30) from e\n\n\n@kopf.on.startup()\ndef configure_kopf(settings: OperatorSettings, **_: Dict[str, object]) -> None:\n    \"\"\"Tune watch timeouts and ensure golden snapshot exists.\"\"\"\n    settings.watching.server_timeout = 210  # seconds\n    logger.info(\"Kopf watch server_timeout set to %s\", settings.watching.server_timeout)\n    # Check for snapshot on startup - operator won't function without it.\n    ensure_golden_snapshot_exists()\n\n\n@kopf.on.create(CYBERDESK_GROUP, CYBERDESK_VERSION, START_OPERATOR_PLURAL)\ndef crd_bootstrap(spec: dict, meta: dict, **_: Dict[str, object]) -> None:\n    \"\"\"Ensure the Cyberdesk CRD exists once the *bootstrap* resource is created.\"\"\"\n    try:\n        APIEXT_V1_API.create_custom_resource_definition(body=CYBERDESK_CRD_MANIFEST)\n        logger.info(\"Cyberdesk CRD applied\")\n    except kubernetes.client.rest.ApiException as exc:\n        if exc.status == 409:  # already present\n            logger.debug(\"Cyberdesk CRD already present\")\n        elif exc.status == 429:\n            raise kopf.TemporaryError(\"API busy, retrying\", delay=10) from exc\n        else:\n            raise kopf.PermanentError(f\"CRD creation failed: {exc.status} {exc.reason}\") from exc\n\n\ndef _ensure_vm_patched_and_running(vm_name: str, namespace: str, logger: kopf.Logger) -> None:\n    \"\"\"Fetch the VM and apply the required patches (metadata, spec, runStrategy).\"\"\"\n    logger.info(f\"Ensuring VM '{vm_name}' is patched and set to run.\")\n    # Labels intended for the VMI must go into spec.template.metadata.labels\n    # Labels only relevant to the VM object itself can stay at the top level.\n    patch_body = {\n        \"metadata\": {\n            \"labels\": {\n                # Optional: Keep labels specific to the VM object itself here if needed.\n                # For instance, if you wanted to label the VM resource differently than the VMI.\n                \"managed-by\": MANAGED_BY, # Can be useful on the VM too\n            }\n            # Add top-level annotations for the VM if needed\n        },\n        \"spec\": {\n            \"runStrategy\": \"Always\", # Ensure VM is set to run\n            \"template\": {\n                \"metadata\": { # <--- Ensure metadata exists here\n                    \"labels\": { # <--- Labels for the VMI go here\n                        \"app\": \"cyberdesk\",\n                        \"cyberdesk-instance\": vm_name,\n                        \"managed-by\": MANAGED_BY, # Also label the VMI for consistency\n                        \"kubevirt.io/domain\": vm_name, # This is often set here\n                    }\n                    # Add annotations for the VMI if needed\n                },\n                \"spec\": {\n                    \"hostname\": vm_name\n                }\n            }\n        }\n    }\n    try:\n        CUSTOM_OBJECTS_API.patch_namespaced_custom_object(\n            group=KUBEVIRT_GROUP,\n            version=KUBEVIRT_VERSION,\n            namespace=namespace,\n            plural=KUBEVIRT_VM_PLURAL,\n            name=vm_name,\n            body=patch_body\n        )\n        logger.info(f\"Successfully patched VM '{vm_name}' metadata, spec, and runStrategy.\")\n    except ApiException as e:\n        logger.error(f\"Error patching VM '{vm_name}': {e.status} {e.reason}\")\n        # If patching fails, it's likely temporary or the VM was deleted.\n        raise kopf.TemporaryError(f\"Failed to patch VM {vm_name}\", delay=10) from e\n\n\n@kopf.on.create(CYBERDESK_GROUP, CYBERDESK_VERSION, CYBERDESK_PLURAL)\ndef cyberdesk_create(spec: dict, meta: dict, status: dict, logger: kopf.Logger, patch: kopf.Patch, body: dict, retry: int, **_: Dict[str, object]): # noqa: WPS211, WPS231\n    \"\"\"Reconcile a new Cyberdesk CR using status-driven warm pool/clone logic.\"\"\"\n    instance_id = meta[\"name\"]\n    namespace = KUBEVIRT_NAMESPACE\n    timeout_ms = spec.get(\"timeoutMs\", 3_600_000)\n    max_clone_wait_retries = 20 # Used later in clone check\n    clone_wait_delay = 5 # Increased delay slightly\n\n    logger.info(f\"Reconciling Cyberdesk CR '{instance_id}' (Attempt #{retry})\")\n\n    # --- Check Status: Determine current state/intent ---\n    current_status = body.get(\"status\", {}).get(\"cyberdesk_create\", {})\n    vm_ref = current_status.get(\"virtualMachineRef\")\n    clone_op_name = current_status.get(\"cloneOperationName\")\n    last_phase = current_status.get(\"lastPhase\")\n\n    # --- Idempotency Check: Already Provisioned? ---\n    if vm_ref and last_phase in [\"AssignedFromPool\", \"Cloned\", \"Running\"]: # \"Running\" for older status compatibility\n        logger.info(f\"Cyberdesk '{instance_id}' already has vmRef '{vm_ref}'. Ensuring patch and returning.\")\n        try:\n            # Make sure the VM (assigned or cloned) is correctly patched\n            # _ensure_vm_patched_and_running should be idempotent\n            _ensure_vm_patched_and_running(vm_ref, namespace, logger)\n\n            # Ensure status reflects reality (especially startTime/expiryTime if they were missed)\n            if \"startTime\" not in current_status or \"expiryTime\" not in current_status:\n                 logger.warning(f\"Status for {instance_id} with vmRef {vm_ref} is incomplete. Re-populating times.\")\n                 now = datetime.now(UTC)\n                 expiry = now + timedelta(milliseconds=timeout_ms)\n                 patch.status[\"cyberdesk_create\"] = {\n                      **current_status, # Keep existing fields like vmRef, lastPhase\n                      \"startTime\": now.isoformat(),\n                      \"expiryTime\": expiry.isoformat(),\n                 }\n            else:\n                 # If status is complete, just ensure it's patched back (no-op if unchanged)\n                 patch.status[\"cyberdesk_create\"] = current_status\n\n            # Clean up potential old top-level status fields\n            if \"virtualMachineRef\" in patch.status: del patch.status[\"virtualMachineRef\"]\n            if \"startTime\" in patch.status: del patch.status[\"startTime\"]\n            if \"expiryTime\" in patch.status: del patch.status[\"expiryTime\"]\n            if \"cloneOperationName\" in patch.status.get(\"cyberdesk_create\", {}):\n                 del patch.status[\"cyberdesk_create\"][\"cloneOperationName\"] # Should be removed if vmRef exists\n\n        except kopf.TemporaryError:\n             raise # Re-raise patch error\n        except ApiException as e:\n             if e.status == 404:\n                  logger.warning(f\"vmRef '{vm_ref}' in status for '{instance_id}' not found! Forcing re-provision.\")\n                  # Clear status to force reprovisioning\n                  patch.status[\"cyberdesk_create\"] = {}\n             else:\n                  logger.error(f\"Error checking existing vmRef '{vm_ref}' for '{instance_id}': {e.reason}\")\n                  raise kopf.TemporaryError(f\"Failed to check existing VM {vm_ref}\", delay=10) from e\n        else:\n            return # AssignedFromPool successful\n\n    # --- State Check: Already decided to clone? ---\n    if clone_op_name:\n        logger.info(f\"Status indicates cloning operation '{clone_op_name}' already initiated for '{instance_id}'. Checking clone status.\")\n        # Skip warm pool check, go directly to checking the clone\n        pass # Logic continues below in \"Check Clone Status\" section\n    else:\n        # --- State: Try Warm Pool ---\n        logger.info(f\"No active clone operation found in status for '{instance_id}'. Checking warm pool.\")\n        assigned_vm_name = get_free_vm_from_pool(namespace, logger) # Renamed variable for clarity\n\n        if assigned_vm_name:\n            logger.info(f\"Using warm VM '{assigned_vm_name}' assigned from pool for Cyberdesk '{instance_id}'.\")\n            try:\n                # --- Patch Assigned VM ---\n                vm_patch_body = {\n                    \"metadata\": {\"labels\": {\"app\": \"cyberdesk\", \"cyberdesk-instance\": instance_id, \"managed-by\": MANAGED_BY}},\n                    \"spec\": {\"template\": {\"metadata\": {\"labels\": {\"app\": \"cyberdesk\", \"cyberdesk-instance\": instance_id, \"managed-by\": MANAGED_BY, \"kubevirt.io/domain\": instance_id}}}}\n                }\n                # Fetch current VM to merge labels correctly\n                current_vm = CUSTOM_OBJECTS_API.get_namespaced_custom_object(KUBEVIRT_GROUP, KUBEVIRT_VERSION, namespace, KUBEVIRT_VM_PLURAL, assigned_vm_name)\n                current_labels = current_vm.get(\"metadata\", {}).get(\"labels\", {})\n                current_vmi_labels = current_vm.get(\"spec\", {}).get(\"template\", {}).get(\"metadata\", {}).get(\"labels\", {})\n\n                vm_patch_body[\"metadata\"][\"labels\"] = {**current_labels, **vm_patch_body[\"metadata\"][\"labels\"]}\n                vm_patch_body[\"spec\"][\"template\"][\"metadata\"][\"labels\"] = {**current_vmi_labels, **vm_patch_body[\"spec\"][\"template\"][\"metadata\"][\"labels\"]}\n\n                CUSTOM_OBJECTS_API.patch_namespaced_custom_object(KUBEVIRT_GROUP, KUBEVIRT_VERSION, namespace, KUBEVIRT_VM_PLURAL, assigned_vm_name, body=vm_patch_body)\n                logger.info(f\"Successfully patched VM '{assigned_vm_name}' assigned from pool for '{instance_id}'.\")\n\n                # --- Verify VMI is running and get IP (Readiness Check) ---\n                try:\n                    vmi = CUSTOM_OBJECTS_API.get_namespaced_custom_object(\n                        group=KUBEVIRT_GROUP,\n                        version=KUBEVIRT_VERSION,\n                        namespace=namespace,\n                        plural=KUBEVIRT_VMI_PLURAL,\n                        name=assigned_vm_name # VMI name assumed to match assigned VM name\n                    )\n                    interfaces = vmi.get('status', {}).get('interfaces', [])\n                    vmi_ip = interfaces[0].get('ipAddress') if interfaces else None\n                    vmi_phase = vmi.get('status', {}).get('phase')\n\n                    if not vmi_ip or vmi_phase != 'Running':\n                         logger.warning(f\"Pool-assigned VMI '{assigned_vm_name}' is not Running or has no IP yet (Phase: {vmi_phase}, IP: {vmi_ip}). Retrying.\")\n                         raise kopf.TemporaryError(f\"Pool-assigned VMI {assigned_vm_name} not fully ready.\")\n                    logger.info(f\"Verified pool-assigned VMI '{assigned_vm_name}' is Running with IP {vmi_ip}.\")\n\n                except ApiException as vmi_exc:\n                    if vmi_exc.status == 404:\n                         logger.warning(f\"VMI '{assigned_vm_name}' not found immediately after patching VM. Retrying.\")\n                         raise kopf.TemporaryError(\"VMI not found yet after patching.\") # Retry\n                    else:\n                         logger.error(f\"API error getting VMI '{assigned_vm_name}' for readiness check: {vmi_exc.reason}\")\n                         raise # Re-raise other API errors\n\n                # --- Notify Gateway ---\n                if not GATEWAY_BASE_URL:\n                    logger.warning(f\"Gateway base URL not configured (In cluster? {IS_IN_CLUSTER}). Skipping notification for {instance_id}.\")\n                else:\n                    gateway_url = f\"{GATEWAY_BASE_URL}/cyberdesk/{instance_id}/ready\"\n                    logger.info(f\"Notifying gateway for pool-assigned instance '{instance_id}' at {gateway_url}\")\n                    try:\n                        req = urllib.request.Request(gateway_url, method=\"POST\")\n                        with urllib.request.urlopen(req, timeout=5) as response:\n                            logger.info(f\"Gateway notified successfully for pool-assigned '{instance_id}', status: {response.status}\")\n                    except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError) as e:\n                        logger.error(f\"Failed to notify gateway for pool-assigned '{instance_id}': {e}\")\n\n            except ApiException as e:\n                logger.error(f\"Error patching/notifying for pool-assigned VM '{assigned_vm_name}': {e.reason}\")\n                raise kopf.TemporaryError(f\"Failed to finalize pool-assigned VM {assigned_vm_name}\", delay=10) from e\n\n            # --- Update Status (AssignedFromPool Success) ---\n            now = datetime.now(UTC)\n            expiry = now + timedelta(milliseconds=timeout_ms)\n            logger.info(f\"Updating status for '{instance_id}': Assigned VM '{assigned_vm_name}' from pool, expires {expiry.isoformat()}\")\n            patch.status[\"cyberdesk_create\"] = {\n                \"virtualMachineRef\": assigned_vm_name,\n                \"startTime\": now.isoformat(),\n                \"expiryTime\": expiry.isoformat(),\n                \"lastPhase\": \"AssignedFromPool\",\n                # Explicitly remove cloneOperationName if it somehow existed\n                \"cloneOperationName\": None,\n            }\n            # Clean up potential old status fields\n            if \"virtualMachineRef\" in patch.status: del patch.status[\"virtualMachineRef\"]\n            if \"startTime\" in patch.status: del patch.status[\"startTime\"]\n            if \"expiryTime\" in patch.status: del patch.status[\"expiryTime\"]\n            return # Assignment from warm pool successful\n\n        else:\n            # --- State: Initiate Cloning ---\n            logger.info(f\"No warm VM available for '{instance_id}'. Initiating clone.\")\n            clone_op_name = f\"clone-for-{instance_id}\" # Define the clone op name\n\n            # --- Update Status (Pre-Clone) ---\n            # Set cloneOperationName *before* creating the clone object\n            logger.info(f\"Updating status for '{instance_id}': Setting cloneOperationName to '{clone_op_name}'\")\n            patch.status[\"cyberdesk_create\"] = {\n                \"cloneOperationName\": clone_op_name,\n                \"lastPhase\": \"CloningInitiated\",\n                # Ensure vmRef is not set here\n                \"virtualMachineRef\": None,\n            }\n            # Return early to allow Kopf to patch the status.\n            # The next reconciliation will pick up 'cloneOperationName' and proceed.\n            # This prevents creating the Clone object if the status patch fails.\n            raise kopf.TemporaryError(f\"Clone operation name '{clone_op_name}' set in status. Retrying shortly to initiate/check clone.\", delay=clone_wait_delay)\n\n    # --- State: Check Clone Status (only reached if clone_op_name is set) ---\n    if not clone_op_name:\n         # This should ideally not be reached due to the logic structure, but acts as a safeguard.\n         logger.error(f\"Reached clone checking state for '{instance_id}' but cloneOperationName is not set in status. Retrying.\")\n         raise kopf.TemporaryError(\"Inconsistent state: clone check without cloneOperationName.\", delay=10)\n\n    logger.info(f\"Checking status of clone operation '{clone_op_name}' for '{instance_id}'.\")\n    try:\n        # --- Get or Create VirtualMachineClone Object ---\n        # We try to *get* it first because the status might have been set on a previous attempt\n        # where the actual clone creation failed afterwards.\n        try:\n            clone_obj = CUSTOM_OBJECTS_API.get_namespaced_custom_object(\n                group=CLONE_GROUP, version=CLONE_VERSION, namespace=namespace, plural=CLONE_PLURAL, name=clone_op_name\n            )\n            logger.debug(f\"Found existing VirtualMachineClone '{clone_op_name}'.\")\n        except ApiException as e:\n            if e.status == 404:\n                logger.info(f\"VirtualMachineClone '{clone_op_name}' not found. Creating it now.\")\n                clone_body = {\n                    \"apiVersion\": f\"{CLONE_GROUP}/{CLONE_VERSION}\",\n                    \"kind\": \"VirtualMachineClone\",\n                    \"metadata\": {\"name\": clone_op_name, \"namespace\": namespace, \"labels\": {\"managed-by\": MANAGED_BY, \"cyberdesk-instance\": instance_id}},\n                    \"spec\": {\n                        \"source\": {\"apiGroup\": SNAPSHOT_GROUP, \"kind\": \"VirtualMachineSnapshot\", \"name\": GOLDEN_SNAPSHOT_NAME},\n                        \"target\": {\n                             \"apiGroup\": KUBEVIRT_GROUP,\n                             \"kind\": \"VirtualMachine\",\n                             \"name\": instance_id,\n                             \"template\": {\n                                 \"spec\": {\n                                     \"readinessProbe\": {\n                                         \"exec\": {\n                                              # Use test -f to check for cloud-init completion flag\n                                             \"command\": [\"test\", \"-f\", \"/var/lib/cloud/instance/boot-finished\"]\n                                         },\n                                         \"initialDelaySeconds\": 30,\n                                         \"periodSeconds\": 10,\n                                         \"failureThreshold\": 3,\n                                         \"successThreshold\": 1,\n                                     }\n                                 }\n                             }\n                        },\n                    },\n                }\n                clone_obj = CUSTOM_OBJECTS_API.create_namespaced_custom_object(\n                    group=CLONE_GROUP, version=CLONE_VERSION, namespace=namespace, plural=CLONE_PLURAL, body=clone_body\n                )\n                logger.info(f\"VirtualMachineClone '{clone_op_name}' created.\")\n                # No need to check status immediately, let the next retry handle it\n                raise kopf.TemporaryError(f\"Clone {clone_op_name} just created. Waiting for status.\", delay=clone_wait_delay)\n            else:\n                # Other API error getting the clone object\n                logger.error(f\"API error getting VirtualMachineClone '{clone_op_name}': {e.reason}\")\n                raise kopf.TemporaryError(f\"Failed to get clone object {clone_op_name}\", delay=clone_wait_delay) from e\n\n        # --- Evaluate Clone Status ---\n        current_clone_status = clone_obj.get(\"status\", {})\n        clone_phase = current_clone_status.get(\"phase\")\n        logger.info(f\"Clone '{clone_op_name}' phase: {clone_phase}\")\n\n        if clone_phase == \"Succeeded\":\n            logger.info(f\"Clone '{clone_op_name}' succeeded. Finalizing VM '{instance_id}'.\")\n            # --- Ensure the newly created VM is patched and running ---\n            _ensure_vm_patched_and_running(instance_id, namespace, logger) # Target VM name is instance_id\n\n            # --- Update Status (Clone Success) ---\n            now = datetime.now(UTC)\n            expiry = now + timedelta(milliseconds=timeout_ms)\n            logger.info(f\"Updating status for '{instance_id}': Cloned VM '{instance_id}', expires {expiry.isoformat()}\")\n            patch.status[\"cyberdesk_create\"] = {\n                \"virtualMachineRef\": instance_id, # VM name matches instance_id\n                \"startTime\": now.isoformat(),\n                \"expiryTime\": expiry.isoformat(),\n                \"lastPhase\": \"Cloned\",\n                \"cloneOperationName\": None, # Remove clone name on success\n            }\n            # Clean up potential old status fields\n            if \"virtualMachineRef\" in patch.status: del patch.status[\"virtualMachineRef\"]\n            if \"startTime\" in patch.status: del patch.status[\"startTime\"]\n            if \"expiryTime\" in patch.status: del patch.status[\"expiryTime\"]\n            return # Clone successful\n\n        elif clone_phase == \"Failed\":\n            logger.error(f\"Clone '{clone_op_name}' failed. Check clone object status for details.\")\n            # Update status to reflect failure\n            patch.status[\"cyberdesk_create\"] = {\n                 **current_status, # Keep existing fields if any\n                 \"lastPhase\": \"CloneFailed\",\n                 \"cloneOperationName\": None, # Remove clone name on failure\n                 \"virtualMachineRef\": None, # Ensure no vmRef\n            }\n            raise kopf.PermanentError(f\"Clone {clone_op_name} failed.\")\n\n        elif clone_phase == \"Unknown\":\n                logger.warning(f\"Clone '{clone_op_name}' phase is Unknown. Retrying status check...\")\n                raise kopf.TemporaryError(f\"Clone {clone_op_name} phase Unknown.\", delay=clone_wait_delay)\n        else: # InProgress phases (SnapshotInProgress, CreatingTargetVM, RestoreInProgress, None)\n            logger.info(f\"Clone '{clone_op_name}' in progress ({clone_phase}). Waiting...\")\n            # Check for timeout only if clone is still in progress\n            if retry >= max_clone_wait_retries: # Use >= for safety\n                logger.error(f\"Clone '{clone_op_name}' did not succeed within {max_clone_wait_retries} attempts.\")\n                # Attempt to delete the stuck clone object\n                try:\n                    CUSTOM_OBJECTS_API.delete_namespaced_custom_object(CLONE_GROUP, CLONE_VERSION, namespace, CLONE_PLURAL, clone_op_name)\n                    logger.info(f\"Deleted timed-out clone object '{clone_op_name}'.\")\n                except ApiException as del_exc:\n                    if del_exc.status != 404:\n                        logger.warning(f\"Failed to delete timed-out clone object '{clone_op_name}': {del_exc.reason}\")\n                # Update status and mark as permanent failure\n                patch.status[\"cyberdesk_create\"] = {\n                     **current_status,\n                     \"lastPhase\": \"CloneTimeout\",\n                     \"cloneOperationName\": None,\n                     \"virtualMachineRef\": None,\n                }\n                raise kopf.PermanentError(f\"Clone {clone_op_name} timed out.\")\n            else:\n                # Still in progress and within retry limit, raise TemporaryError to retry\n                raise kopf.TemporaryError(f\"Clone {clone_op_name} in progress ({clone_phase}). Waiting...\", delay=clone_wait_delay)\n\n    except kopf.TemporaryError:\n        raise # Propagate temporary errors for retry\n    except kopf.PermanentError:\n        raise # Propagate permanent errors\n    except ApiException as e:\n        # Catch API errors during clone check/creation\n        logger.error(f\"API error during clone processing for '{clone_op_name}': {e.reason}\")\n        raise kopf.TemporaryError(f\"API error processing clone {clone_op_name}\", delay=clone_wait_delay) from e\n    except Exception as e:\n        # Catch unexpected errors\n        logger.exception(f\"Unexpected error during reconciliation of '{instance_id}' at clone check state.\") # Use logger.exception to include traceback\n        raise kopf.TemporaryError(f\"Unexpected error for {instance_id}: {e}\", delay=30)\n\n\n@kopf.on.field(KUBEVIRT_GROUP, KUBEVIRT_VERSION, KUBEVIRT_VMI_PLURAL, field=\"status.phase\")\ndef vmi_phase_change(old: str | None, new: str | None, meta: dict, status: dict, logger: kopf.Logger, **_: Dict[str, object]):\n    \"\"\"Sync Supabase when a VMI phase flips, ignoring expected warm pool VMs.\"\"\"\n    if new is None:\n        return  # nothing to do\n\n    labels = meta.get(\"labels\", {})\n    vm_name = meta.get(\"name\", \"unknown-vmi\")\n\n    # Only process VMIs managed by or intended for this operator\n    if labels.get(\"app\") != \"cyberdesk\":\n        logger.debug(f\"Ignoring phase change for VMI {vm_name} (missing 'app: cyberdesk' label)\")\n        return\n\n    instance_id = labels.get(\"cyberdesk-instance\")\n\n    if not instance_id:\n        # If no instance ID, check if it's expected (part of warm pool)\n        is_warm_pool_vm = (\n            labels.get(\"pool.kubevirt.io/warm\") == \"ready\" or\n            labels.get(\"pool.kubevirt.io/in-use\") == \"true\"\n        )\n        if is_warm_pool_vm:\n            # Expected state for a VM in the pool or just assigned\n            logger.debug(f\"Ignoring phase change for warm pool VMI {vm_name} (no instance ID yet)\")\n        else:\n            # Unexpected: Has 'app: cyberdesk' but no instance ID and no pool labels\n            logger.warning(f\"VMI {vm_name} has 'app: cyberdesk' but is missing 'cyberdesk-instance' label and doesn't appear to be a warm pool VM.\")\n        return # Don't proceed to Supabase update\n\n    # --- Proceed with Supabase update only if we have an instance_id ---\n    logger.info(f\"Processing phase change ('{old}' -> '{new}') for VMI {vm_name} linked to instance {instance_id}\")\n    try:\n        current_db = get_instance_status(instance_id)\n        # Added check to prevent infinite loops if status already matches\n        # This check requires get_instance_status to be relatively quick\n        try:\n             phase_enum = KubeVirtVMIPhase(new)\n             desired = VMI_PHASE_TO_SUPABASE_STATUS.get(phase_enum, SupabaseInstanceStatus.ERROR).value\n        except ValueError:\n             logger.error(f\"Unknown VMI phase '{new}' for {vm_name} -> marking ERROR in Supabase\")\n             desired = SupabaseInstanceStatus.ERROR.value\n\n        if current_db != desired:\n            logger.info(f\"Supabase status mismatch for {instance_id} (DB: {current_db}, VMI wants: {desired}). Updating.\")\n            update_instance_status(instance_id, new)\n        else:\n             logger.debug(f\"Supabase status for {instance_id} already matches desired state ({desired}). No update needed.\")\n\n    except Exception as e:\n         # Catch potential errors during DB check/update\n         logger.exception(f\"Error processing VMI phase change for {instance_id} in Supabase: {e}\")\n\n\n@kopf.on.delete(CYBERDESK_GROUP, CYBERDESK_VERSION, CYBERDESK_PLURAL)\ndef cyberdesk_delete(meta: dict, body: dict, logger: kopf.Logger, **_: Dict[str, object]):\n    \"\"\"Tear down the associated VM when *Cyberdesk* is deleted, if provisioned.\"\"\"\n    instance_id = meta[\"name\"]\n    namespace = KUBEVIRT_NAMESPACE\n    logger.info(f\"Handling deletion for Cyberdesk CR '{instance_id}'.\")\n\n    vm_name = body.get(\"status\", {}).get(\"cyberdesk_create\", {}).get(\"virtualMachineRef\")\n\n    if vm_name:\n        logger.info(f\"Found virtualMachineRef '{vm_name}' in status. Attempting VM deletion.\")\n        try:\n            CUSTOM_OBJECTS_API.delete_namespaced_custom_object(\n                group=KUBEVIRT_GROUP,\n                version=KUBEVIRT_VERSION,\n                namespace=namespace,\n                plural=KUBEVIRT_VM_PLURAL,\n                name=vm_name,\n                # Optional: Add grace period if needed\n                # body=kubernetes.client.V1DeleteOptions(grace_period_seconds=0) # Example: Force immediate deletion\n            )\n            logger.info(f\"Successfully initiated deletion for VM '{vm_name}'.\")\n            # Removed cloud-init secret deletion attempt\n        except ApiException as exc:\n            if exc.status not in (404, 410): # Ignore if already deleted\n                logger.error(f\"Failed to delete VM '{vm_name}' during cleanup: {exc.status} {exc.reason}\")\n                raise kopf.TemporaryError(f\"VM cleanup failed for {vm_name}, will retry\", delay=15) from exc\n            else:\n                logger.info(f\"VM '{vm_name}' already deleted or not found.\")\n    else:\n        # --- Handle case where deletion happens before provisioning completes ---\n        logger.warning(f\"No virtualMachineRef found in status for deleted Cyberdesk '{instance_id}'. Provisioning may not have completed.\")\n        clone_op_name = body.get(\"status\", {}).get(\"cyberdesk_create\", {}).get(\"cloneOperationName\")\n        if clone_op_name:\n            logger.info(f\"Found cloneOperationName '{clone_op_name}'. Attempting to delete potentially lingering clone operation.\")\n            try:\n                CUSTOM_OBJECTS_API.delete_namespaced_custom_object(\n                    group=CLONE_GROUP,\n                    version=CLONE_VERSION,\n                    namespace=namespace,\n                    plural=CLONE_PLURAL,\n                    name=clone_op_name,\n                )\n                logger.info(f\"Successfully deleted potentially lingering clone operation '{clone_op_name}'.\")\n            except ApiException as e:\n                if e.status != 404:\n                    logger.warning(f\"Failed to delete clone operation '{clone_op_name}' during cleanup: {e.status} {e.reason}. Manual check might be needed.\")\n                else:\n                    logger.debug(f\"Lingering clone operation '{clone_op_name}' not found.\")\n        else:\n            logger.info(f\"No cloneOperationName found either for '{instance_id}'. No KubeVirt resources to clean up based on status.\")\n\n\n@kopf.on.timer(CYBERDESK_GROUP, CYBERDESK_VERSION, CYBERDESK_PLURAL, interval=60)\ndef cyberdesk_timeout_check(body: dict, logger: kopf.Logger, **_: Dict[str, object]): # Added logger\n    \"\"\"Per‑resource timer: shut down VM once *expiryTime* passes.\"\"\"\n    expiry_str = body.get(\"status\", {}).get(\"cyberdesk_create\", {}).get(\"expiryTime\")\n    instance_id = body[\"metadata\"][\"name\"]\n    namespace = body[\"metadata\"][\"namespace\"]\n\n    if not expiry_str:\n        logger.debug(f\"No expiryTime found in status for '{instance_id}', skipping timeout check.\")\n        return\n\n    try:\n        expiry_dt = datetime.fromisoformat(expiry_str)\n        if datetime.now(UTC) >= expiry_dt:\n            logger.info(f\"Cyberdesk '{instance_id}' expired at {expiry_str} — deleting CR.\")\n            # Deleting the CR will trigger the cyberdesk_delete handler for actual VM cleanup\n            CUSTOM_OBJECTS_API.delete_namespaced_custom_object(\n                group=CYBERDESK_GROUP,\n                version=CYBERDESK_VERSION,\n                namespace=namespace,\n                plural=CYBERDESK_PLURAL,\n                name=instance_id\n            )\n        # else: logger.debug(f\"Cyberdesk '{instance_id}' not expired yet.\")\n    except ValueError:\n         logger.error(f\"Could not parse expiryTime '{expiry_str}' for '{instance_id}'.\")\n    except ApiException as e:\n         logger.error(f\"API error deleting expired Cyberdesk CR '{instance_id}': {e.reason}\")\n         # Raise temporary error to retry deletion\n         raise kopf.TemporaryError(f\"Failed to delete expired CR {instance_id}\", delay=30) from e\n\n\n# NEW Field Watcher for VMI Readiness (Post Cloud-Init)\n@kopf.on.field(KUBEVIRT_GROUP, KUBEVIRT_VERSION, KUBEVIRT_VMI_PLURAL, field='status.conditions')\ndef vmi_ready_watcher(old, new, status, meta, logger: kopf.Logger, **kwargs):\n    \"\"\"Notify gateway when a VMI's Ready condition becomes True after cloud-init.\"\"\"\n    if not new: # Field might be cleared on deletion\n        return\n\n    labels = meta.get(\"labels\", {})\n    vm_name = meta.get(\"name\", \"unknown-vmi\")\n\n    # Filter for VMIs managed by us AND that have an instance ID\n    if labels.get(\"app\") != \"cyberdesk\" or not labels.get(\"cyberdesk-instance\"):\n        # logger.debug(f\"Ignoring condition change for VMI {vm_name} (not a managed cyberdesk instance).\")\n        return\n\n    instance_id = labels[\"cyberdesk-instance\"]\n\n    # Find the 'Ready' condition in the new status\n    ready_condition = None\n    for condition in new:\n        if condition.get(\"type\") == \"Ready\":\n            ready_condition = condition\n            break\n\n    if not ready_condition:\n        # logger.debug(f\"No 'Ready' condition found in status update for VMI {vm_name}.\")\n        return\n\n    is_ready = ready_condition.get(\"status\") == \"True\"\n    # logger.debug(f\"VMI {vm_name} Ready condition status: {ready_condition.get('status')}\")\n\n    # Check if the old status also had Ready=True to avoid re-notifying\n    was_ready = False\n    if old:\n        for condition in old:\n             if condition.get(\"type\") == \"Ready\" and condition.get(\"status\") == \"True\":\n                  was_ready = True\n                  break\n\n    if is_ready and not was_ready:\n        logger.info(f\"VMI '{vm_name}' ({instance_id}) condition changed to Ready=True. Notifying gateway.\")\n\n        # --- Notify Gateway --- #\n        if not GATEWAY_BASE_URL:\n            logger.warning(f\"Gateway base URL not configured (In cluster? {IS_IN_CLUSTER}). Skipping notification for ready instance {instance_id}.\")\n        else:\n            gateway_url = f\"{GATEWAY_BASE_URL}/cyberdesk/{instance_id}/ready\"\n            logger.info(f\"Notifying gateway for ready instance '{instance_id}' at {gateway_url}\")\n            try:\n                # Using a simple synchronous request here for simplicity in handler\n                # Consider making it async if gateway calls become slow/blocking\n                req = urllib.request.Request(gateway_url, method=\"POST\")\n                # Add a timeout to prevent blocking indefinitely\n                with urllib.request.urlopen(req, timeout=10) as response:\n                    logger.info(f\"Gateway notified successfully for ready '{instance_id}', status: {response.status}\")\n            except (urllib.error.URLError, urllib.error.HTTPError, socket.timeout, TimeoutError) as e:\n                # Log error, but don't fail the handler - the VMI *is* ready\n                logger.error(f\"Failed to notify gateway for ready '{instance_id}': {e}\")\n            except Exception as e:\n                 # Catch unexpected errors during notification\n                 logger.exception(f\"Unexpected error notifying gateway for ready '{instance_id}': {e}\")\n    # else:\n         # logger.debug(f\"VMI {vm_name} Ready condition did not change to True, or was already True. No action.\")"
  },
  {
    "path": "services/cyberdesk-operator/requirements.txt",
    "content": "aiohappyeyeballs==2.6.1\naiohttp==3.11.16\naiosignal==1.3.2\nannotated-types==0.7.0\nanyio==4.9.0\nattrs==25.3.0\ncachetools==5.5.2\ncertifi==2025.1.31\ncharset-normalizer==3.4.1\nclick==8.1.8\ncolorama==0.4.6\ndeprecation==2.1.0\ndurationpy==0.9\nfrozenlist==1.5.0\ngoogle-auth==2.38.0\ngotrue==2.12.0\nh11==0.14.0\nh2==4.2.0\nhpack==4.1.0\nhttpcore==1.0.8\nhttpx==0.28.1\nhyperframe==6.1.0\nidna==3.10\niniconfig==2.1.0\niso8601==2.1.0\nkopf==1.37.5\nkubernetes==32.0.1\nmultidict==6.4.3\noauthlib==3.2.2\npackaging==24.2\npluggy==1.5.0\npostgrest==1.0.1\npropcache==0.3.1\npyasn1==0.6.1\npyasn1-modules==0.4.2\npydantic==2.11.3\npydantic-core==2.33.1\npyjwt==2.10.1\npython-dateutil==2.9.0.post0\npython-dotenv==1.1.0\npython-json-logger==3.3.0\npyyaml==6.0.2\nrealtime==2.4.2\nrequests==2.32.3\nrequests-oauthlib==2.0.0\nrsa==4.9\nsix==1.17.0\nsniffio==1.3.1\nstorage3==0.11.3\nstrenum==0.4.15\nsupabase==2.15.0\nsupafunc==0.9.4\ntyping-extensions==4.13.2\ntyping-inspection==0.4.0\nurllib3==2.4.0\nwebsocket-client==1.8.0\nwebsockets==14.2\nyarl==1.19.0\npython-dotenv\n"
  },
  {
    "path": "services/cyberdesk-operator/tests/README.md",
    "content": "# Testing the Cyberdesk Operator Locally\n\nThis directory contains tests for the Cyberdesk operator that can be run locally against a real Kubernetes cluster, leveraging `kopf.testing.KopfRunner`.\nThis allows for rapid development and testing without needing to build and push a Docker image for every code change.\n\n## Prerequisites\n\n1.  **Running Kubernetes Cluster:** You need access to a Kubernetes cluster (like the AKS cluster deployed via Terraform in the `infra` directory, or Minikube, Kind, etc.).\n2.  **`kubectl` Configured:** Your local `kubectl` must be configured to communicate with your target cluster (`kubectl config current-context` should show the correct context).\n3.  **KubeVirt Installed:** KubeVirt must be installed and running on your cluster.\n    You can typically install it by applying the manifests from the `infra/kubevirt` directory (specifically `kubevirt-operator.yaml` and `kubevirt-cr.yaml`).\n4.  **StartCyberdeskOperator CRD Applied:** The Custom Resource Definition for `StartCyberdeskOperator` must be applied to the cluster *before* running the tests. Apply the dedicated test CRD file:\n    ```bash\n    kubectl apply -f tests/test-start-operator-crd.yaml\n    ```\n5.  **Cyberdesk Namespace:** The `cyberdesk-system` namespace needs to exist, as the test applies resources there. If you haven't created it previously, create it manually:\n    ```bash\n    kubectl create namespace cyberdesk-system --dry-run=client -o yaml | kubectl apply -f -\n    ```\n6.  **Python Environment:** You need a Python environment (ideally a virtual environment) with the operator's dependencies installed:\n    ```bash\n    # Navigate to the operator directory if you aren't already there\n    # cd /path/to/services/cyberdesk-operator\n    \n    # Create venv (if you haven't)\n    # python -m venv .venv\n    # source .venv/bin/activate  # Linux/macOS\n    # .\\.venv\\Scripts\\Activate.ps1 # Windows PowerShell\n    \n    # Install dependencies (including kopf and pytest)\n    pip install -r requirements.txt\n    pip install pytest\n    ```\n7.  **Sufficient User Permissions:** The user account associated with your `kubectl` context needs permissions on the cluster to:\n    *   Get/Create/Delete `StartCyberdeskOperator` resources in the `cyberdesk-system` namespace.\n    *   Get/Create/Delete `Cyberdesk` resources in the `cyberdesk-system` namespace.\n    *   Get/Create/Delete `VirtualMachine` resources in the `cyberdesk-system` namespace.\n    *   Get `CustomResourceDefinition` resources (cluster-wide).\n    *(If you are a cluster admin, you likely have these permissions already).*\n\n## Running the Tests\n\n1.  Ensure all prerequisites are met.\n2.  Navigate to the `services/cyberdesk-operator` directory in your terminal.\n3.  Run `pytest` targeting the test file:\n    ```bash\n    pytest tests/test_operator.py -v -s\n    ```\n    *   `-v`: Verbose output.\n    *   `-s`: Show logs (`print` statements and `logging` output) during test execution.\n\n## Understanding the Test (`test_operator.py`)\n\n1.  **Setup:**\n    *   It defines paths to necessary CR manifests (`start-cyberdesk-operator-cr.yaml`, `cyberdesk-cr.yaml`).\n    *   It includes a helper function `run_kubectl` to execute `kubectl` commands via `subprocess`.\n    *   It uses `pytest.mark.skipif` to skip the test if `kubectl` isn't configured.\n2.  **`KopfRunner` Execution:**\n    *   `with kopf.testing.KopfRunner(...) as runner:` starts your operator's `main.py` script in a background thread using your local Python environment.\n    *   The operator code connects to your configured Kubernetes cluster using `load_kube_config()`.\n3.  **Test Steps (within the `with` block):**\n    *   **Apply `StartCyberdeskOperator` CR:** Uses `run_kubectl` to apply the trigger CR.\n    *   **Wait & Verify CRD:** Waits and repeatedly checks (using `run_kubectl get crd`) if the `Cyberdesk` CRD (`cyberdesks.cyberdesk.io`) has been created by the operator. Asserts that it appears and that the operator logged a success message (`runner.stdout`).\n    *   **Apply `Cyberdesk` CR:** Uses `run_kubectl` to apply the sample `Cyberdesk` instance.\n    *   **Wait & Verify VM:** Waits and repeatedly checks (using `run_kubectl get virtualmachine`) if the corresponding `VirtualMachine` object has been created in the correct namespace. Asserts its existence and checks the operator logs (`runner.stdout`) for creation messages.\n    *   **Cleanup:** Uses `run_kubectl delete` to remove the `Cyberdesk` and `StartCyberdeskOperator` CRs created during the test.\n    *   **Wait & Verify VM Deletion:** Waits and checks if the `VirtualMachine` object is deleted (as a consequence of the `Cyberdesk` CR being deleted).\n4.  **Post-Run Assertions:**\n    *   After the `with` block finishes (the operator stops), it checks `runner.exit_code` and `runner.exception` to ensure the operator process ran without errors.\n\n## Debugging\n\n*   **Operator Logs:** The `-s` flag with `pytest` prints the operator's logs directly to your console, making it easy to see what it's doing or where it failed.\n*   **`kubectl`:** While the test is running (especially during the `time.sleep` pauses), you can use `kubectl get ...`, `kubectl describe ...`, and `kubectl logs ...` (if the *actual* operator deployment were running, which it isn't here) in a separate terminal to inspect the state of the cluster.\n*   **Python Debugger:** Since the operator runs as a local Python process, you can use standard Python debugging tools! Add `import pdb; pdb.set_trace()` in your `handlers/controller.py` code where you want to pause, then run the `pytest` command. Execution will stop at the breakpoint, allowing you to inspect variables and step through the code. "
  },
  {
    "path": "services/cyberdesk-operator/tests/test-cyberdesk-cr.yaml",
    "content": "apiVersion: cyberdesk.io/v1alpha1\nkind: Cyberdesk\nmetadata:\n  name: 6ebf5303-1e81-4203-b870-ccfeb590d02f\n  namespace: cyberdesk-system\nspec:\n  timeoutMs: 86400000 # 24 hour timeout\n"
  },
  {
    "path": "services/cyberdesk-operator/tests/test-start-operator-cr.yaml",
    "content": "apiVersion: cyberdesk.io/v1alpha1\nkind: StartCyberdeskOperator\nmetadata:\n  name: bootstrap-cyberdesk-setup \n  namespace: cyberdesk-system\nspec:\n  {} "
  },
  {
    "path": "services/cyberdesk-operator/tests/test-start-operator-crd.yaml",
    "content": "# CRD for the trigger resource - needed for local KopfRunner tests\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  # Note: The name MUST match the one the operator expects: startcyberdeskoperators.cyberdesk.io\n  name: startcyberdeskoperators.cyberdesk.io \nspec:\n  group: cyberdesk.io\n  names:\n    kind: StartCyberdeskOperator\n    plural: startcyberdeskoperators\n    singular: startcyberdeskoperator\n    shortNames:\n      - sco\n  scope: Namespaced\n  versions:\n    - name: v1alpha1\n      served: true\n      storage: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            spec:\n              type: object\n              description: \"Specification for triggering the Cyberdesk operator setup. Currently holds no fields.\"\n              # No specific fields needed for now, just the presence triggers the action "
  },
  {
    "path": "services/cyberdesk-operator/tests/test.py",
    "content": "import time\nimport subprocess\nfrom kopf.testing import KopfRunner\n\ndef test_operator():\n    with KopfRunner(['run', '-A', '--verbose', 'handlers/controller.py']) as runner:\n        # do something while the operator is running.\n\n        subprocess.run(\"kubectl apply -f examples/obj.yaml\", shell=True, check=True)\n        time.sleep(1)  # give it some time to react and to sleep and to retry\n\n        subprocess.run(\"kubectl delete -f examples/obj.yaml\", shell=True, check=True)\n        time.sleep(1)  # give it some time to react\n\n    assert runner.exit_code == 0\n    assert runner.exception is None\n    assert 'And here we are!' in runner.stdout\n    assert 'Deleted, really deleted' in runner.stdout"
  },
  {
    "path": "services/gateway/Dockerfile",
    "content": "FROM python:3.11-slim\n\nWORKDIR /app\n\n# Install dependencies\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\n# Copy app code + noVNC client\nCOPY main.py .\nCOPY noVNC/ ./noVNC/\n\n# Expose HTTP port\nEXPOSE 80\n\n# Launch with Uvicorn\nCMD [\"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"80\"]\n"
  },
  {
    "path": "services/gateway/README.md",
    "content": "# Cyberdesk API Gateway\n\nThis FastAPI application acts as a micro-gateway for the Cyberdesk system. Its primary responsibilities include:\n\n1.  **Serving the noVNC UI:** Provides the static web files for the browser-based VNC client.\n2.  **Proxying VNC WebSockets:** Securely proxies WebSocket connections from the browser's noVNC client to the KubeVirt VNC sub-resource endpoint for a specific Virtual Machine Instance (VMI).\n3.  **Managing Cyberdesk CRs:** Handles API requests to create and delete `Cyberdesk` custom resources within the Kubernetes cluster.\n4.  **(Future)** Proxying terminal commands via execDaemon.\n\n## Running Locally (Development)\n\nThese instructions explain how to run the gateway service locally on your machine (e.g., Windows) for development purposes, connecting to a Kubernetes cluster (like AKS, Minikube, k3d, or Docker Desktop's built-in cluster).\n\n### Prerequisites\n\n1.  **Docker Desktop:** You need Docker installed and running to build and run the container image. Download and install it from [docker.com](https://www.docker.com/products/docker-desktop/).\n2.  **Kubernetes Cluster Access:** You need `kubectl` configured to talk to your Kubernetes cluster.\n    *   **AKS Example:** If using Azure Kubernetes Service (AKS) like the main Cyberdesk deployment, you can fetch credentials using the Azure CLI:\n        ```bash\n        # Login to Azure first if needed: az login\n        az aks get-credentials --resource-group rg-p-scu-kubevirt --name aks-p-scu-kubevirt\n        ```\n        *(Replace the resource group and cluster name if you have a different setup).*\n    *   **Other Clusters:** Ensure your `~/.kube/config` (or `${env:USERPROFILE}\\.kube\\config` on Windows) is correctly set up for `kubectl` to access your cluster.\n\n### Steps\n\n1.  **Build the Docker Image:**\n    Navigate to the `services/gateway` directory in your terminal and run:\n    ```bash\n    docker build -t cyberdesk/gateway:local .\n    ```\n    *(You can change the tag `:local` if you prefer).*\n\n2.  **Adjust Kubeconfig Server Address (If Necessary):**\n    The gateway running inside Docker needs to reach your Kubernetes API server. If your `kubeconfig` file (`${env:USERPROFILE}\\.kube\\config`) has `server:` entries pointing to `https://localhost:...` or `https://127.0.0.1:...`, the container won't be able to connect.\n    *   **Edit the file:** Change `localhost` or `127.0.0.1` to `host.docker.internal`.\n    *   **Example (before):** `server: https://localhost:6443`\n    *   **Example (after):** `server: https://host.docker.internal:6443`\n    *   *(This special DNS name is provided by Docker Desktop).*\n\n3.  **Run the Docker Container:**\n    Open your terminal and run the appropriate command for your shell:\n\n    *   **PowerShell (Windows):**\n        ```powershell\n        docker run --rm -it `\n          -v \"${env:USERPROFILE}\\.kube:/root/.kube:ro\" `\n          --env-file ./.env `\n          -p 3001:80 `\n          cyberdesk/gateway:local\n        ```\n        *(Note: We use `${env:USERPROFILE}\\.kube` to correctly locate your kubeconfig folder on Windows.)*\n\n    *   **Bash (Linux/macOS/WSL):**\n        ```bash\n        docker run --rm -it \\\n          -v ~/.kube:/root/.kube:ro \\\n          --env-file ./.env \\\n          -p 3001:80 \\\n          cyberdesk/gateway:local\n        ```\n        *(Note: We use `~/.kube` for the standard kubeconfig location on Unix-like systems.)*\n\n    **Command Breakdown:**\n    *   `--rm`: Automatically removes the container when it exits.\n    *   `-it`: Runs interactively so you can see logs and stop with Ctrl+C.\n    *   `-v ...:/root/.kube:ro`: **Crucial!** Mounts your host's `.kube` directory (containing the `config` file) into the container at `/root/.kube`. We use the appropriate path for PowerShell or Bash. `:ro` makes it read-only inside the container for safety.\n    *   `-p 3001:80`: Maps port 3001 on your host machine to port 80 inside the container (where Uvicorn runs by default). You can change `3001` if needed.\n    *   `cyberdesk/gateway:local`: The image name and tag you built.\n\n4.  **Access the Service:**\n    The gateway should now be running and accessible at `http://localhost:3001` on your host machine. You can test endpoints like `http://localhost:3001/healthz`.\n\n5.  **For the noVNC stream, you need to run the following command:**\n    ```bash\n    kubectl get pods -n kubevirt\n\n    # Find the pod with id 'virt-launcher-<vm-id>-<pod-id>'\n\n    kubectl port-forward <pod-id> 5901:5901 -n kubevirt\n    ```\n\n    This will forward port 5901 on your host machine to port 5901 on the pod that runs the Kubevirt VM. This allows you to connect to the VM's desktop environment via the noVNC stream via the URL: `http://localhost:3001/vnc/<vm-id>`.\n"
  },
  {
    "path": "services/gateway/main.py",
    "content": "\"\"\"\ncyberdesk_api.py\n\nFastAPI micro-gateway that:\n\n1. Serves the noVNC static front-end.\n2. Proxies a browser WebSocket to a websockify instance (port 5901) in the VM pod.\n3. Creates / deletes *Cyberdesk* custom resources via the Kubernetes API.\n4. Proxies terminal commands to the desired VM, via the execDaemon.\n\nDesigned to run either:\n\n* Inside a Kubernetes cluster (uses ServiceAccount & in-cluster DNS), **or**\n* Locally, picking up ~/.kube/config for dev workflows. Requires manual port-forwarding for VNC.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport logging\nimport os\nimport ssl\nfrom pathlib import Path\nfrom typing import Callable, Awaitable, Optional, List, Any\nimport httpx\nimport websockets\nfrom fastapi import (\n    FastAPI,\n    WebSocket,\n    WebSocketDisconnect,\n    HTTPException,\n    status,\n)\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom fastapi.responses import FileResponse, RedirectResponse, JSONResponse\nfrom fastapi.staticfiles import StaticFiles\nfrom kubernetes import client, config\nfrom kubernetes.client import ApiException, CustomObjectsApi, CoreV1Api\nfrom pydantic import BaseModel, Field\nfrom supabase import create_client, Client\nfrom dotenv import load_dotenv\nimport json\nimport socket\n\n# --------------------------------------------------------------------------- #\n# Logging\n# --------------------------------------------------------------------------- #\n\nlogging.basicConfig(\n    level=logging.INFO,\n    format=\"%(asctime)s │ %(levelname)-8s │ %(name)s │ %(message)s\",\n)\nLOG = logging.getLogger(\"cyberdesk\")\n\n# --------------------------------------------------------------------------- #\n# Constants & Helpers\n# --------------------------------------------------------------------------- #\n\nHOURS = 60 * 60 * 1000  # milliseconds per hour\nDEFAULT_TIMEOUT_MS: int = 24 * HOURS\n\nCYBERDESK_GROUP = \"cyberdesk.io\"\nCYBERDESK_VERSION = \"v1alpha1\"\nCYBERDESK_PLURAL = \"cyberdesks\"\nCYBERDESK_NAMESPACE = \"cyberdesk-system\"\n\nVMI_NAMESPACE = \"kubevirt\"\nGATEWAY_SERVICE_NAME = \"gateway\"\nGATEWAY_NAMESPACE = CYBERDESK_NAMESPACE # Namespace where gateway service runs\n\n# Add KubeVirt constants\nKUBEVIRT_GROUP = \"kubevirt.io\"\nKUBEVIRT_VERSION = \"v1\"\nKUBEVIRT_VMI_PLURAL = \"virtualmachineinstances\"\n\n# --------------------------------------------------------------------------- #\n# Kubernetes client bootstrap\n# --------------------------------------------------------------------------- #\n\n\ndef init_kube_clients() -> tuple[Optional[CustomObjectsApi], Optional[CoreV1Api]]:\n    \"\"\"Attempt to build a Kubernetes CustomObjectsApi, returning *None* on failure.\"\"\"\n    try:\n        config.load_incluster_config()\n        LOG.info(\"Loaded in-cluster Kubernetes config\")\n        return client.CustomObjectsApi(), client.CoreV1Api()\n    except config.ConfigException:\n        try:\n            config.load_kube_config()\n            LOG.info(\"Loaded local ~/.kube/config\")\n            return client.CustomObjectsApi(), client.CoreV1Api()\n        except config.ConfigException as exc:\n            LOG.warning(\"No Kubernetes configuration available: %s\", exc)\n            return None, None\n\n\nK8S_CUSTOM_API, K8S_CORE_V1_API = init_kube_clients()\n\n# --------------------------------------------------------------------------- #\n# Supabase Client Setup\n# --------------------------------------------------------------------------- #\n# Get the directory where main.py is located\nscript_dir = Path(__file__).resolve().parent\n# Construct the path to the .env file in that same directory\ndotenv_path = script_dir / \".env\"\n\n# Load using that specific path\nload_dotenv(dotenv_path=dotenv_path, override=True)\n\nSUPABASE_URL: Optional[str] = os.environ.get(\"SUPABASE_URL\")\nSUPABASE_KEY: Optional[str] = os.environ.get(\"SUPABASE_KEY\")\nSUPABASE_CLIENT: Optional[Client] = None\n\nif SUPABASE_URL and SUPABASE_KEY:\n    try:\n        SUPABASE_CLIENT = create_client(SUPABASE_URL, SUPABASE_KEY)\n        LOG.info(\"Successfully initialized Supabase client.\")\n    except Exception as e:\n        LOG.critical(f\"Failed to initialize Supabase client: {e}\")\n        # Depending on requirements, you might want to prevent startup\n        # raise RuntimeError(f\"Failed to initialize Supabase client: {e}\")\nelse:\n    LOG.warning(\"SUPABASE_URL or SUPABASE_KEY environment variables not set. Supabase integration disabled.\")\n\n\n# --------------------------------------------------------------------------- #\n# Pydantic DTOs\n# --------------------------------------------------------------------------- #\n\n\nclass CyberdeskCreateRequest(BaseModel):\n    \"\"\"Payload for POST /cyberdesk/{vm_id}\"\"\"\n\n    timeout_ms: int = Field(\n        default=DEFAULT_TIMEOUT_MS, alias=\"timeoutMs\", ge=60_000\n    )\n\nclass CommandRequest(BaseModel):\n    \"\"\"Payload for POST /cyberdesk/{vm_id}/execute-command\"\"\"\n    command: str\n\n# --- Response Models ---\n\nclass CyberdeskCreateResponse(BaseModel):\n    \"\"\"Response for POST /cyberdesk/{vm_id}\"\"\"\n    id: str\n\nclass StatusMessageResponse(BaseModel):\n    \"\"\"Generic response model for status and message.\"\"\"\n    status: str\n    message: str\n\nclass CyberdeskReadyResponse(StatusMessageResponse):\n    \"\"\"Response for POST /cyberdesk/{vm_id}/ready\"\"\"\n    stream_url: str\n\nclass VMCommandExecutionResponse(BaseModel):\n    \"\"\"Schema for the response received *from* the VM's /execute-command endpoint.\"\"\"\n    args: List[str]\n    return_code: int\n    stdout: str\n    stderr: str\n    duration_s: float\n\nclass GatewayCommandResponse(BaseModel):\n    \"\"\"Response for POST /cyberdesk/{vm_id}/execute-command\"\"\"\n    status: str\n    vm_status_code: int\n    vm_response: VMCommandExecutionResponse\n\nclass HealthCheckResponse(BaseModel):\n    \"\"\"Response for GET /healthz\"\"\"\n    status: str\n\nclass VmHealthCheckResponse(BaseModel):\n    \"\"\"Response for GET /vm/healthcheck/{vmid}\"\"\"\n    status: str  # Status reported by the VM's health endpoint\n    vm_status_code: int # The HTTP status code received from the VM\n\n\n# --------------------------------------------------------------------------- #\n# FastAPI application\n# --------------------------------------------------------------------------- #\n\napp = FastAPI(title=\"Cyberdesk API Gateway\", version=\"1.0\")\n\n# Optional: allow the browser UI to be hosted from another domain.\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=os.getenv(\"CYBERDESK_CORS_ALLOW_ORIGINS\", \"*\").split(\",\"),\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n\n# Static noVNC artefacts live relative to this file.\nBASE_DIR = Path(__file__).resolve().parent\nNOVNC_DIR = BASE_DIR / \"noVNC\"\napp.mount(\"/static\", StaticFiles(directory=NOVNC_DIR), name=\"static\")\n\n\n# --------------------------------------------------------------------------- #\n# Routes – HTML / root\n# --------------------------------------------------------------------------- #\n\n\n@app.get(\"/vnc/{vm_id}\", include_in_schema=False)\nasync def serve_novnc(vm_id: str) -> FileResponse:\n    \"\"\"\n    Serve the main noVNC HTML page.\n\n    The client-side JavaScript will subsequently establish a WebSocket\n    back to `/vnc/ws/{vm_id}`.\n    \"\"\"\n    return FileResponse(NOVNC_DIR / \"vnc.html\")\n\n\n# --------------------------------------------------------------------------- #\n# WebSocket proxy\n# --------------------------------------------------------------------------- #\n\nasync def _relay(\n    recv: Callable[[], Awaitable[bytes]],\n    send: Callable[[bytes], Awaitable[None]],\n) -> None:\n    \"\"\"\n    Copy bytes from *recv* to *send* until EOF or a normal WebSocket shutdown.\n    \"\"\"\n    try:\n        while True:\n            await send(await recv())\n    except (WebSocketDisconnect, websockets.exceptions.ConnectionClosed):\n        # A graceful close on either side ends the task.\n        return\n\n\n@app.websocket(\"/vnc/ws/{vm_id}\")\nasync def proxy_vnc(websocket: WebSocket, vm_id: str) -> None:\n    \"\"\"Proxy WebSocket to the target VMI's VNC port.\n\n    Connects via VMI IP if running in-cluster.\n    Connects via host.docker.internal:5901 if running locally\n    (requires manual `kubectl port-forward pod/<vmi-pod> 5901:5901`).\n    \"\"\"\n    await websocket.accept()\n    LOG.info(\"VNC WebSocket opened for instance ID: %s\", vm_id)\n\n    target_uri: Optional[str] = None\n    target_port = 5901 # Standard VNC port\n\n    try:\n        # --- Step 1: Determine Environment ---\n        token_path = Path(\"/var/run/secrets/kubernetes.io/serviceaccount/token\")\n        in_cluster = token_path.exists()\n        LOG.info(f\"Running in-cluster: {in_cluster}\")\n\n        # --- Step 2: Get CR & VMI for validation (and IP if in-cluster) ---\n        k8s_custom = require_k8s() # Ensure K8S client is available\n        vm_name: Optional[str] = None\n        vmi_ip: Optional[str] = None\n\n        try:\n            cr = k8s_custom.get_namespaced_custom_object(\n                group=CYBERDESK_GROUP,\n                version=CYBERDESK_VERSION,\n                namespace=CYBERDESK_NAMESPACE,\n                plural=CYBERDESK_PLURAL,\n                name=vm_id,\n            )\n            vm_name = cr.get(\"status\", {}).get(\"cyberdesk_create\", {}).get(\"virtualMachineRef\")\n            if not vm_name:\n                raise ValueError(f\"virtualMachineRef not found in status for Cyberdesk {vm_id}\")\n            LOG.info(\"Found virtualMachineRef '%s' for instance %s\", vm_name, vm_id)\n\n        except ApiException as e:\n            if e.status == 404:\n                raise ValueError(f\"Cyberdesk CR '{vm_id}' not found.\") from e\n            else:\n                raise ValueError(f\"API Error fetching Cyberdesk CR '{vm_id}': {e.reason}\") from e\n        except ValueError as e:\n            LOG.error(str(e))\n            await websocket.close(code=1011, reason=str(e))\n            return\n\n        try:\n            vmi = k8s_custom.get_namespaced_custom_object(\n                group=KUBEVIRT_GROUP,\n                version=KUBEVIRT_VERSION,\n                namespace=VMI_NAMESPACE,\n                plural=KUBEVIRT_VMI_PLURAL,\n                name=vm_name,\n            )\n            interfaces = vmi.get('status', {}).get('interfaces', [])\n            vmi_ip = interfaces[0].get('ipAddress') if interfaces else None\n            vmi_phase = vmi.get('status', {}).get('phase')\n\n            if vmi_phase != 'Running':\n                raise ValueError(f\"Target VMI '{vm_name}' is not Running (phase: {vmi_phase}).\")\n            if in_cluster and not vmi_ip:\n                # Only strictly need IP if in-cluster\n                raise ValueError(f\"Target VMI '{vm_name}' is Running but has no IP address (needed for in-cluster connection).\" )\n            LOG.info(\"Target VMI '%s' is Running. IP: %s\", vm_name, vmi_ip if vmi_ip else \"N/A (local)\")\n\n        except ApiException as e:\n            if e.status == 404:\n                 raise ValueError(f\"VMI '{vm_name}' not found.\") from e\n            else:\n                 raise ValueError(f\"API Error fetching VMI '{vm_name}': {e.reason}\") from e\n        except ValueError as e:\n            LOG.error(str(e))\n            await websocket.close(code=1011, reason=str(e))\n            return\n\n        # --- Step 3: Determine Target URI based on environment ---\n        if in_cluster:\n            if not vmi_ip: # Should have been caught above, but defensive check\n                 raise ValueError(\"Logic error: In-cluster but VMI IP is missing.\")\n            target_uri = f\"ws://{vmi_ip}:{target_port}\"\n            LOG.info(\"Connecting via VMI IP (in-cluster): %s\", target_uri)\n        else:\n            # Assume manual port-forward `kubectl port-forward pod/<vmi-pod> 5901:5901` is running\n            target_uri = f\"ws://host.docker.internal:{target_port}\"\n            LOG.info(\"Connecting via Docker host (local): %s (Requires manual port-forward)\", target_uri)\n\n        # --- Step 4: Establish connection and relay ---\n        LOG.info(\"Attempting WebSocket connection to VMI VNC at %s\", target_uri)\n        async with websockets.connect(target_uri, ping_interval=None, open_timeout=10) as vmi_ws:\n            LOG.info(\"Successfully connected to VMI VNC at %s\", target_uri)\n\n            # Start two tasks to relay messages in both directions\n            consumer_task = asyncio.create_task(\n                _relay(websocket.receive_bytes, vmi_ws.send)\n            )\n            producer_task = asyncio.create_task(\n                _relay(vmi_ws.recv, websocket.send_bytes) # type: ignore[arg-type] -- websockets.recv() returns Data\n            )\n\n            # Wait for either task to complete (or raise an exception)\n            done, pending = await asyncio.wait(\n                [consumer_task, producer_task],\n                return_when=asyncio.FIRST_COMPLETED,\n            )\n\n            # Cancel pending tasks to clean up resources\n            for task in pending:\n                task.cancel()\n\n            # Raise exceptions if any task failed\n            for task in done:\n                if task.exception():\n                    raise task.exception()\n\n    except (websockets.exceptions.ConnectionClosedError, websockets.exceptions.ConnectionClosedOK) as e:\n        LOG.info(\"VNC WebSocket connection closed cleanly: %s\", e)\n    except WebSocketDisconnect as e:\n        LOG.info(\"Browser WebSocket disconnected: %s\", e.code)\n    except Exception as e:\n        LOG.error(\"VNC proxy error: %s\", e, exc_info=True)\n        # Attempt to close the browser WebSocket with an error code\n        try:\n            await websocket.close(code=1011, reason=f\"Proxy error: {e}\")\n        except RuntimeError: # Handle cases where socket might already be closed\n            pass\n    finally:\n        LOG.info(\"VNC WebSocket closed for instance ID: %s\", vm_id)\n\n\n# --------------------------------------------------------------------------- #\n# Cyberdesk custom-resource endpoints\n# --------------------------------------------------------------------------- #\n\n\ndef require_k8s() -> CustomObjectsApi:\n    \"\"\"Return a live CustomObjectsApi or raise 503 HTTPException.\"\"\"\n    if K8S_CUSTOM_API is None:\n        raise HTTPException(\n            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,\n            detail=\"Kubernetes client not configured.\",\n        )\n    return K8S_CUSTOM_API\n\ndef require_k8s_core() -> CoreV1Api:\n    \"\"\"Return a live CoreV1Api or raise 503 HTTPException.\"\"\"\n    if K8S_CORE_V1_API is None:\n        raise HTTPException(\n            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,\n            detail=\"Kubernetes CoreV1Api client not configured.\",\n        )\n    return K8S_CORE_V1_API\n\ndef require_supabase() -> Client:\n    \"\"\"Return a live Supabase client or raise 503 HTTPException.\"\"\"\n    \n    if SUPABASE_CLIENT is None:\n         raise HTTPException(\n            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,\n            detail=\"Supabase client not configured (missing SUPABASE_URL/KEY env vars?).\",\n        )\n    return SUPABASE_CLIENT\n\n# --- Helper Function to Update Supabase ---\nasync def update_supabase_instance(vm_id: str, stream_url: str):\n    \"\"\"Updates the Supabase instance entry with stream URL and status.\"\"\"\n    supabase_client = require_supabase()\n    try:\n        # Run blocking Supabase call in a separate thread\n        # Assign the entire response object\n        response = await asyncio.to_thread(\n            lambda: supabase_client.table(\"cyberdesk_instances\")\n            .update({\"status\": \"running\", \"stream_url\": stream_url})\n            .eq(\"id\", vm_id)\n            .execute()\n        )\n        # Log the actual response structure for clarity (can be removed later)\n        LOG.info(f\"Supabase raw response object for {vm_id}: {response}\")\n\n        # Access the data list via response.data\n        updated_data = response.data\n        # Access count via response.count (though not strictly needed for the check here)\n        # updated_count = response.count\n\n        LOG.info(f\"Supabase update response for {vm_id}: data={updated_data}, count={getattr(response, 'count', 'N/A')}\")\n\n        # Check if the update was successful using the actual data list\n        if isinstance(updated_data, list) and len(updated_data) > 0 and updated_data[0]:\n            LOG.info(f\"Successfully updated Supabase for instance {vm_id}\")\n            return True\n        else:\n            # This might happen if the row doesn't exist or based on Supabase return preferences\n            LOG.warning(f\"Supabase update for instance {vm_id} completed, but response data indicates no rows updated or an unexpected format: {updated_data}\")\n            return False\n    except Exception as e:\n        LOG.exception(f\"Error updating Supabase for instance {vm_id}: {e}\")\n        return False\n\n@app.post(\n    \"/cyberdesk/{vm_id}\",\n    status_code=status.HTTP_201_CREATED,\n    response_model=CyberdeskCreateResponse\n)\nasync def create_cyberdesk(vm_id: str, payload: CyberdeskCreateRequest):\n    \"\"\"Create a Cyberdesk CR in the cluster.\"\"\"\n    api = require_k8s()\n\n    body = {\n        \"apiVersion\": f\"{CYBERDESK_GROUP}/{CYBERDESK_VERSION}\",\n        \"kind\": \"Cyberdesk\",\n        \"metadata\": {\"name\": vm_id, \"namespace\": CYBERDESK_NAMESPACE},\n        \"spec\": {\"timeoutMs\": payload.timeout_ms},\n    }\n\n    try:\n        resp = await asyncio.to_thread(\n            api.create_namespaced_custom_object,\n            group=CYBERDESK_GROUP,\n            version=CYBERDESK_VERSION,\n            namespace=CYBERDESK_NAMESPACE,\n            plural=CYBERDESK_PLURAL,\n            body=body,\n        )\n        return {\"id\": resp[\"metadata\"][\"name\"]}\n    except ApiException as exc:\n        LOG.error(\"Kubernetes API error: %s\", exc, exc_info=False)\n        if exc.status == 409:\n            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=\"Already exists\") from exc\n        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.reason) from exc\n\n\n@app.post(\n    \"/cyberdesk/{vm_id}/stop\",\n    status_code=status.HTTP_200_OK,\n    response_model=StatusMessageResponse\n)\nasync def stop_cyberdesk(vm_id: str):\n    \"\"\"Delete a Cyberdesk CR from the cluster.\"\"\"\n    api = require_k8s()\n\n    try:\n        await asyncio.to_thread(\n            api.delete_namespaced_custom_object,\n            group=CYBERDESK_GROUP,\n            version=CYBERDESK_VERSION,\n            namespace=CYBERDESK_NAMESPACE,\n            plural=CYBERDESK_PLURAL,\n            name=vm_id,\n            body=client.V1DeleteOptions(),\n        )\n        # Return a dictionary matching the response model\n        return {\"status\": \"success\", \"message\": f\"Deletion of '{vm_id}' initiated.\"}\n    except ApiException as exc:\n        LOG.error(\"Kubernetes API error: %s\", exc, exc_info=False)\n        if exc.status == 404:\n            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=\"Not found\") from exc\n        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.reason) from exc\n\n# --- NEW Endpoint ---\n@app.post(\n    \"/cyberdesk/{vm_id}/ready\",\n    status_code=status.HTTP_200_OK,\n    response_model=CyberdeskReadyResponse\n)\nasync def cyberdesk_ready(vm_id: str):\n    \"\"\"\n    Signal that a VM is ready. Updates Supabase with the stream URL.\n    \"\"\"\n    LOG.info(f\"Received ready signal for VM: {vm_id}\")\n    # 2. Construct Stream URL\n    # Assuming default HTTP port 80 for the gateway service\n    stream_url = f\"https://gateway.cyberdesk.io/vnc/{vm_id}\"\n    LOG.info(f\"Constructed stream URL for {vm_id}: {stream_url}\")\n\n    # 3. Update Supabase\n    success = await update_supabase_instance(vm_id, stream_url)\n\n    if success:\n        LOG.info(f\"Successfully processed ready signal for {vm_id}\")\n        # Return a dictionary matching the response model\n        return {\"status\": \"success\", \"message\": f\"Instance {vm_id} marked as running.\", \"stream_url\": stream_url}\n    else:\n        LOG.error(f\"Failed to update Supabase for {vm_id} after getting IP.\")\n        # Indicate failure - maybe the instance ID was wrong or DB issue\n        raise HTTPException(\n            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,\n            detail=f\"Failed to update instance status in database for {vm_id}.\"\n        )\n\n@app.post(\n    \"/cyberdesk/{vm_id}/execute-command\",\n    response_model=GatewayCommandResponse\n)\nasync def execute_vm_command(vm_id: str, payload: CommandRequest):\n    \"\"\"\n    Sends a command string to the execute-command endpoint of the specified VM\n    using the proxy helper.\n    \"\"\"\n    command_port = 8000\n    command_path = \"execute-command\" # Path on the VM service\n    command_timeout = 30.0 # Longer timeout for potential command execution\n\n    command_to_execute = payload.command\n    # IMPORTANT: Ensure the receiving service expects this JSON structure\n    request_payload = {\"cmd\": command_to_execute}\n\n    LOG.info(f\"Attempting command execution for VM {vm_id}: '{command_to_execute[:80]}...'\")\n\n    try:\n        status_code, response_json = await _proxy_request_to_vm(\n            vmid=vm_id,\n            port=command_port,\n            path=command_path,\n            method=\"POST\",\n            json_payload=request_payload,\n            timeout=command_timeout\n        )\n\n        # Check if the VM's command endpoint returned a success status\n        if 200 <= status_code < 300:\n            LOG.info(f\"Command execution for {vm_id} successful: {status_code}, Response: { response_json}\")\n            return {\n                \"status\": \"success\",\n                \"vm_status_code\": status_code,\n                \"vm_response\": {\n                    \"args\": response_json[\"args\"],\n                    \"return_code\": response_json[\"return_code\"],\n                    \"stdout\": response_json[\"stdout\"],\n                    \"stderr\": response_json[\"stderr\"],\n                    \"duration_s\": response_json[\"duration_s\"]\n                }\n            }\n        else:\n            # VM is reachable, but the command endpoint returned an error\n            LOG.error(f\"VM {vm_id} command execution failed with status {status_code}.\")\n            raise HTTPException(\n                status_code=502, # Bad Gateway, as the upstream VM endpoint failed\n                detail=f\"VM {vm_id} command execution failed: {status_code}\",\n                headers={\"X-VM-Status-Code\": str(status_code)}\n            )\n\n    except HTTPException as e:\n         # Re-raise known HTTP exceptions from the proxy helper\n         LOG.error(f\"Command execution failed for {vm_id} due to proxy error: {e.status_code} - {e.detail}\")\n         raise e\n    except Exception as e:\n        # Catch any other unexpected errors during the process\n        LOG.exception(f\"Unexpected error during command execution processing for {vm_id}: {e}\")\n        raise HTTPException(status_code=500, detail=f\"An unexpected error occurred during command execution for VM {vm_id}\")\n\n\n# --------------------------------------------------------------------------- #\n# Liveness / readiness\n# --------------------------------------------------------------------------- #\n\n\n@app.get(\"/healthz\", response_model=HealthCheckResponse)\nasync def health_check():\n    \"\"\"Kubernetes livenessProbe target.\"\"\"\n    return {\"status\": \"ok\"}\n\n@app.get(\"/vm/healthcheck/{vmid}\", response_model=VmHealthCheckResponse)\nasync def vm_health_check(vmid: str):\n    \"\"\"\n    Performs a health check on the specified VM instance using the proxy helper.\n    \"\"\"\n    health_port = 8000\n    health_path = \"health\"\n\n    try:\n        status_code, response_json = await _proxy_request_to_vm(\n            vmid=vmid,\n            port=health_port,\n            path=health_path,\n            method=\"GET\",\n            timeout=10.0\n        )\n\n        # Check if the VM's health endpoint returned a success status\n        if 200 <= status_code < 300:\n            LOG.info(f\"Health check for {vmid} successful: {status_code}\")\n            return {\n                \"status\": \"ok\",\n                \"vm_status_code\": status_code\n            }\n        else:\n            # VM is reachable, but reported unhealthy\n            LOG.warning(f\"VM {vmid} health check failed with status {status_code}\")\n            raise HTTPException(\n                status_code=502, # Bad Gateway, as the upstream VM is unhealthy\n                detail=f\"VM {vmid} health check reported failure: {status_code}\",\n                headers={\"X-VM-Status-Code\": str(status_code)}\n            )\n\n    except HTTPException as e:\n         # Re-raise known HTTP exceptions from the proxy helper\n         # (e.g., 404 if pod not found, 503 if connection failed, 504 timeout)\n         LOG.error(f\"Health check failed for {vmid} due to proxy error: {e.status_code} - {e.detail}\")\n         raise e\n    except Exception as e:\n        # Catch any other unexpected errors during the process\n        LOG.exception(f\"Unexpected error during health check processing for {vmid}: {e}\")\n        raise HTTPException(status_code=500, detail=f\"An unexpected error occurred during health check for VM {vmid}\")\n\n# --- Generic VM Pod Communication Helper ---\n\nasync def _proxy_request_to_vm(\n    vmid: str,\n    port: int,\n    path: str,\n    method: str = \"GET\",\n    json_payload: Optional[dict] = None,\n    timeout: float = 10.0\n) -> tuple[int, Any]:\n    \"\"\"\n    Sends an HTTP request to a specific port/path on the VM pod.\n\n    Handles routing via internal DNS (in-cluster) or K8s API proxy (local).\n    Finds the correct virt-launcher pod name when running locally.\n\n    Args:\n        vmid: The target Virtual Machine ID.\n        port: The target port on the VM pod.\n        path: The target URL path on the VM pod (e.g., \"health\", \"execute-command\").\n        method: HTTP method (\"GET\", \"POST\", etc.).\n        json_payload: Optional dictionary to send as JSON body (for POST/PUT).\n        timeout: Request timeout in seconds.\n\n    Returns:\n        A tuple containing (status_code, response_body). The response_body\n        will be a parsed JSON object (dict/list) if possible, otherwise raw text.\n\n    Raises:\n        HTTPException: If the request fails due to connection errors, timeouts,\n                       API errors, non-2xx VM responses, or pod lookup issues.\n    \"\"\"\n    vm_namespace = \"kubevirt\"\n    path = path.lstrip('/') # Ensure path doesn't start with /\n\n    # Check if running in-cluster\n    token_path = Path(\"/var/run/secrets/kubernetes.io/serviceaccount/token\")\n    in_cluster = token_path.exists()\n\n    if in_cluster:\n        # --- In-Cluster Logic (Get VMI IP) ---\n        LOG.info(f\"Proxying {method} to VM {vmid} (in-cluster) via IP lookup -> :{port}/{path}\")\n        k8s_custom = require_k8s()\n        vm_name: Optional[str] = None\n        vmi_ip: Optional[str] = None\n        target_url: Optional[str] = None\n\n        try:\n            # 1. Get CR to find VM name\n            try:\n                cr = k8s_custom.get_namespaced_custom_object(\n                    group=CYBERDESK_GROUP,\n                    version=CYBERDESK_VERSION,\n                    namespace=CYBERDESK_NAMESPACE,\n                    plural=CYBERDESK_PLURAL,\n                    name=vmid, # vmid is the instance ID here\n                )\n                vm_name = cr.get(\"status\", {}).get(\"cyberdesk_create\", {}).get(\"virtualMachineRef\")\n                if not vm_name:\n                    raise ValueError(f\"virtualMachineRef not found in status for Cyberdesk {vmid}\")\n                LOG.debug(f\"Found virtualMachineRef '{vm_name}' for instance {vmid}\")\n            except ApiException as e:\n                if e.status == 404:\n                    raise ValueError(f\"Cyberdesk CR '{vmid}' not found.\") from e\n                else:\n                    raise ValueError(f\"API Error fetching Cyberdesk CR '{vmid}': {e.reason}\") from e\n\n            # 2. Get VMI to find IP address\n            try:\n                vmi = k8s_custom.get_namespaced_custom_object(\n                    group=KUBEVIRT_GROUP,\n                    version=KUBEVIRT_VERSION,\n                    namespace=vm_namespace, # Defined earlier in function\n                    plural=KUBEVIRT_VMI_PLURAL,\n                    name=vm_name,\n                )\n                interfaces = vmi.get('status', {}).get('interfaces', [])\n                vmi_ip = interfaces[0].get('ipAddress') if interfaces else None\n                vmi_phase = vmi.get('status', {}).get('phase')\n\n                if vmi_phase != 'Running':\n                     raise ValueError(f\"Target VMI '{vm_name}' is not Running (phase: {vmi_phase}).\")\n                if not vmi_ip:\n                     raise ValueError(f\"Target VMI '{vm_name}' is Running but has no IP address.\")\n                LOG.debug(f\"Found target VMI IP '{vmi_ip}' for VM '{vm_name}'\")\n            except ApiException as e:\n                if e.status == 404:\n                     raise ValueError(f\"VMI '{vm_name}' not found.\") from e\n                else:\n                     raise ValueError(f\"API Error fetching VMI '{vm_name}': {e.reason}\") from e\n\n            # 3. Construct Target URL\n            target_url = f\"http://{vmi_ip}:{port}/{path}\"\n            LOG.debug(f\"Target URL (in-cluster, via IP): {target_url}\")\n\n        except ValueError as e:\n             # Handle all lookup errors gracefully\n             LOG.error(f\"Failed lookup for VM {vmid} proxy target: {e}\")\n             raise HTTPException(status_code=404, detail=f\"Target VM or its resources not found/ready: {e}\")\n\n        # --- Make Request using IP-based URL ---\n        async with httpx.AsyncClient(timeout=timeout) as client:\n            try:\n                response = await client.request(\n                    method,\n                    target_url,\n                    json=json_payload # httpx handles None payload correctly\n                )\n                response.raise_for_status() # Raise exception for 4xx/5xx responses\n\n                # Attempt to parse response as JSON\n                try:\n                    json_response = response.json()\n                    LOG.debug(f\"Successfully parsed JSON response from {target_url}\")\n                    return response.status_code, json_response\n                except Exception as json_exc: # Catches JSONDecodeError and others\n                    LOG.warning(f\"Failed to parse response from {target_url} as JSON: {json_exc}. Returning raw text.\")\n                    return response.status_code, response.text\n            except httpx.TimeoutException:\n                LOG.error(f\"Timeout connecting to VM {vmid} (in-cluster) at {target_url}\")\n                raise HTTPException(status_code=504, detail=f\"Request timed out connecting to VM {vmid}\")\n            except httpx.ConnectError as e:\n                LOG.error(f\"Connection error to VM {vmid} (in-cluster) at {target_url}: {e}\")\n                raise HTTPException(status_code=503, detail=f\"Could not connect to VM {vmid} (DNS issue?): {e}\")\n            except Exception as e:\n                LOG.exception(f\"Unexpected error during httpx request to VM {vmid} (in-cluster): {e}\")\n                raise HTTPException(status_code=500, detail=f\"Unexpected error connecting to VM {vmid}\")\n\n    else:\n        # --- Local Logic (Kubernetes API Proxy) ---\n        LOG.info(f\"Proxying {method} to VM {vmid} (local) via K8s API -> :{port}/{path}\")\n        core_api = require_k8s_core() # Ensures K8s client is loaded\n        k8s_custom = require_k8s()   # Need custom objects API as well\n        api_client = core_api.api_client # Get the underlying ApiClient\n\n        # 1. Find the VM Name from CR\n        vm_name: Optional[str] = None\n        try:\n            cr = k8s_custom.get_namespaced_custom_object(\n                group=CYBERDESK_GROUP,\n                version=CYBERDESK_VERSION,\n                namespace=CYBERDESK_NAMESPACE,\n                plural=CYBERDESK_PLURAL,\n                name=vmid,\n            )\n            vm_name = cr.get(\"status\", {}).get(\"cyberdesk_create\", {}).get(\"virtualMachineRef\")\n            if not vm_name:\n                raise ValueError(f\"virtualMachineRef not found in status for Cyberdesk {vmid}\")\n            LOG.debug(f\"Found virtualMachineRef '{vm_name}' for instance {vmid}\")\n        except ApiException as e:\n            if e.status == 404:\n                raise HTTPException(status_code=404, detail=f\"Cyberdesk CR '{vmid}' not found.\") from e\n            else:\n                raise HTTPException(status_code=500, detail=f\"API Error fetching Cyberdesk CR '{vmid}': {e.reason}\") from e\n        except ValueError as e:\n            LOG.error(str(e))\n            raise HTTPException(status_code=404, detail=str(e))\n\n        # 2. Find the Pod Name using the VM Name by checking annotations\n        pod_name: Optional[str] = None\n        running_pod_found = False\n        try:\n            LOG.debug(f\"Listing pods in namespace '{vm_namespace}' to find one for VM '{vm_name}'.\")\n            # List all pods in the namespace - might need adjustment if too many pods\n            pod_list_response = await asyncio.to_thread(\n                core_api.list_namespaced_pod,\n                namespace=vm_namespace,\n                _request_timeout=10 # Increase timeout slightly for list operation\n            )\n            pods = pod_list_response.items\n            LOG.debug(f\"Found {len(pods)} pods in namespace. Iterating to find match.\")\n\n            for pod in pods:\n                annotations = pod.metadata.annotations\n                pod_domain = annotations.get(\"kubevirt.io/domain\")\n                \n                # Check if annotation matches the target VM name\n                if pod_domain == vm_name:\n                    pod_name = pod.metadata.name\n                    pod_phase = pod.status.phase\n                    LOG.debug(f\"Found candidate pod '{pod_name}' with matching domain annotation. Phase: {pod_phase}\")\n                    if pod_phase == \"Running\":\n                        LOG.info(f\"Found running virt-launcher pod '{pod_name}' for VM '{vm_name}'.\")\n                        running_pod_found = True\n                        break # Found the running pod we need\n                    else:\n                        # Found a pod, but it's not running. Keep looking in case\n                        # there's an older non-running one and a newer running one somehow.\n                         LOG.warning(f\"Found pod {pod_name} for VM {vm_name}, but phase is {pod_phase}. Continuing search.\")\n                         pod_name = None # Reset pod_name if not running\n                \n            if not running_pod_found:\n                 # If loop finishes and we didn't find a running pod\n                 if pod_name:\n                      # We found a pod but it wasn't running\n                      raise HTTPException(status_code=503, detail=f\"VM pod {pod_name} for {vm_name} found but not in Running phase.\")\n                 else:\n                      # We didn't find any pod with the matching annotation\n                      LOG.warning(f\"No virt-launcher pod found with annotation kubevirt.io/domain={vm_name}\")\n                      raise HTTPException(status_code=404, detail=f\"VM pod for {vm_name} not found.\")\n\n        except ApiException as e:\n            LOG.error(f\"K8s API error listing pods for {vm_name}: {e.status} {e.reason}\")\n            raise HTTPException(status_code=500, detail=f\"API error listing pods for VM {vm_name}: {e.reason}\")\n\n        if not pod_name:\n             # Should be caught above, but defensive check\n             raise HTTPException(status_code=500, detail=\"Could not determine pod name\")\n\n        # 3. Make the Proxied Request (using the found pod_name)\n        api_proxy_path = f\"/api/v1/namespaces/{vm_namespace}/pods/{pod_name}:{port}/proxy/{path}\"\n        LOG.debug(f\"Attempting {method} via K8s API proxy path: {api_proxy_path}\")\n\n        try:\n            # Prepare arguments for call_api\n            call_api_args = {\n                'resource_path': api_proxy_path,\n                'method': method,\n                'auth_settings': ['BearerToken'],\n                'response_type': 'str', # Expect text back\n                '_request_timeout': timeout\n            }\n            header_params = {}\n            # Set body and Content-Type for methods that have payloads\n            if json_payload is not None and method in [\"POST\", \"PUT\", \"PATCH\"]:\n                call_api_args['body'] = json_payload\n                header_params['Content-Type'] = api_client.select_header_content_type(['application/json'])\n                call_api_args['header_params'] = header_params\n\n            # Run synchronous call_api in thread\n            response_data = await asyncio.to_thread(\n                api_client.call_api,\n                **call_api_args\n            )\n\n            # response_data = (data, status_code, headers)\n            status_code = response_data[1]\n            response_text = response_data[0]\n            LOG.debug(f\"K8s API proxy request to VM {vmid} completed with status: {status_code}\")\n            LOG.debug(f\"Response data: {response_data}...\")\n            # Attempt to parse response as JSON, fall back to text\n            try:\n                corrected_json_string = response_text.replace(\"'\", '\"') # Basic, might break\n                json_response = json.loads(corrected_json_string)\n                LOG.debug(f\"Successfully parsed JSON response from K8s proxy for {vmid}\")\n                return status_code, json_response\n            except json.JSONDecodeError:\n                LOG.warning(f\"Failed to parse response from K8s proxy for {vmid} as JSON. Returning raw text. {response_text[:100]}...\")\n                return status_code, response_text\n\n        except ApiException as e:\n            LOG.error(f\"K8s API error during proxy request to {vmid}: {e.status} {e.reason} - Body: {e.body}\")\n            # Map common K8s API errors during proxying\n            if e.status == 404:\n                 detail = f\"Proxy path not found on pod '{pod_name}' (or pod disappeared).\"\n                 http_status = 502 # Treat as bad gateway, pod endpoint issue\n            elif e.status == 503 or e.status == 504:\n                detail = f\"Could not connect to Pod '{pod_name}' via K8s API proxy: {e.reason}\"\n                http_status = 503\n            elif e.status == 401 or e.status == 403:\n                detail = f\"Permission denied accessing K8s pod proxy: {e.reason}\"\n                http_status = 500\n            else:\n                 detail = f\"Kubernetes API error during proxy: {e.reason}\"\n                 http_status = 500\n            raise HTTPException(status_code=http_status, detail=detail)\n        except asyncio.TimeoutError:\n             LOG.error(f\"Timeout during K8s API proxy request to {vmid}\")\n             raise HTTPException(status_code=504, detail=f\"Request via K8s API timed out for VM {vmid}\")\n        except Exception as e:\n            LOG.exception(f\"Unexpected error during K8s API proxy request to {vmid}: {e}\")\n            raise HTTPException(status_code=500, detail=f\"Unexpected error during proxy request to VM {vmid}\")\n\n"
  },
  {
    "path": "services/gateway/noVNC/.github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Client (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser: [e.g. chrome, safari]\n - Browser version: [e.g. 22]\n\n**Server (please complete the following information):**\n - noVNC version: [e.g. 1.0.0 or git commit id]\n - VNC server: [e.g. QEMU, TigerVNC]\n - WebSocket proxy: [e.g. websockify]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": "services/gateway/noVNC/.github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Question or discussion\n    url: https://groups.google.com/forum/?fromgroups#!forum/novnc\n    about: Ask a question or start a discussion\n"
  },
  {
    "path": "services/gateway/noVNC/.github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": "services/gateway/noVNC/.github/workflows/deploy.yml",
    "content": "name: Publish\n\non:\n  push:\n  pull_request:\n  release:\n    types: [published]\n\njobs:\n  npm:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: |\n          GITREV=$(git rev-parse --short HEAD)\n          echo $GITREV\n          sed -i \"s/^\\(.*\\\"version\\\".*\\)\\\"\\([^\\\"]\\+\\)\\\"\\(.*\\)\\$/\\1\\\"\\2-g$GITREV\\\"\\3/\" package.json\n        if: github.event_name != 'release'\n      - uses: actions/setup-node@v4\n        with:\n          # Needs to be explicitly specified for auth to work\n          registry-url: 'https://registry.npmjs.org'\n      - run: npm install\n      - uses: actions/upload-artifact@v4\n        with:\n          name: npm\n          path: lib\n      - run: npm publish --access public\n        env:\n          NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}\n        if: |\n          github.repository == 'novnc/noVNC' &&\n          github.event_name == 'release' &&\n          !github.event.release.prerelease\n      - run: npm publish --access public --tag beta\n        env:\n          NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}\n        if: |\n          github.repository == 'novnc/noVNC' &&\n          github.event_name == 'release' &&\n          github.event.release.prerelease\n      - run: npm publish --access public --tag dev\n        env:\n          NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}\n        if: |\n          github.repository == 'novnc/noVNC' &&\n          github.event_name == 'push' &&\n          github.event.ref == 'refs/heads/master'\n  snap:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - run: |\n          GITREV=$(git rev-parse --short HEAD)\n          echo $GITREV\n          sed -i \"s/^\\(.*\\\"version\\\".*\\)\\\"\\([^\\\"]\\+\\)\\\"\\(.*\\)\\$/\\1\\\"\\2-g$GITREV\\\"\\3/\" package.json\n        if: github.event_name != 'release'\n      - run: |\n          VERSION=$(grep '\"version\"' package.json | cut -d '\"' -f 4)\n          echo $VERSION\n          sed -i \"s/^version:.*/version: '$VERSION'/\" snap/snapcraft.yaml\n      - uses: snapcore/action-build@v1\n        id: snapcraft\n      - uses: actions/upload-artifact@v4\n        with:\n          name: snap\n          path: ${{ steps.snapcraft.outputs.snap }}\n      - uses: snapcore/action-publish@v1\n        with:\n          snap: ${{ steps.snapcraft.outputs.snap }}\n          release: stable\n        env:\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}\n        if: |\n          github.repository == 'novnc/noVNC' &&\n          github.event_name == 'release' &&\n          !github.event.release.prerelease\n      - uses: snapcore/action-publish@v1\n        with:\n          snap: ${{ steps.snapcraft.outputs.snap }}\n          release: beta\n        env:\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}\n        if: |\n          github.repository == 'novnc/noVNC' &&\n          github.event_name == 'release' &&\n          github.event.release.prerelease\n      - uses: snapcore/action-publish@v1\n        with:\n          snap: ${{ steps.snapcraft.outputs.snap }}\n          release: edge\n        env:\n          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}\n        if: |\n          github.repository == 'novnc/noVNC' &&\n          github.event_name == 'push' &&\n          github.event.ref == 'refs/heads/master'\n"
  },
  {
    "path": "services/gateway/noVNC/.github/workflows/lint.yml",
    "content": "name: Lint\n\non: [push, pull_request]\n\njobs:\n  eslint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n      - run: npm update\n      - run: npm run lint\n  html:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n      - run: npm update\n      - run: git ls-tree --name-only -r HEAD | grep -E \"[.](html|css)$\" | xargs ./utils/validate\n"
  },
  {
    "path": "services/gateway/noVNC/.github/workflows/test.yml",
    "content": "name: Test\n\non: [push, pull_request]\n\njobs:\n  test:\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          - windows-latest\n        browser:\n          - ChromeHeadless\n          - FirefoxHeadless\n        include:\n          - os: macos-latest\n            browser: Safari\n          - os: windows-latest\n            browser: EdgeHeadless\n      fail-fast: false\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n      - run: npm update\n      - run: npm run test\n        env:\n          TEST_BROWSER_NAME: ${{ matrix.browser }}\n"
  },
  {
    "path": "services/gateway/noVNC/.github/workflows/translate.yml",
    "content": "name: Translate\n\non: [push, pull_request]\n\njobs:\n  translate:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n      - run: npm update\n      - run: sudo apt-get install gettext\n      - run: make -C po update-pot\n      - run: make -C po update-po\n      - run: make -C po update-js\n"
  },
  {
    "path": "services/gateway/noVNC/.gitignore",
    "content": "# *.pyc\n# *.o\n# tests/data_*.js\n# utils/rebind.so\n# utils/websockify\n# /node_modules\n# /build\n# /lib\n# recordings\n# *.swp\n# *~\n# noVNC-*.tgz\n"
  },
  {
    "path": "services/gateway/noVNC/.gitmodules",
    "content": ""
  },
  {
    "path": "services/gateway/noVNC/AUTHORS",
    "content": "maintainers:\n- Samuel Mannehed for Cendio AB (@samhed)\n- Pierre Ossman for Cendio AB (@CendioOssman)\nmaintainersEmeritus:\n- Joel Martin (@kanaka)\n- Solly Ross (@directxman12)\n- @astrand \ncontributors:\n# There are a bunch of people that should be here.\n# If you want to be on this list, feel free send a PR\n# to add yourself.\n- jalf <git@jalf.dk>\n- NTT corp.\n"
  },
  {
    "path": "services/gateway/noVNC/LICENSE.txt",
    "content": "noVNC is Copyright (C) 2022 The noVNC authors\n(./AUTHORS)\n\nThe noVNC core library files are licensed under the MPL 2.0 (Mozilla\nPublic License 2.0). The noVNC core library is composed of the\nJavascript code necessary for full noVNC operation. This includes (but\nis not limited to):\n\n    core/**/*.js\n    app/*.js\n    test/playback.js\n\nThe HTML, CSS, font and images files that included with the noVNC\nsource distibution (or repository) are not considered part of the\nnoVNC core library and are licensed under more permissive licenses.\nThe intent is to allow easy integration of noVNC into existing web\nsites and web applications.\n\nThe HTML, CSS, font and image files are licensed as follows:\n\n    *.html                     : 2-Clause BSD license\n\n    app/styles/*.css           : 2-Clause BSD license\n\n    app/styles/Orbitron*       : SIL Open Font License 1.1\n                                 (Copyright 2009 Matt McInerney)\n\n    app/images/                : Creative Commons Attribution-ShareAlike\n                                 http://creativecommons.org/licenses/by-sa/3.0/\n\nSome portions of noVNC are copyright to their individual authors.\nPlease refer to the individual source files and/or to the noVNC commit\nhistory: https://github.com/novnc/noVNC/commits/master\n\nThe are several files and projects that have been incorporated into\nthe noVNC core library. Here is a list of those files and the original\nlicenses (all MPL 2.0 compatible):\n\n    core/base64.js          : MPL 2.0\n\n    core/des.js             : Various BSD style licenses\n\n    vendor/pako/            : MIT\n\nAny other files not mentioned above are typically marked with\na copyright/license header at the top of the file. The default noVNC\nlicense is MPL-2.0.\n\nThe following license texts are included:\n\n    docs/LICENSE.MPL-2.0\n    docs/LICENSE.OFL-1.1\n    docs/LICENSE.BSD-3-Clause (New BSD)\n    docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)\n    vendor/pako/LICENSE (MIT)\n\nOr alternatively the license texts may be found here:\n\n    http://www.mozilla.org/MPL/2.0/\n    http://scripts.sil.org/OFL\n    http://en.wikipedia.org/wiki/BSD_licenses\n    https://opensource.org/licenses/MIT\n"
  },
  {
    "path": "services/gateway/noVNC/README.md",
    "content": "## noVNC: HTML VNC client library and application\n\n[![Test Status](https://github.com/novnc/noVNC/workflows/Test/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ATest)\n[![Lint Status](https://github.com/novnc/noVNC/workflows/Lint/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ALint)\n\n### Description\n\nnoVNC is both a HTML VNC client JavaScript library and an application built on\ntop of that library. noVNC runs well in any modern browser including mobile\nbrowsers (iOS and Android).\n\n*CYBERDESK NOTE: This is a fork of the noVNC project with the following changes:*\n- The base href is set to /static/ so the noVNC client assets are served from the /static/ directory.\n- The vnc.html and vnc_lite.html files are served from the /static/ directory.\n- The WebSocket path is dynamically constructed from the URL path, and autoconnect is enabled by default.\n  See the ui.js file for relevant changes.\n\nMany companies, projects and products have integrated noVNC including\n[OpenStack](http://www.openstack.org),\n[OpenNebula](http://opennebula.org/),\n[LibVNCServer](http://libvncserver.sourceforge.net), and\n[ThinLinc](https://cendio.com/thinlinc). See\n[the Projects and companies wiki page](https://github.com/novnc/noVNC/wiki/Projects-and-companies-using-noVNC)\nfor a more complete list with additional info and links.\n\n### Table of contents\n\n- [News/help/contact](#newshelpcontact)\n- [Features](#features)\n- [Screenshots](#screenshots)\n- [Browser requirements](#browser-requirements)\n- [Server requirements](#server-requirements)\n- [Quick start](#quick-start)\n- [Installation from snap package](#installation-from-snap-package)\n- [Integration and deployment](#integration-and-deployment)\n- [Authors/Contributors](#authorscontributors)\n\n### News/help/contact\n\nThe project website is found at [novnc.com](http://novnc.com).\n\nIf you are a noVNC developer/integrator/user (or want to be) please join the\n[noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).\n\nBugs and feature requests can be submitted via\n[github issues](https://github.com/novnc/noVNC/issues). If you have questions\nabout using noVNC then please first use the\n[discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).\nWe also have a [wiki](https://github.com/novnc/noVNC/wiki/) with lots of\nhelpful information.\n\nIf you are looking for a place to start contributing to noVNC, a good place to\nstart would be the issues that are marked as\n[\"patchwelcome\"](https://github.com/novnc/noVNC/issues?labels=patchwelcome).\nPlease check our\n[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) though.\n\nIf you want to show appreciation for noVNC you could donate to a great non-\nprofits such as:\n[Compassion International](http://www.compassion.com/),\n[SIL](http://www.sil.org),\n[Habitat for Humanity](http://www.habitat.org),\n[Electronic Frontier Foundation](https://www.eff.org/),\n[Against Malaria Foundation](http://www.againstmalaria.com/),\n[Nothing But Nets](http://www.nothingbutnets.net/), etc.\n\n\n### Features\n\n* Supports all modern browsers including mobile (iOS, Android)\n* Supported authentication methods: none, classical VNC, RealVNC's\n  RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman,\n  UltraVNC's MSLogonII\n* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG,\n  ZRLE, JPEG, Zlib, H.264\n* Supports scaling, clipping and resizing the desktop\n* Supports back & forward mouse buttons\n* Local cursor rendering\n* Clipboard copy/paste with full Unicode support\n* Translations\n* Touch gestures for emulating common mouse actions\n* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see\n  [the license document](LICENSE.txt) for details\n\n### Screenshots\n\nRunning in Firefox before and after connecting:\n\n<img src=\"http://novnc.com/img/noVNC-1-login.png\" width=400>&nbsp;\n<img src=\"http://novnc.com/img/noVNC-3-connected.png\" width=400>\n\nSee more screenshots\n[here](http://novnc.com/screenshots.html).\n\n\n### Browser requirements\n\nnoVNC uses many modern web technologies so a formal requirement list is\nnot available. However these are the minimum versions we are currently\naware of:\n\n* Chrome 89, Firefox 89, Safari 15, Opera 75, Edge 89\n\n\n### Server requirements\n\nnoVNC follows the standard VNC protocol, but unlike other VNC clients it does\nrequire WebSockets support. Many servers include support (e.g.\n[x11vnc/libvncserver](http://libvncserver.sourceforge.net/),\n[QEMU](http://www.qemu.org/), and\n[MobileVNC](http://www.smartlab.at/mobilevnc/)), but for the others you need to\nuse a WebSockets to TCP socket proxy. noVNC has a sister project\n[websockify](https://github.com/novnc/websockify) that provides a simple such\nproxy.\n\n\n### Quick start\n\n* Use the `novnc_proxy` script to automatically download and start websockify, which\n  includes a mini-webserver and the WebSockets proxy. The `--vnc` option is\n  used to specify the location of a running VNC server:\n\n    `./utils/novnc_proxy --vnc localhost:5901`\n    \n* If you don't need to expose the web server to public internet, you can\n  bind to localhost:\n  \n    `./utils/novnc_proxy --vnc localhost:5901 --listen localhost:6081`\n\n* Point your browser to the cut-and-paste URL that is output by the `novnc_proxy`\n  script. Hit the Connect button, enter a password if the VNC server has one\n  configured, and enjoy!\n\n### Installation from snap package\nRunning the command below will install the latest release of noVNC from snap:\n\n`sudo snap install novnc`\n\n#### Running noVNC from snap directly\n\nYou can run the snap package installed novnc directly with, for example:\n\n`novnc --listen 6081 --vnc localhost:5901 # /snap/bin/novnc if /snap/bin is not in your PATH`\n\nIf you want to use certificate files, due to standard snap confinement restrictions you need to have them in the /home/\\<user\\>/snap/novnc/current/ directory. If your username is jsmith an example command would be:\n  \n  `novnc --listen 8443 --cert ~jsmith/snap/novnc/current/self.crt --key ~jsmith/snap/novnc/current/self.key --vnc ubuntu.example.com:5901`\n\n#### Running noVNC from snap as a service (daemon)\nThe snap package also has the capability to run a 'novnc' service which can be\nconfigured to listen on multiple ports connecting to multiple VNC servers \n(effectively a service running multiple instances of novnc).\nInstructions (with example values):\n\nList current services (out-of-box this will be blank):\n\n```\nsudo snap get novnc services\nKey             Value\nservices.n6080  {...}\nservices.n6081  {...}\n```\n\nCreate a new service that listens on port 6082 and connects to the VNC server \nrunning on port 5902 on localhost:\n\n`sudo snap set novnc services.n6082.listen=6082 services.n6082.vnc=localhost:5902`\n\n(Any services you define with 'snap set' will be automatically started)\nNote that the name of the service, 'n6082' in this example, can be anything \nas long as it doesn't start with a number or contain spaces/special characters.\n\nView the configuration of the service just created:\n\n```\nsudo snap get novnc services.n6082\nKey                    Value\nservices.n6082.listen  6082\nservices.n6082.vnc     localhost:5902\n```\n\nDisable a service (note that because of a limitation in snap it's currently not\npossible to unset config variables, setting them to blank values is the way \nto disable a service):\n\n`sudo snap set novnc services.n6082.listen='' services.n6082.vnc=''`\n\n(Any services you set to blank with 'snap set' like this will be automatically stopped)\n\nVerify that the service is disabled (blank values):\n\n```\nsudo snap get novnc services.n6082\nKey                    Value\nservices.n6082.listen  \nservices.n6082.vnc\n```\n\n### Integration and deployment\n\nPlease see our other documents for how to integrate noVNC in your own software,\nor deploying the noVNC application in production environments:\n\n* [Embedding](docs/EMBEDDING.md) - For the noVNC application\n* [Library](docs/LIBRARY.md) - For the noVNC JavaScript library\n\n\n### Authors/Contributors\n\nSee [AUTHORS](AUTHORS) for a (full-ish) list of authors.  If you're not on\nthat list and you think you should be, feel free to send a PR to fix that.\n\n* Core team:\n    * [Samuel Mannehed](https://github.com/samhed) (Cendio)\n    * [Pierre Ossman](https://github.com/CendioOssman) (Cendio)\n\n* Previous core contributors:\n    * [Joel Martin](https://github.com/kanaka) (Project founder)\n    * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)\n\n* Notable contributions:\n    * UI and icons : Pierre Ossman, Chris Gordon\n    * Original logo : Michael Sersen\n    * tight encoding : Michael Tinglof (Mercuri.ca)\n    * RealVNC RSA AES authentication : USTC Vlab Team\n\n* Included libraries:\n    * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)\n    * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)\n    * Pako : Vitaly Puzrin (https://github.com/nodeca/pako)\n\nDo you want to be on this list? Check out our\n[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) and\nstart hacking!\n"
  },
  {
    "path": "services/gateway/noVNC/app/error-handler.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\n// Fallback for all uncaught errors\nfunction handleError(event, err) {\n    try {\n        const msg = document.getElementById('noVNC_fallback_errormsg');\n\n        // Work around Firefox bug:\n        // https://bugzilla.mozilla.org/show_bug.cgi?id=1685038\n        if (event.message === \"ResizeObserver loop completed with undelivered notifications.\") {\n            return false;\n        }\n\n        // Only show the initial error\n        if (msg.hasChildNodes()) {\n            return false;\n        }\n\n        let div = document.createElement(\"div\");\n        div.classList.add('noVNC_message');\n        div.appendChild(document.createTextNode(event.message));\n        msg.appendChild(div);\n\n        if (event.filename) {\n            div = document.createElement(\"div\");\n            div.className = 'noVNC_location';\n            let text = event.filename;\n            if (event.lineno !== undefined) {\n                text += \":\" + event.lineno;\n                if (event.colno !== undefined) {\n                    text += \":\" + event.colno;\n                }\n            }\n            div.appendChild(document.createTextNode(text));\n            msg.appendChild(div);\n        }\n\n        if (err && err.stack) {\n            div = document.createElement(\"div\");\n            div.className = 'noVNC_stack';\n            div.appendChild(document.createTextNode(err.stack));\n            msg.appendChild(div);\n        }\n\n        document.getElementById('noVNC_fallback_error')\n            .classList.add(\"noVNC_open\");\n\n    } catch (exc) {\n        document.write(\"noVNC encountered an error.\");\n    }\n\n    // Try to disable keyboard interaction, best effort\n    try {\n        // Remove focus from the currently focused element in order to\n        // prevent keyboard interaction from continuing\n        if (document.activeElement) { document.activeElement.blur(); }\n\n        // Don't let any element be focusable when showing the error\n        let keyboardFocusable = 'a[href], button, input, textarea, select, details, [tabindex]';\n        document.querySelectorAll(keyboardFocusable).forEach((elem) => {\n            elem.setAttribute(\"tabindex\", \"-1\");\n        });\n    } catch (exc) {\n        // Do nothing\n    }\n\n    // Don't return true since this would prevent the error\n    // from being printed to the browser console.\n    return false;\n}\n\nwindow.addEventListener('error', evt => handleError(evt, evt.error));\nwindow.addEventListener('unhandledrejection', evt => handleError(evt.reason, evt.reason));\n"
  },
  {
    "path": "services/gateway/noVNC/app/images/icons/Makefile",
    "content": "BROWSER_SIZES := 16 24 32 48 64\n#ANDROID_SIZES := 72 96 144 192\n# FIXME: The ICO is limited to 8 icons due to a Chrome bug:\n#        https://bugs.chromium.org/p/chromium/issues/detail?id=1381393\nANDROID_SIZES := 96 144 192\nWEB_ICON_SIZES := $(BROWSER_SIZES) $(ANDROID_SIZES)\n\n#IOS_1X_SIZES := 20 29 40 76 # No such devices exist anymore\nIOS_2X_SIZES := 40 58 80 120 152 167\nIOS_3X_SIZES := 60 87 120 180\nALL_IOS_SIZES := $(IOS_1X_SIZES) $(IOS_2X_SIZES) $(IOS_3X_SIZES)\n\nALL_ICONS := \\\n\t$(ALL_IOS_SIZES:%=novnc-ios-%.png) \\\n\tnovnc.ico\n\nall: $(ALL_ICONS)\n\n# Our testing shows that the ICO file need to be sorted in largest to\n# smallest to get the apporpriate behviour\nWEB_ICON_SIZES_REVERSE := $(shell echo $(WEB_ICON_SIZES) | tr ' ' '\\n' | sort -nr | tr '\\n' ' ')\nWEB_BASE_ICONS := $(WEB_ICON_SIZES_REVERSE:%=novnc-%.png)\n.INTERMEDIATE: $(WEB_BASE_ICONS)\n\nnovnc.ico: $(WEB_BASE_ICONS)\n\tconvert $(WEB_BASE_ICONS) \"$@\"\n\n# General conversion\nnovnc-%.png: novnc-icon.svg\n\tconvert -depth 8 -background transparent \\\n\t\t-size $*x$* \"$(lastword $^)\" \"$@\"\n\n# iOS icons use their own SVG\nnovnc-ios-%.png: novnc-ios-icon.svg\n\tconvert -depth 8 -background transparent \\\n\t\t-size $*x$* \"$(lastword $^)\" \"$@\"\n\n# The smallest sizes are generated using a different SVG\nnovnc-16.png novnc-24.png novnc-32.png: novnc-icon-sm.svg\n\nclean:\n\trm -f *.png\n"
  },
  {
    "path": "services/gateway/noVNC/app/locale/README",
    "content": "DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES.\n"
  },
  {
    "path": "services/gateway/noVNC/app/locale/cs.json",
    "content": "{\n    \"Connecting...\": \"Připojení...\",\n    \"Disconnecting...\": \"Odpojení...\",\n    \"Reconnecting...\": \"Obnova připojení...\",\n    \"Internal error\": \"Vnitřní chyba\",\n    \"Must set host\": \"Hostitel musí být nastavení\",\n    \"Connected (encrypted) to \": \"Připojení (šifrované) k \",\n    \"Connected (unencrypted) to \": \"Připojení (nešifrované) k \",\n    \"Something went wrong, connection is closed\": \"Něco se pokazilo, odpojeno\",\n    \"Failed to connect to server\": \"Chyba připojení k serveru\",\n    \"Disconnected\": \"Odpojeno\",\n    \"New connection has been rejected with reason: \": \"Nové připojení bylo odmítnuto s odůvodněním: \",\n    \"New connection has been rejected\": \"Nové připojení bylo odmítnuto\",\n    \"Password is required\": \"Je vyžadováno heslo\",\n    \"noVNC encountered an error:\": \"noVNC narazilo na chybu:\",\n    \"Hide/Show the control bar\": \"Skrýt/zobrazit ovládací panel\",\n    \"Move/Drag viewport\": \"Přesunout/přetáhnout výřez\",\n    \"viewport drag\": \"přesun výřezu\",\n    \"Active Mouse Button\": \"Aktivní tlačítka myši\",\n    \"No mousebutton\": \"Žádné\",\n    \"Left mousebutton\": \"Levé tlačítko myši\",\n    \"Middle mousebutton\": \"Prostřední tlačítko myši\",\n    \"Right mousebutton\": \"Pravé tlačítko myši\",\n    \"Keyboard\": \"Klávesnice\",\n    \"Show keyboard\": \"Zobrazit klávesnici\",\n    \"Extra keys\": \"Extra klávesy\",\n    \"Show extra keys\": \"Zobrazit extra klávesy\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Přepnout Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Přepnout Alt\",\n    \"Send Tab\": \"Odeslat tabulátor\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Odeslat Esc\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Poslat Ctrl-Alt-Del\",\n    \"Shutdown/Reboot\": \"Vypnutí/Restart\",\n    \"Shutdown/Reboot...\": \"Vypnutí/Restart...\",\n    \"Power\": \"Napájení\",\n    \"Shutdown\": \"Vypnout\",\n    \"Reboot\": \"Restart\",\n    \"Reset\": \"Reset\",\n    \"Clipboard\": \"Schránka\",\n    \"Clear\": \"Vymazat\",\n    \"Fullscreen\": \"Celá obrazovka\",\n    \"Settings\": \"Nastavení\",\n    \"Shared mode\": \"Sdílený režim\",\n    \"View only\": \"Pouze prohlížení\",\n    \"Clip to window\": \"Přizpůsobit oknu\",\n    \"Scaling mode:\": \"Přizpůsobení velikosti\",\n    \"None\": \"Žádné\",\n    \"Local scaling\": \"Místní\",\n    \"Remote resizing\": \"Vzdálené\",\n    \"Advanced\": \"Pokročilé\",\n    \"Repeater ID:\": \"ID opakovače\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Šifrování:\",\n    \"Host:\": \"Hostitel:\",\n    \"Port:\": \"Port:\",\n    \"Path:\": \"Cesta\",\n    \"Automatic reconnect\": \"Automatická obnova připojení\",\n    \"Reconnect delay (ms):\": \"Zpoždění připojení (ms)\",\n    \"Show dot when no cursor\": \"Tečka místo chybějícího kurzoru myši\",\n    \"Logging:\": \"Logování:\",\n    \"Disconnect\": \"Odpojit\",\n    \"Connect\": \"Připojit\",\n    \"Password:\": \"Heslo\",\n    \"Send Password\": \"Odeslat heslo\",\n    \"Cancel\": \"Zrušit\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/de.json",
    "content": "{\n    \"Connecting...\": \"Verbinden...\",\n    \"Disconnecting...\": \"Verbindung trennen...\",\n    \"Reconnecting...\": \"Verbindung wiederherstellen...\",\n    \"Internal error\": \"Interner Fehler\",\n    \"Must set host\": \"Richten Sie den Server ein\",\n    \"Connected (encrypted) to \": \"Verbunden mit (verschlüsselt) \",\n    \"Connected (unencrypted) to \": \"Verbunden mit (unverschlüsselt) \",\n    \"Something went wrong, connection is closed\": \"Etwas lief schief, Verbindung wurde getrennt\",\n    \"Disconnected\": \"Verbindung zum Server getrennt\",\n    \"New connection has been rejected with reason: \": \"Verbindung wurde aus folgendem Grund abgelehnt: \",\n    \"New connection has been rejected\": \"Verbindung wurde abgelehnt\",\n    \"Password is required\": \"Passwort ist erforderlich\",\n    \"noVNC encountered an error:\": \"Ein Fehler ist aufgetreten:\",\n    \"Hide/Show the control bar\": \"Kontrollleiste verstecken/anzeigen\",\n    \"Move/Drag viewport\": \"Ansichtsfenster verschieben/ziehen\",\n    \"viewport drag\": \"Ansichtsfenster ziehen\",\n    \"Active Mouse Button\": \"Aktive Maustaste\",\n    \"No mousebutton\": \"Keine Maustaste\",\n    \"Left mousebutton\": \"Linke Maustaste\",\n    \"Middle mousebutton\": \"Mittlere Maustaste\",\n    \"Right mousebutton\": \"Rechte Maustaste\",\n    \"Keyboard\": \"Tastatur\",\n    \"Show keyboard\": \"Tastatur anzeigen\",\n    \"Extra keys\": \"Zusatztasten\",\n    \"Show extra keys\": \"Zusatztasten anzeigen\",\n    \"Ctrl\": \"Strg\",\n    \"Toggle Ctrl\": \"Strg umschalten\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Alt umschalten\",\n    \"Send Tab\": \"Tab senden\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Escape senden\",\n    \"Ctrl+Alt+Del\": \"Strg+Alt+Entf\",\n    \"Send Ctrl-Alt-Del\": \"Strg+Alt+Entf senden\",\n    \"Shutdown/Reboot\": \"Herunterfahren/Neustarten\",\n    \"Shutdown/Reboot...\": \"Herunterfahren/Neustarten...\",\n    \"Power\": \"Energie\",\n    \"Shutdown\": \"Herunterfahren\",\n    \"Reboot\": \"Neustarten\",\n    \"Reset\": \"Zurücksetzen\",\n    \"Clipboard\": \"Zwischenablage\",\n    \"Clear\": \"Löschen\",\n    \"Fullscreen\": \"Vollbild\",\n    \"Settings\": \"Einstellungen\",\n    \"Shared mode\": \"Geteilter Modus\",\n    \"View only\": \"Nur betrachten\",\n    \"Clip to window\": \"Auf Fenster begrenzen\",\n    \"Scaling mode:\": \"Skalierungsmodus:\",\n    \"None\": \"Keiner\",\n    \"Local scaling\": \"Lokales skalieren\",\n    \"Remote resizing\": \"Serverseitiges skalieren\",\n    \"Advanced\": \"Erweitert\",\n    \"Repeater ID:\": \"Repeater ID:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Verschlüsselt\",\n    \"Host:\": \"Server:\",\n    \"Port:\": \"Port:\",\n    \"Path:\": \"Pfad:\",\n    \"Automatic reconnect\": \"Automatisch wiederverbinden\",\n    \"Reconnect delay (ms):\": \"Wiederverbindungsverzögerung (ms):\",\n    \"Logging:\": \"Protokollierung:\",\n    \"Disconnect\": \"Verbindung trennen\",\n    \"Connect\": \"Verbinden\",\n    \"Password:\": \"Passwort:\",\n    \"Cancel\": \"Abbrechen\",\n    \"Canvas not supported.\": \"Canvas nicht unterstützt.\",\n    \"Disconnect timeout\": \"Zeitüberschreitung beim Trennen\",\n    \"Local Downscaling\": \"Lokales herunterskalieren\",\n    \"Local Cursor\": \"Lokaler Mauszeiger\",\n    \"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen\": \"'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt\",\n    \"True Color\": \"True Color\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/el.json",
    "content": "{\n    \"HTTPS is required for full functionality\": \"Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα\",\n    \"Connecting...\": \"Συνδέεται...\",\n    \"Disconnecting...\": \"Aποσυνδέεται...\",\n    \"Reconnecting...\": \"Επανασυνδέεται...\",\n    \"Internal error\": \"Εσωτερικό σφάλμα\",\n    \"Must set host\": \"Πρέπει να οριστεί ο διακομιστής\",\n    \"Connected (encrypted) to \": \"Συνδέθηκε (κρυπτογραφημένα) με το \",\n    \"Connected (unencrypted) to \": \"Συνδέθηκε (μη κρυπτογραφημένα) με το \",\n    \"Something went wrong, connection is closed\": \"Κάτι πήγε στραβά, η σύνδεση διακόπηκε\",\n    \"Failed to connect to server\": \"Αποτυχία στη σύνδεση με το διακομιστή\",\n    \"Disconnected\": \"Αποσυνδέθηκε\",\n    \"New connection has been rejected with reason: \": \"Η νέα σύνδεση απορρίφθηκε διότι: \",\n    \"New connection has been rejected\": \"Η νέα σύνδεση απορρίφθηκε \",\n    \"Credentials are required\": \"Απαιτούνται διαπιστευτήρια\",\n    \"noVNC encountered an error:\": \"το noVNC αντιμετώπισε ένα σφάλμα:\",\n    \"Hide/Show the control bar\": \"Απόκρυψη/Εμφάνιση γραμμής ελέγχου\",\n    \"Drag\": \"Σύρσιμο\",\n    \"Move/Drag Viewport\": \"Μετακίνηση/Σύρσιμο Θεατού πεδίου\",\n    \"Keyboard\": \"Πληκτρολόγιο\",\n    \"Show Keyboard\": \"Εμφάνιση Πληκτρολογίου\",\n    \"Extra keys\": \"Επιπλέον πλήκτρα\",\n    \"Show Extra Keys\": \"Εμφάνιση Επιπλέον Πλήκτρων\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Εναλλαγή Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Εναλλαγή Alt\",\n    \"Toggle Windows\": \"Εναλλαγή Παράθυρων\",\n    \"Windows\": \"Παράθυρα\",\n    \"Send Tab\": \"Αποστολή Tab\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Αποστολή Escape\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Αποστολή Ctrl-Alt-Del\",\n    \"Shutdown/Reboot\": \"Κλείσιμο/Επανεκκίνηση\",\n    \"Shutdown/Reboot...\": \"Κλείσιμο/Επανεκκίνηση...\",\n    \"Power\": \"Απενεργοποίηση\",\n    \"Shutdown\": \"Κλείσιμο\",\n    \"Reboot\": \"Επανεκκίνηση\",\n    \"Reset\": \"Επαναφορά\",\n    \"Clipboard\": \"Πρόχειρο\",\n    \"Edit clipboard content in the textarea below.\": \"Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.\",\n    \"Full Screen\": \"Πλήρης Οθόνη\",\n    \"Settings\": \"Ρυθμίσεις\",\n    \"Shared Mode\": \"Κοινόχρηστη Λειτουργία\",\n    \"View Only\": \"Μόνο Θέαση\",\n    \"Clip to Window\": \"Αποκοπή στο όριο του Παράθυρου\",\n    \"Scaling Mode:\": \"Λειτουργία Κλιμάκωσης:\",\n    \"None\": \"Καμία\",\n    \"Local Scaling\": \"Τοπική Κλιμάκωση\",\n    \"Remote Resizing\": \"Απομακρυσμένη Αλλαγή μεγέθους\",\n    \"Advanced\": \"Για προχωρημένους\",\n    \"Quality:\": \"Ποιότητα:\",\n    \"Compression level:\": \"Επίπεδο συμπίεσης:\",\n    \"Repeater ID:\": \"Repeater ID:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Κρυπτογράφηση\",\n    \"Host:\": \"Όνομα διακομιστή:\",\n    \"Port:\": \"Πόρτα διακομιστή:\",\n    \"Path:\": \"Διαδρομή:\",\n    \"Automatic Reconnect\": \"Αυτόματη επανασύνδεση\",\n    \"Reconnect Delay (ms):\": \"Καθυστέρηση επανασύνδεσης (ms):\",\n    \"Show Dot when No Cursor\": \"Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας\",\n    \"Logging:\": \"Καταγραφή:\",\n    \"Version:\": \"Έκδοση:\",\n    \"Disconnect\": \"Αποσύνδεση\",\n    \"Connect\": \"Σύνδεση\",\n    \"Server identity\": \"Ταυτότητα Διακομιστή\",\n    \"The server has provided the following identifying information:\": \"Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:\",\n    \"Fingerprint:\": \"Δακτυλικό αποτύπωμα:\",\n    \"Please verify that the information is correct and press \\\"Approve\\\". Otherwise press \\\"Reject\\\".\": \"Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \\\"Αποδοχή\\\". Αλλιώς πιέστε \\\"Απόρριψη\\\".\",\n    \"Approve\": \"Αποδοχή\",\n    \"Reject\": \"Απόρριψη\",\n    \"Credentials\": \"Διαπιστευτήρια\",\n    \"Username:\": \"Κωδικός Χρήστη:\",\n    \"Password:\": \"Κωδικός Πρόσβασης:\",\n    \"Send Credentials\": \"Αποστολή Διαπιστευτηρίων\",\n    \"Cancel\": \"Ακύρωση\",\n    \"Password is required\": \"Απαιτείται ο κωδικός πρόσβασης\",\n    \"viewport drag\": \"σύρσιμο θεατού πεδίου\",\n    \"Active Mouse Button\": \"Ενεργό Πλήκτρο Ποντικιού\",\n    \"No mousebutton\": \"Χωρίς Πλήκτρο Ποντικιού\",\n    \"Left mousebutton\": \"Αριστερό Πλήκτρο Ποντικιού\",\n    \"Middle mousebutton\": \"Μεσαίο Πλήκτρο Ποντικιού\",\n    \"Right mousebutton\": \"Δεξί Πλήκτρο Ποντικιού\",\n    \"Clear\": \"Καθάρισμα\",\n    \"Canvas not supported.\": \"Δεν υποστηρίζεται το στοιχείο Canvas\",\n    \"Disconnect timeout\": \"Παρέλευση χρονικού ορίου αποσύνδεσης\",\n    \"Local Downscaling\": \"Τοπική Συρρίκνωση\",\n    \"Local Cursor\": \"Τοπικός Δρομέας\",\n    \"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen\": \"Εφαρμογή λειτουργίας αποκοπής αφού δεν υποστηρίζονται οι λωρίδες κύλισης σε πλήρη οθόνη στον IE\",\n    \"True Color\": \"Πραγματικά Χρώματα\",\n    \"Style:\": \"Στυλ:\",\n    \"default\": \"προεπιλεγμένο\",\n    \"Apply\": \"Εφαρμογή\",\n    \"Connection\": \"Σύνδεση\",\n    \"Token:\": \"Διακριτικό:\",\n    \"Send Password\": \"Αποστολή Κωδικού Πρόσβασης\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/es.json",
    "content": "{\n    \"Connecting...\": \"Conectando...\",\n    \"Connected (encrypted) to \": \"Conectado (con encriptación) a\",\n    \"Connected (unencrypted) to \": \"Conectado (sin encriptación) a\",\n    \"Disconnecting...\": \"Desconectando...\",\n    \"Disconnected\": \"Desconectado\",\n    \"Must set host\": \"Se debe configurar el host\",\n    \"Reconnecting...\": \"Reconectando...\",\n    \"Password is required\": \"La contraseña es obligatoria\",\n    \"Disconnect timeout\": \"Tiempo de desconexión agotado\",\n    \"noVNC encountered an error:\": \"noVNC ha encontrado un error:\",\n    \"Hide/Show the control bar\": \"Ocultar/Mostrar la barra de control\",\n    \"Move/Drag viewport\": \"Mover/Arrastrar la ventana\",\n    \"viewport drag\": \"Arrastrar la ventana\",\n    \"Active Mouse Button\": \"Botón activo del ratón\",\n    \"No mousebutton\": \"Ningún botón del ratón\",\n    \"Left mousebutton\": \"Botón izquierdo del ratón\",\n    \"Middle mousebutton\": \"Botón central del ratón\",\n    \"Right mousebutton\": \"Botón derecho del ratón\",\n    \"Keyboard\": \"Teclado\",\n    \"Show keyboard\": \"Mostrar teclado\",\n    \"Extra keys\": \"Teclas adicionales\",\n    \"Show Extra Keys\": \"Mostrar Teclas Adicionales\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Pulsar/Soltar Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Pulsar/Soltar Alt\",\n    \"Send Tab\": \"Enviar Tabulación\",\n    \"Tab\": \"Tabulación\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Enviar Escape\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Enviar Ctrl+Alt+Del\",\n    \"Shutdown/Reboot\": \"Apagar/Reiniciar\",\n    \"Shutdown/Reboot...\": \"Apagar/Reiniciar...\",\n    \"Power\": \"Encender\",\n    \"Shutdown\": \"Apagar\",\n    \"Reboot\": \"Reiniciar\",\n    \"Reset\": \"Restablecer\",\n    \"Clipboard\": \"Portapapeles\",\n    \"Clear\": \"Vaciar\",\n    \"Fullscreen\": \"Pantalla Completa\",\n    \"Settings\": \"Configuraciones\",\n    \"Encrypt\": \"Encriptar\",\n    \"Shared Mode\": \"Modo Compartido\",\n    \"View only\": \"Solo visualización\",\n    \"Clip to window\": \"Recortar al tamaño de la ventana\",\n    \"Scaling mode:\": \"Modo de escalado:\",\n    \"None\": \"Ninguno\",\n    \"Local Scaling\": \"Escalado Local\",\n    \"Local Downscaling\": \"Reducción de escala local\",\n    \"Remote resizing\": \"Cambio de tamaño remoto\",\n    \"Advanced\": \"Avanzado\",\n    \"Local Cursor\": \"Cursor Local\",\n    \"Repeater ID:\": \"ID del Repetidor:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Host:\": \"Host:\",\n    \"Port:\": \"Puerto:\",\n    \"Path:\": \"Ruta:\",\n    \"Automatic reconnect\": \"Reconexión automática\",\n    \"Reconnect delay (ms):\": \"Retraso en la reconexión (ms):\",\n    \"Logging:\": \"Registrando:\",\n    \"Disconnect\": \"Desconectar\",\n    \"Connect\": \"Conectar\",\n    \"Password:\": \"Contraseña:\",\n    \"Cancel\": \"Cancelar\",\n    \"Canvas not supported.\": \"Canvas no soportado.\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/fr.json",
    "content": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"Lancer sans HTTPS n'est pas recommandé, crashs ou autres problèmes en vue.\",\n    \"Connecting...\": \"En cours de connexion...\",\n    \"Disconnecting...\": \"Déconnexion en cours...\",\n    \"Reconnecting...\": \"Reconnexion en cours...\",\n    \"Internal error\": \"Erreur interne\",\n    \"Failed to connect to server: \": \"Échec de connexion au serveur \",\n    \"Connected (encrypted) to \": \"Connecté (chiffré) à \",\n    \"Connected (unencrypted) to \": \"Connecté (non chiffré) à \",\n    \"Something went wrong, connection is closed\": \"Quelque chose s'est mal passé, la connexion a été fermée\",\n    \"Failed to connect to server\": \"Échec de connexion au serveur\",\n    \"Disconnected\": \"Déconnecté\",\n    \"New connection has been rejected with reason: \": \"Une nouvelle connexion a été rejetée avec motif : \",\n    \"New connection has been rejected\": \"Une nouvelle connexion a été rejetée\",\n    \"Credentials are required\": \"Les identifiants sont requis\",\n    \"noVNC encountered an error:\": \"noVNC a rencontré une erreur :\",\n    \"Hide/Show the control bar\": \"Masquer/Afficher la barre de contrôle\",\n    \"Drag\": \"Faire glisser\",\n    \"Move/Drag viewport\": \"Déplacer la fenêtre de visualisation\",\n    \"Keyboard\": \"Clavier\",\n    \"Show keyboard\": \"Afficher le clavier\",\n    \"Extra keys\": \"Touches supplémentaires\",\n    \"Show extra keys\": \"Afficher les touches supplémentaires\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Basculer Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Basculer Alt\",\n    \"Toggle Windows\": \"Basculer Windows\",\n    \"Windows\": \"Fenêtre\",\n    \"Send Tab\": \"Envoyer Tab\",\n    \"Tab\": \"Tabulation\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Envoyer Escape\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Envoyer Ctrl-Alt-Del\",\n    \"Shutdown/Reboot\": \"Arrêter/Redémarrer\",\n    \"Shutdown/Reboot...\": \"Arrêter/Redémarrer...\",\n    \"Power\": \"Alimentation\",\n    \"Shutdown\": \"Arrêter\",\n    \"Reboot\": \"Redémarrer\",\n    \"Reset\": \"Réinitialiser\",\n    \"Clipboard\": \"Presse-papiers\",\n    \"Edit clipboard content in the textarea below.\": \"Editer le contenu du presse-papier dans la zone ci-dessous.\",\n    \"Full screen\": \"Plein écran\",\n    \"Settings\": \"Paramètres\",\n    \"Shared mode\": \"Mode partagé\",\n    \"View only\": \"Afficher uniquement\",\n    \"Clip to window\": \"Ajuster à la fenêtre\",\n    \"Scaling mode:\": \"Mode mise à l'échelle :\",\n    \"None\": \"Aucun\",\n    \"Local scaling\": \"Mise à l'échelle locale\",\n    \"Remote resizing\": \"Redimensionnement à distance\",\n    \"Advanced\": \"Avancé\",\n    \"Quality:\": \"Qualité :\",\n    \"Compression level:\": \"Niveau de compression :\",\n    \"Repeater ID:\": \"ID Répéteur :\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Chiffrer\",\n    \"Host:\": \"Hôte :\",\n    \"Port:\": \"Port :\",\n    \"Path:\": \"Chemin :\",\n    \"Automatic reconnect\": \"Reconnecter automatiquement\",\n    \"Reconnect delay (ms):\": \"Délai de reconnexion (ms) :\",\n    \"Show dot when no cursor\": \"Afficher le point lorsqu'il n'y a pas de curseur\",\n    \"Logging:\": \"Se connecter :\",\n    \"Version:\": \"Version :\",\n    \"Disconnect\": \"Déconnecter\",\n    \"Connect\": \"Connecter\",\n    \"Server identity\": \"Identité du serveur\",\n    \"The server has provided the following identifying information:\": \"Le serveur a fourni l'identification suivante :\",\n    \"Fingerprint:\": \"Empreinte digitale :\",\n    \"Please verify that the information is correct and press \\\"Approve\\\". Otherwise press \\\"Reject\\\".\": \"SVP, verifiez que l'information est correcte et pressez \\\"Accepter\\\". Sinon pressez \\\"Refuser\\\".\",\n    \"Approve\": \"Accepter\",\n    \"Reject\": \"Refuser\",\n    \"Credentials\": \"Envoyer les identifiants\",\n    \"Username:\": \"Nom d'utilisateur :\",\n    \"Password:\": \"Mot de passe :\",\n    \"Send credentials\": \"Envoyer les identifiants\",\n    \"Cancel\": \"Annuler\",\n    \"Must set host\": \"Doit définir l'hôte\",\n    \"Clear\": \"Effacer\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/it.json",
    "content": "{\n    \"Connecting...\": \"Connessione in corso...\",\n    \"Disconnecting...\": \"Disconnessione...\",\n    \"Reconnecting...\": \"Riconnessione...\",\n    \"Internal error\": \"Errore interno\",\n    \"Must set host\": \"Devi impostare l'host\",\n    \"Connected (encrypted) to \": \"Connesso (crittografato) a \",\n    \"Connected (unencrypted) to \": \"Connesso (non crittografato) a\",\n    \"Something went wrong, connection is closed\": \"Qualcosa è andato storto, la connessione è stata chiusa\",\n    \"Failed to connect to server\": \"Impossibile connettersi al server\",\n    \"Disconnected\": \"Disconnesso\",\n    \"New connection has been rejected with reason: \": \"La nuova connessione è stata rifiutata con motivo: \",\n    \"New connection has been rejected\": \"La nuova connessione è stata rifiutata\",\n    \"Credentials are required\": \"Le credenziali sono obbligatorie\",\n    \"noVNC encountered an error:\": \"noVNC ha riscontrato un errore:\",\n    \"Hide/Show the control bar\": \"Nascondi/Mostra la barra di controllo\",\n    \"Keyboard\": \"Tastiera\",\n    \"Show keyboard\": \"Mostra tastiera\",\n    \"Extra keys\": \"Tasti Aggiuntivi\",\n    \"Show Extra Keys\": \"Mostra Tasti Aggiuntivi\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Tieni premuto Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Tieni premuto Alt\",\n    \"Toggle Windows\": \"Tieni premuto Windows\",\n    \"Windows\": \"Windows\",\n    \"Send Tab\": \"Invia Tab\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Invia Esc\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Canc\",\n    \"Send Ctrl-Alt-Del\": \"Invia Ctrl-Alt-Canc\",\n    \"Shutdown/Reboot\": \"Spegnimento/Riavvio\",\n    \"Shutdown/Reboot...\": \"Spegnimento/Riavvio...\",\n    \"Power\": \"Alimentazione\",\n    \"Shutdown\": \"Spegnimento\",\n    \"Reboot\": \"Riavvio\",\n    \"Reset\": \"Reset\",\n    \"Clipboard\": \"Clipboard\",\n    \"Clear\": \"Pulisci\",\n    \"Fullscreen\": \"Schermo intero\",\n    \"Settings\": \"Impostazioni\",\n    \"Shared mode\": \"Modalità condivisa\",\n    \"View Only\": \"Sola Visualizzazione\",\n    \"Scaling mode:\": \"Modalità di ridimensionamento:\",\n    \"None\": \"Nessuna\",\n    \"Local Scaling\": \"Ridimensionamento Locale\",\n    \"Remote Resizing\": \"Ridimensionamento Remoto\",\n    \"Advanced\": \"Avanzate\",\n    \"Quality:\": \"Qualità:\",\n    \"Compression level:\": \"Livello Compressione:\",\n    \"Repeater ID:\": \"ID Ripetitore:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Crittografa\",\n    \"Host:\": \"Host:\",\n    \"Port:\": \"Porta:\",\n    \"Path:\": \"Percorso:\",\n    \"Automatic Reconnect\": \"Riconnessione Automatica\",\n    \"Reconnect Delay (ms):\": \"Ritardo Riconnessione (ms):\",\n    \"Show Dot when No Cursor\": \"Mostra Punto quando Nessun Cursore\",\n    \"Version:\": \"Versione:\",\n    \"Disconnect\": \"Disconnetti\",\n    \"Connect\": \"Connetti\",\n    \"Username:\": \"Utente:\",\n    \"Password:\": \"Password:\",\n    \"Send Credentials\": \"Invia Credenziale\",\n    \"Cancel\": \"Annulla\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/ja.json",
    "content": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"HTTPS接続なしで実行することは推奨されません。クラッシュしたりその他の問題が発生したりする可能性があります。\",\n    \"Connecting...\": \"接続しています...\",\n    \"Disconnecting...\": \"切断しています...\",\n    \"Reconnecting...\": \"再接続しています...\",\n    \"Internal error\": \"内部エラー\",\n    \"Must set host\": \"ホストを設定する必要があります\",\n    \"Failed to connect to server: \": \"サーバーへの接続に失敗しました: \",\n    \"Connected (encrypted) to \": \"接続しました (暗号化済み): \",\n    \"Connected (unencrypted) to \": \"接続しました (暗号化されていません): \",\n    \"Something went wrong, connection is closed\": \"問題が発生したため、接続が閉じられました\",\n    \"Failed to connect to server\": \"サーバーへの接続に失敗しました\",\n    \"Disconnected\": \"切断しました\",\n    \"New connection has been rejected with reason: \": \"新規接続は次の理由で拒否されました: \",\n    \"New connection has been rejected\": \"新規接続は拒否されました\",\n    \"Credentials are required\": \"資格情報が必要です\",\n    \"noVNC encountered an error:\": \"noVNC でエラーが発生しました:\",\n    \"Hide/Show the control bar\": \"コントロールバーを隠す/表示する\",\n    \"Drag\": \"ドラッグ\",\n    \"Move/Drag viewport\": \"ビューポートを移動/ドラッグ\",\n    \"Keyboard\": \"キーボード\",\n    \"Show keyboard\": \"キーボードを表示\",\n    \"Extra keys\": \"追加キー\",\n    \"Show extra keys\": \"追加キーを表示\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Ctrl キーをトグル\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Alt キーをトグル\",\n    \"Toggle Windows\": \"Windows キーをトグル\",\n    \"Windows\": \"Windows\",\n    \"Send Tab\": \"Tab キーを送信\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Escape キーを送信\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Ctrl-Alt-Del を送信\",\n    \"Shutdown/Reboot\": \"シャットダウン/再起動\",\n    \"Shutdown/Reboot...\": \"シャットダウン/再起動...\",\n    \"Power\": \"電源\",\n    \"Shutdown\": \"シャットダウン\",\n    \"Reboot\": \"再起動\",\n    \"Reset\": \"リセット\",\n    \"Clipboard\": \"クリップボード\",\n    \"Edit clipboard content in the textarea below.\": \"以下の入力欄からクリップボードの内容を編集できます。\",\n    \"Full screen\": \"全画面表示\",\n    \"Settings\": \"設定\",\n    \"Shared mode\": \"共有モード\",\n    \"View only\": \"表示専用\",\n    \"Clip to window\": \"ウィンドウにクリップ\",\n    \"Scaling mode:\": \"スケーリングモード:\",\n    \"None\": \"なし\",\n    \"Local scaling\": \"ローカルでスケーリング\",\n    \"Remote resizing\": \"リモートでリサイズ\",\n    \"Advanced\": \"高度\",\n    \"Quality:\": \"品質:\",\n    \"Compression level:\": \"圧縮レベル:\",\n    \"Repeater ID:\": \"リピーター ID:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"暗号化\",\n    \"Host:\": \"ホスト:\",\n    \"Port:\": \"ポート:\",\n    \"Path:\": \"パス:\",\n    \"Automatic reconnect\": \"自動再接続\",\n    \"Reconnect delay (ms):\": \"再接続する遅延 (ミリ秒):\",\n    \"Show dot when no cursor\": \"カーソルがないときにドットを表示する\",\n    \"Logging:\": \"ロギング:\",\n    \"Version:\": \"バージョン:\",\n    \"Disconnect\": \"切断\",\n    \"Connect\": \"接続\",\n    \"Server identity\": \"サーバーの識別情報\",\n    \"The server has provided the following identifying information:\": \"サーバーは以下の識別情報を提供しています:\",\n    \"Fingerprint:\": \"フィンガープリント:\",\n    \"Please verify that the information is correct and press \\\"Approve\\\". Otherwise press \\\"Reject\\\".\": \"この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。\",\n    \"Approve\": \"承認\",\n    \"Reject\": \"拒否\",\n    \"Credentials\": \"資格情報\",\n    \"Username:\": \"ユーザー名:\",\n    \"Password:\": \"パスワード:\",\n    \"Send credentials\": \"資格情報を送信\",\n    \"Cancel\": \"キャンセル\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/ko.json",
    "content": "{\n    \"Connecting...\": \"연결중...\",\n    \"Disconnecting...\": \"연결 해제중...\",\n    \"Reconnecting...\": \"재연결중...\",\n    \"Internal error\": \"내부 오류\",\n    \"Must set host\": \"호스트는 설정되어야 합니다.\",\n    \"Connected (encrypted) to \": \"다음과 (암호화되어) 연결되었습니다:\",\n    \"Connected (unencrypted) to \": \"다음과 (암호화 없이) 연결되었습니다:\",\n    \"Something went wrong, connection is closed\": \"무언가 잘못되었습니다, 연결이 닫혔습니다.\",\n    \"Failed to connect to server\": \"서버에 연결하지 못했습니다.\",\n    \"Disconnected\": \"연결이 해제되었습니다.\",\n    \"New connection has been rejected with reason: \": \"새 연결이 다음 이유로 거부되었습니다:\",\n    \"New connection has been rejected\": \"새 연결이 거부되었습니다.\",\n    \"Password is required\": \"비밀번호가 필요합니다.\",\n    \"noVNC encountered an error:\": \"noVNC에 오류가 발생했습니다:\",\n    \"Hide/Show the control bar\": \"컨트롤 바 숨기기/보이기\",\n    \"Move/Drag viewport\": \"움직이기/드래그 뷰포트\",\n    \"viewport drag\": \"뷰포트 드래그\",\n    \"Active Mouse Button\": \"마우스 버튼 활성화\",\n    \"No mousebutton\": \"마우스 버튼 없음\",\n    \"Left mousebutton\": \"왼쪽 마우스 버튼\",\n    \"Middle mousebutton\": \"중간 마우스 버튼\",\n    \"Right mousebutton\": \"오른쪽 마우스 버튼\",\n    \"Keyboard\": \"키보드\",\n    \"Show keyboard\": \"키보드 보이기\",\n    \"Extra keys\": \"기타 키들\",\n    \"Show extra keys\": \"기타 키들 보이기\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Ctrl 켜기/끄기\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Alt 켜기/끄기\",\n    \"Send Tab\": \"Tab 보내기\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Esc 보내기\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Ctrl+Alt+Del 보내기\",\n    \"Shutdown/Reboot\": \"셧다운/리붓\",\n    \"Shutdown/Reboot...\": \"셧다운/리붓...\",\n    \"Power\": \"전원\",\n    \"Shutdown\": \"셧다운\",\n    \"Reboot\": \"리붓\",\n    \"Reset\": \"리셋\",\n    \"Clipboard\": \"클립보드\",\n    \"Clear\": \"지우기\",\n    \"Fullscreen\": \"전체화면\",\n    \"Settings\": \"설정\",\n    \"Shared mode\": \"공유 모드\",\n    \"View only\": \"보기 전용\",\n    \"Clip to window\": \"창에 클립\",\n    \"Scaling mode:\": \"스케일링 모드:\",\n    \"None\": \"없음\",\n    \"Local scaling\": \"로컬 스케일링\",\n    \"Remote resizing\": \"원격 크기 조절\",\n    \"Advanced\": \"고급\",\n    \"Repeater ID:\": \"중계 ID\",\n    \"WebSocket\": \"웹소켓\",\n    \"Encrypt\": \"암호화\",\n    \"Host:\": \"호스트:\",\n    \"Port:\": \"포트:\",\n    \"Path:\": \"위치:\",\n    \"Automatic reconnect\": \"자동 재연결\",\n    \"Reconnect delay (ms):\": \"재연결 지연 시간 (ms)\",\n    \"Logging:\": \"로깅\",\n    \"Disconnect\": \"연결 해제\",\n    \"Connect\": \"연결\",\n    \"Password:\": \"비밀번호:\",\n    \"Send Password\": \"비밀번호 전송\",\n    \"Cancel\": \"취소\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/nl.json",
    "content": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"Het is niet aan te raden om zonder HTTPS te werken, crashes of andere problemen zijn dan waarschijnlijk.\",\n    \"Connecting...\": \"Aan het verbinden…\",\n    \"Disconnecting...\": \"Bezig om verbinding te verbreken...\",\n    \"Reconnecting...\": \"Opnieuw verbinding maken...\",\n    \"Internal error\": \"Interne fout\",\n    \"Failed to connect to server: \": \"Verbinding maken met server is mislukt\",\n    \"Connected (encrypted) to \": \"Verbonden (versleuteld) met \",\n    \"Connected (unencrypted) to \": \"Verbonden (onversleuteld) met \",\n    \"Something went wrong, connection is closed\": \"Er iets fout gelopen, verbinding werd verbroken\",\n    \"Failed to connect to server\": \"Verbinding maken met server is mislukt\",\n    \"Disconnected\": \"Verbinding verbroken\",\n    \"New connection has been rejected with reason: \": \"Nieuwe verbinding is geweigerd met de volgende reden: \",\n    \"New connection has been rejected\": \"Nieuwe verbinding is geweigerd\",\n    \"Credentials are required\": \"Inloggegevens zijn nodig\",\n    \"noVNC encountered an error:\": \"noVNC heeft een fout bemerkt:\",\n    \"Hide/Show the control bar\": \"Verberg/Toon de bedieningsbalk\",\n    \"Drag\": \"Sleep\",\n    \"Move/Drag viewport\": \"Verplaats/Versleep Kijkvenster\",\n    \"Keyboard\": \"Toetsenbord\",\n    \"Show keyboard\": \"Toon Toetsenbord\",\n    \"Extra keys\": \"Extra toetsen\",\n    \"Show extra keys\": \"Toon Extra Toetsen\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Ctrl omschakelen\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Alt omschakelen\",\n    \"Toggle Windows\": \"Vensters omschakelen\",\n    \"Windows\": \"Vensters\",\n    \"Send Tab\": \"Tab Sturen\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Escape Sturen\",\n    \"Ctrl+Alt+Del\": \"Ctrl-Alt-Del\",\n    \"Send Ctrl-Alt-Del\": \"Ctrl-Alt-Del Sturen\",\n    \"Shutdown/Reboot\": \"Uitschakelen/Herstarten\",\n    \"Shutdown/Reboot...\": \"Uitschakelen/Herstarten...\",\n    \"Power\": \"Systeem\",\n    \"Shutdown\": \"Uitschakelen\",\n    \"Reboot\": \"Herstarten\",\n    \"Reset\": \"Resetten\",\n    \"Clipboard\": \"Klembord\",\n    \"Edit clipboard content in the textarea below.\": \"Edit de inhoud van het klembord in het tekstveld hieronder\",\n    \"Full screen\": \"Volledig Scherm\",\n    \"Settings\": \"Instellingen\",\n    \"Shared mode\": \"Gedeelde Modus\",\n    \"View only\": \"Alleen Kijken\",\n    \"Clip to window\": \"Randen buiten venster afsnijden\",\n    \"Scaling mode:\": \"Schaalmodus:\",\n    \"None\": \"Geen\",\n    \"Local scaling\": \"Lokaal Schalen\",\n    \"Remote resizing\": \"Op Afstand Formaat Wijzigen\",\n    \"Advanced\": \"Geavanceerd\",\n    \"Quality:\": \"Kwaliteit:\",\n    \"Compression level:\": \"Compressieniveau:\",\n    \"Repeater ID:\": \"Repeater ID:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Versleutelen\",\n    \"Host:\": \"Host:\",\n    \"Port:\": \"Poort:\",\n    \"Path:\": \"Pad:\",\n    \"Automatic reconnect\": \"Automatisch Opnieuw Verbinden\",\n    \"Reconnect delay (ms):\": \"Vertraging voor Opnieuw Verbinden (ms):\",\n    \"Show dot when no cursor\": \"Geef stip weer indien geen cursor\",\n    \"Logging:\": \"Logmeldingen:\",\n    \"Version:\": \"Versie:\",\n    \"Disconnect\": \"Verbinding verbreken\",\n    \"Connect\": \"Verbinden\",\n    \"Server identity\": \"Serveridentiteit\",\n    \"The server has provided the following identifying information:\": \"De server geeft de volgende identificerende informatie:\",\n    \"Fingerprint:\": \"Vingerafdruk:\",\n    \"Please verify that the information is correct and press \\\"Approve\\\". Otherwise press \\\"Reject\\\".\": \"Verifieer dat de informatie is correct en druk “OK”. Druk anders op “Afwijzen”.\",\n    \"Approve\": \"OK\",\n    \"Reject\": \"Afwijzen\",\n    \"Credentials\": \"Inloggegevens\",\n    \"Username:\": \"Gebruikersnaam:\",\n    \"Password:\": \"Wachtwoord:\",\n    \"Send credentials\": \"Stuur inloggegevens\",\n    \"Cancel\": \"Annuleren\",\n    \"Must set host\": \"Host moeten worden ingesteld\",\n    \"Password is required\": \"Wachtwoord is vereist\",\n    \"viewport drag\": \"kijkvenster slepen\",\n    \"Active Mouse Button\": \"Actieve Muisknop\",\n    \"No mousebutton\": \"Geen muisknop\",\n    \"Left mousebutton\": \"Linker muisknop\",\n    \"Middle mousebutton\": \"Middelste muisknop\",\n    \"Right mousebutton\": \"Rechter muisknop\",\n    \"Clear\": \"Wissen\",\n    \"Send Password\": \"Verzend Wachtwoord:\",\n    \"Disconnect timeout\": \"Timeout tijdens verbreken van verbinding\",\n    \"Local Downscaling\": \"Lokaal Neerschalen\",\n    \"Local Cursor\": \"Lokale Cursor\",\n    \"Canvas not supported.\": \"Canvas wordt niet ondersteund.\",\n    \"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen\": \"''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-modus in IE niet worden ondersteund\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/pl.json",
    "content": "{\n    \"Connecting...\": \"Łączenie...\",\n    \"Disconnecting...\": \"Rozłączanie...\",\n    \"Reconnecting...\": \"Łączenie...\",\n    \"Internal error\": \"Błąd wewnętrzny\",\n    \"Must set host\": \"Host i port są wymagane\",\n    \"Connected (encrypted) to \": \"Połączenie (szyfrowane) z \",\n    \"Connected (unencrypted) to \": \"Połączenie (nieszyfrowane) z \",\n    \"Something went wrong, connection is closed\": \"Coś poszło źle, połączenie zostało zamknięte\",\n    \"Disconnected\": \"Rozłączony\",\n    \"New connection has been rejected with reason: \": \"Nowe połączenie zostało odrzucone z powodu: \",\n    \"New connection has been rejected\": \"Nowe połączenie zostało odrzucone\",\n    \"Password is required\": \"Hasło jest wymagane\",\n    \"noVNC encountered an error:\": \"noVNC napotkało błąd:\",\n    \"Hide/Show the control bar\": \"Pokaż/Ukryj pasek ustawień\",\n    \"Move/Drag Viewport\": \"Ruszaj/Przeciągaj Viewport\",\n    \"viewport drag\": \"przeciągnij viewport\",\n    \"Active Mouse Button\": \"Aktywny Przycisk Myszy\",\n    \"No mousebutton\": \"Brak przycisku myszy\",\n    \"Left mousebutton\": \"Lewy przycisk myszy\",\n    \"Middle mousebutton\": \"Środkowy przycisk myszy\",\n    \"Right mousebutton\": \"Prawy przycisk myszy\",\n    \"Keyboard\": \"Klawiatura\",\n    \"Show keyboard\": \"Pokaż klawiaturę\",\n    \"Extra keys\": \"Przyciski dodatkowe\",\n    \"Show extra keys\": \"Pokaż przyciski dodatkowe\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Przełącz Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Przełącz Alt\",\n    \"Send Tab\": \"Wyślij Tab\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Wyślij Escape\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Wyślij Ctrl-Alt-Del\",\n    \"Shutdown/Reboot\": \"Wyłącz/Uruchom ponownie\",\n    \"Shutdown/Reboot...\": \"Wyłącz/Uruchom ponownie...\",\n    \"Power\": \"Włączony\",\n    \"Shutdown\": \"Wyłącz\",\n    \"Reboot\": \"Uruchom ponownie\",\n    \"Reset\": \"Resetuj\",\n    \"Clipboard\": \"Schowek\",\n    \"Clear\": \"Wyczyść\",\n    \"Fullscreen\": \"Pełny ekran\",\n    \"Settings\": \"Ustawienia\",\n    \"Shared Mode\": \"Tryb Współdzielenia\",\n    \"View Only\": \"Tylko Podgląd\",\n    \"Clip to Window\": \"Przytnij do Okna\",\n    \"Scaling Mode:\": \"Tryb Skalowania:\",\n    \"None\": \"Brak\",\n    \"Local scaling\": \"Skalowanie lokalne\",\n    \"Remote resizing\": \"Skalowanie zdalne\",\n    \"Advanced\": \"Zaawansowane\",\n    \"Repeater ID:\": \"ID Repeatera:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Szyfrowanie\",\n    \"Host:\": \"Host:\",\n    \"Port:\": \"Port:\",\n    \"Path:\": \"Ścieżka:\",\n    \"Automatic reconnect\": \"Automatycznie wznawiaj połączenie\",\n    \"Reconnect delay (ms):\": \"Opóźnienie wznawiania (ms):\",\n    \"Logging:\": \"Poziom logowania:\",\n    \"Disconnect\": \"Rozłącz\",\n    \"Connect\": \"Połącz\",\n    \"Password:\": \"Hasło:\",\n    \"Cancel\": \"Anuluj\",\n    \"Canvas not supported.\": \"Element Canvas nie jest wspierany.\",\n    \"Disconnect timeout\": \"Timeout rozłączenia\",\n    \"Local Downscaling\": \"Downscaling lokalny\",\n    \"Local Cursor\": \"Lokalny kursor\",\n    \"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen\": \"Wymuszam clipping mode ponieważ paski przewijania nie są wspierane przez IE w trybie pełnoekranowym\",\n    \"True Color\": \"True Color\",\n    \"Style:\": \"Styl:\",\n    \"default\": \"domyślny\",\n    \"Apply\": \"Zapisz\",\n    \"Connection\": \"Połączenie\",\n    \"Token:\": \"Token:\",\n    \"Send Password\": \"Wyślij Hasło\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/pt_BR.json",
    "content": "{\n    \"Connecting...\": \"Conectando...\",\n    \"Disconnecting...\": \"Desconectando...\",\n    \"Reconnecting...\": \"Reconectando...\",\n    \"Internal error\": \"Erro interno\",\n    \"Must set host\": \"É necessário definir o host\",\n    \"Connected (encrypted) to \": \"Conectado (com criptografia) a \",\n    \"Connected (unencrypted) to \": \"Conectado (sem criptografia) a \",\n    \"Something went wrong, connection is closed\": \"Algo deu errado. A conexão foi encerrada.\",\n    \"Failed to connect to server\": \"Falha ao conectar-se ao servidor\",\n    \"Disconnected\": \"Desconectado\",\n    \"New connection has been rejected with reason: \": \"A nova conexão foi rejeitada pelo motivo: \",\n    \"New connection has been rejected\": \"A nova conexão foi rejeitada\",\n    \"Credentials are required\": \"Credenciais são obrigatórias\",\n    \"noVNC encountered an error:\": \"O noVNC encontrou um erro:\",\n    \"Hide/Show the control bar\": \"Esconder/mostrar a barra de controles\",\n    \"Drag\": \"Arrastar\",\n    \"Move/Drag viewport\": \"Mover/arrastar a janela\",\n    \"Keyboard\": \"Teclado\",\n    \"Show keyboard\": \"Mostrar teclado\",\n    \"Extra keys\": \"Teclas adicionais\",\n    \"Show extra keys\": \"Mostrar teclas adicionais\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Pressionar/soltar Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Pressionar/soltar Alt\",\n    \"Toggle Windows\": \"Pressionar/soltar Windows\",\n    \"Windows\": \"Windows\",\n    \"Send Tab\": \"Enviar Tab\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Enviar Esc\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Enviar Ctrl-Alt-Del\",\n    \"Shutdown/Reboot\": \"Desligar/reiniciar\",\n    \"Shutdown/Reboot...\": \"Desligar/reiniciar...\",\n    \"Power\": \"Ligar\",\n    \"Shutdown\": \"Desligar\",\n    \"Reboot\": \"Reiniciar\",\n    \"Reset\": \"Reiniciar (forçado)\",\n    \"Clipboard\": \"Área de transferência\",\n    \"Clear\": \"Limpar\",\n    \"Fullscreen\": \"Tela cheia\",\n    \"Settings\": \"Configurações\",\n    \"Shared mode\": \"Modo compartilhado\",\n    \"View only\": \"Apenas visualizar\",\n    \"Clip to window\": \"Recortar à janela\",\n    \"Scaling mode:\": \"Modo de dimensionamento:\",\n    \"None\": \"Nenhum\",\n    \"Local scaling\": \"Local\",\n    \"Remote resizing\": \"Remoto\",\n    \"Advanced\": \"Avançado\",\n    \"Quality:\": \"Qualidade:\",\n    \"Compression level:\": \"Nível de compressão:\",\n    \"Repeater ID:\": \"ID do repetidor:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Criptografar\",\n    \"Host:\": \"Host:\",\n    \"Port:\": \"Porta:\",\n    \"Path:\": \"Caminho:\",\n    \"Automatic reconnect\": \"Reconexão automática\",\n    \"Reconnect delay (ms):\": \"Atraso da reconexão (ms)\",\n    \"Show dot when no cursor\": \"Mostrar ponto quando não há cursor\",\n    \"Logging:\": \"Registros:\",\n    \"Version:\": \"Versão:\",\n    \"Disconnect\": \"Desconectar\",\n    \"Connect\": \"Conectar\",\n    \"Username:\": \"Nome de usuário:\",\n    \"Password:\": \"Senha:\",\n    \"Send credentials\": \"Enviar credenciais\",\n    \"Cancel\": \"Cancelar\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/ru.json",
    "content": "{\n    \"Connecting...\": \"Подключение...\",\n    \"Disconnecting...\": \"Отключение...\",\n    \"Reconnecting...\": \"Переподключение...\",\n    \"Internal error\": \"Внутренняя ошибка\",\n    \"Must set host\": \"Задайте имя сервера или IP\",\n    \"Connected (encrypted) to \": \"Подключено (с шифрованием) к \",\n    \"Connected (unencrypted) to \": \"Подключено (без шифрования) к \",\n    \"Something went wrong, connection is closed\": \"Что-то пошло не так, подключение разорвано\",\n    \"Failed to connect to server\": \"Ошибка подключения к серверу\",\n    \"Disconnected\": \"Отключено\",\n    \"New connection has been rejected with reason: \": \"Новое соединение отклонено по причине: \",\n    \"New connection has been rejected\": \"Новое соединение отклонено\",\n    \"Credentials are required\": \"Требуются учетные данные\",\n    \"noVNC encountered an error:\": \"Ошибка noVNC: \",\n    \"Hide/Show the control bar\": \"Скрыть/Показать контрольную панель\",\n    \"Drag\": \"Переместить\",\n    \"Move/Drag viewport\": \"Переместить окно\",\n    \"Keyboard\": \"Клавиатура\",\n    \"Show keyboard\": \"Показать клавиатуру\",\n    \"Extra keys\": \"Дополнительные Кнопки\",\n    \"Show Extra Keys\": \"Показать Дополнительные Кнопки\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Зажать Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Зажать Alt\",\n    \"Toggle Windows\": \"Зажать Windows\",\n    \"Windows\": \"Вкладка\",\n    \"Send Tab\": \"Передать нажатие Tab\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Передать нажатие Escape\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Передать нажатие Ctrl-Alt-Del\",\n    \"Shutdown/Reboot\": \"Выключить/Перезагрузить\",\n    \"Shutdown/Reboot...\": \"Выключить/Перезагрузить...\",\n    \"Power\": \"Питание\",\n    \"Shutdown\": \"Выключить\",\n    \"Reboot\": \"Перезагрузить\",\n    \"Reset\": \"Сброс\",\n    \"Clipboard\": \"Буфер обмена\",\n    \"Clear\": \"Очистить\",\n    \"Fullscreen\": \"Во весь экран\",\n    \"Settings\": \"Настройки\",\n    \"Shared mode\": \"Общий режим\",\n    \"View Only\": \"Только Просмотр\",\n    \"Clip to window\": \"В окно\",\n    \"Scaling mode:\": \"Масштаб:\",\n    \"None\": \"Нет\",\n    \"Local scaling\": \"Локальный масштаб\",\n    \"Remote resizing\": \"Удаленная перенастройка размера\",\n    \"Advanced\": \"Дополнительно\",\n    \"Quality:\": \"Качество\",\n    \"Compression level:\": \"Уровень Сжатия\",\n    \"Repeater ID:\": \"Идентификатор ID:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Шифрование\",\n    \"Host:\": \"Сервер:\",\n    \"Port:\": \"Порт:\",\n    \"Path:\": \"Путь:\",\n    \"Automatic reconnect\": \"Автоматическое переподключение\",\n    \"Reconnect delay (ms):\": \"Задержка переподключения (мс):\",\n    \"Show dot when no cursor\": \"Показать точку вместо курсора\",\n    \"Logging:\": \"Лог:\",\n    \"Version:\": \"Версия\",\n    \"Disconnect\": \"Отключение\",\n    \"Connect\": \"Подключение\",\n    \"Username:\": \"Имя Пользователя\",\n    \"Password:\": \"Пароль:\",\n    \"Send Credentials\": \"Передача Учетных Данных\",\n    \"Cancel\": \"Выход\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/sv.json",
    "content": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är troliga.\",\n    \"Connecting...\": \"Ansluter...\",\n    \"Disconnecting...\": \"Kopplar ner...\",\n    \"Reconnecting...\": \"Återansluter...\",\n    \"Internal error\": \"Internt fel\",\n    \"Failed to connect to server: \": \"Misslyckades att ansluta till servern: \",\n    \"Connected (encrypted) to \": \"Ansluten (krypterat) till \",\n    \"Connected (unencrypted) to \": \"Ansluten (okrypterat) till \",\n    \"Something went wrong, connection is closed\": \"Något gick fel, anslutningen avslutades\",\n    \"Failed to connect to server\": \"Misslyckades att ansluta till servern\",\n    \"Disconnected\": \"Frånkopplad\",\n    \"New connection has been rejected with reason: \": \"Ny anslutning har blivit nekad med följande skäl: \",\n    \"New connection has been rejected\": \"Ny anslutning har blivit nekad\",\n    \"Credentials are required\": \"Användaruppgifter krävs\",\n    \"noVNC encountered an error:\": \"noVNC stötte på ett problem:\",\n    \"Hide/Show the control bar\": \"Göm/Visa kontrollbaren\",\n    \"Drag\": \"Dra\",\n    \"Move/Drag viewport\": \"Flytta/Dra vyn\",\n    \"Keyboard\": \"Tangentbord\",\n    \"Show keyboard\": \"Visa tangentbord\",\n    \"Extra keys\": \"Extraknappar\",\n    \"Show extra keys\": \"Visa extraknappar\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Växla Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Växla Alt\",\n    \"Toggle Windows\": \"Växla Windows\",\n    \"Windows\": \"Windows\",\n    \"Send Tab\": \"Skicka Tab\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Skicka Escape\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"Skicka Ctrl-Alt-Del\",\n    \"Shutdown/Reboot\": \"Stäng av/Boota om\",\n    \"Shutdown/Reboot...\": \"Stäng av/Boota om...\",\n    \"Power\": \"Ström\",\n    \"Shutdown\": \"Stäng av\",\n    \"Reboot\": \"Boota om\",\n    \"Reset\": \"Återställ\",\n    \"Clipboard\": \"Urklipp\",\n    \"Edit clipboard content in the textarea below.\": \"Redigera urklippets innehåll i fältet nedan.\",\n    \"Full screen\": \"Fullskärm\",\n    \"Settings\": \"Inställningar\",\n    \"Shared mode\": \"Delat läge\",\n    \"View only\": \"Endast visning\",\n    \"Clip to window\": \"Begränsa till fönster\",\n    \"Scaling mode:\": \"Skalningsläge:\",\n    \"None\": \"Ingen\",\n    \"Local scaling\": \"Lokal skalning\",\n    \"Remote resizing\": \"Ändra storlek\",\n    \"Advanced\": \"Avancerat\",\n    \"Quality:\": \"Kvalitet:\",\n    \"Compression level:\": \"Kompressionsnivå:\",\n    \"Repeater ID:\": \"Repeater-ID:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Kryptera\",\n    \"Host:\": \"Värd:\",\n    \"Port:\": \"Port:\",\n    \"Path:\": \"Sökväg:\",\n    \"Automatic reconnect\": \"Automatisk återanslutning\",\n    \"Reconnect delay (ms):\": \"Fördröjning (ms):\",\n    \"Show dot when no cursor\": \"Visa prick när ingen muspekare finns\",\n    \"Logging:\": \"Loggning:\",\n    \"Version:\": \"Version:\",\n    \"Disconnect\": \"Koppla från\",\n    \"Connect\": \"Anslut\",\n    \"Server identity\": \"Server-identitet\",\n    \"The server has provided the following identifying information:\": \"Servern har gett följande identifierande information:\",\n    \"Fingerprint:\": \"Fingeravtryck:\",\n    \"Please verify that the information is correct and press \\\"Approve\\\". Otherwise press \\\"Reject\\\".\": \"Kontrollera att informationen är korrekt och tryck sedan \\\"Godkänn\\\". Tryck annars \\\"Neka\\\".\",\n    \"Approve\": \"Godkänn\",\n    \"Reject\": \"Neka\",\n    \"Credentials\": \"Användaruppgifter\",\n    \"Username:\": \"Användarnamn:\",\n    \"Password:\": \"Lösenord:\",\n    \"Send credentials\": \"Skicka användaruppgifter\",\n    \"Cancel\": \"Avbryt\",\n    \"Must set host\": \"Du måste specifiera en värd\",\n    \"HTTPS is required for full functionality\": \"HTTPS krävs för full funktionalitet\",\n    \"Clear\": \"Rensa\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/tr.json",
    "content": "{\n    \"Connecting...\": \"Bağlanıyor...\",\n    \"Disconnecting...\": \"Bağlantı kesiliyor...\",\n    \"Reconnecting...\": \"Yeniden bağlantı kuruluyor...\",\n    \"Internal error\": \"İç hata\",\n    \"Must set host\": \"Sunucuyu kur\",\n    \"Connected (encrypted) to \": \"Bağlı (şifrelenmiş)\",\n    \"Connected (unencrypted) to \": \"Bağlandı (şifrelenmemiş)\",\n    \"Something went wrong, connection is closed\": \"Bir şeyler ters gitti, bağlantı kesildi\",\n    \"Disconnected\": \"Bağlantı kesildi\",\n    \"New connection has been rejected with reason: \": \"Bağlantı aşağıdaki nedenlerden dolayı reddedildi: \",\n    \"New connection has been rejected\": \"Bağlantı reddedildi\",\n    \"Password is required\": \"Şifre gerekli\",\n    \"noVNC encountered an error:\": \"Bir hata oluştu:\",\n    \"Hide/Show the control bar\": \"Denetim masasını Gizle/Göster\",\n    \"Move/Drag Viewport\": \"Görünümü Taşı/Sürükle\",\n    \"viewport drag\": \"Görüntü penceresini sürükle\",\n    \"Active Mouse Button\": \"Aktif Fare Düğmesi\",\n    \"No mousebutton\": \"Fare düğmesi yok\",\n    \"Left mousebutton\": \"Farenin sol düğmesi\",\n    \"Middle mousebutton\": \"Farenin orta düğmesi\",\n    \"Right mousebutton\": \"Farenin sağ düğmesi\",\n    \"Keyboard\": \"Klavye\",\n    \"Show Keyboard\": \"Klavye Düzenini Göster\",\n    \"Extra keys\": \"Ekstra tuşlar\",\n    \"Show extra keys\": \"Ekstra tuşları göster\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"Ctrl Değiştir \",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"Alt Değiştir\",\n    \"Send Tab\": \"Sekme Gönder\",\n    \"Tab\": \"Sekme\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"Boşluk Gönder\",\n    \"Ctrl+Alt+Del\": \"Ctrl + Alt + Del\",\n    \"Send Ctrl-Alt-Del\": \"Ctrl-Alt-Del Gönder\",\n    \"Shutdown/Reboot\": \"Kapat/Yeniden Başlat\",\n    \"Shutdown/Reboot...\": \"Kapat/Yeniden Başlat...\",\n    \"Power\": \"Güç\",\n    \"Shutdown\": \"Kapat\",\n    \"Reboot\": \"Yeniden Başlat\",\n    \"Reset\": \"Sıfırla\",\n    \"Clipboard\": \"Pano\",\n    \"Clear\": \"Temizle\",\n    \"Fullscreen\": \"Tam Ekran\",\n    \"Settings\": \"Ayarlar\",\n    \"Shared Mode\": \"Paylaşım Modu\",\n    \"View Only\": \"Sadece Görüntüle\",\n    \"Clip to Window\": \"Pencereye Tıkla\",\n    \"Scaling Mode:\": \"Ölçekleme Modu:\",\n    \"None\": \"Bilinmeyen\",\n    \"Local Scaling\": \"Yerel Ölçeklendirme\",\n    \"Remote Resizing\": \"Uzaktan Yeniden Boyutlandırma\",\n    \"Advanced\": \"Gelişmiş\",\n    \"Repeater ID:\": \"Tekralayıcı ID:\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"Şifrele\",\n    \"Host:\": \"Ana makine:\",\n    \"Port:\": \"Port:\",\n    \"Path:\": \"Yol:\",\n    \"Automatic Reconnect\": \"Otomatik Yeniden Bağlan\",\n    \"Reconnect Delay (ms):\": \"Yeniden Bağlanma Süreci (ms):\",\n    \"Logging:\": \"Giriş yapılıyor:\",\n    \"Disconnect\": \"Bağlantıyı Kes\",\n    \"Connect\": \"Bağlan\",\n    \"Password:\": \"Parola:\",\n    \"Cancel\": \"Vazgeç\",\n    \"Canvas not supported.\": \"Tuval desteklenmiyor.\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/zh_CN.json",
    "content": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"不建议在没有 HTTPS 的情况下运行，可能会出现崩溃或出现其他问题。\",\n    \"Connecting...\": \"连接中...\",\n    \"Disconnecting...\": \"正在断开连接...\",\n    \"Reconnecting...\": \"重新连接中...\",\n    \"Internal error\": \"内部错误\",\n    \"Must set host\": \"必须设置主机\",\n    \"Failed to connect to server: \": \"无法连接到服务器：\",\n    \"Connected (encrypted) to \": \"已连接（已加密）到\",\n    \"Connected (unencrypted) to \": \"已连接（未加密）到\",\n    \"Something went wrong, connection is closed\": \"出了点问题，连接已关闭\",\n    \"Failed to connect to server\": \"无法连接到服务器\",\n    \"Disconnected\": \"已断开连接\",\n    \"New connection has been rejected with reason: \": \"新连接被拒绝，原因如下：\",\n    \"New connection has been rejected\": \"新连接已被拒绝\",\n    \"Credentials are required\": \"需要凭证\",\n    \"noVNC encountered an error:\": \"noVNC 遇到一个错误：\",\n    \"Hide/Show the control bar\": \"显示/隐藏控制栏\",\n    \"Drag\": \"拖动\",\n    \"Move/Drag viewport\": \"移动/拖动窗口\",\n    \"Keyboard\": \"键盘\",\n    \"Show keyboard\": \"显示键盘\",\n    \"Extra keys\": \"额外按键\",\n    \"Show extra keys\": \"显示额外按键\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"切换 Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"切换 Alt\",\n    \"Toggle Windows\": \"切换窗口\",\n    \"Windows\": \"窗口\",\n    \"Send Tab\": \"发送 Tab 键\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"发送 Escape 键\",\n    \"Ctrl+Alt+Del\": \"Ctrl+Alt+Del\",\n    \"Send Ctrl-Alt-Del\": \"发送 Ctrl+Alt+Del 键\",\n    \"Shutdown/Reboot\": \"关机/重启\",\n    \"Shutdown/Reboot...\": \"关机/重启...\",\n    \"Power\": \"电源\",\n    \"Shutdown\": \"关机\",\n    \"Reboot\": \"重启\",\n    \"Reset\": \"重置\",\n    \"Clipboard\": \"剪贴板\",\n    \"Edit clipboard content in the textarea below.\": \"在下面的文本区域中编辑剪贴板内容。\",\n    \"Full screen\": \"全屏\",\n    \"Settings\": \"设置\",\n    \"Shared mode\": \"分享模式\",\n    \"View only\": \"仅查看\",\n    \"Clip to window\": \"限制/裁切窗口大小\",\n    \"Scaling mode:\": \"缩放模式：\",\n    \"None\": \"无\",\n    \"Local scaling\": \"本地缩放\",\n    \"Remote resizing\": \"远程调整大小\",\n    \"Advanced\": \"高级\",\n    \"Quality:\": \"品质：\",\n    \"Compression level:\": \"压缩级别：\",\n    \"Repeater ID:\": \"中继站 ID\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"加密\",\n    \"Host:\": \"主机：\",\n    \"Port:\": \"端口：\",\n    \"Path:\": \"路径：\",\n    \"Automatic reconnect\": \"自动重新连接\",\n    \"Reconnect delay (ms):\": \"重新连接间隔 (ms)：\",\n    \"Show dot when no cursor\": \"无光标时显示点\",\n    \"Logging:\": \"日志级别：\",\n    \"Version:\": \"版本：\",\n    \"Disconnect\": \"断开连接\",\n    \"Connect\": \"连接\",\n    \"Server identity\": \"服务器身份\",\n    \"The server has provided the following identifying information:\": \"服务器提供了以下识别信息：\",\n    \"Fingerprint:\": \"指纹：\",\n    \"Please verify that the information is correct and press \\\"Approve\\\". Otherwise press \\\"Reject\\\".\": \"请核实信息是否正确，并按 “同意”，否则按 “拒绝”。\",\n    \"Approve\": \"同意\",\n    \"Reject\": \"拒绝\",\n    \"Credentials\": \"凭证\",\n    \"Username:\": \"用户名:\",\n    \"Password:\": \"密码：\",\n    \"Send credentials\": \"发送凭证\",\n    \"Cancel\": \"取消\",\n    \"Password is required\": \"请提供密码\",\n    \"Disconnect timeout\": \"超时断开\",\n    \"viewport drag\": \"窗口拖动\",\n    \"Active Mouse Button\": \"启动鼠标按键\",\n    \"No mousebutton\": \"禁用鼠标按键\",\n    \"Left mousebutton\": \"鼠标左键\",\n    \"Middle mousebutton\": \"鼠标中键\",\n    \"Right mousebutton\": \"鼠标右键\",\n    \"Clear\": \"清除\",\n    \"Local Downscaling\": \"降低本地尺寸\",\n    \"Local Cursor\": \"本地光标\",\n    \"Canvas not supported.\": \"不支持 Canvas。\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/locale/zh_TW.json",
    "content": "{\n    \"Connecting...\": \"連線中...\",\n    \"Disconnecting...\": \"正在中斷連線...\",\n    \"Reconnecting...\": \"重新連線中...\",\n    \"Internal error\": \"內部錯誤\",\n    \"Must set host\": \"請提供主機資訊\",\n    \"Connected (encrypted) to \": \"已加密連線到\",\n    \"Connected (unencrypted) to \": \"未加密連線到\",\n    \"Something went wrong, connection is closed\": \"發生錯誤，連線已關閉\",\n    \"Failed to connect to server\": \"無法連線到伺服器\",\n    \"Disconnected\": \"連線已中斷\",\n    \"New connection has been rejected with reason: \": \"連線被拒絕，原因：\",\n    \"New connection has been rejected\": \"連線被拒絕\",\n    \"Password is required\": \"請提供密碼\",\n    \"noVNC encountered an error:\": \"noVNC 遇到一個錯誤：\",\n    \"Hide/Show the control bar\": \"顯示/隱藏控制列\",\n    \"Move/Drag viewport\": \"拖放顯示範圍\",\n    \"viewport drag\": \"顯示範圍拖放\",\n    \"Active Mouse Button\": \"啟用滑鼠按鍵\",\n    \"No mousebutton\": \"無滑鼠按鍵\",\n    \"Left mousebutton\": \"滑鼠左鍵\",\n    \"Middle mousebutton\": \"滑鼠中鍵\",\n    \"Right mousebutton\": \"滑鼠右鍵\",\n    \"Keyboard\": \"鍵盤\",\n    \"Show keyboard\": \"顯示鍵盤\",\n    \"Extra keys\": \"額外按鍵\",\n    \"Show extra keys\": \"顯示額外按鍵\",\n    \"Ctrl\": \"Ctrl\",\n    \"Toggle Ctrl\": \"切換 Ctrl\",\n    \"Alt\": \"Alt\",\n    \"Toggle Alt\": \"切換 Alt\",\n    \"Send Tab\": \"送出 Tab 鍵\",\n    \"Tab\": \"Tab\",\n    \"Esc\": \"Esc\",\n    \"Send Escape\": \"送出 Escape 鍵\",\n    \"Ctrl+Alt+Del\": \"Ctrl-Alt-Del\",\n    \"Send Ctrl-Alt-Del\": \"送出 Ctrl-Alt-Del 快捷鍵\",\n    \"Shutdown/Reboot\": \"關機/重新啟動\",\n    \"Shutdown/Reboot...\": \"關機/重新啟動...\",\n    \"Power\": \"電源\",\n    \"Shutdown\": \"關機\",\n    \"Reboot\": \"重新啟動\",\n    \"Reset\": \"重設\",\n    \"Clipboard\": \"剪貼簿\",\n    \"Clear\": \"清除\",\n    \"Fullscreen\": \"全螢幕\",\n    \"Settings\": \"設定\",\n    \"Shared mode\": \"分享模式\",\n    \"View only\": \"僅檢視\",\n    \"Clip to window\": \"限制/裁切視窗大小\",\n    \"Scaling mode:\": \"縮放模式：\",\n    \"None\": \"無\",\n    \"Local scaling\": \"本機縮放\",\n    \"Remote resizing\": \"遠端調整大小\",\n    \"Advanced\": \"進階\",\n    \"Repeater ID:\": \"中繼站 ID\",\n    \"WebSocket\": \"WebSocket\",\n    \"Encrypt\": \"加密\",\n    \"Host:\": \"主機：\",\n    \"Port:\": \"連接埠：\",\n    \"Path:\": \"路徑：\",\n    \"Automatic reconnect\": \"自動重新連線\",\n    \"Reconnect delay (ms):\": \"重新連線間隔 (ms)：\",\n    \"Logging:\": \"日誌級別：\",\n    \"Disconnect\": \"中斷連線\",\n    \"Connect\": \"連線\",\n    \"Password:\": \"密碼：\",\n    \"Cancel\": \"取消\"\n}"
  },
  {
    "path": "services/gateway/noVNC/app/localization.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\n/*\n * Localization utilities\n */\n\nexport class Localizer {\n    constructor() {\n        // Currently configured language\n        this.language = 'en';\n\n        // Current dictionary of translations\n        this._dictionary = undefined;\n    }\n\n    // Configure suitable language based on user preferences\n    async setup(supportedLanguages, baseURL) {\n        this.language = 'en'; // Default: US English\n        this._dictionary = undefined;\n\n        this._setupLanguage(supportedLanguages);\n        await this._setupDictionary(baseURL);\n    }\n\n    _setupLanguage(supportedLanguages) {\n        /*\n         * Navigator.languages only available in Chrome (32+) and FireFox (32+)\n         * Fall back to navigator.language for other browsers\n         */\n        let userLanguages;\n        if (typeof window.navigator.languages == 'object') {\n            userLanguages = window.navigator.languages;\n        } else {\n            userLanguages = [navigator.language || navigator.userLanguage];\n        }\n\n        for (let i = 0;i < userLanguages.length;i++) {\n            const userLang = userLanguages[i]\n                .toLowerCase()\n                .replace(\"_\", \"-\")\n                .split(\"-\");\n\n            // First pass: perfect match\n            for (let j = 0; j < supportedLanguages.length; j++) {\n                const supLang = supportedLanguages[j]\n                    .toLowerCase()\n                    .replace(\"_\", \"-\")\n                    .split(\"-\");\n\n                if (userLang[0] !== supLang[0]) {\n                    continue;\n                }\n                if (userLang[1] !== supLang[1]) {\n                    continue;\n                }\n\n                this.language = supportedLanguages[j];\n                return;\n            }\n\n            // Second pass: English fallback\n            if (userLang[0] === 'en') {\n                return;\n            }\n\n            // Third pass pass: other fallback\n            for (let j = 0;j < supportedLanguages.length;j++) {\n                const supLang = supportedLanguages[j]\n                    .toLowerCase()\n                    .replace(\"_\", \"-\")\n                    .split(\"-\");\n\n                if (userLang[0] !== supLang[0]) {\n                    continue;\n                }\n                if (supLang[1] !== undefined) {\n                    continue;\n                }\n\n                this.language = supportedLanguages[j];\n                return;\n            }\n        }\n    }\n\n    async _setupDictionary(baseURL) {\n        if (baseURL) {\n            if (!baseURL.endsWith(\"/\")) {\n                baseURL = baseURL + \"/\";\n            }\n        } else {\n            baseURL = \"\";\n        }\n\n        if (this.language === \"en\") {\n            return;\n        }\n\n        let response = await fetch(baseURL + this.language + \".json\");\n        if (!response.ok) {\n            throw Error(\"\" + response.status + \" \" + response.statusText);\n        }\n\n        this._dictionary = await response.json();\n    }\n\n    // Retrieve localised text\n    get(id) {\n        if (typeof this._dictionary !== 'undefined' &&\n            this._dictionary[id]) {\n            return this._dictionary[id];\n        } else {\n            return id;\n        }\n    }\n\n    // Traverses the DOM and translates relevant fields\n    // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate\n    translateDOM() {\n        const self = this;\n\n        function process(elem, enabled) {\n            function isAnyOf(searchElement, items) {\n                return items.indexOf(searchElement) !== -1;\n            }\n\n            function translateString(str) {\n                // We assume surrounding whitespace, and whitespace around line\n                // breaks is just for source formatting\n                str = str.split(\"\\n\").map(s => s.trim()).join(\" \").trim();\n                return self.get(str);\n            }\n\n            function translateAttribute(elem, attr) {\n                const str = translateString(elem.getAttribute(attr));\n                elem.setAttribute(attr, str);\n            }\n\n            function translateTextNode(node) {\n                const str = translateString(node.data);\n                node.data = str;\n            }\n\n            if (elem.hasAttribute(\"translate\")) {\n                if (isAnyOf(elem.getAttribute(\"translate\"), [\"\", \"yes\"])) {\n                    enabled = true;\n                } else if (isAnyOf(elem.getAttribute(\"translate\"), [\"no\"])) {\n                    enabled = false;\n                }\n            }\n\n            if (enabled) {\n                if (elem.hasAttribute(\"abbr\") &&\n                    elem.tagName === \"TH\") {\n                    translateAttribute(elem, \"abbr\");\n                }\n                if (elem.hasAttribute(\"alt\") &&\n                    isAnyOf(elem.tagName, [\"AREA\", \"IMG\", \"INPUT\"])) {\n                    translateAttribute(elem, \"alt\");\n                }\n                if (elem.hasAttribute(\"download\") &&\n                    isAnyOf(elem.tagName, [\"A\", \"AREA\"])) {\n                    translateAttribute(elem, \"download\");\n                }\n                if (elem.hasAttribute(\"label\") &&\n                    isAnyOf(elem.tagName, [\"MENUITEM\", \"MENU\", \"OPTGROUP\",\n                                           \"OPTION\", \"TRACK\"])) {\n                    translateAttribute(elem, \"label\");\n                }\n                // FIXME: Should update \"lang\"\n                if (elem.hasAttribute(\"placeholder\") &&\n                    isAnyOf(elem.tagName, [\"INPUT\", \"TEXTAREA\"])) {\n                    translateAttribute(elem, \"placeholder\");\n                }\n                if (elem.hasAttribute(\"title\")) {\n                    translateAttribute(elem, \"title\");\n                }\n                if (elem.hasAttribute(\"value\") &&\n                    elem.tagName === \"INPUT\" &&\n                    isAnyOf(elem.getAttribute(\"type\"), [\"reset\", \"button\", \"submit\"])) {\n                    translateAttribute(elem, \"value\");\n                }\n            }\n\n            for (let i = 0; i < elem.childNodes.length; i++) {\n                const node = elem.childNodes[i];\n                if (node.nodeType === node.ELEMENT_NODE) {\n                    process(node, enabled);\n                } else if (node.nodeType === node.TEXT_NODE && enabled) {\n                    translateTextNode(node);\n                }\n            }\n        }\n\n        process(document.body, true);\n    }\n}\n\nexport const l10n = new Localizer();\nexport default l10n.get.bind(l10n);\n"
  },
  {
    "path": "services/gateway/noVNC/app/sounds/CREDITS",
    "content": "bell\n        Copyright: Dr. Richard Boulanger et al\n        URL: http://www.archive.org/details/Berklee44v12\n        License: CC-BY Attribution 3.0 Unported\n"
  },
  {
    "path": "services/gateway/noVNC/app/styles/base.css",
    "content": "/*\n * noVNC base CSS\n * Copyright (C) 2019 The noVNC authors\n * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)\n * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).\n */\n\n/*\n * Z index layers:\n *\n * 0: Main screen\n * 10: Control bar\n * 50: Transition blocker\n * 60: Connection popups\n * 100: Status bar\n * ...\n * 1000: Javascript crash\n * ...\n * 10000: Max (used for polyfills)\n */\n\n/*\n * State variables (set on :root):\n *\n * noVNC_loading: Page is still loading\n * noVNC_connecting: Connecting to server\n * noVNC_reconnecting: Re-establishing a connection\n * noVNC_connected: Connected to server (most common state)\n * noVNC_disconnecting: Disconnecting from server\n */\n\n:root {\n    font-family: sans-serif;\n    line-height: 1.6;\n}\n\nbody {\n    margin:0;\n    padding:0;\n    /*Background image with light grey curve.*/\n    background-color:#494949;\n    background-repeat:no-repeat;\n    background-position:right bottom;\n    height:100%;\n    touch-action: none;\n}\n\nhtml {\n    height:100%;\n}\n\n.noVNC_only_touch.noVNC_hidden {\n    display: none;\n}\n\n.noVNC_disabled {\n    color: var(--novnc-grey);\n}\n\n/* ----------------------------------------\n * Spinner\n * ----------------------------------------\n */\n\n.noVNC_spinner {\n    position: relative;\n}\n.noVNC_spinner, .noVNC_spinner::before, .noVNC_spinner::after {\n    width: 10px;\n    height: 10px;\n    border-radius: 2px;\n    box-shadow: -60px 10px 0 rgba(255, 255, 255, 0);\n    animation: noVNC_spinner 1.0s linear infinite;\n}\n.noVNC_spinner::before {\n    content: \"\";\n    position: absolute;\n    left: 0px;\n    top: 0px;\n    animation-delay: -0.1s;\n}\n.noVNC_spinner::after {\n    content: \"\";\n    position: absolute;\n    top: 0px;\n    left: 0px;\n    animation-delay: 0.1s;\n}\n@keyframes noVNC_spinner {\n    0% { box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); width: 20px; }\n    25% { box-shadow: 20px 10px 0 rgba(255, 255, 255, 1); width: 10px; }\n    50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; }\n}\n\n/* ----------------------------------------\n * WebKit centering hacks\n * ----------------------------------------\n */\n\n.noVNC_center {\n    /*\n     * This is a workaround because webkit misrenders transforms and\n     * uses non-integer coordinates, resulting in blurry content.\n     * Ideally we'd use \"top: 50%; transform: translateY(-50%);\" on\n     * the objects instead.\n     */\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    pointer-events: none;\n}\n.noVNC_center > * {\n    pointer-events: auto;\n}\n.noVNC_vcenter {\n    display: flex !important;\n    flex-direction: column;\n    justify-content: center;\n    position: fixed;\n    top: 0;\n    left: 0;\n    height: 100%;\n    margin: 0 !important;\n    padding: 0 !important;\n    pointer-events: none;\n}\n.noVNC_vcenter > * {\n    pointer-events: auto;\n}\n\n/* ----------------------------------------\n * Layering\n * ----------------------------------------\n */\n\n.noVNC_connect_layer {\n    z-index: 60;\n}\n\n/* ----------------------------------------\n * Fallback error\n * ----------------------------------------\n */\n\n#noVNC_fallback_error {\n    z-index: 1000;\n    visibility: hidden;\n    /* Put a dark background in front of everything but the error,\n       and don't let mouse events pass through */\n    background: rgba(0, 0, 0, 0.8);\n    pointer-events: all;\n}\n#noVNC_fallback_error.noVNC_open {\n    visibility: visible;\n}\n\n#noVNC_fallback_error > div {\n    max-width: calc(100vw - 30px - 30px);\n    max-height: calc(100vh - 30px - 30px);\n    overflow: auto;\n\n    padding: 15px;\n\n    transition: 0.5s ease-in-out;\n\n    transform: translateY(-50px);\n    opacity: 0;\n\n    text-align: center;\n    font-weight: bold;\n    color: #fff;\n\n    border-radius: 12px;\n    box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);\n    background: rgba(200,55,55,0.8);\n}\n#noVNC_fallback_error.noVNC_open > div {\n    transform: translateY(0);\n    opacity: 1;\n}\n\n#noVNC_fallback_errormsg {\n    font-weight: normal;\n}\n\n#noVNC_fallback_errormsg .noVNC_message {\n    display: inline-block;\n    text-align: left;\n    font-family: monospace;\n    white-space: pre-wrap;\n}\n\n#noVNC_fallback_error .noVNC_location {\n    font-style: italic;\n    font-size: 0.8em;\n    color: rgba(255, 255, 255, 0.8);\n}\n\n#noVNC_fallback_error .noVNC_stack {\n    padding: 10px;\n    margin: 10px;\n    font-size: 0.8em;\n    text-align: left;\n    font-family: monospace;\n    white-space: pre;\n    border: 1px solid rgba(0, 0, 0, 0.5);\n    background: rgba(0, 0, 0, 0.2);\n    overflow: auto;\n}\n\n/* ----------------------------------------\n * Control bar\n * ----------------------------------------\n */\n\n#noVNC_control_bar_anchor {\n    /* The anchor is needed to get z-stacking to work */\n    position: fixed;\n    z-index: 10;\n\n    transition: 0.5s ease-in-out;\n\n    /* Edge misrenders animations wihthout this */\n    transform: translateX(0);\n}\n:root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle {\n    opacity: 0.8;\n}\n#noVNC_control_bar_anchor.noVNC_right {\n    left: auto;\n    right: 0;\n}\n\n#noVNC_control_bar {\n    position: relative;\n    left: -100%;\n\n    transition: 0.5s ease-in-out;\n\n    background-color: var(--novnc-blue);\n    border-radius: 0 12px 12px 0;\n\n    user-select: none;\n    -webkit-user-select: none;\n    -webkit-touch-callout: none; /* Disable iOS image long-press popup */\n}\n#noVNC_control_bar.noVNC_open {\n    box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);\n    left: 0;\n}\n#noVNC_control_bar::before {\n    /* This extra element is to get a proper shadow */\n    content: \"\";\n    position: absolute;\n    z-index: -1;\n    height: 100%;\n    width: 30px;\n    left: -30px;\n    transition: box-shadow 0.5s ease-in-out;\n}\n#noVNC_control_bar.noVNC_open::before {\n    box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);\n}\n.noVNC_right #noVNC_control_bar {\n    left: 100%;\n    border-radius: 12px 0 0 12px;\n}\n.noVNC_right #noVNC_control_bar.noVNC_open {\n    left: 0;\n}\n.noVNC_right #noVNC_control_bar::before {\n    visibility: hidden;\n}\n\n#noVNC_control_bar_handle {\n    position: absolute;\n    left: -15px;\n    top: 0;\n    transform: translateY(35px);\n    width: calc(100% + 30px);\n    height: 50px;\n    z-index: -1;\n    cursor: pointer;\n    border-radius: 6px;\n    background-color: var(--novnc-darkblue);\n    background-image: url(\"../images/handle_bg.svg\");\n    background-repeat: no-repeat;\n    background-position: right;\n    box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);\n}\n#noVNC_control_bar_handle:after {\n    content: \"\";\n    transition: transform 0.5s ease-in-out;\n    background: url(\"../images/handle.svg\");\n    position: absolute;\n    top: 22px; /* (50px-6px)/2 */\n    right: 5px;\n    width: 5px;\n    height: 6px;\n}\n#noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {\n    transform: translateX(1px) rotate(180deg);\n}\n:root:not(.noVNC_connected) #noVNC_control_bar_handle {\n    display: none;\n}\n.noVNC_right #noVNC_control_bar_handle {\n    background-position: left;\n}\n.noVNC_right #noVNC_control_bar_handle:after {\n    left: 5px;\n    right: 0;\n    transform: translateX(1px) rotate(180deg);\n}\n.noVNC_right #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {\n    transform: none;\n}\n/* Larger touch area for the handle, used when a touch screen is available */\n#noVNC_control_bar_handle div {\n    position: absolute;\n    right: -35px;\n    top: 0;\n    width: 50px;\n    height: 100%;\n    display: none;\n}\n@media (any-pointer: coarse) {\n    #noVNC_control_bar_handle div {\n        display: initial;\n    }\n}\n.noVNC_right #noVNC_control_bar_handle div {\n    left: -35px;\n    right: auto;\n}\n\n#noVNC_control_bar > .noVNC_scroll {\n    max-height: 100vh; /* Chrome is buggy with 100% */\n    overflow-x: hidden;\n    overflow-y: auto;\n    padding: 0 10px;\n}\n\n#noVNC_control_bar > .noVNC_scroll > * {\n    display: block;\n    margin: 10px auto;\n}\n\n/* Control bar hint */\n#noVNC_hint_anchor {\n    position: fixed;\n    right: -50px;\n    left: auto;\n}\n#noVNC_control_bar_anchor.noVNC_right + #noVNC_hint_anchor {\n    left: -50px;\n    right: auto;\n}\n#noVNC_control_bar_hint {\n    position: relative;\n    transform: scale(0);\n    width: 100px;\n    height: 50%;\n    max-height: 600px;\n\n    visibility: hidden;\n    opacity: 0;\n    transition: 0.2s ease-in-out;\n    background: transparent;\n    box-shadow: 0 0 10px black, inset 0 0 10px 10px var(--novnc-darkblue);\n    border-radius: 12px;\n    transition-delay: 0s;\n}\n#noVNC_control_bar_hint.noVNC_active {\n    visibility: visible;\n    opacity: 1;\n    transition-delay: 0.2s;\n    transform: scale(1);\n}\n#noVNC_control_bar_hint.noVNC_notransition {\n    transition: none !important;\n}\n\n/* Control bar buttons */\n#noVNC_control_bar .noVNC_button {\n    min-width: unset;\n    padding: 4px 4px;\n    vertical-align: middle;\n    border:1px solid rgba(255, 255, 255, 0.2);\n    border-radius: 6px;\n    background-color: transparent;\n}\n#noVNC_control_bar .noVNC_button.noVNC_selected {\n    border-color: rgba(0, 0, 0, 0.8);\n    background-color: rgba(0, 0, 0, 0.5);\n}\n#noVNC_control_bar .noVNC_button.noVNC_hidden {\n    display: none !important;\n}\n\n/* Panels */\n.noVNC_panel {\n    transform: translateX(25px);\n\n    transition: 0.5s ease-in-out;\n\n    box-sizing: border-box; /* so max-width don't have to care about padding */\n    max-width: calc(100vw - 75px - 25px); /* minus left and right margins */\n    max-height: 100vh; /* Chrome is buggy with 100% */\n    overflow-x: hidden;\n    overflow-y: auto;\n\n    visibility: hidden;\n    opacity: 0;\n\n    padding: 15px;\n\n    background: #fff;\n    border-radius: 12px;\n    color: #000;\n    border: 2px solid #E0E0E0;\n    box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);\n}\n.noVNC_panel.noVNC_open {\n    visibility: visible;\n    opacity: 1;\n    transform: translateX(75px);\n}\n.noVNC_right .noVNC_vcenter {\n    left: auto;\n    right: 0;\n}\n.noVNC_right .noVNC_panel {\n    transform: translateX(-25px);\n}\n.noVNC_right .noVNC_panel.noVNC_open {\n    transform: translateX(-75px);\n}\n\n.noVNC_panel > * {\n    display: block;\n    margin: 10px auto;\n}\n.noVNC_panel > *:first-child {\n    margin-top: 0 !important;\n}\n.noVNC_panel > *:last-child {\n    margin-bottom: 0 !important;\n}\n\n.noVNC_panel hr {\n    border: none;\n    border-top: 1px solid var(--novnc-lightgrey);\n    width: 100%; /* <hr> inside a flexbox will otherwise be 0px wide */\n}\n\n.noVNC_panel label {\n    display: block;\n    white-space: nowrap;\n    margin: 5px;\n}\n@media (max-width: 540px) {\n    /* Allow wrapping on small screens */\n    .noVNC_panel label {\n        white-space: unset;\n    }\n}\n\n.noVNC_panel li {\n    margin: 5px;\n}\n\n.noVNC_panel .noVNC_heading {\n    background-color: var(--novnc-blue);\n    border-radius: 6px;\n    padding: 5px 8px;\n    /* Compensate for padding in image */\n    padding-right: 11px;\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    color: white;\n    font-size: 20px;\n    font-weight: bold;\n    white-space: nowrap;\n}\n.noVNC_panel .noVNC_heading img {\n    vertical-align: bottom;\n}\n\n.noVNC_panel form {\n    display: flex;\n    flex-direction: column;\n    gap: 12px\n}\n\n.noVNC_panel .button_row {\n    margin-top: 10px;\n    display: flex;\n    gap: 10px;\n    justify-content: space-between;\n}\n.noVNC_panel .button_row *:only-child {\n    margin-left: auto; /* Align single buttons to the right */\n}\n\n/* Expanders */\n.noVNC_expander {\n    cursor: pointer;\n}\n.noVNC_expander::before {\n    content: url(\"../images/expander.svg\");\n    display: inline-block;\n    margin-right: 5px;\n    transition: 0.2s ease-in-out;\n}\n.noVNC_expander.noVNC_open::before {\n    transform: rotateZ(90deg);\n}\n.noVNC_expander ~ * {\n    margin: 5px;\n    margin-left: 10px;\n    padding: 5px;\n    background: rgba(0, 0, 0, 0.04);\n    border-radius: 6px;\n}\n.noVNC_expander:not(.noVNC_open) ~ * {\n    display: none;\n}\n\n/* Control bar content */\n\n#noVNC_control_bar .noVNC_logo {\n    font-size: 13px;\n}\n\n.noVNC_logo + hr {\n    /* Remove all but top border */\n    border: none;\n    border-top: 1px solid rgba(255, 255, 255, 0.2);\n}\n\n:root:not(.noVNC_connected) #noVNC_view_drag_button {\n    display: none;\n}\n\n/* noVNC Touch Device only buttons */\n:root:not(.noVNC_connected) #noVNC_mobile_buttons {\n    display: none;\n}\n@media not all and (any-pointer: coarse) {\n    /* FIXME: The button for the virtual keyboard is the only button in this\n              group of \"mobile buttons\". It is bad to assume that no touch\n              devices have physical keyboards available. Hopefully we can get\n              a media query for this:\n              https://github.com/w3c/csswg-drafts/issues/3871 */\n    :root.noVNC_connected #noVNC_mobile_buttons {\n        display: none;\n    }\n}\n\n/* Extra manual keys */\n:root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button {\n    display: none;\n}\n\n#noVNC_modifiers {\n    background-color: var(--novnc-darkgrey);\n    border: none;\n    padding: 10px;\n}\n\n/* Shutdown/Reboot */\n:root:not(.noVNC_connected) #noVNC_power_button {\n    display: none;\n}\n#noVNC_power {\n}\n#noVNC_power_buttons {\n    display: none;\n}\n\n#noVNC_power input[type=button] {\n    width: 100%;\n}\n\n/* Clipboard */\n:root:not(.noVNC_connected) #noVNC_clipboard_button {\n    display: none;\n}\n#noVNC_clipboard_text {\n    width: 360px;\n    min-width: 150px;\n    height: 160px;\n    min-height: 70px;\n\n    box-sizing: border-box;\n    max-width: 100%;\n    /* minus approximate height of title, height of subtitle, and margin */\n    max-height: calc(100vh - 10em - 25px);\n}\n\n/* Settings */\n#noVNC_settings {\n}\n#noVNC_settings ul {\n    list-style: none;\n    padding: 0px;\n}\n#noVNC_settings button,\n#noVNC_settings select,\n#noVNC_settings textarea,\n#noVNC_settings input:not([type=checkbox]):not([type=radio]) {\n    margin-left: 6px;\n    /* Prevent inputs in settings from being too wide */\n    max-width: calc(100% - 6px - var(--input-xpadding) * 2);\n}\n\n#noVNC_setting_port {\n    width: 80px;\n}\n#noVNC_setting_path {\n    width: 100px;\n}\n\n/* Version */\n\n.noVNC_version_wrapper {\n    font-size: small;\n}\n\n.noVNC_version {\n    margin-left: 1rem;\n}\n\n/* Connection controls */\n:root:not(.noVNC_connected) #noVNC_disconnect_button {\n    display: none;\n}\n\n/* ----------------------------------------\n * Status dialog\n * ----------------------------------------\n */\n\n#noVNC_status {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    z-index: 100;\n    transform: translateY(-100%);\n\n    cursor: pointer;\n\n    transition: 0.5s ease-in-out;\n\n    visibility: hidden;\n    opacity: 0;\n\n    padding: 5px;\n\n    display: flex;\n    flex-direction: row;\n    justify-content: center;\n    align-content: center;\n\n    line-height: 1.6;\n    word-wrap: break-word;\n    color: #fff;\n\n    border-bottom: 1px solid rgba(0, 0, 0, 0.9);\n}\n#noVNC_status.noVNC_open {\n    transform: translateY(0);\n    visibility: visible;\n    opacity: 1;\n}\n\n#noVNC_status::before {\n    content: \"\";\n    display: inline-block;\n    width: 25px;\n    height: 25px;\n    margin-right: 5px;\n}\n\n#noVNC_status.noVNC_status_normal {\n    background: rgba(128,128,128,0.9);\n}\n#noVNC_status.noVNC_status_normal::before {\n    content: url(\"../images/info.svg\") \" \";\n}\n#noVNC_status.noVNC_status_error {\n    background: rgba(200,55,55,0.9);\n}\n#noVNC_status.noVNC_status_error::before {\n    content: url(\"../images/error.svg\") \" \";\n}\n#noVNC_status.noVNC_status_warn {\n    background: rgba(180,180,30,0.9);\n}\n#noVNC_status.noVNC_status_warn::before {\n    content: url(\"../images/warning.svg\") \" \";\n}\n\n/* ----------------------------------------\n * Connect dialog\n * ----------------------------------------\n */\n\n#noVNC_connect_dlg {\n    transition: 0.5s ease-in-out;\n\n    transform: scale(0, 0);\n    visibility: hidden;\n    opacity: 0;\n}\n#noVNC_connect_dlg.noVNC_open {\n    transform: scale(1, 1);\n    visibility: visible;\n    opacity: 1;\n}\n#noVNC_connect_dlg .noVNC_logo {\n    transition: 0.5s ease-in-out;\n    padding: 10px;\n    margin-bottom: 10px;\n\n    font-size: 80px;\n    text-align: center;\n\n    border-radius: 6px;\n}\n@media (max-width: 440px) {\n    #noVNC_connect_dlg {\n        max-width: calc(100vw - 100px);\n    }\n    #noVNC_connect_dlg .noVNC_logo {\n        font-size: calc(25vw - 30px);\n    }\n}\n#noVNC_connect_dlg div {\n    padding: 18px;\n\n    background-color: var(--novnc-darkgrey);\n    border-radius: 12px;\n    text-align: center;\n    font-size: 20px;\n\n    box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);\n}\n#noVNC_connect_button {\n    width: 100%;\n    padding: 6px 30px;\n    cursor: pointer;\n    border-color: transparent;\n    border-radius: 12px;\n    background-color: var(--novnc-blue);\n    color: white;\n\n    display: flex;\n    justify-content: center;\n    place-items: center;\n    gap: 4px;\n}\n\n#noVNC_connect_button img {\n    vertical-align: bottom;\n    height: 1.3em;\n}\n\n/* ----------------------------------------\n * Server verification dialog\n * ----------------------------------------\n */\n\n#noVNC_verify_server_dlg {\n    position: relative;\n\n    transform: translateY(-50px);\n}\n#noVNC_verify_server_dlg.noVNC_open {\n    transform: translateY(0);\n}\n#noVNC_fingerprint_block {\n    margin: 10px;\n}\n\n/* ----------------------------------------\n * Password dialog\n * ----------------------------------------\n */\n\n#noVNC_credentials_dlg {\n    position: relative;\n\n    transform: translateY(-50px);\n}\n#noVNC_credentials_dlg.noVNC_open {\n    transform: translateY(0);\n}\n#noVNC_username_block.noVNC_hidden,\n#noVNC_password_block.noVNC_hidden {\n    display: none;\n}\n\n\n/* ----------------------------------------\n * Main area\n * ----------------------------------------\n */\n\n/* Transition screen */\n#noVNC_transition {\n    transition: 0.5s ease-in-out;\n\n    display: flex;\n    opacity: 0;\n    visibility: hidden;\n\n    position: fixed;\n    top: 0;\n    left: 0;\n    bottom: 0;\n    right: 0;\n\n    color: white;\n    background: rgba(0, 0, 0, 0.5);\n    z-index: 50;\n\n    /*display: flex;*/\n    align-items: center;\n    justify-content: center;\n    flex-direction: column;\n}\n:root.noVNC_loading #noVNC_transition,\n:root.noVNC_connecting #noVNC_transition,\n:root.noVNC_disconnecting #noVNC_transition,\n:root.noVNC_reconnecting #noVNC_transition {\n    opacity: 1;\n    visibility: visible;\n}\n:root:not(.noVNC_reconnecting) #noVNC_cancel_reconnect_button {\n    display: none;\n}\n#noVNC_transition_text {\n    font-size: 1.5em;\n}\n\n/* Main container */\n#noVNC_container {\n    width: 100%;\n    height: 100%;\n    background-color: #313131;\n    border-bottom-right-radius: 800px 600px;\n    /*border-top-left-radius: 800px 600px;*/\n\n    /* If selection isn't disabled, long-pressing stuff in the sidebar\n       can accidentally select the container or the canvas. This can\n       happen when attempting to move the handle. */\n    user-select: none;\n    -webkit-user-select: none;\n}\n\n#noVNC_keyboardinput {\n    width: 1px;\n    height: 1px;\n    background-color: #fff;\n    color: #fff;\n    border: 0;\n    position: absolute;\n    left: -40px;\n    z-index: -1;\n    ime-mode: disabled;\n}\n\n/*Default noVNC logo.*/\n/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */\n@font-face {\n    font-family: 'Orbitron';\n    font-style: normal;\n    font-weight: 700;\n    src: local('?'), url('Orbitron700.woff') format('woff'),\n                     url('Orbitron700.ttf') format('truetype');\n}\n\n.noVNC_logo {\n    color: var(--novnc-yellow);\n    font-family: 'Orbitron', 'OrbitronTTF', sans-serif;\n    line-height: 0.9;\n    text-shadow: 0.1em 0.1em 0 black;\n}\n.noVNC_logo span{\n    color: var(--novnc-green);\n}\n\n#noVNC_bell {\n    display: none;\n}\n\n/* ----------------------------------------\n * Media sizing\n * ----------------------------------------\n */\n\n@media screen and (max-width: 640px){\n    #noVNC_logo {\n        font-size: 150px;\n    }\n}\n\n@media screen and (min-width: 321px) and (max-width: 480px) {\n    #noVNC_logo {\n        font-size: 110px;\n    }\n}\n\n@media screen and (max-width: 320px) {\n    #noVNC_logo {\n        font-size: 90px;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/app/styles/constants.css",
    "content": "/*\n * noVNC general CSS constant variables\n * Copyright (C) 2025 The noVNC authors\n * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)\n * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).\n */\n\n/* ---------- COLORS ----------- */\n\n:root {\n    --novnc-grey: rgb(128, 128, 128);\n    --novnc-lightgrey: rgb(192, 192, 192);\n    --novnc-darkgrey: rgb(92, 92, 92);\n\n    /* Transparent to make button colors adapt to the background */\n    --novnc-buttongrey: rgba(192, 192, 192, 0.5);\n\n    --novnc-blue: rgb(110, 132, 163);\n    --novnc-lightblue: rgb(74, 144, 217);\n    --novnc-darkblue: rgb(83, 99, 122);\n\n    --novnc-green: rgb(0, 128, 0);\n    --novnc-yellow: rgb(255, 255, 0);\n}\n\n/* ------ MISC PROPERTIES ------ */\n\n:root {\n    --input-xpadding: 1em;\n}\n"
  },
  {
    "path": "services/gateway/noVNC/app/styles/input.css",
    "content": "/*\n * noVNC general input element CSS\n * Copyright (C) 2025 The noVNC authors\n * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)\n * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).\n */\n\n/* ------- SHARED BETWEEN INPUT ELEMENTS -------- */\n\ninput,\ntextarea,\nbutton,\nselect,\ninput::file-selector-button {\n    padding: 0.5em var(--input-xpadding);\n    border-radius: 6px;\n    appearance: none;\n    text-overflow: ellipsis;\n\n    /* Respect standard font settings */\n    font: inherit;\n    line-height: 1.6;\n}\ninput:disabled,\ntextarea:disabled,\nbutton:disabled,\nselect:disabled,\nlabel[disabled] {\n    opacity: 0.4;\n}\n\ninput:focus-visible,\ntextarea:focus-visible,\nbutton:focus-visible,\nselect:focus-visible,\ninput:focus-visible::file-selector-button {\n    outline: 2px solid var(--novnc-lightblue);\n    outline-offset: 1px;\n}\n\n/* ------- TEXT INPUT -------- */\n\ninput:not([type]),\ninput[type=date],\ninput[type=datetime-local],\ninput[type=email],\ninput[type=month],\ninput[type=number],\ninput[type=password],\ninput[type=search],\ninput[type=tel],\ninput[type=text],\ninput[type=time],\ninput[type=url],\ninput[type=week],\ntextarea {\n    border: 1px solid var(--novnc-lightgrey);\n    /* Account for borders on text inputs, buttons dont have borders */\n    padding: calc(0.5em - 1px) var(--input-xpadding);\n}\ninput:not([type]):focus-visible,\ninput[type=date]:focus-visible,\ninput[type=datetime-local]:focus-visible,\ninput[type=email]:focus-visible,\ninput[type=month]:focus-visible,\ninput[type=number]:focus-visible,\ninput[type=password]:focus-visible,\ninput[type=search]:focus-visible,\ninput[type=tel]:focus-visible,\ninput[type=text]:focus-visible,\ninput[type=time]:focus-visible,\ninput[type=url]:focus-visible,\ninput[type=week]:focus-visible,\ntextarea:focus-visible {\n    outline-offset: -1px;\n}\n\ntextarea {\n    margin: unset; /* Remove Firefox's built in margin */\n    /* Prevent layout from shifting when scrollbars show */\n    scrollbar-gutter: stable;\n    /* Make textareas show at minimum one line. This does not work when\n       using box-sizing border-box, in which case, vertical padding and\n       border width needs to be taken into account. */\n    min-height: 1lh;\n    vertical-align: baseline; /* Firefox gives \"text-bottom\" by default */\n}\n\n/* ------- NUMBER PICKERS ------- */\n\n/* We can't style the number spinner buttons:\n   https://github.com/w3c/csswg-drafts/issues/8777 */\ninput[type=number]::-webkit-inner-spin-button,\ninput[type=number]::-webkit-outer-spin-button {\n    /* Get rid of increase/decrease buttons in WebKit */\n    appearance: none;\n}\ninput[type=number] {\n    /* Get rid of increase/decrease buttons in Firefox */\n    appearance: textfield;\n}\n\n/* ------- BUTTON ACTIVATIONS -------- */\n\n/* A color overlay that depends on the activation level. The level can then be\n   set for different states on an element, for example hover and click on a\n   <button>. */\ninput, button, select, option,\ninput::file-selector-button,\n.button-activations {\n    --button-activation-level: 0;\n    /* Note that CSS variables aren't functions, beware when inheriting */\n    --button-activation-alpha: calc(0.08 * var(--button-activation-level));\n    /* FIXME: We want the image() function instead of the linear-gradient()\n              function below. But it's not supported in the browsers yet. */\n    --button-activation-overlay:\n        linear-gradient(rgba(0, 0, 0, var(--button-activation-alpha))\n        100%, transparent);\n    --button-activation-overlay-light:\n        linear-gradient(rgba(255, 255, 255, calc(0.23 * var(--button-activation-level)))\n        100%, transparent);\n}\n.button-activations {\n    background-image: var(--button-activation-overlay);\n\n    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */\n    -webkit-tap-highlight-color: transparent;\n}\n/* When we want the light overlay on activations instead.\n   This is best used on elements with darker backgrounds. */\n.button-activations.light-overlay {\n    background-image: var(--button-activation-overlay-light);\n    /* Can't use the normal blend mode since that gives washed out colors. */\n    /* FIXME: For elements with these activation overlays we'd like only\n              the luminosity to change. The proprty \"background-blend-mode\" set\n              to \"luminosity\" sounds good, but it doesn't work as intended,\n              see: https://bugzilla.mozilla.org/show_bug.cgi?id=1806417 */\n    background-blend-mode: overlay;\n}\n\ninput:hover, button:hover, select:hover, option:hover,\ninput::file-selector-button:hover,\n.button-activations:hover {\n    --button-activation-level: 1;\n}\n/* Unfortunately we have to disable the :hover effect on touch devices,\n   otherwise the style lingers after tapping the button. */\n@media (any-pointer: coarse) {\n    input:hover, button:hover, select:hover, option:hover,\n    input::file-selector-button:hover,\n    .button-activations:hover {\n        --button-activation-level: 0;\n    }\n}\ninput:active, button:active, select:active, option:active,\ninput::file-selector-button:active,\n.button-activations:active {\n    --button-activation-level: 2;\n}\ninput:disabled, button:disabled, select:disabled, select:disabled option,\ninput:disabled::file-selector-button,\n.button-activations:disabled {\n    --button-activation-level: 0;\n}\n\n/* ------- BUTTONS -------- */\n\ninput[type=button],\ninput[type=color],\ninput[type=image],\ninput[type=reset],\ninput[type=submit],\ninput::file-selector-button,\nbutton,\nselect {\n    min-width: 8em;\n    border: none;\n    color: black;\n    font-weight: bold;\n    background-color: var(--novnc-buttongrey);\n    background-image: var(--button-activation-overlay);\n    cursor: pointer;\n    /* Disable Chrome's touch tap highlight */\n    -webkit-tap-highlight-color: transparent;\n}\ninput[type=button]:disabled,\ninput[type=color]:disabled,\ninput[type=image]:disabled,\ninput[type=reset]:disabled,\ninput[type=submit]:disabled,\ninput:disabled::file-selector-button,\nbutton:disabled,\nselect:disabled {\n    /* See Firefox bug:\n       https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */\n    cursor: default;\n}\n\ninput[type=button],\ninput[type=color],\ninput[type=reset],\ninput[type=submit] {\n    /* Workaround for text-overflow bugs in Firefox and Chromium:\n        https://bugzilla.mozilla.org/show_bug.cgi?id=1800077\n        https://bugs.chromium.org/p/chromium/issues/detail?id=1383144 */\n    overflow: clip;\n}\n\n/* ------- COLOR PICKERS ------- */\n\ninput[type=color] {\n    min-width: unset;\n    box-sizing: content-box;\n    width: 1.4em;\n    height: 1.4em;\n}\ninput[type=color]::-webkit-color-swatch-wrapper {\n    padding: 0;\n}\n/* -webkit-color-swatch & -moz-color-swatch cant be in a selector list:\n   https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */\ninput[type=color]::-webkit-color-swatch {\n    border: none;\n    border-radius: 6px;\n}\ninput[type=color]::-moz-color-swatch {\n    border: none;\n    border-radius: 6px;\n}\n\n/* -- SHARED BETWEEN CHECKBOXES, RADIOBUTTONS AND THE TOGGLE CLASS -- */\n\ninput[type=radio],\ninput[type=checkbox] {\n    display: inline-flex;\n    justify-content: center;\n    align-items: center;\n    background-color: var(--novnc-buttongrey);\n    background-image: var(--button-activation-overlay);\n    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */\n    -webkit-tap-highlight-color: transparent;\n    width: 16px;\n    --checkradio-height: 16px;\n    height: var(--checkradio-height);\n    padding: 0;\n    margin: 0 6px 0 0;\n    /* Don't have transitions for outline in order to be consistent\n       with other elements */\n    transition: all 0.2s, outline-color 0s, outline-offset 0s;\n\n    /* A transparent outline in order to work around a graphical clipping issue\n       in WebKit. See bug: https://bugs.webkit.org/show_bug.cgi?id=256003 */\n    outline: 1px solid transparent;\n    position: relative; /* Since ::before & ::after are absolute positioned */\n\n    /* We want to align with the middle of capital letters, this requires\n       a workaround. The default behavior is to align the bottom of the element\n       on top of the text baseline, this is too far up.\n       We want to push the element down half the difference in height between\n       it and a capital X. In our font, the height of a capital \"X\" is 0.698em.\n     */\n    vertical-align: calc(0px - (var(--checkradio-height) - 0.698em) / 2);\n    /* FIXME: Could write 1cap instead of 0.698em, but it's only supported in\n              Firefox as of 2023 */\n    /* FIXME: We probably want to use round() here, see bug 8148 */\n}\ninput[type=radio]:focus-visible,\ninput[type=checkbox]:focus-visible {\n    outline-color: var(--novnc-lightblue);\n}\ninput[type=checkbox]::before,\ninput[type=checkbox]:not(.toggle)::after,\ninput[type=radio]::before,\ninput[type=radio]::after {\n    content: \"\";\n    display: block; /* width & height doesn't work on inline elements */\n    transition: inherit;\n    /* Let's prevent the pseudo-elements from taking up layout space so that\n       the ::before and ::after pseudo-elements can be in the same place. This\n       is also required for vertical-align: baseline to work like we want it to\n       on radio/checkboxes. If the pseudo-elements take up layout space, the\n       baseline of text inside them will be used instead. */\n    position: absolute;\n}\ninput[type=checkbox]:not(.toggle)::after,\ninput[type=radio]::after {\n    width: 10px;\n    height: 2px;\n    background-color: transparent;\n    border-radius: 2px;\n}\n\n/* ------- CHECKBOXES ------- */\n\ninput[type=checkbox]:not(.toggle) {\n    border-radius: 4px;\n}\ninput[type=checkbox]:not(.toggle):checked,\ninput[type=checkbox]:not(.toggle):indeterminate {\n    background-color: var(--novnc-blue);\n    background-image: var(--button-activation-overlay-light);\n    background-blend-mode: overlay;\n}\ninput[type=checkbox]:not(.toggle)::before {\n    width: 25%;\n    height: 55%;\n    border-style: solid;\n    border-color: transparent;\n    border-width: 0 2px 2px 0;\n    border-radius: 1px;\n    transform: translateY(-1px) rotate(35deg);\n}\ninput[type=checkbox]:not(.toggle):checked::before {\n    border-color: white;\n}\ninput[type=checkbox]:not(.toggle):indeterminate::after {\n    background-color: white;\n}\n\n/* ------- RADIO BUTTONS ------- */\n\ninput[type=radio] {\n    border-radius: 50%;\n    border: 1px solid transparent; /* To ensure a smooth transition */\n}\ninput[type=radio]:checked {\n    border: 4px solid var(--novnc-blue);\n    background-color: white;\n    /* button-activation-overlay should be removed from the radio\n       element to not interfere with button-activation-overlay-light\n       that is set on the ::before element. */\n    background-image: none;\n}\ninput[type=radio]::before {\n    width: inherit;\n    height: inherit;\n    border-radius: inherit;\n    /* We can achieve the highlight overlay effect on border colors by\n       setting button-activation-overlay-light on an element that stays\n       on top (z-axis) of the element with a border. */\n    background-image: var(--button-activation-overlay-light);\n    mix-blend-mode: overlay;\n    opacity: 0;\n}\ninput[type=radio]:checked::before {\n    opacity: 1;\n}\ninput[type=radio]:indeterminate::after {\n    background-color: black;\n}\n\n/* ------- TOGGLE SWITCHES ------- */\n\n/* These are meant to be used instead of checkboxes in some cases. If all of\n   the following critera are true you should use a toggle switch:\n\n    * The choice is a simple ON/OFF or ENABLE/DISABLE\n    * The choice doesn't give the feeling of \"I agree\" or \"I confirm\"\n    * There are not multiple related & grouped options\n */\n\ninput[type=checkbox].toggle {\n    display: inline-block;\n    --checkradio-height: 18px; /* Height value used in calc, see above */\n    width: 31px;\n    cursor: pointer;\n    user-select: none;\n    -webkit-user-select: none;\n    border-radius: 9px;\n}\ninput[type=checkbox].toggle:disabled {\n    cursor: default;\n}\ninput[type=checkbox].toggle:indeterminate {\n    background-color: var(--novnc-buttongrey);\n    background-image: var(--button-activation-overlay);\n}\ninput[type=checkbox].toggle:checked {\n    background-color: var(--novnc-blue);\n    background-image: var(--button-activation-overlay-light);\n    background-blend-mode: overlay;\n}\ninput[type=checkbox].toggle::before {\n    --circle-diameter: 10px;\n    --circle-offset: 4px;\n    width: var(--circle-diameter);\n    height: var(--circle-diameter);\n    top: var(--circle-offset);\n    left: var(--circle-offset);\n    background: white;\n    border-radius: 6px;\n}\ninput[type=checkbox].toggle:checked::before {\n    left: calc(100% - var(--circle-offset) - var(--circle-diameter));\n}\ninput[type=checkbox].toggle:indeterminate::before {\n    left: calc(50% - var(--circle-diameter) / 2);\n}\n\n/* ------- RANGE SLIDERS ------- */\n\ninput[type=range] {\n    border: unset;\n    border-radius: 8px;\n    height: 15px;\n    padding: 0;\n    background: transparent;\n    /* Needed to get properly rounded corners on -moz-range-progress\n       when the thumb is all the way to the right. Without overflow\n       hidden, the pointy edges of the progress track shows to the\n       right of the thumb. */\n    overflow: hidden;\n}\n@supports selector(::-webkit-slider-thumb) {\n    input[type=range] {\n        /* Needs a fixed width to match clip-path */\n        width: 125px;\n        /* overflow: hidden is not ideal for hiding the left part of the box\n           shadow of -webkit-slider-thumb since it doesn't match the smaller\n           border-radius of the progress track. The below clip-path has two\n           circular sides to make the ends of the track have correctly rounded\n           corners. The clip path shape looks something like this:\n\n                  +-------------------------------+\n              /---|                               |---\\\n             |                                         |\n              \\---|                               |---/\n                  +-------------------------------+\n\n           The larger middle part of the clip path is made to have room for the\n           thumb. By using margins on the track, we prevent the thumb from\n           touching the ends of the track.\n         */\n        clip-path: path(' \\\n         M 4.5 3 \\\n         L 4.5 0 \\\n         L 120.5 0 \\\n         L 120.5 3 \\\n         A 1 1 0 0 1 120.5 12 \\\n         L 120.5 15 \\\n         L 4.5 15 \\\n         L 4.5 12 \\\n         A 1 1 0 0 1 4.5 3 \\\n        ');\n    }\n}\ninput[type=range]:hover {\n    cursor: grab;\n}\ninput[type=range]:active {\n    cursor: grabbing;\n}\ninput[type=range]:disabled {\n    cursor: default;\n}\ninput[type=range]:focus-visible {\n    clip-path: none; /* Otherwise it hides the outline */\n}\n/* -webkit-slider.. & -moz-range.. cant be in selector lists:\n   https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */\ninput[type=range]::-webkit-slider-runnable-track {\n    background-color: var(--novnc-buttongrey);\n    height: 7px;\n    border-radius: 4px;\n    margin: 0 3px;\n}\ninput[type=range]::-moz-range-track {\n    background-color: var(--novnc-buttongrey);\n    height: 7px;\n    border-radius: 4px;\n}\ninput[type=range]::-moz-range-progress {\n    background-color: var(--novnc-blue);\n    height: 9px;\n    /* Needs rounded corners only on the left side. Otherwise the rounding of\n       the progress track starts before the thumb, when the thumb is close to\n       the left edge. */\n    border-radius: 5px 0 0 5px;\n}\ninput[type=range]::-webkit-slider-thumb {\n    appearance: none;\n    width: 15px;\n    height: 15px;\n    border-radius: 50%;\n    background-color: white;\n    background-image: var(--button-activation-overlay);\n    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */\n    -webkit-tap-highlight-color: transparent;\n    border: 3px solid var(--novnc-blue);\n    margin-top: -4px; /* (track height / 2) - (thumb height /2) */\n\n    /* Since there is no way to style the left part of the range track in\n       webkit, we add a large shadow (1000px wide) to the left of the thumb and\n       then crop it with a clip-path shaped like this:\n                              ___\n        +-------------------/     \\\n        |      progress     |Thumb|\n        +-------------------\\ ___ /\n\n        The large left part of the shadow is clipped by another clip-path on on\n        the main range input element. */\n    /* FIXME: We can remove the box shadow workaround when this is standardized:\n              https://github.com/w3c/csswg-drafts/issues/4410 */\n\n    box-shadow: calc(-100vw - 8px) 0 0 100vw var(--novnc-blue);\n    clip-path: path(' \\\n     M -1000 3 \\\n     L 3 3 \\\n     L 15 7.5 \\\n     A 1 1 0 0 1 0 7.5 \\\n     A 1 1 0 0 1 15 7.5 \\\n     L 3 12 \\\n     L -1000 12 Z \\\n    ');\n}\ninput[type=range]::-moz-range-thumb {\n    appearance: none;\n    width: 15px;\n    height: 15px;\n    border-radius: 50%;\n    box-sizing: border-box;\n    background-color: white;\n    background-image: var(--button-activation-overlay);\n    border: 3px solid var(--novnc-blue);\n    margin-top: -7px;\n}\n\n/* ------- FILE CHOOSERS ------- */\n\ninput[type=file] {\n    background-image: none;\n    border: none;\n}\ninput::file-selector-button {\n    margin-right: 6px;\n}\ninput[type=file]:focus-visible {\n    outline: none; /* We outline the button instead of the entire element */\n}\n\n/* ------- SELECT BUTTONS ------- */\n\nselect {\n    --select-arrow: url('data:image/svg+xml;utf8, \\\n        <svg width=\"11\" height=\"6\" version=\"1.1\" viewBox=\"0 0 11 6\" \\\n             xmlns=\"http://www.w3.org/2000/svg\"> \\\n            <path d=\"m10.5.5-5 5-5-5\" fill=\"none\" \\\n                  stroke=\"black\" stroke-width=\"1.5\" \\\n                  stroke-linecap=\"round\" stroke-linejoin=\"round\"/> \\\n        </svg>');\n\n    /* FIXME: A bug in Firefox, requires a workaround for the background:\n              https://bugzilla.mozilla.org/show_bug.cgi?id=1810958 */\n    /* The dropdown list will show the select element's background above and\n       below the options in Firefox. We want the entire dropdown to be white. */\n    background-color: white;\n    /* However, we don't want the select element to actually show a white\n       background, so let's place a gradient above it with the color we want. */\n    --grey-background: linear-gradient(var(--novnc-buttongrey) 100%,\n                                       transparent);\n    background-image:\n        var(--select-arrow),\n        var(--button-activation-overlay),\n        var(--grey-background);\n    background-position: calc(100% - var(--input-xpadding)), left top, left top;\n    background-repeat: no-repeat;\n    padding-right: calc(2*var(--input-xpadding) + 11px);\n    overflow: auto;\n}\n/* FIXME: :active isn't set when the <select> is opened in Firefox:\n          https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */\nselect:active {\n    /* Rotated arrow */\n    background-image: url('data:image/svg+xml;utf8, \\\n        <svg width=\"11\" height=\"6\" version=\"1.1\" viewBox=\"0 0 11 6\" \\\n             xmlns=\"http://www.w3.org/2000/svg\" transform=\"rotate(180)\"> \\\n            <path d=\"m10.5.5-5 5-5-5\" fill=\"none\" \\\n                  stroke=\"black\" stroke-width=\"1.5\" \\\n                  stroke-linecap=\"round\" stroke-linejoin=\"round\"/> \\\n        </svg>'),\n        var(--button-activation-overlay),\n        var(--grey-background);\n}\nselect:disabled {\n    background-image:\n        var(--select-arrow),\n        var(--grey-background);\n}\n/* Note that styling for <option> doesn't work in all browsers\n   since its often drawn directly by the OS. We are generally very\n   limited in what we can change here. */\noption {\n    /* Prevent Chrome from inheriting background-color from the <select> */\n    background-color: white;\n    color: black;\n    font-weight: normal;\n    background-image: var(--button-activation-overlay);\n}\noption:checked {\n    background-color: var(--novnc-lightgrey);\n}\n/* Change the look when the <select> isn't used as a dropdown. When \"size\"\n   or \"multiple\" are set, these elements behaves more like lists. */\nselect[size]:not([size=\"1\"]), select[multiple] {\n    background-color: white;\n    background-image: unset; /* Don't show the arrow and other gradients */\n    border: 1px solid var(--novnc-lightgrey);\n    padding: 0;\n    font-weight: normal; /* Without this, options get bold font in WebKit. */\n\n    /* As an exception to the \"list\"-look, multi-selects in Chrome on Android,\n       and Safari on iOS, are unfortunately designed to be shown as a single\n       line. We can mitigate this inconsistency by at least fixing the height\n       here. By setting a min-height that matches other input elements, it\n       doesn't look too much out of place:\n         (1px border * 2) + (6.5px padding * 2) + 24px line-height = 39px */\n    min-height: 39px;\n}\nselect[size]:not([size=\"1\"]):focus-visible,\nselect[multiple]:focus-visible {\n    /* Text input style focus-visible highlight */\n    outline-offset: -1px;\n}\nselect[size]:not([size=\"1\"]) option, select[multiple] option {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    padding: 4px var(--input-xpadding);\n}\n"
  },
  {
    "path": "services/gateway/noVNC/app/ui.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\nimport * as Log from '../core/util/logging.js';\nimport _, { l10n } from './localization.js';\nimport { isTouchDevice, isMac, isIOS, isAndroid, isChromeOS, isSafari,\n         hasScrollbarGutter, dragThreshold }\n    from '../core/util/browser.js';\nimport { setCapture, getPointerEvent } from '../core/util/events.js';\nimport KeyTable from \"../core/input/keysym.js\";\nimport keysyms from \"../core/input/keysymdef.js\";\nimport Keyboard from \"../core/input/keyboard.js\";\nimport RFB from \"../core/rfb.js\";\nimport * as WebUtil from \"./webutil.js\";\n\nconst PAGE_TITLE = \"noVNC\";\n\nconst LINGUAS = [\"cs\", \"de\", \"el\", \"es\", \"fr\", \"it\", \"ja\", \"ko\", \"nl\", \"pl\", \"pt_BR\", \"ru\", \"sv\", \"tr\", \"zh_CN\", \"zh_TW\"];\n\nconst UI = {\n\n    customSettings: {},\n\n    connected: false,\n    desktopName: \"\",\n\n    statusTimeout: null,\n    hideKeyboardTimeout: null,\n    idleControlbarTimeout: null,\n    closeControlbarTimeout: null,\n\n    controlbarGrabbed: false,\n    controlbarDrag: false,\n    controlbarMouseDownClientY: 0,\n    controlbarMouseDownOffsetY: 0,\n\n    lastKeyboardinput: null,\n    defaultKeyboardinputLen: 100,\n\n    inhibitReconnect: true,\n    reconnectCallback: null,\n    reconnectPassword: null,\n\n    async start(options={}) {\n        UI.customSettings = options.settings || {};\n        if (UI.customSettings.defaults === undefined) {\n            UI.customSettings.defaults = {};\n        }\n        if (UI.customSettings.mandatory === undefined) {\n            UI.customSettings.mandatory = {};\n        }\n\n        // Set up translations\n        try {\n            await l10n.setup(LINGUAS, \"app/locale/\");\n        } catch (err) {\n            Log.Error(\"Failed to load translations: \" + err);\n        }\n\n        // Initialize setting storage\n        await WebUtil.initSettings();\n\n        // Wait for the page to load\n        if (document.readyState !== \"interactive\" && document.readyState !== \"complete\") {\n            await new Promise((resolve, reject) => {\n                document.addEventListener('DOMContentLoaded', resolve);\n            });\n        }\n\n        UI.initSettings();\n\n        // Translate the DOM\n        l10n.translateDOM();\n\n        // We rely on modern APIs which might not be available in an\n        // insecure context\n        if (!window.isSecureContext) {\n            // FIXME: This gets hidden when connecting\n            UI.showStatus(_(\"Running without HTTPS is not recommended, crashes or other issues are likely.\"), 'error');\n        }\n\n        // Try to fetch version number\n        try {\n            let response = await fetch('./package.json');\n            if (!response.ok) {\n                throw Error(\"\" + response.status + \" \" + response.statusText);\n            }\n\n            let packageInfo = await response.json();\n            Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);\n        } catch (err) {\n            Log.Error(\"Couldn't fetch package.json: \" + err);\n            Array.from(document.getElementsByClassName('noVNC_version_wrapper'))\n                .concat(Array.from(document.getElementsByClassName('noVNC_version_separator')))\n                .forEach(el => el.style.display = 'none');\n        }\n\n        // Adapt the interface for touch screen devices\n        if (isTouchDevice) {\n            // Remove the address bar\n            setTimeout(() => window.scrollTo(0, 1), 100);\n        }\n\n        // Restore control bar position\n        if (WebUtil.readSetting('controlbar_pos') === 'right') {\n            UI.toggleControlbarSide();\n        }\n\n        UI.initFullscreen();\n\n        // Setup event handlers\n        UI.addControlbarHandlers();\n        UI.addTouchSpecificHandlers();\n        UI.addExtraKeysHandlers();\n        UI.addMachineHandlers();\n        UI.addConnectionControlHandlers();\n        UI.addClipboardHandlers();\n        UI.addSettingsHandlers();\n        document.getElementById(\"noVNC_status\")\n            .addEventListener('click', UI.hideStatus);\n\n        // Bootstrap fallback input handler\n        UI.keyboardinputReset();\n\n        UI.openControlbar();\n\n        UI.updateVisualState('init');\n\n        document.documentElement.classList.remove(\"noVNC_loading\");\n\n        // Always attempt to connect automatically, ignoring the 'autoconnect' setting\n        UI.connect();\n    },\n\n    initFullscreen() {\n        // Only show the button if fullscreen is properly supported\n        // * Safari doesn't support alphanumerical input while in fullscreen\n        if (!isSafari() &&\n            (document.documentElement.requestFullscreen ||\n             document.documentElement.mozRequestFullScreen ||\n             document.documentElement.webkitRequestFullscreen ||\n             document.body.msRequestFullscreen)) {\n            document.getElementById('noVNC_fullscreen_button')\n                .classList.remove(\"noVNC_hidden\");\n            UI.addFullscreenHandlers();\n        }\n    },\n\n    initSettings() {\n        // Logging selection dropdown\n        const llevels = ['error', 'warn', 'info', 'debug'];\n        for (let i = 0; i < llevels.length; i += 1) {\n            UI.addOption(document.getElementById('noVNC_setting_logging'), llevels[i], llevels[i]);\n        }\n\n        // Settings with immediate effects\n        UI.initSetting('logging', 'warn');\n        UI.updateLogging();\n\n        UI.setupSettingLabels();\n\n        /* Populate the controls if defaults are provided in the URL */\n        UI.initSetting('host', '');\n        UI.initSetting('port', 0);\n        UI.initSetting('encrypt', (window.location.protocol === \"https:\"));\n        UI.initSetting('password');\n        UI.initSetting('autoconnect', false);\n        UI.initSetting('view_clip', false);\n        UI.initSetting('resize', 'scale');\n        UI.initSetting('quality', 6);\n        UI.initSetting('compression', 2);\n        UI.initSetting('shared', true);\n        UI.initSetting('bell', 'on');\n        UI.initSetting('view_only', false);\n        UI.initSetting('show_dot', false);\n        UI.initSetting('path', 'websockify');\n        UI.initSetting('repeaterID', '');\n        UI.initSetting('reconnect', false);\n        UI.initSetting('reconnect_delay', 5000);\n    },\n    // Adds a link to the label elements on the corresponding input elements\n    setupSettingLabels() {\n        const labels = document.getElementsByTagName('LABEL');\n        for (let i = 0; i < labels.length; i++) {\n            const htmlFor = labels[i].htmlFor;\n            if (htmlFor != '') {\n                const elem = document.getElementById(htmlFor);\n                if (elem) elem.label = labels[i];\n            } else {\n                // If 'for' isn't set, use the first input element child\n                const children = labels[i].children;\n                for (let j = 0; j < children.length; j++) {\n                    if (children[j].form !== undefined) {\n                        children[j].label = labels[i];\n                        break;\n                    }\n                }\n            }\n        }\n    },\n\n/* ------^-------\n*     /INIT\n* ==============\n* EVENT HANDLERS\n* ------v------*/\n\n    addControlbarHandlers() {\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('mousemove', UI.activateControlbar);\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('mouseup', UI.activateControlbar);\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('mousedown', UI.activateControlbar);\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('keydown', UI.activateControlbar);\n\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('mousedown', UI.keepControlbar);\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('keydown', UI.keepControlbar);\n\n        document.getElementById(\"noVNC_view_drag_button\")\n            .addEventListener('click', UI.toggleViewDrag);\n\n        document.getElementById(\"noVNC_control_bar_handle\")\n            .addEventListener('mousedown', UI.controlbarHandleMouseDown);\n        document.getElementById(\"noVNC_control_bar_handle\")\n            .addEventListener('mouseup', UI.controlbarHandleMouseUp);\n        document.getElementById(\"noVNC_control_bar_handle\")\n            .addEventListener('mousemove', UI.dragControlbarHandle);\n        // resize events aren't available for elements\n        window.addEventListener('resize', UI.updateControlbarHandle);\n\n        const exps = document.getElementsByClassName(\"noVNC_expander\");\n        for (let i = 0;i < exps.length;i++) {\n            exps[i].addEventListener('click', UI.toggleExpander);\n        }\n    },\n\n    addTouchSpecificHandlers() {\n        document.getElementById(\"noVNC_keyboard_button\")\n            .addEventListener('click', UI.toggleVirtualKeyboard);\n\n        UI.touchKeyboard = new Keyboard(document.getElementById('noVNC_keyboardinput'));\n        UI.touchKeyboard.onkeyevent = UI.keyEvent;\n        UI.touchKeyboard.grab();\n        document.getElementById(\"noVNC_keyboardinput\")\n            .addEventListener('input', UI.keyInput);\n        document.getElementById(\"noVNC_keyboardinput\")\n            .addEventListener('focus', UI.onfocusVirtualKeyboard);\n        document.getElementById(\"noVNC_keyboardinput\")\n            .addEventListener('blur', UI.onblurVirtualKeyboard);\n        document.getElementById(\"noVNC_keyboardinput\")\n            .addEventListener('submit', () => false);\n\n        document.documentElement\n            .addEventListener('mousedown', UI.keepVirtualKeyboard, true);\n\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('touchstart', UI.activateControlbar);\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('touchmove', UI.activateControlbar);\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('touchend', UI.activateControlbar);\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('input', UI.activateControlbar);\n\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('touchstart', UI.keepControlbar);\n        document.getElementById(\"noVNC_control_bar\")\n            .addEventListener('input', UI.keepControlbar);\n\n        document.getElementById(\"noVNC_control_bar_handle\")\n            .addEventListener('touchstart', UI.controlbarHandleMouseDown);\n        document.getElementById(\"noVNC_control_bar_handle\")\n            .addEventListener('touchend', UI.controlbarHandleMouseUp);\n        document.getElementById(\"noVNC_control_bar_handle\")\n            .addEventListener('touchmove', UI.dragControlbarHandle);\n    },\n\n    addExtraKeysHandlers() {\n        document.getElementById(\"noVNC_toggle_extra_keys_button\")\n            .addEventListener('click', UI.toggleExtraKeys);\n        document.getElementById(\"noVNC_toggle_ctrl_button\")\n            .addEventListener('click', UI.toggleCtrl);\n        document.getElementById(\"noVNC_toggle_windows_button\")\n            .addEventListener('click', UI.toggleWindows);\n        document.getElementById(\"noVNC_toggle_alt_button\")\n            .addEventListener('click', UI.toggleAlt);\n        document.getElementById(\"noVNC_send_tab_button\")\n            .addEventListener('click', UI.sendTab);\n        document.getElementById(\"noVNC_send_esc_button\")\n            .addEventListener('click', UI.sendEsc);\n        document.getElementById(\"noVNC_send_ctrl_alt_del_button\")\n            .addEventListener('click', UI.sendCtrlAltDel);\n    },\n\n    addMachineHandlers() {\n        document.getElementById(\"noVNC_shutdown_button\")\n            .addEventListener('click', () => UI.rfb.machineShutdown());\n        document.getElementById(\"noVNC_reboot_button\")\n            .addEventListener('click', () => UI.rfb.machineReboot());\n        document.getElementById(\"noVNC_reset_button\")\n            .addEventListener('click', () => UI.rfb.machineReset());\n        document.getElementById(\"noVNC_power_button\")\n            .addEventListener('click', UI.togglePowerPanel);\n    },\n\n    addConnectionControlHandlers() {\n        document.getElementById(\"noVNC_disconnect_button\")\n            .addEventListener('click', UI.disconnect);\n        document.getElementById(\"noVNC_connect_button\")\n            .addEventListener('click', UI.connect);\n        document.getElementById(\"noVNC_cancel_reconnect_button\")\n            .addEventListener('click', UI.cancelReconnect);\n\n        document.getElementById(\"noVNC_approve_server_button\")\n            .addEventListener('click', UI.approveServer);\n        document.getElementById(\"noVNC_reject_server_button\")\n            .addEventListener('click', UI.rejectServer);\n        document.getElementById(\"noVNC_credentials_button\")\n            .addEventListener('click', UI.setCredentials);\n    },\n\n    addClipboardHandlers() {\n        document.getElementById(\"noVNC_clipboard_button\")\n            .addEventListener('click', UI.toggleClipboardPanel);\n        document.getElementById(\"noVNC_clipboard_text\")\n            .addEventListener('change', UI.clipboardSend);\n    },\n\n    // Add a call to save settings when the element changes,\n    // unless the optional parameter changeFunc is used instead.\n    addSettingChangeHandler(name, changeFunc) {\n        const settingElem = document.getElementById(\"noVNC_setting_\" + name);\n        if (changeFunc === undefined) {\n            changeFunc = () => UI.saveSetting(name);\n        }\n        settingElem.addEventListener('change', changeFunc);\n    },\n\n    addSettingsHandlers() {\n        document.getElementById(\"noVNC_settings_button\")\n            .addEventListener('click', UI.toggleSettingsPanel);\n\n        UI.addSettingChangeHandler('encrypt');\n        UI.addSettingChangeHandler('resize');\n        UI.addSettingChangeHandler('resize', UI.applyResizeMode);\n        UI.addSettingChangeHandler('resize', UI.updateViewClip);\n        UI.addSettingChangeHandler('quality');\n        UI.addSettingChangeHandler('quality', UI.updateQuality);\n        UI.addSettingChangeHandler('compression');\n        UI.addSettingChangeHandler('compression', UI.updateCompression);\n        UI.addSettingChangeHandler('view_clip');\n        UI.addSettingChangeHandler('view_clip', UI.updateViewClip);\n        UI.addSettingChangeHandler('shared');\n        UI.addSettingChangeHandler('view_only');\n        UI.addSettingChangeHandler('view_only', UI.updateViewOnly);\n        UI.addSettingChangeHandler('show_dot');\n        UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor);\n        UI.addSettingChangeHandler('host');\n        UI.addSettingChangeHandler('port');\n        UI.addSettingChangeHandler('path');\n        UI.addSettingChangeHandler('repeaterID');\n        UI.addSettingChangeHandler('logging');\n        UI.addSettingChangeHandler('logging', UI.updateLogging);\n        UI.addSettingChangeHandler('reconnect');\n        UI.addSettingChangeHandler('reconnect_delay');\n    },\n\n    addFullscreenHandlers() {\n        document.getElementById(\"noVNC_fullscreen_button\")\n            .addEventListener('click', UI.toggleFullscreen);\n\n        window.addEventListener('fullscreenchange', UI.updateFullscreenButton);\n        window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);\n        window.addEventListener('webkitfullscreenchange', UI.updateFullscreenButton);\n        window.addEventListener('msfullscreenchange', UI.updateFullscreenButton);\n    },\n\n/* ------^-------\n * /EVENT HANDLERS\n * ==============\n *     VISUAL\n * ------v------*/\n\n    // Disable/enable controls depending on connection state\n    updateVisualState(state) {\n\n        document.documentElement.classList.remove(\"noVNC_connecting\");\n        document.documentElement.classList.remove(\"noVNC_connected\");\n        document.documentElement.classList.remove(\"noVNC_disconnecting\");\n        document.documentElement.classList.remove(\"noVNC_reconnecting\");\n\n        const transitionElem = document.getElementById(\"noVNC_transition_text\");\n        switch (state) {\n            case 'init':\n                break;\n            case 'connecting':\n                transitionElem.textContent = _(\"Connecting...\");\n                document.documentElement.classList.add(\"noVNC_connecting\");\n                break;\n            case 'connected':\n                document.documentElement.classList.add(\"noVNC_connected\");\n                break;\n            case 'disconnecting':\n                transitionElem.textContent = _(\"Disconnecting...\");\n                document.documentElement.classList.add(\"noVNC_disconnecting\");\n                break;\n            case 'disconnected':\n                break;\n            case 'reconnecting':\n                transitionElem.textContent = _(\"Reconnecting...\");\n                document.documentElement.classList.add(\"noVNC_reconnecting\");\n                break;\n            default:\n                Log.Error(\"Invalid visual state: \" + state);\n                UI.showStatus(_(\"Internal error\"), 'error');\n                return;\n        }\n\n        if (UI.connected) {\n            UI.updateViewClip();\n\n            UI.disableSetting('encrypt');\n            UI.disableSetting('shared');\n            UI.disableSetting('host');\n            UI.disableSetting('port');\n            UI.disableSetting('path');\n            UI.disableSetting('repeaterID');\n\n            // Hide the controlbar after 2 seconds\n            UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);\n        } else {\n            UI.enableSetting('encrypt');\n            UI.enableSetting('shared');\n            UI.enableSetting('host');\n            UI.enableSetting('port');\n            UI.enableSetting('path');\n            UI.enableSetting('repeaterID');\n            UI.updatePowerButton();\n            UI.keepControlbar();\n        }\n\n        // State change closes dialogs as they may not be relevant\n        // anymore\n        UI.closeAllPanels();\n        document.getElementById('noVNC_verify_server_dlg')\n            .classList.remove('noVNC_open');\n        document.getElementById('noVNC_credentials_dlg')\n            .classList.remove('noVNC_open');\n    },\n\n    showStatus(text, statusType, time) {\n        const statusElem = document.getElementById('noVNC_status');\n\n        if (typeof statusType === 'undefined') {\n            statusType = 'normal';\n        }\n\n        // Don't overwrite more severe visible statuses and never\n        // errors. Only shows the first error.\n        if (statusElem.classList.contains(\"noVNC_open\")) {\n            if (statusElem.classList.contains(\"noVNC_status_error\")) {\n                return;\n            }\n            if (statusElem.classList.contains(\"noVNC_status_warn\") &&\n                statusType === 'normal') {\n                return;\n            }\n        }\n\n        clearTimeout(UI.statusTimeout);\n\n        switch (statusType) {\n            case 'error':\n                statusElem.classList.remove(\"noVNC_status_warn\");\n                statusElem.classList.remove(\"noVNC_status_normal\");\n                statusElem.classList.add(\"noVNC_status_error\");\n                break;\n            case 'warning':\n            case 'warn':\n                statusElem.classList.remove(\"noVNC_status_error\");\n                statusElem.classList.remove(\"noVNC_status_normal\");\n                statusElem.classList.add(\"noVNC_status_warn\");\n                break;\n            case 'normal':\n            case 'info':\n            default:\n                statusElem.classList.remove(\"noVNC_status_error\");\n                statusElem.classList.remove(\"noVNC_status_warn\");\n                statusElem.classList.add(\"noVNC_status_normal\");\n                break;\n        }\n\n        statusElem.textContent = text;\n        statusElem.classList.add(\"noVNC_open\");\n\n        // If no time was specified, show the status for 1.5 seconds\n        if (typeof time === 'undefined') {\n            time = 1500;\n        }\n\n        // Error messages do not timeout\n        if (statusType !== 'error') {\n            UI.statusTimeout = window.setTimeout(UI.hideStatus, time);\n        }\n    },\n\n    hideStatus() {\n        clearTimeout(UI.statusTimeout);\n        document.getElementById('noVNC_status').classList.remove(\"noVNC_open\");\n    },\n\n    activateControlbar(event) {\n        clearTimeout(UI.idleControlbarTimeout);\n        // We manipulate the anchor instead of the actual control\n        // bar in order to avoid creating new a stacking group\n        document.getElementById('noVNC_control_bar_anchor')\n            .classList.remove(\"noVNC_idle\");\n        UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000);\n    },\n\n    idleControlbar() {\n        // Don't fade if a child of the control bar has focus\n        if (document.getElementById('noVNC_control_bar')\n            .contains(document.activeElement) && document.hasFocus()) {\n            UI.activateControlbar();\n            return;\n        }\n\n        document.getElementById('noVNC_control_bar_anchor')\n            .classList.add(\"noVNC_idle\");\n    },\n\n    keepControlbar() {\n        clearTimeout(UI.closeControlbarTimeout);\n    },\n\n    openControlbar() {\n        document.getElementById('noVNC_control_bar')\n            .classList.add(\"noVNC_open\");\n    },\n\n    closeControlbar() {\n        UI.closeAllPanels();\n        document.getElementById('noVNC_control_bar')\n            .classList.remove(\"noVNC_open\");\n        UI.rfb.focus();\n    },\n\n    toggleControlbar() {\n        if (document.getElementById('noVNC_control_bar')\n            .classList.contains(\"noVNC_open\")) {\n            UI.closeControlbar();\n        } else {\n            UI.openControlbar();\n        }\n    },\n\n    toggleControlbarSide() {\n        // Temporarily disable animation, if bar is displayed, to avoid weird\n        // movement. The transitionend-event will not fire when display=none.\n        const bar = document.getElementById('noVNC_control_bar');\n        const barDisplayStyle = window.getComputedStyle(bar).display;\n        if (barDisplayStyle !== 'none') {\n            bar.style.transitionDuration = '0s';\n            bar.addEventListener('transitionend', () => bar.style.transitionDuration = '');\n        }\n\n        const anchor = document.getElementById('noVNC_control_bar_anchor');\n        if (anchor.classList.contains(\"noVNC_right\")) {\n            WebUtil.writeSetting('controlbar_pos', 'left');\n            anchor.classList.remove(\"noVNC_right\");\n        } else {\n            WebUtil.writeSetting('controlbar_pos', 'right');\n            anchor.classList.add(\"noVNC_right\");\n        }\n\n        // Consider this a movement of the handle\n        UI.controlbarDrag = true;\n\n        // The user has \"followed\" hint, let's hide it until the next drag\n        UI.showControlbarHint(false, false);\n    },\n\n    showControlbarHint(show, animate=true) {\n        const hint = document.getElementById('noVNC_control_bar_hint');\n\n        if (animate) {\n            hint.classList.remove(\"noVNC_notransition\");\n        } else {\n            hint.classList.add(\"noVNC_notransition\");\n        }\n\n        if (show) {\n            hint.classList.add(\"noVNC_active\");\n        } else {\n            hint.classList.remove(\"noVNC_active\");\n        }\n    },\n\n    dragControlbarHandle(e) {\n        if (!UI.controlbarGrabbed) return;\n\n        const ptr = getPointerEvent(e);\n\n        const anchor = document.getElementById('noVNC_control_bar_anchor');\n        if (ptr.clientX < (window.innerWidth * 0.1)) {\n            if (anchor.classList.contains(\"noVNC_right\")) {\n                UI.toggleControlbarSide();\n            }\n        } else if (ptr.clientX > (window.innerWidth * 0.9)) {\n            if (!anchor.classList.contains(\"noVNC_right\")) {\n                UI.toggleControlbarSide();\n            }\n        }\n\n        if (!UI.controlbarDrag) {\n            const dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);\n\n            if (dragDistance < dragThreshold) return;\n\n            UI.controlbarDrag = true;\n        }\n\n        const eventY = ptr.clientY - UI.controlbarMouseDownOffsetY;\n\n        UI.moveControlbarHandle(eventY);\n\n        e.preventDefault();\n        e.stopPropagation();\n        UI.keepControlbar();\n        UI.activateControlbar();\n    },\n\n    // Move the handle but don't allow any position outside the bounds\n    moveControlbarHandle(viewportRelativeY) {\n        const handle = document.getElementById(\"noVNC_control_bar_handle\");\n        const handleHeight = handle.getBoundingClientRect().height;\n        const controlbarBounds = document.getElementById(\"noVNC_control_bar\")\n            .getBoundingClientRect();\n        const margin = 10;\n\n        // These heights need to be non-zero for the below logic to work\n        if (handleHeight === 0 || controlbarBounds.height === 0) {\n            return;\n        }\n\n        let newY = viewportRelativeY;\n\n        // Check if the coordinates are outside the control bar\n        if (newY < controlbarBounds.top + margin) {\n            // Force coordinates to be below the top of the control bar\n            newY = controlbarBounds.top + margin;\n\n        } else if (newY > controlbarBounds.top +\n                   controlbarBounds.height - handleHeight - margin) {\n            // Force coordinates to be above the bottom of the control bar\n            newY = controlbarBounds.top +\n                controlbarBounds.height - handleHeight - margin;\n        }\n\n        // Corner case: control bar too small for stable position\n        if (controlbarBounds.height < (handleHeight + margin * 2)) {\n            newY = controlbarBounds.top +\n                (controlbarBounds.height - handleHeight) / 2;\n        }\n\n        // The transform needs coordinates that are relative to the parent\n        const parentRelativeY = newY - controlbarBounds.top;\n        handle.style.transform = \"translateY(\" + parentRelativeY + \"px)\";\n    },\n\n    updateControlbarHandle() {\n        // Since the control bar is fixed on the viewport and not the page,\n        // the move function expects coordinates relative the the viewport.\n        const handle = document.getElementById(\"noVNC_control_bar_handle\");\n        const handleBounds = handle.getBoundingClientRect();\n        UI.moveControlbarHandle(handleBounds.top);\n    },\n\n    controlbarHandleMouseUp(e) {\n        if ((e.type == \"mouseup\") && (e.button != 0)) return;\n\n        // mouseup and mousedown on the same place toggles the controlbar\n        if (UI.controlbarGrabbed && !UI.controlbarDrag) {\n            UI.toggleControlbar();\n            e.preventDefault();\n            e.stopPropagation();\n            UI.keepControlbar();\n            UI.activateControlbar();\n        }\n        UI.controlbarGrabbed = false;\n        UI.showControlbarHint(false);\n    },\n\n    controlbarHandleMouseDown(e) {\n        if ((e.type == \"mousedown\") && (e.button != 0)) return;\n\n        const ptr = getPointerEvent(e);\n\n        const handle = document.getElementById(\"noVNC_control_bar_handle\");\n        const bounds = handle.getBoundingClientRect();\n\n        // Touch events have implicit capture\n        if (e.type === \"mousedown\") {\n            setCapture(handle);\n        }\n\n        UI.controlbarGrabbed = true;\n        UI.controlbarDrag = false;\n\n        UI.showControlbarHint(true);\n\n        UI.controlbarMouseDownClientY = ptr.clientY;\n        UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;\n        e.preventDefault();\n        e.stopPropagation();\n        UI.keepControlbar();\n        UI.activateControlbar();\n    },\n\n    toggleExpander(e) {\n        if (this.classList.contains(\"noVNC_open\")) {\n            this.classList.remove(\"noVNC_open\");\n        } else {\n            this.classList.add(\"noVNC_open\");\n        }\n    },\n\n/* ------^-------\n *    /VISUAL\n * ==============\n *    SETTINGS\n * ------v------*/\n\n    // Initial page load read/initialization of settings\n    initSetting(name, defVal) {\n        // Has the user overridden the default value?\n        if (name in UI.customSettings.defaults) {\n            defVal = UI.customSettings.defaults[name];\n        }\n        // Check Query string followed by cookie\n        let val = WebUtil.getConfigVar(name);\n        if (val === null) {\n            val = WebUtil.readSetting(name, defVal);\n        }\n        WebUtil.setSetting(name, val);\n        UI.updateSetting(name);\n        // Has the user forced a value?\n        if (name in UI.customSettings.mandatory) {\n            val = UI.customSettings.mandatory[name];\n            UI.forceSetting(name, val);\n        }\n        return val;\n    },\n\n    // Set the new value, update and disable form control setting\n    forceSetting(name, val) {\n        WebUtil.setSetting(name, val);\n        UI.updateSetting(name);\n        UI.disableSetting(name);\n    },\n\n    // Update cookie and form control setting. If value is not set, then\n    // updates from control to current cookie setting.\n    updateSetting(name) {\n\n        // Update the settings control\n        let value = UI.getSetting(name);\n\n        const ctrl = document.getElementById('noVNC_setting_' + name);\n        if (ctrl === null) {\n            return;\n        }\n\n        if (ctrl.type === 'checkbox') {\n            ctrl.checked = value;\n        } else if (typeof ctrl.options !== 'undefined') {\n            for (let i = 0; i < ctrl.options.length; i += 1) {\n                if (ctrl.options[i].value === value) {\n                    ctrl.selectedIndex = i;\n                    break;\n                }\n            }\n        } else {\n            ctrl.value = value;\n        }\n    },\n\n    // Save control setting to cookie\n    saveSetting(name) {\n        const ctrl = document.getElementById('noVNC_setting_' + name);\n        let val;\n        if (ctrl.type === 'checkbox') {\n            val = ctrl.checked;\n        } else if (typeof ctrl.options !== 'undefined') {\n            val = ctrl.options[ctrl.selectedIndex].value;\n        } else {\n            val = ctrl.value;\n        }\n        WebUtil.writeSetting(name, val);\n        //Log.Debug(\"Setting saved '\" + name + \"=\" + val + \"'\");\n        return val;\n    },\n\n    // Read form control compatible setting from cookie\n    getSetting(name) {\n        const ctrl = document.getElementById('noVNC_setting_' + name);\n        let val = WebUtil.readSetting(name);\n        if (typeof val !== 'undefined' && val !== null &&\n            ctrl !== null && ctrl.type === 'checkbox') {\n            if (val.toString().toLowerCase() in {'0': 1, 'no': 1, 'false': 1}) {\n                val = false;\n            } else {\n                val = true;\n            }\n        }\n        return val;\n    },\n\n    // These helpers compensate for the lack of parent-selectors and\n    // previous-sibling-selectors in CSS which are needed when we want to\n    // disable the labels that belong to disabled input elements.\n    disableSetting(name) {\n        const ctrl = document.getElementById('noVNC_setting_' + name);\n        if (ctrl !== null) {\n            ctrl.disabled = true;\n            if (ctrl.label !== undefined) {\n                ctrl.label.classList.add('noVNC_disabled');\n            }\n        }\n    },\n\n    enableSetting(name) {\n        const ctrl = document.getElementById('noVNC_setting_' + name);\n        if (ctrl !== null) {\n            ctrl.disabled = false;\n            if (ctrl.label !== undefined) {\n                ctrl.label.classList.remove('noVNC_disabled');\n            }\n        }\n    },\n\n/* ------^-------\n *   /SETTINGS\n * ==============\n *    PANELS\n * ------v------*/\n\n    closeAllPanels() {\n        UI.closeSettingsPanel();\n        UI.closePowerPanel();\n        UI.closeClipboardPanel();\n        UI.closeExtraKeys();\n    },\n\n/* ------^-------\n *   /PANELS\n * ==============\n * SETTINGS (panel)\n * ------v------*/\n\n    openSettingsPanel() {\n        UI.closeAllPanels();\n        UI.openControlbar();\n\n        // Refresh UI elements from saved cookies\n        UI.updateSetting('encrypt');\n        UI.updateSetting('view_clip');\n        UI.updateSetting('resize');\n        UI.updateSetting('quality');\n        UI.updateSetting('compression');\n        UI.updateSetting('shared');\n        UI.updateSetting('view_only');\n        UI.updateSetting('path');\n        UI.updateSetting('repeaterID');\n        UI.updateSetting('logging');\n        UI.updateSetting('reconnect');\n        UI.updateSetting('reconnect_delay');\n\n        document.getElementById('noVNC_settings')\n            .classList.add(\"noVNC_open\");\n        document.getElementById('noVNC_settings_button')\n            .classList.add(\"noVNC_selected\");\n    },\n\n    closeSettingsPanel() {\n        document.getElementById('noVNC_settings')\n            .classList.remove(\"noVNC_open\");\n        document.getElementById('noVNC_settings_button')\n            .classList.remove(\"noVNC_selected\");\n    },\n\n    toggleSettingsPanel() {\n        if (document.getElementById('noVNC_settings')\n            .classList.contains(\"noVNC_open\")) {\n            UI.closeSettingsPanel();\n        } else {\n            UI.openSettingsPanel();\n        }\n    },\n\n/* ------^-------\n *   /SETTINGS\n * ==============\n *     POWER\n * ------v------*/\n\n    openPowerPanel() {\n        UI.closeAllPanels();\n        UI.openControlbar();\n\n        document.getElementById('noVNC_power')\n            .classList.add(\"noVNC_open\");\n        document.getElementById('noVNC_power_button')\n            .classList.add(\"noVNC_selected\");\n    },\n\n    closePowerPanel() {\n        document.getElementById('noVNC_power')\n            .classList.remove(\"noVNC_open\");\n        document.getElementById('noVNC_power_button')\n            .classList.remove(\"noVNC_selected\");\n    },\n\n    togglePowerPanel() {\n        if (document.getElementById('noVNC_power')\n            .classList.contains(\"noVNC_open\")) {\n            UI.closePowerPanel();\n        } else {\n            UI.openPowerPanel();\n        }\n    },\n\n    // Disable/enable power button\n    updatePowerButton() {\n        if (UI.connected &&\n            UI.rfb.capabilities.power &&\n            !UI.rfb.viewOnly) {\n            document.getElementById('noVNC_power_button')\n                .classList.remove(\"noVNC_hidden\");\n        } else {\n            document.getElementById('noVNC_power_button')\n                .classList.add(\"noVNC_hidden\");\n            // Close power panel if open\n            UI.closePowerPanel();\n        }\n    },\n\n/* ------^-------\n *    /POWER\n * ==============\n *   CLIPBOARD\n * ------v------*/\n\n    openClipboardPanel() {\n        UI.closeAllPanels();\n        UI.openControlbar();\n\n        document.getElementById('noVNC_clipboard')\n            .classList.add(\"noVNC_open\");\n        document.getElementById('noVNC_clipboard_button')\n            .classList.add(\"noVNC_selected\");\n    },\n\n    closeClipboardPanel() {\n        document.getElementById('noVNC_clipboard')\n            .classList.remove(\"noVNC_open\");\n        document.getElementById('noVNC_clipboard_button')\n            .classList.remove(\"noVNC_selected\");\n    },\n\n    toggleClipboardPanel() {\n        if (document.getElementById('noVNC_clipboard')\n            .classList.contains(\"noVNC_open\")) {\n            UI.closeClipboardPanel();\n        } else {\n            UI.openClipboardPanel();\n        }\n    },\n\n    clipboardReceive(e) {\n        Log.Debug(\">> UI.clipboardReceive: \" + e.detail.text.substr(0, 40) + \"...\");\n        document.getElementById('noVNC_clipboard_text').value = e.detail.text;\n        Log.Debug(\"<< UI.clipboardReceive\");\n    },\n\n    clipboardSend() {\n        const text = document.getElementById('noVNC_clipboard_text').value;\n        Log.Debug(\">> UI.clipboardSend: \" + text.substr(0, 40) + \"...\");\n        UI.rfb.clipboardPasteFrom(text);\n        Log.Debug(\"<< UI.clipboardSend\");\n    },\n\n/* ------^-------\n *  /CLIPBOARD\n * ==============\n *  CONNECTION\n * ------v------*/\n\n    openConnectPanel() {\n        document.getElementById('noVNC_connect_dlg')\n            .classList.add(\"noVNC_open\");\n    },\n\n    closeConnectPanel() {\n        document.getElementById('noVNC_connect_dlg')\n            .classList.remove(\"noVNC_open\");\n    },\n\n    connect(event, password) {\n\n        // Ignore when rfb already exists\n        if (typeof UI.rfb !== 'undefined') {\n            return;\n        }\n\n        const host = UI.getSetting('host');\n        const port = UI.getSetting('port');\n        const path = UI.getSetting('path');\n\n        if (typeof password === 'undefined') {\n            password = UI.getSetting('password');\n            UI.reconnectPassword = password;\n        }\n\n        if (password === null) {\n            password = undefined;\n        }\n\n        UI.hideStatus();\n\n        UI.closeConnectPanel();\n\n        UI.updateVisualState('connecting');\n\n        // Extract vmid from the current page URL path\n        // and construct the WebSocket path dynamically.\n        const pathSegments = window.location.pathname.split('/');\n        let wsPath;\n        // Expected path format: /vnc/{vmid}\n        if (pathSegments.length >= 3 && pathSegments[1] === 'vnc') {\n            const vmid = pathSegments[2];\n            wsPath = `/vnc/ws/${vmid}`;\n            Log.Info(`Using dynamic WebSocket path: ${wsPath}`);\n        } else {\n            // Fallback if URL format is unexpected (should not happen in normal use)\n            wsPath = UI.getSetting('path'); // This still defaults to 'websockify'\n            Log.Error(`Unexpected URL format: ${window.location.pathname}. Cannot determine WebSocket path. Falling back to: ${wsPath}`);\n            // Consider showing an error to the user here if this fallback is not desired\n            UI.showStatus(_(\"Could not determine VNC connection path from URL.\"), 'error');\n            UI.updateVisualState('disconnected');\n            return; // Stop connection attempt\n        }\n\n        let url;\n\n        if (host) {\n            url = new URL(\"https://\" + host);\n\n            url.protocol = UI.getSetting('encrypt') ? 'wss:' : 'ws:';\n            if (port) {\n                url.port = port;\n            }\n\n            // \"./\" is needed to force URL() to interpret the path-variable as\n            // a path and not as an URL. This is relevant if for example path\n            // starts with more than one \"/\", in which case it would be\n            // interpreted as a host name instead.\n            // Use the dynamic path even if host/port are specified via settings/params (though unlikely)\n            url = new URL(\"./\" + wsPath, url);\n        } else {\n            // Current (May 2024) browsers support relative WebSocket\n            // URLs natively, but we need to support older browsers for\n            // some time.\n            // Use the dynamic path constructed above relative to the current location\n            url = new URL(wsPath, location.href);\n            url.protocol = (window.location.protocol === \"https:\") ? 'wss:' : 'ws:';\n        }\n\n        try {\n            UI.rfb = new RFB(document.getElementById('noVNC_container'),\n                             url.href,\n                             { shared: UI.getSetting('shared'),\n                               repeaterID: UI.getSetting('repeaterID'),\n                               credentials: { password: password } });\n        } catch (exc) {\n            Log.Error(\"Failed to connect to server: \" + exc);\n            UI.updateVisualState('disconnected');\n            UI.showStatus(_(\"Failed to connect to server: \") + exc, 'error');\n            return;\n        }\n\n        UI.rfb.addEventListener(\"connect\", UI.connectFinished);\n        UI.rfb.addEventListener(\"disconnect\", UI.disconnectFinished);\n        UI.rfb.addEventListener(\"serververification\", UI.serverVerify);\n        UI.rfb.addEventListener(\"credentialsrequired\", UI.credentials);\n        UI.rfb.addEventListener(\"securityfailure\", UI.securityFailed);\n        UI.rfb.addEventListener(\"clippingviewport\", UI.updateViewDrag);\n        UI.rfb.addEventListener(\"capabilities\", UI.updatePowerButton);\n        UI.rfb.addEventListener(\"clipboard\", UI.clipboardReceive);\n        UI.rfb.addEventListener(\"bell\", UI.bell);\n        UI.rfb.addEventListener(\"desktopname\", UI.updateDesktopName);\n        UI.rfb.clipViewport = UI.getSetting('view_clip');\n        UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';\n        UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';\n        UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));\n        UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));\n        UI.rfb.showDotCursor = UI.getSetting('show_dot');\n\n        UI.updateViewOnly(); // requires UI.rfb\n    },\n\n    disconnect() {\n        UI.rfb.disconnect();\n\n        UI.connected = false;\n\n        // Disable automatic reconnecting\n        UI.inhibitReconnect = true;\n\n        UI.updateVisualState('disconnecting');\n\n        // Don't display the connection settings until we're actually disconnected\n    },\n\n    reconnect() {\n        UI.reconnectCallback = null;\n\n        // if reconnect has been disabled in the meantime, do nothing.\n        if (UI.inhibitReconnect) {\n            return;\n        }\n\n        UI.connect(null, UI.reconnectPassword);\n    },\n\n    cancelReconnect() {\n        if (UI.reconnectCallback !== null) {\n            clearTimeout(UI.reconnectCallback);\n            UI.reconnectCallback = null;\n        }\n\n        UI.updateVisualState('disconnected');\n\n        UI.openControlbar();\n        UI.openConnectPanel();\n    },\n\n    connectFinished(e) {\n        UI.connected = true;\n        UI.inhibitReconnect = false;\n\n        let msg;\n        if (UI.getSetting('encrypt')) {\n            msg = _(\"Connected (encrypted) to \") + UI.desktopName;\n        } else {\n            msg = _(\"Connected (unencrypted) to \") + UI.desktopName;\n        }\n        UI.showStatus(msg);\n        UI.updateVisualState('connected');\n\n        // Do this last because it can only be used on rendered elements\n        UI.rfb.focus();\n    },\n\n    disconnectFinished(e) {\n        const wasConnected = UI.connected;\n\n        // This variable is ideally set when disconnection starts, but\n        // when the disconnection isn't clean or if it is initiated by\n        // the server, we need to do it here as well since\n        // UI.disconnect() won't be used in those cases.\n        UI.connected = false;\n\n        UI.rfb = undefined;\n\n        if (!e.detail.clean) {\n            UI.updateVisualState('disconnected');\n            if (wasConnected) {\n                UI.showStatus(_(\"Something went wrong, connection is closed\"),\n                              'error');\n            } else {\n                UI.showStatus(_(\"Failed to connect to server\"), 'error');\n            }\n        }\n        // If reconnecting is allowed process it now\n        if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {\n            UI.updateVisualState('reconnecting');\n\n            const delay = parseInt(UI.getSetting('reconnect_delay'));\n            UI.reconnectCallback = setTimeout(UI.reconnect, delay);\n            return;\n        } else {\n            UI.updateVisualState('disconnected');\n            UI.showStatus(_(\"Disconnected\"), 'normal');\n        }\n\n        document.title = PAGE_TITLE;\n\n        UI.openControlbar();\n        UI.openConnectPanel();\n    },\n\n    securityFailed(e) {\n        let msg = \"\";\n        // On security failures we might get a string with a reason\n        // directly from the server. Note that we can't control if\n        // this string is translated or not.\n        if ('reason' in e.detail) {\n            msg = _(\"New connection has been rejected with reason: \") +\n                e.detail.reason;\n        } else {\n            msg = _(\"New connection has been rejected\");\n        }\n        UI.showStatus(msg, 'error');\n    },\n\n/* ------^-------\n *  /CONNECTION\n * ==============\n * SERVER VERIFY\n * ------v------*/\n\n    async serverVerify(e) {\n        const type = e.detail.type;\n        if (type === 'RSA') {\n            const publickey = e.detail.publickey;\n            let fingerprint = await window.crypto.subtle.digest(\"SHA-1\", publickey);\n            // The same fingerprint format as RealVNC\n            fingerprint = Array.from(new Uint8Array(fingerprint).slice(0, 8)).map(\n                x => x.toString(16).padStart(2, '0')).join('-');\n            document.getElementById('noVNC_verify_server_dlg').classList.add('noVNC_open');\n            document.getElementById('noVNC_fingerprint').innerHTML = fingerprint;\n        }\n    },\n\n    approveServer(e) {\n        e.preventDefault();\n        document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');\n        UI.rfb.approveServer();\n    },\n\n    rejectServer(e) {\n        e.preventDefault();\n        document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');\n        UI.disconnect();\n    },\n\n/* ------^-------\n * /SERVER VERIFY\n * ==============\n *   PASSWORD\n * ------v------*/\n\n    credentials(e) {\n        // FIXME: handle more types\n\n        document.getElementById(\"noVNC_username_block\").classList.remove(\"noVNC_hidden\");\n        document.getElementById(\"noVNC_password_block\").classList.remove(\"noVNC_hidden\");\n\n        let inputFocus = \"none\";\n        if (e.detail.types.indexOf(\"username\") === -1) {\n            document.getElementById(\"noVNC_username_block\").classList.add(\"noVNC_hidden\");\n        } else {\n            inputFocus = inputFocus === \"none\" ? \"noVNC_username_input\" : inputFocus;\n        }\n        if (e.detail.types.indexOf(\"password\") === -1) {\n            document.getElementById(\"noVNC_password_block\").classList.add(\"noVNC_hidden\");\n        } else {\n            inputFocus = inputFocus === \"none\" ? \"noVNC_password_input\" : inputFocus;\n        }\n        document.getElementById('noVNC_credentials_dlg')\n            .classList.add('noVNC_open');\n\n        setTimeout(() => document\n            .getElementById(inputFocus).focus(), 100);\n\n        Log.Warn(\"Server asked for credentials\");\n        UI.showStatus(_(\"Credentials are required\"), \"warning\");\n    },\n\n    setCredentials(e) {\n        // Prevent actually submitting the form\n        e.preventDefault();\n\n        let inputElemUsername = document.getElementById('noVNC_username_input');\n        const username = inputElemUsername.value;\n\n        let inputElemPassword = document.getElementById('noVNC_password_input');\n        const password = inputElemPassword.value;\n        // Clear the input after reading the password\n        inputElemPassword.value = \"\";\n\n        UI.rfb.sendCredentials({ username: username, password: password });\n        UI.reconnectPassword = password;\n        document.getElementById('noVNC_credentials_dlg')\n            .classList.remove('noVNC_open');\n    },\n\n/* ------^-------\n *  /PASSWORD\n * ==============\n *   FULLSCREEN\n * ------v------*/\n\n    toggleFullscreen() {\n        if (document.fullscreenElement || // alternative standard method\n            document.mozFullScreenElement || // currently working methods\n            document.webkitFullscreenElement ||\n            document.msFullscreenElement) {\n            if (document.exitFullscreen) {\n                document.exitFullscreen();\n            } else if (document.mozCancelFullScreen) {\n                document.mozCancelFullScreen();\n            } else if (document.webkitExitFullscreen) {\n                document.webkitExitFullscreen();\n            } else if (document.msExitFullscreen) {\n                document.msExitFullscreen();\n            }\n        } else {\n            if (document.documentElement.requestFullscreen) {\n                document.documentElement.requestFullscreen();\n            } else if (document.documentElement.mozRequestFullScreen) {\n                document.documentElement.mozRequestFullScreen();\n            } else if (document.documentElement.webkitRequestFullscreen) {\n                document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);\n            } else if (document.body.msRequestFullscreen) {\n                document.body.msRequestFullscreen();\n            }\n        }\n        UI.updateFullscreenButton();\n    },\n\n    updateFullscreenButton() {\n        if (document.fullscreenElement || // alternative standard method\n            document.mozFullScreenElement || // currently working methods\n            document.webkitFullscreenElement ||\n            document.msFullscreenElement ) {\n            document.getElementById('noVNC_fullscreen_button')\n                .classList.add(\"noVNC_selected\");\n        } else {\n            document.getElementById('noVNC_fullscreen_button')\n                .classList.remove(\"noVNC_selected\");\n        }\n    },\n\n/* ------^-------\n *  /FULLSCREEN\n * ==============\n *     RESIZE\n * ------v------*/\n\n    // Apply remote resizing or local scaling\n    applyResizeMode() {\n        if (!UI.rfb) return;\n\n        UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';\n        UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';\n    },\n\n/* ------^-------\n *    /RESIZE\n * ==============\n * VIEW CLIPPING\n * ------v------*/\n\n    // Update viewport clipping property for the connection. The normal\n    // case is to get the value from the setting. There are special cases\n    // for when the viewport is scaled or when a touch device is used.\n    updateViewClip() {\n        if (!UI.rfb) return;\n\n        const scaling = UI.getSetting('resize') === 'scale';\n\n        // Some platforms have overlay scrollbars that are difficult\n        // to use in our case, which means we have to force panning\n        // FIXME: Working scrollbars can still be annoying to use with\n        //        touch, so we should ideally be able to have both\n        //        panning and scrollbars at the same time\n\n        let brokenScrollbars = false;\n\n        if (!hasScrollbarGutter) {\n            if (isIOS() || isAndroid() || isMac() || isChromeOS()) {\n                brokenScrollbars = true;\n            }\n        }\n\n        if (scaling) {\n            // Can't be clipping if viewport is scaled to fit\n            UI.forceSetting('view_clip', false);\n            UI.rfb.clipViewport  = false;\n        } else if (brokenScrollbars) {\n            UI.forceSetting('view_clip', true);\n            UI.rfb.clipViewport = true;\n        } else {\n            UI.enableSetting('view_clip');\n            UI.rfb.clipViewport = UI.getSetting('view_clip');\n        }\n\n        // Changing the viewport may change the state of\n        // the dragging button\n        UI.updateViewDrag();\n    },\n\n/* ------^-------\n * /VIEW CLIPPING\n * ==============\n *    VIEWDRAG\n * ------v------*/\n\n    toggleViewDrag() {\n        if (!UI.rfb) return;\n\n        UI.rfb.dragViewport = !UI.rfb.dragViewport;\n        UI.updateViewDrag();\n    },\n\n    updateViewDrag() {\n        if (!UI.connected) return;\n\n        const viewDragButton = document.getElementById('noVNC_view_drag_button');\n\n        if ((!UI.rfb.clipViewport || !UI.rfb.clippingViewport) &&\n            UI.rfb.dragViewport) {\n            // We are no longer clipping the viewport. Make sure\n            // viewport drag isn't active when it can't be used.\n            UI.rfb.dragViewport = false;\n        }\n\n        if (UI.rfb.dragViewport) {\n            viewDragButton.classList.add(\"noVNC_selected\");\n        } else {\n            viewDragButton.classList.remove(\"noVNC_selected\");\n        }\n\n        if (UI.rfb.clipViewport) {\n            viewDragButton.classList.remove(\"noVNC_hidden\");\n        } else {\n            viewDragButton.classList.add(\"noVNC_hidden\");\n        }\n\n        viewDragButton.disabled = !UI.rfb.clippingViewport;\n    },\n\n/* ------^-------\n *   /VIEWDRAG\n * ==============\n *    QUALITY\n * ------v------*/\n\n    updateQuality() {\n        if (!UI.rfb) return;\n\n        UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));\n    },\n\n/* ------^-------\n *   /QUALITY\n * ==============\n *  COMPRESSION\n * ------v------*/\n\n    updateCompression() {\n        if (!UI.rfb) return;\n\n        UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));\n    },\n\n/* ------^-------\n *  /COMPRESSION\n * ==============\n *    KEYBOARD\n * ------v------*/\n\n    showVirtualKeyboard() {\n        if (!isTouchDevice) return;\n\n        const input = document.getElementById('noVNC_keyboardinput');\n\n        if (document.activeElement == input) return;\n\n        input.focus();\n\n        try {\n            const l = input.value.length;\n            // Move the caret to the end\n            input.setSelectionRange(l, l);\n        } catch (err) {\n            // setSelectionRange is undefined in Google Chrome\n        }\n    },\n\n    hideVirtualKeyboard() {\n        if (!isTouchDevice) return;\n\n        const input = document.getElementById('noVNC_keyboardinput');\n\n        if (document.activeElement != input) return;\n\n        input.blur();\n    },\n\n    toggleVirtualKeyboard() {\n        if (document.getElementById('noVNC_keyboard_button')\n            .classList.contains(\"noVNC_selected\")) {\n            UI.hideVirtualKeyboard();\n        } else {\n            UI.showVirtualKeyboard();\n        }\n    },\n\n    onfocusVirtualKeyboard(event) {\n        document.getElementById('noVNC_keyboard_button')\n            .classList.add(\"noVNC_selected\");\n        if (UI.rfb) {\n            UI.rfb.focusOnClick = false;\n        }\n    },\n\n    onblurVirtualKeyboard(event) {\n        document.getElementById('noVNC_keyboard_button')\n            .classList.remove(\"noVNC_selected\");\n        if (UI.rfb) {\n            UI.rfb.focusOnClick = true;\n        }\n    },\n\n    keepVirtualKeyboard(event) {\n        const input = document.getElementById('noVNC_keyboardinput');\n\n        // Only prevent focus change if the virtual keyboard is active\n        if (document.activeElement != input) {\n            return;\n        }\n\n        // Only allow focus to move to other elements that need\n        // focus to function properly\n        if (event.target.form !== undefined) {\n            switch (event.target.type) {\n                case 'text':\n                case 'email':\n                case 'search':\n                case 'password':\n                case 'tel':\n                case 'url':\n                case 'textarea':\n                case 'select-one':\n                case 'select-multiple':\n                    return;\n            }\n        }\n\n        event.preventDefault();\n    },\n\n    keyboardinputReset() {\n        const kbi = document.getElementById('noVNC_keyboardinput');\n        kbi.value = new Array(UI.defaultKeyboardinputLen).join(\"_\");\n        UI.lastKeyboardinput = kbi.value;\n    },\n\n    keyEvent(keysym, code, down) {\n        if (!UI.rfb) return;\n\n        UI.rfb.sendKey(keysym, code, down);\n    },\n\n    // When normal keyboard events are left uncought, use the input events from\n    // the keyboardinput element instead and generate the corresponding key events.\n    // This code is required since some browsers on Android are inconsistent in\n    // sending keyCodes in the normal keyboard events when using on screen keyboards.\n    keyInput(event) {\n\n        if (!UI.rfb) return;\n\n        const newValue = event.target.value;\n\n        if (!UI.lastKeyboardinput) {\n            UI.keyboardinputReset();\n        }\n        const oldValue = UI.lastKeyboardinput;\n\n        let newLen;\n        try {\n            // Try to check caret position since whitespace at the end\n            // will not be considered by value.length in some browsers\n            newLen = Math.max(event.target.selectionStart, newValue.length);\n        } catch (err) {\n            // selectionStart is undefined in Google Chrome\n            newLen = newValue.length;\n        }\n        const oldLen = oldValue.length;\n\n        let inputs = newLen - oldLen;\n        let backspaces = inputs < 0 ? -inputs : 0;\n\n        // Compare the old string with the new to account for\n        // text-corrections or other input that modify existing text\n        for (let i = 0; i < Math.min(oldLen, newLen); i++) {\n            if (newValue.charAt(i) != oldValue.charAt(i)) {\n                inputs = newLen - i;\n                backspaces = oldLen - i;\n                break;\n            }\n        }\n\n        // Send the key events\n        for (let i = 0; i < backspaces; i++) {\n            UI.rfb.sendKey(KeyTable.XK_BackSpace, \"Backspace\");\n        }\n        for (let i = newLen - inputs; i < newLen; i++) {\n            UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i)));\n        }\n\n        // Control the text content length in the keyboardinput element\n        if (newLen > 2 * UI.defaultKeyboardinputLen) {\n            UI.keyboardinputReset();\n        } else if (newLen < 1) {\n            // There always have to be some text in the keyboardinput\n            // element with which backspace can interact.\n            UI.keyboardinputReset();\n            // This sometimes causes the keyboard to disappear for a second\n            // but it is required for the android keyboard to recognize that\n            // text has been added to the field\n            event.target.blur();\n            // This has to be ran outside of the input handler in order to work\n            setTimeout(event.target.focus.bind(event.target), 0);\n        } else {\n            UI.lastKeyboardinput = newValue;\n        }\n    },\n\n/* ------^-------\n *   /KEYBOARD\n * ==============\n *   EXTRA KEYS\n * ------v------*/\n\n    openExtraKeys() {\n        UI.closeAllPanels();\n        UI.openControlbar();\n\n        document.getElementById('noVNC_modifiers')\n            .classList.add(\"noVNC_open\");\n        document.getElementById('noVNC_toggle_extra_keys_button')\n            .classList.add(\"noVNC_selected\");\n    },\n\n    closeExtraKeys() {\n        document.getElementById('noVNC_modifiers')\n            .classList.remove(\"noVNC_open\");\n        document.getElementById('noVNC_toggle_extra_keys_button')\n            .classList.remove(\"noVNC_selected\");\n    },\n\n    toggleExtraKeys() {\n        if (document.getElementById('noVNC_modifiers')\n            .classList.contains(\"noVNC_open\")) {\n            UI.closeExtraKeys();\n        } else  {\n            UI.openExtraKeys();\n        }\n    },\n\n    sendEsc() {\n        UI.sendKey(KeyTable.XK_Escape, \"Escape\");\n    },\n\n    sendTab() {\n        UI.sendKey(KeyTable.XK_Tab, \"Tab\");\n    },\n\n    toggleCtrl() {\n        const btn = document.getElementById('noVNC_toggle_ctrl_button');\n        if (btn.classList.contains(\"noVNC_selected\")) {\n            UI.sendKey(KeyTable.XK_Control_L, \"ControlLeft\", false);\n            btn.classList.remove(\"noVNC_selected\");\n        } else {\n            UI.sendKey(KeyTable.XK_Control_L, \"ControlLeft\", true);\n            btn.classList.add(\"noVNC_selected\");\n        }\n    },\n\n    toggleWindows() {\n        const btn = document.getElementById('noVNC_toggle_windows_button');\n        if (btn.classList.contains(\"noVNC_selected\")) {\n            UI.sendKey(KeyTable.XK_Super_L, \"MetaLeft\", false);\n            btn.classList.remove(\"noVNC_selected\");\n        } else {\n            UI.sendKey(KeyTable.XK_Super_L, \"MetaLeft\", true);\n            btn.classList.add(\"noVNC_selected\");\n        }\n    },\n\n    toggleAlt() {\n        const btn = document.getElementById('noVNC_toggle_alt_button');\n        if (btn.classList.contains(\"noVNC_selected\")) {\n            UI.sendKey(KeyTable.XK_Alt_L, \"AltLeft\", false);\n            btn.classList.remove(\"noVNC_selected\");\n        } else {\n            UI.sendKey(KeyTable.XK_Alt_L, \"AltLeft\", true);\n            btn.classList.add(\"noVNC_selected\");\n        }\n    },\n\n    sendCtrlAltDel() {\n        UI.rfb.sendCtrlAltDel();\n        // See below\n        UI.rfb.focus();\n        UI.idleControlbar();\n    },\n\n    sendKey(keysym, code, down) {\n        UI.rfb.sendKey(keysym, code, down);\n\n        // Move focus to the screen in order to be able to use the\n        // keyboard right after these extra keys.\n        // The exception is when a virtual keyboard is used, because\n        // if we focus the screen the virtual keyboard would be closed.\n        // In this case we focus our special virtual keyboard input\n        // element instead.\n        if (document.getElementById('noVNC_keyboard_button')\n            .classList.contains(\"noVNC_selected\")) {\n            document.getElementById('noVNC_keyboardinput').focus();\n        } else {\n            UI.rfb.focus();\n        }\n        // fade out the controlbar to highlight that\n        // the focus has been moved to the screen\n        UI.idleControlbar();\n    },\n\n/* ------^-------\n *   /EXTRA KEYS\n * ==============\n *     MISC\n * ------v------*/\n\n    updateViewOnly() {\n        if (!UI.rfb) return;\n        UI.rfb.viewOnly = UI.getSetting('view_only');\n\n        // Hide input related buttons in view only mode\n        if (UI.rfb.viewOnly) {\n            document.getElementById('noVNC_keyboard_button')\n                .classList.add('noVNC_hidden');\n            document.getElementById('noVNC_toggle_extra_keys_button')\n                .classList.add('noVNC_hidden');\n            document.getElementById('noVNC_clipboard_button')\n                .classList.add('noVNC_hidden');\n        } else {\n            document.getElementById('noVNC_keyboard_button')\n                .classList.remove('noVNC_hidden');\n            document.getElementById('noVNC_toggle_extra_keys_button')\n                .classList.remove('noVNC_hidden');\n            document.getElementById('noVNC_clipboard_button')\n                .classList.remove('noVNC_hidden');\n        }\n    },\n\n    updateShowDotCursor() {\n        if (!UI.rfb) return;\n        UI.rfb.showDotCursor = UI.getSetting('show_dot');\n    },\n\n    updateLogging() {\n        WebUtil.initLogging(UI.getSetting('logging'));\n    },\n\n    updateDesktopName(e) {\n        UI.desktopName = e.detail.name;\n        // Display the desktop name in the document title\n        document.title = e.detail.name + \" - \" + PAGE_TITLE;\n    },\n\n    bell(e) {\n        if (UI.getSetting('bell') === 'on') {\n            const promise = document.getElementById('noVNC_bell').play();\n            // The standards disagree on the return value here\n            if (promise) {\n                promise.catch((e) => {\n                    if (e.name === \"NotAllowedError\") {\n                        // Ignore when the browser doesn't let us play audio.\n                        // It is common that the browsers require audio to be\n                        // initiated from a user action.\n                    } else {\n                        Log.Error(\"Unable to play bell: \" + e);\n                    }\n                });\n            }\n        }\n    },\n\n    //Helper to add options to dropdown.\n    addOption(selectbox, text, value) {\n        const optn = document.createElement(\"OPTION\");\n        optn.text = text;\n        optn.value = value;\n        selectbox.options.add(optn);\n    },\n\n/* ------^-------\n *    /MISC\n * ==============\n */\n};\n\nexport default UI;\n"
  },
  {
    "path": "services/gateway/noVNC/app/webutil.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\nimport * as Log from '../core/util/logging.js';\n\n// init log level reading the logging HTTP param\nexport function initLogging(level) {\n    \"use strict\";\n    if (typeof level !== \"undefined\") {\n        Log.initLogging(level);\n    } else {\n        const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);\n        Log.initLogging(param || undefined);\n    }\n}\n\n// Read a query string variable\n// A URL with a query parameter can look like this (But will most probably get logged on the http server):\n// https://www.example.com?myqueryparam=myvalue\n//\n// For privacy (Using a hastag #, the parameters will not be sent to the server)\n// the url can be requested in the following way:\n// https://www.example.com#myqueryparam=myvalue&password=secretvalue\n//\n// Even mixing public and non public parameters will work:\n// https://www.example.com?nonsecretparam=example.com#password=secretvalue\nexport function getQueryVar(name, defVal) {\n    \"use strict\";\n    const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),\n          match = document.location.href.match(re);\n    if (typeof defVal === 'undefined') { defVal = null; }\n\n    if (match) {\n        return decodeURIComponent(match[1]);\n    }\n\n    return defVal;\n}\n\n// Read a hash fragment variable\nexport function getHashVar(name, defVal) {\n    \"use strict\";\n    const re = new RegExp('.*[&#]' + name + '=([^&]*)'),\n          match = document.location.hash.match(re);\n    if (typeof defVal === 'undefined') { defVal = null; }\n\n    if (match) {\n        return decodeURIComponent(match[1]);\n    }\n\n    return defVal;\n}\n\n// Read a variable from the fragment or the query string\n// Fragment takes precedence\nexport function getConfigVar(name, defVal) {\n    \"use strict\";\n    const val = getHashVar(name);\n\n    if (val === null) {\n        return getQueryVar(name, defVal);\n    }\n\n    return val;\n}\n\n/*\n * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html\n */\n\n// No days means only for this browser session\nexport function createCookie(name, value, days) {\n    \"use strict\";\n    let date, expires;\n    if (days) {\n        date = new Date();\n        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));\n        expires = \"; expires=\" + date.toGMTString();\n    } else {\n        expires = \"\";\n    }\n\n    let secure;\n    if (document.location.protocol === \"https:\") {\n        secure = \"; secure\";\n    } else {\n        secure = \"\";\n    }\n    document.cookie = name + \"=\" + value + expires + \"; path=/\" + secure;\n}\n\nexport function readCookie(name, defaultValue) {\n    \"use strict\";\n    const nameEQ = name + \"=\";\n    const ca = document.cookie.split(';');\n\n    for (let i = 0; i < ca.length; i += 1) {\n        let c = ca[i];\n        while (c.charAt(0) === ' ') {\n            c = c.substring(1, c.length);\n        }\n        if (c.indexOf(nameEQ) === 0) {\n            return c.substring(nameEQ.length, c.length);\n        }\n    }\n\n    return (typeof defaultValue !== 'undefined') ? defaultValue : null;\n}\n\nexport function eraseCookie(name) {\n    \"use strict\";\n    createCookie(name, \"\", -1);\n}\n\n/*\n * Setting handling.\n */\n\nlet settings = {};\n\nexport function initSettings() {\n    if (!window.chrome || !window.chrome.storage) {\n        settings = {};\n        return Promise.resolve();\n    }\n\n    return new Promise(resolve => window.chrome.storage.sync.get(resolve))\n        .then((cfg) => { settings = cfg; });\n}\n\n// Update the settings cache, but do not write to permanent storage\nexport function setSetting(name, value) {\n    settings[name] = value;\n}\n\n// No days means only for this browser session\nexport function writeSetting(name, value) {\n    \"use strict\";\n    if (settings[name] === value) return;\n    settings[name] = value;\n    if (window.chrome && window.chrome.storage) {\n        window.chrome.storage.sync.set(settings);\n    } else {\n        localStorageSet(name, value);\n    }\n}\n\nexport function readSetting(name, defaultValue) {\n    \"use strict\";\n    let value;\n    if ((name in settings) || (window.chrome && window.chrome.storage)) {\n        value = settings[name];\n    } else {\n        value = localStorageGet(name);\n        settings[name] = value;\n    }\n    if (typeof value === \"undefined\") {\n        value = null;\n    }\n\n    if (value === null && typeof defaultValue !== \"undefined\") {\n        return defaultValue;\n    }\n\n    return value;\n}\n\nexport function eraseSetting(name) {\n    \"use strict\";\n    // Deleting here means that next time the setting is read when using local\n    // storage, it will be pulled from local storage again.\n    // If the setting in local storage is changed (e.g. in another tab)\n    // between this delete and the next read, it could lead to an unexpected\n    // value change.\n    delete settings[name];\n    if (window.chrome && window.chrome.storage) {\n        window.chrome.storage.sync.remove(name);\n    } else {\n        localStorageRemove(name);\n    }\n}\n\nlet loggedMsgs = [];\nfunction logOnce(msg, level = \"warn\") {\n    if (!loggedMsgs.includes(msg)) {\n        switch (level) {\n            case \"error\":\n                Log.Error(msg);\n                break;\n            case \"warn\":\n                Log.Warn(msg);\n                break;\n            case \"debug\":\n                Log.Debug(msg);\n                break;\n            default:\n                Log.Info(msg);\n        }\n        loggedMsgs.push(msg);\n    }\n}\n\nlet cookiesMsg = \"Couldn't access noVNC settings, are cookies disabled?\";\n\nfunction localStorageGet(name) {\n    let r;\n    try {\n        r = localStorage.getItem(name);\n    } catch (e) {\n        if (e instanceof DOMException) {\n            logOnce(cookiesMsg);\n            logOnce(\"'localStorage.getItem(\" + name + \")' failed: \" + e,\n                    \"debug\");\n        } else {\n            throw e;\n        }\n    }\n    return r;\n}\nfunction localStorageSet(name, value) {\n    try {\n        localStorage.setItem(name, value);\n    } catch (e) {\n        if (e instanceof DOMException) {\n            logOnce(cookiesMsg);\n            logOnce(\"'localStorage.setItem(\" + name + \",\" + value +\n                    \")' failed: \" + e, \"debug\");\n        } else {\n            throw e;\n        }\n    }\n}\nfunction localStorageRemove(name) {\n    try {\n        localStorage.removeItem(name);\n    } catch (e) {\n        if (e instanceof DOMException) {\n            logOnce(cookiesMsg);\n            logOnce(\"'localStorage.removeItem(\" + name + \")' failed: \" + e,\n                    \"debug\");\n        } else {\n            throw e;\n        }\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/base64.js",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n\n// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js\n\nimport * as Log from './util/logging.js';\n\nexport default {\n    /* Convert data (an array of integers) to a Base64 string. */\n    toBase64Table: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),\n    base64Pad: '=',\n\n    encode(data) {\n        \"use strict\";\n        let result = '';\n        const length = data.length;\n        const lengthpad = (length % 3);\n        // Convert every three bytes to 4 ascii characters.\n\n        for (let i = 0; i < (length - 2); i += 3) {\n            result += this.toBase64Table[data[i] >> 2];\n            result += this.toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];\n            result += this.toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];\n            result += this.toBase64Table[data[i + 2] & 0x3f];\n        }\n\n        // Convert the remaining 1 or 2 bytes, pad out to 4 characters.\n        const j = length - lengthpad;\n        if (lengthpad === 2) {\n            result += this.toBase64Table[data[j] >> 2];\n            result += this.toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)];\n            result += this.toBase64Table[(data[j + 1] & 0x0f) << 2];\n            result += this.toBase64Table[64];\n        } else if (lengthpad === 1) {\n            result += this.toBase64Table[data[j] >> 2];\n            result += this.toBase64Table[(data[j] & 0x03) << 4];\n            result += this.toBase64Table[64];\n            result += this.toBase64Table[64];\n        }\n\n        return result;\n    },\n\n    /* Convert Base64 data to a string */\n    /* eslint-disable comma-spacing */\n    toBinaryTable: [\n        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,\n        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,\n        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,\n        52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,\n        -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,\n        15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,\n        -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,\n        41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1\n    ],\n    /* eslint-enable comma-spacing */\n\n    decode(data, offset = 0) {\n        let dataLength = data.indexOf('=') - offset;\n        if (dataLength < 0) { dataLength = data.length - offset; }\n\n        /* Every four characters is 3 resulting numbers */\n        const resultLength = (dataLength >> 2) * 3 + Math.floor((dataLength % 4) / 1.5);\n        const result = new Array(resultLength);\n\n        // Convert one by one.\n\n        let leftbits = 0; // number of bits decoded, but yet to be appended\n        let leftdata = 0; // bits decoded, but yet to be appended\n        for (let idx = 0, i = offset; i < data.length; i++) {\n            const c = this.toBinaryTable[data.charCodeAt(i) & 0x7f];\n            const padding = (data.charAt(i) === this.base64Pad);\n            // Skip illegal characters and whitespace\n            if (c === -1) {\n                Log.Error(\"Illegal character code \" + data.charCodeAt(i) + \" at position \" + i);\n                continue;\n            }\n\n            // Collect data into leftdata, update bitcount\n            leftdata = (leftdata << 6) | c;\n            leftbits += 6;\n\n            // If we have 8 or more bits, append 8 bits to the result\n            if (leftbits >= 8) {\n                leftbits -= 8;\n                // Append if not padding.\n                if (!padding) {\n                    result[idx++] = (leftdata >> leftbits) & 0xff;\n                }\n                leftdata &= (1 << leftbits) - 1;\n            }\n        }\n\n        // If there are any bits left, the base64 string was corrupted\n        if (leftbits) {\n            const err = new Error('Corrupted base64 string');\n            err.name = 'Base64-Error';\n            throw err;\n        }\n\n        return result;\n    }\n}; /* End of Base64 namespace */\n"
  },
  {
    "path": "services/gateway/noVNC/core/crypto/aes.js",
    "content": "export class AESECBCipher {\n    constructor() {\n        this._key = null;\n    }\n\n    get algorithm() {\n        return { name: \"AES-ECB\" };\n    }\n\n    static async importKey(key, _algorithm, extractable, keyUsages) {\n        const cipher = new AESECBCipher;\n        await cipher._importKey(key, extractable, keyUsages);\n        return cipher;\n    }\n\n    async _importKey(key, extractable, keyUsages) {\n        this._key = await window.crypto.subtle.importKey(\n            \"raw\", key, {name: \"AES-CBC\"}, extractable, keyUsages);\n    }\n\n    async encrypt(_algorithm, plaintext) {\n        const x = new Uint8Array(plaintext);\n        if (x.length % 16 !== 0 || this._key === null) {\n            return null;\n        }\n        const n = x.length / 16;\n        for (let i = 0; i < n; i++) {\n            const y = new Uint8Array(await window.crypto.subtle.encrypt({\n                name: \"AES-CBC\",\n                iv: new Uint8Array(16),\n            }, this._key, x.slice(i * 16, i * 16 + 16))).slice(0, 16);\n            x.set(y, i * 16);\n        }\n        return x;\n    }\n}\n\nexport class AESEAXCipher {\n    constructor() {\n        this._rawKey = null;\n        this._ctrKey = null;\n        this._cbcKey = null;\n        this._zeroBlock = new Uint8Array(16);\n        this._prefixBlock0 = this._zeroBlock;\n        this._prefixBlock1 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);\n        this._prefixBlock2 = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);\n    }\n\n    get algorithm() {\n        return { name: \"AES-EAX\" };\n    }\n\n    async _encryptBlock(block) {\n        const encrypted = await window.crypto.subtle.encrypt({\n            name: \"AES-CBC\",\n            iv: this._zeroBlock,\n        }, this._cbcKey, block);\n        return new Uint8Array(encrypted).slice(0, 16);\n    }\n\n    async _initCMAC() {\n        const k1 = await this._encryptBlock(this._zeroBlock);\n        const k2 = new Uint8Array(16);\n        const v = k1[0] >>> 6;\n        for (let i = 0; i < 15; i++) {\n            k2[i] = (k1[i + 1] >> 6) | (k1[i] << 2);\n            k1[i] = (k1[i + 1] >> 7) | (k1[i] << 1);\n        }\n        const lut = [0x0, 0x87, 0x0e, 0x89];\n        k2[14] ^= v >>> 1;\n        k2[15] = (k1[15] << 2) ^ lut[v];\n        k1[15] = (k1[15] << 1) ^ lut[v >> 1];\n        this._k1 = k1;\n        this._k2 = k2;\n    }\n\n    async _encryptCTR(data, counter) {\n        const encrypted = await window.crypto.subtle.encrypt({\n            name: \"AES-CTR\",\n            counter: counter,\n            length: 128\n        }, this._ctrKey, data);\n        return new Uint8Array(encrypted);\n    }\n\n    async _decryptCTR(data, counter) {\n        const decrypted = await window.crypto.subtle.decrypt({\n            name: \"AES-CTR\",\n            counter: counter,\n            length: 128\n        }, this._ctrKey, data);\n        return new Uint8Array(decrypted);\n    }\n\n    async _computeCMAC(data, prefixBlock) {\n        if (prefixBlock.length !== 16) {\n            return null;\n        }\n        const n = Math.floor(data.length / 16);\n        const m = Math.ceil(data.length / 16);\n        const r = data.length - n * 16;\n        const cbcData = new Uint8Array((m + 1) * 16);\n        cbcData.set(prefixBlock);\n        cbcData.set(data, 16);\n        if (r === 0) {\n            for (let i = 0; i < 16; i++) {\n                cbcData[n * 16 + i] ^= this._k1[i];\n            }\n        } else {\n            cbcData[(n + 1) * 16 + r] = 0x80;\n            for (let i = 0; i < 16; i++) {\n                cbcData[(n + 1) * 16 + i] ^= this._k2[i];\n            }\n        }\n        let cbcEncrypted = await window.crypto.subtle.encrypt({\n            name: \"AES-CBC\",\n            iv: this._zeroBlock,\n        }, this._cbcKey, cbcData);\n\n        cbcEncrypted = new Uint8Array(cbcEncrypted);\n        const mac = cbcEncrypted.slice(cbcEncrypted.length - 32, cbcEncrypted.length - 16);\n        return mac;\n    }\n\n    static async importKey(key, _algorithm, _extractable, _keyUsages) {\n        const cipher = new AESEAXCipher;\n        await cipher._importKey(key);\n        return cipher;\n    }\n\n    async _importKey(key) {\n        this._rawKey = key;\n        this._ctrKey = await window.crypto.subtle.importKey(\n            \"raw\", key, {name: \"AES-CTR\"}, false, [\"encrypt\", \"decrypt\"]);\n        this._cbcKey = await window.crypto.subtle.importKey(\n            \"raw\", key, {name: \"AES-CBC\"}, false, [\"encrypt\"]);\n        await this._initCMAC();\n    }\n\n    async encrypt(algorithm, message) {\n        const ad = algorithm.additionalData;\n        const nonce = algorithm.iv;\n        const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);\n        const encrypted = await this._encryptCTR(message, nCMAC);\n        const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);\n        const mac = await this._computeCMAC(encrypted, this._prefixBlock2);\n        for (let i = 0; i < 16; i++) {\n            mac[i] ^= nCMAC[i] ^ adCMAC[i];\n        }\n        const res = new Uint8Array(16 + encrypted.length);\n        res.set(encrypted);\n        res.set(mac, encrypted.length);\n        return res;\n    }\n\n    async decrypt(algorithm, data) {\n        const encrypted = data.slice(0, data.length - 16);\n        const ad = algorithm.additionalData;\n        const nonce = algorithm.iv;\n        const mac = data.slice(data.length - 16);\n        const nCMAC = await this._computeCMAC(nonce, this._prefixBlock0);\n        const adCMAC = await this._computeCMAC(ad, this._prefixBlock1);\n        const computedMac = await this._computeCMAC(encrypted, this._prefixBlock2);\n        for (let i = 0; i < 16; i++) {\n            computedMac[i] ^= nCMAC[i] ^ adCMAC[i];\n        }\n        if (computedMac.length !== mac.length) {\n            return null;\n        }\n        for (let i = 0; i < mac.length; i++) {\n            if (computedMac[i] !== mac[i]) {\n                return null;\n            }\n        }\n        const res = await this._decryptCTR(encrypted, nCMAC);\n        return res;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/crypto/bigint.js",
    "content": "export function modPow(b, e, m) {\n    let r = 1n;\n    b = b % m;\n    while (e > 0n) {\n        if ((e & 1n) === 1n) {\n            r = (r * b) % m;\n        }\n        e = e >> 1n;\n        b = (b * b) % m;\n    }\n    return r;\n}\n\nexport function bigIntToU8Array(bigint, padLength=0) {\n    let hex = bigint.toString(16);\n    if (padLength === 0) {\n        padLength = Math.ceil(hex.length / 2);\n    }\n    hex = hex.padStart(padLength * 2, '0');\n    const length = hex.length / 2;\n    const arr = new Uint8Array(length);\n    for (let i = 0; i < length; i++) {\n        arr[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n    }\n    return arr;\n}\n\nexport function u8ArrayToBigInt(arr) {\n    let hex = '0x';\n    for (let i = 0; i < arr.length; i++) {\n        hex += arr[i].toString(16).padStart(2, '0');\n    }\n    return BigInt(hex);\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/crypto/crypto.js",
    "content": "import { AESECBCipher, AESEAXCipher } from \"./aes.js\";\nimport { DESCBCCipher, DESECBCipher } from \"./des.js\";\nimport { RSACipher } from \"./rsa.js\";\nimport { DHCipher } from \"./dh.js\";\nimport { MD5 } from \"./md5.js\";\n\n// A single interface for the cryptographic algorithms not supported by SubtleCrypto.\n// Both synchronous and asynchronous implmentations are allowed.\nclass LegacyCrypto {\n    constructor() {\n        this._algorithms = {\n            \"AES-ECB\": AESECBCipher,\n            \"AES-EAX\": AESEAXCipher,\n            \"DES-ECB\": DESECBCipher,\n            \"DES-CBC\": DESCBCCipher,\n            \"RSA-PKCS1-v1_5\": RSACipher,\n            \"DH\": DHCipher,\n            \"MD5\": MD5,\n        };\n    }\n\n    encrypt(algorithm, key, data) {\n        if (key.algorithm.name !== algorithm.name) {\n            throw new Error(\"algorithm does not match\");\n        }\n        if (typeof key.encrypt !== \"function\") {\n            throw new Error(\"key does not support encryption\");\n        }\n        return key.encrypt(algorithm, data);\n    }\n\n    decrypt(algorithm, key, data) {\n        if (key.algorithm.name !== algorithm.name) {\n            throw new Error(\"algorithm does not match\");\n        }\n        if (typeof key.decrypt !== \"function\") {\n            throw new Error(\"key does not support encryption\");\n        }\n        return key.decrypt(algorithm, data);\n    }\n\n    importKey(format, keyData, algorithm, extractable, keyUsages) {\n        if (format !== \"raw\") {\n            throw new Error(\"key format is not supported\");\n        }\n        const alg = this._algorithms[algorithm.name];\n        if (typeof alg === \"undefined\" || typeof alg.importKey !== \"function\") {\n            throw new Error(\"algorithm is not supported\");\n        }\n        return alg.importKey(keyData, algorithm, extractable, keyUsages);\n    }\n\n    generateKey(algorithm, extractable, keyUsages) {\n        const alg = this._algorithms[algorithm.name];\n        if (typeof alg === \"undefined\" || typeof alg.generateKey !== \"function\") {\n            throw new Error(\"algorithm is not supported\");\n        }\n        return alg.generateKey(algorithm, extractable, keyUsages);\n    }\n\n    exportKey(format, key) {\n        if (format !== \"raw\") {\n            throw new Error(\"key format is not supported\");\n        }\n        if (typeof key.exportKey !== \"function\") {\n            throw new Error(\"key does not support exportKey\");\n        }\n        return key.exportKey();\n    }\n\n    digest(algorithm, data) {\n        const alg = this._algorithms[algorithm];\n        if (typeof alg !== \"function\") {\n            throw new Error(\"algorithm is not supported\");\n        }\n        return alg(data);\n    }\n\n    deriveBits(algorithm, key, length) {\n        if (key.algorithm.name !== algorithm.name) {\n            throw new Error(\"algorithm does not match\");\n        }\n        if (typeof key.deriveBits !== \"function\") {\n            throw new Error(\"key does not support deriveBits\");\n        }\n        return key.deriveBits(algorithm, length);\n    }\n}\n\nexport default new LegacyCrypto;\n"
  },
  {
    "path": "services/gateway/noVNC/core/crypto/des.js",
    "content": "/*\n * Ported from Flashlight VNC ActionScript implementation:\n *     http://www.wizhelp.com/flashlight-vnc/\n *\n * Full attribution follows:\n *\n * -------------------------------------------------------------------------\n *\n * This DES class has been extracted from package Acme.Crypto for use in VNC.\n * The unnecessary odd parity code has been removed.\n *\n * These changes are:\n *  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.\n *\n * This software is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n *\n\n * DesCipher - the DES encryption method\n *\n * The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:\n *\n * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.\n *\n * Permission to use, copy, modify, and distribute this software\n * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and\n * without fee is hereby granted, provided that this copyright notice is kept\n * intact.\n *\n * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY\n * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\n * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE\n * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR\n * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.\n *\n * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE\n * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE\n * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT\n * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE\n * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE\n * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE\n * PHYSICAL OR ENVIRONMENTAL DAMAGE (\"HIGH RISK ACTIVITIES\").  WIDGET WORKSHOP\n * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR\n * HIGH RISK ACTIVITIES.\n *\n *\n * The rest is:\n *\n * Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n * Visit the ACME Labs Java page for up-to-date versions of this and other\n * fine Java utilities: http://www.acme.com/java/\n */\n\n/* eslint-disable comma-spacing */\n\n// Tables, permutations, S-boxes, etc.\nconst PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,\n             25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,\n             50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],\n      totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28];\n\nconst z = 0x0;\nlet a,b,c,d,e,f;\na=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;\nconst SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,\n             z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,\n             a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,\n             c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];\na=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;\nconst SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,\n             a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,\n             z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,\n             z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];\na=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;\nconst SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,\n             b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,\n             c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,\n             b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];\na=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;\nconst SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,\n             z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,\n             b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,\n             c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];\na=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;\nconst SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,\n             a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,\n             z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,\n             c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];\na=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;\nconst SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,\n             z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,\n             b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,\n             a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];\na=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;\nconst SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,\n             b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,\n             b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,\n             z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];\na=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;\nconst SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,\n             c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,\n             a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,\n             z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];\n\n/* eslint-enable comma-spacing */\n\nclass DES {\n    constructor(password) {\n        this.keys = [];\n\n        // Set the key.\n        const pc1m = [], pcr = [], kn = [];\n\n        for (let j = 0, l = 56; j < 56; ++j, l -= 8) {\n            l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1\n            const m = l & 0x7;\n            pc1m[j] = ((password[l >>> 3] & (1<<m)) !== 0) ? 1: 0;\n        }\n\n        for (let i = 0; i < 16; ++i) {\n            const m = i << 1;\n            const n = m + 1;\n            kn[m] = kn[n] = 0;\n            for (let o = 28; o < 59; o += 28) {\n                for (let j = o - 28; j < o; ++j) {\n                    const l = j + totrot[i];\n                    pcr[j] = l < o ? pc1m[l] : pc1m[l - 28];\n                }\n            }\n            for (let j = 0; j < 24; ++j) {\n                if (pcr[PC2[j]] !== 0) {\n                    kn[m] |= 1 << (23 - j);\n                }\n                if (pcr[PC2[j + 24]] !== 0) {\n                    kn[n] |= 1 << (23 - j);\n                }\n            }\n        }\n\n        // cookey\n        for (let i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {\n            const raw0 = kn[rawi++];\n            const raw1 = kn[rawi++];\n            this.keys[KnLi] = (raw0 & 0x00fc0000) << 6;\n            this.keys[KnLi] |= (raw0 & 0x00000fc0) << 10;\n            this.keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;\n            this.keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;\n            ++KnLi;\n            this.keys[KnLi] = (raw0 & 0x0003f000) << 12;\n            this.keys[KnLi] |= (raw0 & 0x0000003f) << 16;\n            this.keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;\n            this.keys[KnLi] |= (raw1 & 0x0000003f);\n            ++KnLi;\n        }\n    }\n\n    // Encrypt 8 bytes of text\n    enc8(text) {\n        const b = text.slice();\n        let i = 0, l, r, x; // left, right, accumulator\n\n        // Squash 8 bytes to 2 ints\n        l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];\n        r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];\n\n        x = ((l >>> 4) ^ r) & 0x0f0f0f0f;\n        r ^= x;\n        l ^= (x << 4);\n        x = ((l >>> 16) ^ r) & 0x0000ffff;\n        r ^= x;\n        l ^= (x << 16);\n        x = ((r >>> 2) ^ l) & 0x33333333;\n        l ^= x;\n        r ^= (x << 2);\n        x = ((r >>> 8) ^ l) & 0x00ff00ff;\n        l ^= x;\n        r ^= (x << 8);\n        r = (r << 1) | ((r >>> 31) & 1);\n        x = (l ^ r) & 0xaaaaaaaa;\n        l ^= x;\n        r ^= x;\n        l = (l << 1) | ((l >>> 31) & 1);\n\n        for (let i = 0, keysi = 0; i < 8; ++i) {\n            x = (r << 28) | (r >>> 4);\n            x ^= this.keys[keysi++];\n            let fval =  SP7[x & 0x3f];\n            fval |= SP5[(x >>> 8) & 0x3f];\n            fval |= SP3[(x >>> 16) & 0x3f];\n            fval |= SP1[(x >>> 24) & 0x3f];\n            x = r ^ this.keys[keysi++];\n            fval |= SP8[x & 0x3f];\n            fval |= SP6[(x >>> 8) & 0x3f];\n            fval |= SP4[(x >>> 16) & 0x3f];\n            fval |= SP2[(x >>> 24) & 0x3f];\n            l ^= fval;\n            x = (l << 28) | (l >>> 4);\n            x ^= this.keys[keysi++];\n            fval =  SP7[x & 0x3f];\n            fval |= SP5[(x >>> 8) & 0x3f];\n            fval |= SP3[(x >>> 16) & 0x3f];\n            fval |= SP1[(x >>> 24) & 0x3f];\n            x = l ^ this.keys[keysi++];\n            fval |= SP8[x & 0x0000003f];\n            fval |= SP6[(x >>> 8) & 0x3f];\n            fval |= SP4[(x >>> 16) & 0x3f];\n            fval |= SP2[(x >>> 24) & 0x3f];\n            r ^= fval;\n        }\n\n        r = (r << 31) | (r >>> 1);\n        x = (l ^ r) & 0xaaaaaaaa;\n        l ^= x;\n        r ^= x;\n        l = (l << 31) | (l >>> 1);\n        x = ((l >>> 8) ^ r) & 0x00ff00ff;\n        r ^= x;\n        l ^= (x << 8);\n        x = ((l >>> 2) ^ r) & 0x33333333;\n        r ^= x;\n        l ^= (x << 2);\n        x = ((r >>> 16) ^ l) & 0x0000ffff;\n        l ^= x;\n        r ^= (x << 16);\n        x = ((r >>> 4) ^ l) & 0x0f0f0f0f;\n        l ^= x;\n        r ^= (x << 4);\n\n        // Spread ints to bytes\n        x = [r, l];\n        for (i = 0; i < 8; i++) {\n            b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;\n            if (b[i] < 0) { b[i] += 256; } // unsigned\n        }\n        return b;\n    }\n}\n\nexport class DESECBCipher {\n    constructor() {\n        this._cipher = null;\n    }\n\n    get algorithm() {\n        return { name: \"DES-ECB\" };\n    }\n\n    static importKey(key, _algorithm, _extractable, _keyUsages) {\n        const cipher = new DESECBCipher;\n        cipher._importKey(key);\n        return cipher;\n    }\n\n    _importKey(key, _extractable, _keyUsages) {\n        this._cipher = new DES(key);\n    }\n\n    encrypt(_algorithm, plaintext) {\n        const x = new Uint8Array(plaintext);\n        if (x.length % 8 !== 0 || this._cipher === null) {\n            return null;\n        }\n        const n = x.length / 8;\n        for (let i = 0; i < n; i++) {\n            x.set(this._cipher.enc8(x.slice(i * 8, i * 8 + 8)), i * 8);\n        }\n        return x;\n    }\n}\n\nexport class DESCBCCipher {\n    constructor() {\n        this._cipher = null;\n    }\n\n    get algorithm() {\n        return { name: \"DES-CBC\" };\n    }\n\n    static importKey(key, _algorithm, _extractable, _keyUsages) {\n        const cipher = new DESCBCCipher;\n        cipher._importKey(key);\n        return cipher;\n    }\n\n    _importKey(key) {\n        this._cipher = new DES(key);\n    }\n\n    encrypt(algorithm, plaintext) {\n        const x = new Uint8Array(plaintext);\n        let y = new Uint8Array(algorithm.iv);\n        if (x.length % 8 !== 0 || this._cipher === null) {\n            return null;\n        }\n        const n = x.length / 8;\n        for (let i = 0; i < n; i++) {\n            for (let j = 0; j < 8; j++) {\n                y[j] ^= plaintext[i * 8 + j];\n            }\n            y = this._cipher.enc8(y);\n            x.set(y, i * 8);\n        }\n        return x;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/crypto/dh.js",
    "content": "import { modPow, bigIntToU8Array, u8ArrayToBigInt } from \"./bigint.js\";\n\nclass DHPublicKey {\n    constructor(key) {\n        this._key = key;\n    }\n\n    get algorithm() {\n        return { name: \"DH\" };\n    }\n\n    exportKey() {\n        return this._key;\n    }\n}\n\nexport class DHCipher {\n    constructor() {\n        this._g = null;\n        this._p = null;\n        this._gBigInt = null;\n        this._pBigInt = null;\n        this._privateKey = null;\n    }\n\n    get algorithm() {\n        return { name: \"DH\" };\n    }\n\n    static generateKey(algorithm, _extractable) {\n        const cipher = new DHCipher;\n        cipher._generateKey(algorithm);\n        return { privateKey: cipher, publicKey: new DHPublicKey(cipher._publicKey) };\n    }\n\n    _generateKey(algorithm) {\n        const g = algorithm.g;\n        const p = algorithm.p;\n        this._keyBytes = p.length;\n        this._gBigInt = u8ArrayToBigInt(g);\n        this._pBigInt = u8ArrayToBigInt(p);\n        this._privateKey = window.crypto.getRandomValues(new Uint8Array(this._keyBytes));\n        this._privateKeyBigInt = u8ArrayToBigInt(this._privateKey);\n        this._publicKey = bigIntToU8Array(modPow(\n            this._gBigInt, this._privateKeyBigInt, this._pBigInt), this._keyBytes);\n    }\n\n    deriveBits(algorithm, length) {\n        const bytes = Math.ceil(length / 8);\n        const pkey = new Uint8Array(algorithm.public);\n        const len = bytes > this._keyBytes ? bytes : this._keyBytes;\n        const secret = modPow(u8ArrayToBigInt(pkey), this._privateKeyBigInt, this._pBigInt);\n        return bigIntToU8Array(secret, len).slice(0, len);\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/crypto/md5.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2021 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\n/*\n * Performs MD5 hashing on an array of bytes, returns an array of bytes\n */\n\nexport async function MD5(d) {\n    let s = \"\";\n    for (let i = 0; i < d.length; i++) {\n        s += String.fromCharCode(d[i]);\n    }\n    return M(V(Y(X(s), 8 * s.length)));\n}\n\nfunction M(d) {\n    let f = new Uint8Array(d.length);\n    for (let i=0;i<d.length;i++) {\n        f[i] = d.charCodeAt(i);\n    }\n    return f;\n}\n\nfunction X(d) {\n    let r = Array(d.length >> 2);\n    for (let m = 0; m < r.length; m++) r[m] = 0;\n    for (let m = 0; m < 8 * d.length; m += 8) r[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32;\n    return r;\n}\n\nfunction V(d) {\n    let r = \"\";\n    for (let m = 0; m < 32 * d.length; m += 8) r += String.fromCharCode(d[m >> 5] >>> m % 32 & 255);\n    return r;\n}\n\nfunction Y(d, g) {\n    d[g >> 5] |= 128 << g % 32, d[14 + (g + 64 >>> 9 << 4)] = g;\n    let m = 1732584193, f = -271733879, r = -1732584194, i = 271733878;\n    for (let n = 0; n < d.length; n += 16) {\n        let h = m,\n            t = f,\n            g = r,\n            e = i;\n        f = ii(f = ii(f = ii(f = ii(f = hh(f = hh(f = hh(f = hh(f = gg(f = gg(f = gg(f = gg(f = ff(f = ff(f = ff(f = ff(f, r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = ff(r, i = ff(i, m = ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = gg(r, i = gg(i, m = gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = hh(r, i = hh(i, m = hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = ii(r, i = ii(i, m = ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = add(m, h), f = add(f, t), r = add(r, g), i = add(i, e);\n    }\n    return Array(m, f, r, i);\n}\n\nfunction cmn(d, g, m, f, r, i) {\n    return add(rol(add(add(g, d), add(f, i)), r), m);\n}\n\nfunction ff(d, g, m, f, r, i, n) {\n    return cmn(g & m | ~g & f, d, g, r, i, n);\n}\n\nfunction gg(d, g, m, f, r, i, n) {\n    return cmn(g & f | m & ~f, d, g, r, i, n);\n}\n\nfunction hh(d, g, m, f, r, i, n) {\n    return cmn(g ^ m ^ f, d, g, r, i, n);\n}\n\nfunction ii(d, g, m, f, r, i, n) {\n    return cmn(m ^ (g | ~f), d, g, r, i, n);\n}\n\nfunction add(d, g) {\n    let m = (65535 & d) + (65535 & g);\n    return (d >> 16) + (g >> 16) + (m >> 16) << 16 | 65535 & m;\n}\n\nfunction rol(d, g) {\n    return d << g | d >>> 32 - g;\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/crypto/rsa.js",
    "content": "import Base64 from \"../base64.js\";\nimport { modPow, bigIntToU8Array, u8ArrayToBigInt } from \"./bigint.js\";\n\nexport class RSACipher {\n    constructor() {\n        this._keyLength = 0;\n        this._keyBytes = 0;\n        this._n = null;\n        this._e = null;\n        this._d = null;\n        this._nBigInt = null;\n        this._eBigInt = null;\n        this._dBigInt = null;\n        this._extractable = false;\n    }\n\n    get algorithm() {\n        return { name: \"RSA-PKCS1-v1_5\" };\n    }\n\n    _base64urlDecode(data) {\n        data = data.replace(/-/g, \"+\").replace(/_/g, \"/\");\n        data = data.padEnd(Math.ceil(data.length / 4) * 4, \"=\");\n        return Base64.decode(data);\n    }\n\n    _padArray(arr, length) {\n        const res = new Uint8Array(length);\n        res.set(arr, length - arr.length);\n        return res;\n    }\n\n    static async generateKey(algorithm, extractable, _keyUsages) {\n        const cipher = new RSACipher;\n        await cipher._generateKey(algorithm, extractable);\n        return { privateKey: cipher };\n    }\n\n    async _generateKey(algorithm, extractable) {\n        this._keyLength = algorithm.modulusLength;\n        this._keyBytes = Math.ceil(this._keyLength / 8);\n        const key = await window.crypto.subtle.generateKey(\n            {\n                name: \"RSA-OAEP\",\n                modulusLength: algorithm.modulusLength,\n                publicExponent: algorithm.publicExponent,\n                hash: {name: \"SHA-256\"},\n            },\n            true, [\"encrypt\", \"decrypt\"]);\n        const privateKey = await window.crypto.subtle.exportKey(\"jwk\", key.privateKey);\n        this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);\n        this._nBigInt = u8ArrayToBigInt(this._n);\n        this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);\n        this._eBigInt = u8ArrayToBigInt(this._e);\n        this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);\n        this._dBigInt = u8ArrayToBigInt(this._d);\n        this._extractable = extractable;\n    }\n\n    static async importKey(key, _algorithm, extractable, keyUsages) {\n        if (keyUsages.length !== 1 || keyUsages[0] !== \"encrypt\") {\n            throw new Error(\"only support importing RSA public key\");\n        }\n        const cipher = new RSACipher;\n        await cipher._importKey(key, extractable);\n        return cipher;\n    }\n\n    async _importKey(key, extractable) {\n        const n = key.n;\n        const e = key.e;\n        if (n.length !== e.length) {\n            throw new Error(\"the sizes of modulus and public exponent do not match\");\n        }\n        this._keyBytes = n.length;\n        this._keyLength = this._keyBytes * 8;\n        this._n = new Uint8Array(this._keyBytes);\n        this._e = new Uint8Array(this._keyBytes);\n        this._n.set(n);\n        this._e.set(e);\n        this._nBigInt = u8ArrayToBigInt(this._n);\n        this._eBigInt = u8ArrayToBigInt(this._e);\n        this._extractable = extractable;\n    }\n\n    async encrypt(_algorithm, message) {\n        if (message.length > this._keyBytes - 11) {\n            return null;\n        }\n        const ps = new Uint8Array(this._keyBytes - message.length - 3);\n        window.crypto.getRandomValues(ps);\n        for (let i = 0; i < ps.length; i++) {\n            ps[i] = Math.floor(ps[i] * 254 / 255 + 1);\n        }\n        const em = new Uint8Array(this._keyBytes);\n        em[1] = 0x02;\n        em.set(ps, 2);\n        em.set(message, ps.length + 3);\n        const emBigInt = u8ArrayToBigInt(em);\n        const c = modPow(emBigInt, this._eBigInt, this._nBigInt);\n        return bigIntToU8Array(c, this._keyBytes);\n    }\n\n    async decrypt(_algorithm, message) {\n        if (message.length !== this._keyBytes) {\n            return null;\n        }\n        const msgBigInt = u8ArrayToBigInt(message);\n        const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);\n        const em = bigIntToU8Array(emBigInt, this._keyBytes);\n        if (em[0] !== 0x00 || em[1] !== 0x02) {\n            return null;\n        }\n        let i = 2;\n        for (; i < em.length; i++) {\n            if (em[i] === 0x00) {\n                break;\n            }\n        }\n        if (i === em.length) {\n            return null;\n        }\n        return em.slice(i + 1, em.length);\n    }\n\n    async exportKey() {\n        if (!this._extractable) {\n            throw new Error(\"key is not extractable\");\n        }\n        return { n: this._n, e: this._e, d: this._d };\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/copyrect.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nexport default class CopyRectDecoder {\n    decodeRect(x, y, width, height, sock, display, depth) {\n        if (sock.rQwait(\"COPYRECT\", 4)) {\n            return false;\n        }\n\n        let deltaX = sock.rQshift16();\n        let deltaY = sock.rQshift16();\n\n        if ((width === 0) || (height === 0)) {\n            return true;\n        }\n\n        display.copyImage(deltaX, deltaY, x, y, width, height);\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/h264.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2024 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nimport * as Log from '../util/logging.js';\n\nexport class H264Parser {\n    constructor(data) {\n        this._data = data;\n        this._index = 0;\n        this.profileIdc = null;\n        this.constraintSet = null;\n        this.levelIdc = null;\n    }\n\n    _getStartSequenceLen(index) {\n        let data = this._data;\n        if (data[index + 0] == 0 && data[index + 1] == 0 && data[index + 2] == 0 && data[index + 3] == 1) {\n            return 4;\n        }\n        if (data[index + 0] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {\n            return 3;\n        }\n        return 0;\n    }\n\n    _indexOfNextNalUnit(index) {\n        let data = this._data;\n        for (let i = index; i < data.length; ++i) {\n            if (this._getStartSequenceLen(i) != 0) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    _parseSps(index) {\n        this.profileIdc = this._data[index];\n        this.constraintSet = this._data[index + 1];\n        this.levelIdc = this._data[index + 2];\n    }\n\n    _parseNalUnit(index) {\n        const firstByte = this._data[index];\n        if (firstByte & 0x80) {\n            throw new Error('H264 parsing sanity check failed, forbidden zero bit is set');\n        }\n        const unitType = firstByte & 0x1f;\n\n        switch (unitType) {\n            case 1: // coded slice, non-idr\n                return { slice: true };\n            case 5: // coded slice, idr\n                return { slice: true, key: true };\n            case 6: // sei\n                return {};\n            case 7: // sps\n                this._parseSps(index + 1);\n                return {};\n            case 8: // pps\n                return {};\n            default:\n                Log.Warn(\"Unhandled unit type: \", unitType);\n                break;\n        }\n        return {};\n    }\n\n    parse() {\n        const startIndex = this._index;\n        let isKey = false;\n\n        while (this._index < this._data.length) {\n            const startSequenceLen = this._getStartSequenceLen(this._index);\n            if (startSequenceLen == 0) {\n                throw new Error('Invalid start sequence in bit stream');\n            }\n\n            const { slice, key } = this._parseNalUnit(this._index + startSequenceLen);\n\n            let nextIndex = this._indexOfNextNalUnit(this._index + startSequenceLen);\n            if (nextIndex == -1) {\n                this._index = this._data.length;\n            } else {\n                this._index = nextIndex;\n            }\n\n            if (key) {\n                isKey = true;\n            }\n            if (slice) {\n                break;\n            }\n        }\n\n        if (startIndex === this._index) {\n            return null;\n        }\n\n        return {\n            frame: this._data.subarray(startIndex, this._index),\n            key: isKey,\n        };\n    }\n}\n\nexport class H264Context {\n    constructor(width, height) {\n        this.lastUsed = 0;\n        this._width = width;\n        this._height = height;\n        this._profileIdc = null;\n        this._constraintSet = null;\n        this._levelIdc = null;\n        this._decoder = null;\n        this._pendingFrames = [];\n    }\n\n    _handleFrame(frame) {\n        let pending = this._pendingFrames.shift();\n        if (pending === undefined) {\n            throw new Error(\"Pending frame queue empty when receiving frame from decoder\");\n        }\n\n        if (pending.timestamp != frame.timestamp) {\n            throw new Error(\"Video frame timestamp mismatch. Expected \" +\n                frame.timestamp + \" but but got \" + pending.timestamp);\n        }\n\n        pending.frame = frame;\n        pending.ready = true;\n        pending.resolve();\n\n        if (!pending.keep) {\n            frame.close();\n        }\n    }\n\n    _handleError(e) {\n        throw new Error(\"Failed to decode frame: \" + e.message);\n    }\n\n    _configureDecoder(profileIdc, constraintSet, levelIdc) {\n        if (this._decoder === null || this._decoder.state === 'closed') {\n            this._decoder = new VideoDecoder({\n                output: frame => this._handleFrame(frame),\n                error: e => this._handleError(e),\n            });\n        }\n        const codec = 'avc1.' +\n            profileIdc.toString(16).padStart(2, '0') +\n            constraintSet.toString(16).padStart(2, '0') +\n            levelIdc.toString(16).padStart(2, '0');\n        this._decoder.configure({\n            codec: codec,\n            codedWidth: this._width,\n            codedHeight: this._height,\n            optimizeForLatency: true,\n        });\n    }\n\n    _preparePendingFrame(timestamp) {\n        let pending = {\n            timestamp: timestamp,\n            promise: null,\n            resolve: null,\n            frame: null,\n            ready: false,\n            keep: false,\n        };\n        pending.promise = new Promise((resolve) => {\n            pending.resolve = resolve;\n        });\n        this._pendingFrames.push(pending);\n\n        return pending;\n    }\n\n    decode(payload) {\n        let parser = new H264Parser(payload);\n        let result = null;\n\n        // Ideally, this timestamp should come from the server, but we'll just\n        // approximate it instead.\n        let timestamp = Math.round(window.performance.now() * 1e3);\n\n        while (true) {\n            let encodedFrame = parser.parse();\n            if (encodedFrame === null) {\n                break;\n            }\n\n            if (parser.profileIdc !== null) {\n                self._profileIdc = parser.profileIdc;\n                self._constraintSet = parser.constraintSet;\n                self._levelIdc = parser.levelIdc;\n            }\n\n            if (this._decoder === null || this._decoder.state !== 'configured') {\n                if (!encodedFrame.key) {\n                    Log.Warn(\"Missing key frame. Can't decode until one arrives\");\n                    continue;\n                }\n                if (self._profileIdc === null) {\n                    Log.Warn('Cannot config decoder. Have not received SPS and PPS yet.');\n                    continue;\n                }\n                this._configureDecoder(self._profileIdc, self._constraintSet,\n                                       self._levelIdc);\n            }\n\n            result = this._preparePendingFrame(timestamp);\n\n            const chunk = new EncodedVideoChunk({\n                timestamp: timestamp,\n                type: encodedFrame.key ? 'key' : 'delta',\n                data: encodedFrame.frame,\n            });\n\n            try {\n                this._decoder.decode(chunk);\n            } catch (e) {\n                Log.Warn(\"Failed to decode:\", e);\n            }\n        }\n\n        // We only keep last frame of each payload\n        if (result !== null) {\n            result.keep = true;\n        }\n\n        return result;\n    }\n}\n\nexport default class H264Decoder {\n    constructor() {\n        this._tick = 0;\n        this._contexts = {};\n    }\n\n    _contextId(x, y, width, height) {\n        return [x, y, width, height].join(',');\n    }\n\n    _findOldestContextId() {\n        let oldestTick = Number.MAX_VALUE;\n        let oldestKey = undefined;\n        for (const [key, value] of Object.entries(this._contexts)) {\n            if (value.lastUsed < oldestTick) {\n                oldestTick = value.lastUsed;\n                oldestKey = key;\n            }\n        }\n        return oldestKey;\n    }\n\n    _createContext(x, y, width, height) {\n        const maxContexts = 64;\n        if (Object.keys(this._contexts).length >= maxContexts) {\n            let oldestContextId = this._findOldestContextId();\n            delete this._contexts[oldestContextId];\n        }\n        let context = new H264Context(width, height);\n        this._contexts[this._contextId(x, y, width, height)] = context;\n        return context;\n    }\n\n    _getContext(x, y, width, height) {\n        let context = this._contexts[this._contextId(x, y, width, height)];\n        return context !== undefined ? context : this._createContext(x, y, width, height);\n    }\n\n    _resetContext(x, y, width, height) {\n        delete this._contexts[this._contextId(x, y, width, height)];\n    }\n\n    _resetAllContexts() {\n        this._contexts = {};\n    }\n\n    decodeRect(x, y, width, height, sock, display, depth) {\n        const resetContextFlag = 1;\n        const resetAllContextsFlag = 2;\n\n        if (sock.rQwait(\"h264 header\", 8)) {\n            return false;\n        }\n\n        const length = sock.rQshift32();\n        const flags = sock.rQshift32();\n\n        if (sock.rQwait(\"h264 payload\", length, 8)) {\n            return false;\n        }\n\n        if (flags & resetAllContextsFlag) {\n            this._resetAllContexts();\n        } else if (flags & resetContextFlag) {\n            this._resetContext(x, y, width, height);\n        }\n\n        let context = this._getContext(x, y, width, height);\n        context.lastUsed = this._tick++;\n\n        if (length !== 0) {\n            let payload = sock.rQshiftBytes(length, false);\n            let frame = context.decode(payload);\n            if (frame !== null) {\n                display.videoFrame(x, y, width, height, frame);\n            }\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/hextile.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nimport * as Log from '../util/logging.js';\n\nexport default class HextileDecoder {\n    constructor() {\n        this._tiles = 0;\n        this._lastsubencoding = 0;\n        this._tileBuffer = new Uint8Array(16 * 16 * 4);\n    }\n\n    decodeRect(x, y, width, height, sock, display, depth) {\n        if (this._tiles === 0) {\n            this._tilesX = Math.ceil(width / 16);\n            this._tilesY = Math.ceil(height / 16);\n            this._totalTiles = this._tilesX * this._tilesY;\n            this._tiles = this._totalTiles;\n        }\n\n        while (this._tiles > 0) {\n            let bytes = 1;\n\n            if (sock.rQwait(\"HEXTILE\", bytes)) {\n                return false;\n            }\n\n            let subencoding = sock.rQpeek8();\n            if (subencoding > 30) {  // Raw\n                throw new Error(\"Illegal hextile subencoding (subencoding: \" +\n                            subencoding + \")\");\n            }\n\n            const currTile = this._totalTiles - this._tiles;\n            const tileX = currTile % this._tilesX;\n            const tileY = Math.floor(currTile / this._tilesX);\n            const tx = x + tileX * 16;\n            const ty = y + tileY * 16;\n            const tw = Math.min(16, (x + width) - tx);\n            const th = Math.min(16, (y + height) - ty);\n\n            // Figure out how much we are expecting\n            if (subencoding & 0x01) {  // Raw\n                bytes += tw * th * 4;\n            } else {\n                if (subencoding & 0x02) {  // Background\n                    bytes += 4;\n                }\n                if (subencoding & 0x04) {  // Foreground\n                    bytes += 4;\n                }\n                if (subencoding & 0x08) {  // AnySubrects\n                    bytes++;  // Since we aren't shifting it off\n\n                    if (sock.rQwait(\"HEXTILE\", bytes)) {\n                        return false;\n                    }\n\n                    let subrects = sock.rQpeekBytes(bytes).at(-1);\n                    if (subencoding & 0x10) {  // SubrectsColoured\n                        bytes += subrects * (4 + 2);\n                    } else {\n                        bytes += subrects * 2;\n                    }\n                }\n            }\n\n            if (sock.rQwait(\"HEXTILE\", bytes)) {\n                return false;\n            }\n\n            // We know the encoding and have a whole tile\n            sock.rQshift8();\n            if (subencoding === 0) {\n                if (this._lastsubencoding & 0x01) {\n                    // Weird: ignore blanks are RAW\n                    Log.Debug(\"     Ignoring blank after RAW\");\n                } else {\n                    display.fillRect(tx, ty, tw, th, this._background);\n                }\n            } else if (subencoding & 0x01) {  // Raw\n                let pixels = tw * th;\n                let data = sock.rQshiftBytes(pixels * 4, false);\n                // Max sure the image is fully opaque\n                for (let i = 0;i <  pixels;i++) {\n                    data[i * 4 + 3] = 255;\n                }\n                display.blitImage(tx, ty, tw, th, data, 0);\n            } else {\n                if (subencoding & 0x02) {  // Background\n                    this._background = new Uint8Array(sock.rQshiftBytes(4));\n                }\n                if (subencoding & 0x04) {  // Foreground\n                    this._foreground = new Uint8Array(sock.rQshiftBytes(4));\n                }\n\n                this._startTile(tx, ty, tw, th, this._background);\n                if (subencoding & 0x08) {  // AnySubrects\n                    let subrects = sock.rQshift8();\n\n                    for (let s = 0; s < subrects; s++) {\n                        let color;\n                        if (subencoding & 0x10) {  // SubrectsColoured\n                            color = sock.rQshiftBytes(4);\n                        } else {\n                            color = this._foreground;\n                        }\n                        const xy = sock.rQshift8();\n                        const sx = (xy >> 4);\n                        const sy = (xy & 0x0f);\n\n                        const wh = sock.rQshift8();\n                        const sw = (wh >> 4) + 1;\n                        const sh = (wh & 0x0f) + 1;\n\n                        this._subTile(sx, sy, sw, sh, color);\n                    }\n                }\n                this._finishTile(display);\n            }\n            this._lastsubencoding = subencoding;\n            this._tiles--;\n        }\n\n        return true;\n    }\n\n    // start updating a tile\n    _startTile(x, y, width, height, color) {\n        this._tileX = x;\n        this._tileY = y;\n        this._tileW = width;\n        this._tileH = height;\n\n        const red = color[0];\n        const green = color[1];\n        const blue = color[2];\n\n        const data = this._tileBuffer;\n        for (let i = 0; i < width * height * 4; i += 4) {\n            data[i]     = red;\n            data[i + 1] = green;\n            data[i + 2] = blue;\n            data[i + 3] = 255;\n        }\n    }\n\n    // update sub-rectangle of the current tile\n    _subTile(x, y, w, h, color) {\n        const red = color[0];\n        const green = color[1];\n        const blue = color[2];\n        const xend = x + w;\n        const yend = y + h;\n\n        const data = this._tileBuffer;\n        const width = this._tileW;\n        for (let j = y; j < yend; j++) {\n            for (let i = x; i < xend; i++) {\n                const p = (i + (j * width)) * 4;\n                data[p]     = red;\n                data[p + 1] = green;\n                data[p + 2] = blue;\n                data[p + 3] = 255;\n            }\n        }\n    }\n\n    // draw the current tile to the screen\n    _finishTile(display) {\n        display.blitImage(this._tileX, this._tileY,\n                          this._tileW, this._tileH,\n                          this._tileBuffer, 0);\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/jpeg.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nexport default class JPEGDecoder {\n    constructor() {\n        // RealVNC will reuse the quantization tables\n        // and Huffman tables, so we need to cache them.\n        this._cachedQuantTables = [];\n        this._cachedHuffmanTables = [];\n\n        this._segments = [];\n    }\n\n    decodeRect(x, y, width, height, sock, display, depth) {\n        // A rect of JPEG encodings is simply a JPEG file\n        while (true) {\n            let segment = this._readSegment(sock);\n            if (segment === null) {\n                return false;\n            }\n            this._segments.push(segment);\n            // End of image?\n            if (segment[1] === 0xD9) {\n                break;\n            }\n        }\n\n        let huffmanTables = [];\n        let quantTables = [];\n        for (let segment of this._segments) {\n            let type = segment[1];\n            if (type === 0xC4) {\n                // Huffman tables\n                huffmanTables.push(segment);\n            } else if (type === 0xDB) {\n                // Quantization tables\n                quantTables.push(segment);\n            }\n        }\n\n        const sofIndex = this._segments.findIndex(\n            x => x[1] == 0xC0 || x[1] == 0xC2\n        );\n        if (sofIndex == -1) {\n            throw new Error(\"Illegal JPEG image without SOF\");\n        }\n\n        if (quantTables.length === 0) {\n            this._segments.splice(sofIndex+1, 0,\n                                  ...this._cachedQuantTables);\n        }\n        if (huffmanTables.length === 0) {\n            this._segments.splice(sofIndex+1, 0,\n                                  ...this._cachedHuffmanTables);\n        }\n\n        let length = 0;\n        for (let segment of this._segments) {\n            length += segment.length;\n        }\n\n        let data = new Uint8Array(length);\n        length = 0;\n        for (let segment of this._segments) {\n            data.set(segment, length);\n            length += segment.length;\n        }\n\n        display.imageRect(x, y, width, height, \"image/jpeg\", data);\n\n        if (huffmanTables.length !== 0) {\n            this._cachedHuffmanTables = huffmanTables;\n        }\n        if (quantTables.length !== 0) {\n            this._cachedQuantTables = quantTables;\n        }\n\n        this._segments = [];\n\n        return true;\n    }\n\n    _readSegment(sock) {\n        if (sock.rQwait(\"JPEG\", 2)) {\n            return null;\n        }\n\n        let marker = sock.rQshift8();\n        if (marker != 0xFF) {\n            throw new Error(\"Illegal JPEG marker received (byte: \" +\n                               marker + \")\");\n        }\n        let type = sock.rQshift8();\n        if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {\n            // No length after marker\n            return new Uint8Array([marker, type]);\n        }\n\n        if (sock.rQwait(\"JPEG\", 2, 2)) {\n            return null;\n        }\n\n        let length = sock.rQshift16();\n        if (length < 2) {\n            throw new Error(\"Illegal JPEG length received (length: \" +\n                               length + \")\");\n        }\n\n        if (sock.rQwait(\"JPEG\", length-2, 4)) {\n            return null;\n        }\n\n        let extra = 0;\n        if (type === 0xDA) {\n            // start of scan\n            extra += 2;\n            while (true) {\n                if (sock.rQwait(\"JPEG\", length-2+extra, 4)) {\n                    return null;\n                }\n                let data = sock.rQpeekBytes(length-2+extra, false);\n                if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&\n                    !(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {\n                    extra -= 2;\n                    break;\n                }\n                extra++;\n            }\n        }\n\n        let segment = new Uint8Array(2 + length + extra);\n        segment[0] = marker;\n        segment[1] = type;\n        segment[2] = length >> 8;\n        segment[3] = length;\n        segment.set(sock.rQshiftBytes(length-2+extra, false), 4);\n\n        return segment;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/raw.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nexport default class RawDecoder {\n    constructor() {\n        this._lines = 0;\n    }\n\n    decodeRect(x, y, width, height, sock, display, depth) {\n        if ((width === 0) || (height === 0)) {\n            return true;\n        }\n\n        if (this._lines === 0) {\n            this._lines = height;\n        }\n\n        const pixelSize = depth == 8 ? 1 : 4;\n        const bytesPerLine = width * pixelSize;\n\n        while (this._lines > 0) {\n            if (sock.rQwait(\"RAW\", bytesPerLine)) {\n                return false;\n            }\n\n            const curY = y + (height - this._lines);\n\n            let data = sock.rQshiftBytes(bytesPerLine, false);\n\n            // Convert data if needed\n            if (depth == 8) {\n                const newdata = new Uint8Array(width * 4);\n                for (let i = 0; i < width; i++) {\n                    newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;\n                    newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;\n                    newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;\n                    newdata[i * 4 + 3] = 255;\n                }\n                data = newdata;\n            }\n\n            // Max sure the image is fully opaque\n            for (let i = 0; i < width; i++) {\n                data[i * 4 + 3] = 255;\n            }\n\n            display.blitImage(x, curY, width, 1, data, 0);\n            this._lines--;\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/rre.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nexport default class RREDecoder {\n    constructor() {\n        this._subrects = 0;\n    }\n\n    decodeRect(x, y, width, height, sock, display, depth) {\n        if (this._subrects === 0) {\n            if (sock.rQwait(\"RRE\", 4 + 4)) {\n                return false;\n            }\n\n            this._subrects = sock.rQshift32();\n\n            let color = sock.rQshiftBytes(4);  // Background\n            display.fillRect(x, y, width, height, color);\n        }\n\n        while (this._subrects > 0) {\n            if (sock.rQwait(\"RRE\", 4 + 8)) {\n                return false;\n            }\n\n            let color = sock.rQshiftBytes(4);\n            let sx = sock.rQshift16();\n            let sy = sock.rQshift16();\n            let swidth = sock.rQshift16();\n            let sheight = sock.rQshift16();\n            display.fillRect(x + sx, y + sy, swidth, sheight, color);\n\n            this._subrects--;\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/tight.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nimport * as Log from '../util/logging.js';\nimport Inflator from \"../inflator.js\";\n\nexport default class TightDecoder {\n    constructor() {\n        this._ctl = null;\n        this._filter = null;\n        this._numColors = 0;\n        this._palette = new Uint8Array(1024);  // 256 * 4 (max palette size * max bytes-per-pixel)\n        this._len = 0;\n\n        this._zlibs = [];\n        for (let i = 0; i < 4; i++) {\n            this._zlibs[i] = new Inflator();\n        }\n    }\n\n    decodeRect(x, y, width, height, sock, display, depth) {\n        if (this._ctl === null) {\n            if (sock.rQwait(\"TIGHT compression-control\", 1)) {\n                return false;\n            }\n\n            this._ctl = sock.rQshift8();\n\n            // Reset streams if the server requests it\n            for (let i = 0; i < 4; i++) {\n                if ((this._ctl >> i) & 1) {\n                    this._zlibs[i].reset();\n                    Log.Info(\"Reset zlib stream \" + i);\n                }\n            }\n\n            // Figure out filter\n            this._ctl = this._ctl >> 4;\n        }\n\n        let ret;\n\n        if (this._ctl === 0x08) {\n            ret = this._fillRect(x, y, width, height,\n                                 sock, display, depth);\n        } else if (this._ctl === 0x09) {\n            ret = this._jpegRect(x, y, width, height,\n                                 sock, display, depth);\n        } else if (this._ctl === 0x0A) {\n            ret = this._pngRect(x, y, width, height,\n                                sock, display, depth);\n        } else if ((this._ctl & 0x08) == 0) {\n            ret = this._basicRect(this._ctl, x, y, width, height,\n                                  sock, display, depth);\n        } else {\n            throw new Error(\"Illegal tight compression received (ctl: \" +\n                                   this._ctl + \")\");\n        }\n\n        if (ret) {\n            this._ctl = null;\n        }\n\n        return ret;\n    }\n\n    _fillRect(x, y, width, height, sock, display, depth) {\n        if (sock.rQwait(\"TIGHT\", 3)) {\n            return false;\n        }\n\n        let pixel = sock.rQshiftBytes(3);\n        display.fillRect(x, y, width, height, pixel, false);\n\n        return true;\n    }\n\n    _jpegRect(x, y, width, height, sock, display, depth) {\n        let data = this._readData(sock);\n        if (data === null) {\n            return false;\n        }\n\n        display.imageRect(x, y, width, height, \"image/jpeg\", data);\n\n        return true;\n    }\n\n    _pngRect(x, y, width, height, sock, display, depth) {\n        throw new Error(\"PNG received in standard Tight rect\");\n    }\n\n    _basicRect(ctl, x, y, width, height, sock, display, depth) {\n        if (this._filter === null) {\n            if (ctl & 0x4) {\n                if (sock.rQwait(\"TIGHT\", 1)) {\n                    return false;\n                }\n\n                this._filter = sock.rQshift8();\n            } else {\n                // Implicit CopyFilter\n                this._filter = 0;\n            }\n        }\n\n        let streamId = ctl & 0x3;\n\n        let ret;\n\n        switch (this._filter) {\n            case 0: // CopyFilter\n                ret = this._copyFilter(streamId, x, y, width, height,\n                                       sock, display, depth);\n                break;\n            case 1: // PaletteFilter\n                ret = this._paletteFilter(streamId, x, y, width, height,\n                                          sock, display, depth);\n                break;\n            case 2: // GradientFilter\n                ret = this._gradientFilter(streamId, x, y, width, height,\n                                           sock, display, depth);\n                break;\n            default:\n                throw new Error(\"Illegal tight filter received (ctl: \" +\n                                       this._filter + \")\");\n        }\n\n        if (ret) {\n            this._filter = null;\n        }\n\n        return ret;\n    }\n\n    _copyFilter(streamId, x, y, width, height, sock, display, depth) {\n        const uncompressedSize = width * height * 3;\n        let data;\n\n        if (uncompressedSize === 0) {\n            return true;\n        }\n\n        if (uncompressedSize < 12) {\n            if (sock.rQwait(\"TIGHT\", uncompressedSize)) {\n                return false;\n            }\n\n            data = sock.rQshiftBytes(uncompressedSize);\n        } else {\n            data = this._readData(sock);\n            if (data === null) {\n                return false;\n            }\n\n            this._zlibs[streamId].setInput(data);\n            data = this._zlibs[streamId].inflate(uncompressedSize);\n            this._zlibs[streamId].setInput(null);\n        }\n\n        let rgbx = new Uint8Array(width * height * 4);\n        for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {\n            rgbx[i]     = data[j];\n            rgbx[i + 1] = data[j + 1];\n            rgbx[i + 2] = data[j + 2];\n            rgbx[i + 3] = 255;  // Alpha\n        }\n\n        display.blitImage(x, y, width, height, rgbx, 0, false);\n\n        return true;\n    }\n\n    _paletteFilter(streamId, x, y, width, height, sock, display, depth) {\n        if (this._numColors === 0) {\n            if (sock.rQwait(\"TIGHT palette\", 1)) {\n                return false;\n            }\n\n            const numColors = sock.rQpeek8() + 1;\n            const paletteSize = numColors * 3;\n\n            if (sock.rQwait(\"TIGHT palette\", 1 + paletteSize)) {\n                return false;\n            }\n\n            this._numColors = numColors;\n            sock.rQskipBytes(1);\n\n            sock.rQshiftTo(this._palette, paletteSize);\n        }\n\n        const bpp = (this._numColors <= 2) ? 1 : 8;\n        const rowSize = Math.floor((width * bpp + 7) / 8);\n        const uncompressedSize = rowSize * height;\n\n        let data;\n\n        if (uncompressedSize === 0) {\n            return true;\n        }\n\n        if (uncompressedSize < 12) {\n            if (sock.rQwait(\"TIGHT\", uncompressedSize)) {\n                return false;\n            }\n\n            data = sock.rQshiftBytes(uncompressedSize);\n        } else {\n            data = this._readData(sock);\n            if (data === null) {\n                return false;\n            }\n\n            this._zlibs[streamId].setInput(data);\n            data = this._zlibs[streamId].inflate(uncompressedSize);\n            this._zlibs[streamId].setInput(null);\n        }\n\n        // Convert indexed (palette based) image data to RGB\n        if (this._numColors == 2) {\n            this._monoRect(x, y, width, height, data, this._palette, display);\n        } else {\n            this._paletteRect(x, y, width, height, data, this._palette, display);\n        }\n\n        this._numColors = 0;\n\n        return true;\n    }\n\n    _monoRect(x, y, width, height, data, palette, display) {\n        // Convert indexed (palette based) image data to RGB\n        // TODO: reduce number of calculations inside loop\n        const dest = this._getScratchBuffer(width * height * 4);\n        const w = Math.floor((width + 7) / 8);\n        const w1 = Math.floor(width / 8);\n\n        for (let y = 0; y < height; y++) {\n            let dp, sp, x;\n            for (x = 0; x < w1; x++) {\n                for (let b = 7; b >= 0; b--) {\n                    dp = (y * width + x * 8 + 7 - b) * 4;\n                    sp = (data[y * w + x] >> b & 1) * 3;\n                    dest[dp]     = palette[sp];\n                    dest[dp + 1] = palette[sp + 1];\n                    dest[dp + 2] = palette[sp + 2];\n                    dest[dp + 3] = 255;\n                }\n            }\n\n            for (let b = 7; b >= 8 - width % 8; b--) {\n                dp = (y * width + x * 8 + 7 - b) * 4;\n                sp = (data[y * w + x] >> b & 1) * 3;\n                dest[dp]     = palette[sp];\n                dest[dp + 1] = palette[sp + 1];\n                dest[dp + 2] = palette[sp + 2];\n                dest[dp + 3] = 255;\n            }\n        }\n\n        display.blitImage(x, y, width, height, dest, 0, false);\n    }\n\n    _paletteRect(x, y, width, height, data, palette, display) {\n        // Convert indexed (palette based) image data to RGB\n        const dest = this._getScratchBuffer(width * height * 4);\n        const total = width * height * 4;\n        for (let i = 0, j = 0; i < total; i += 4, j++) {\n            const sp = data[j] * 3;\n            dest[i]     = palette[sp];\n            dest[i + 1] = palette[sp + 1];\n            dest[i + 2] = palette[sp + 2];\n            dest[i + 3] = 255;\n        }\n\n        display.blitImage(x, y, width, height, dest, 0, false);\n    }\n\n    _gradientFilter(streamId, x, y, width, height, sock, display, depth) {\n        // assume the TPIXEL is 3 bytes long\n        const uncompressedSize = width * height * 3;\n        let data;\n\n        if (uncompressedSize === 0) {\n            return true;\n        }\n\n        if (uncompressedSize < 12) {\n            if (sock.rQwait(\"TIGHT\", uncompressedSize)) {\n                return false;\n            }\n\n            data = sock.rQshiftBytes(uncompressedSize);\n        } else {\n            data = this._readData(sock);\n            if (data === null) {\n                return false;\n            }\n\n            this._zlibs[streamId].setInput(data);\n            data = this._zlibs[streamId].inflate(uncompressedSize);\n            this._zlibs[streamId].setInput(null);\n        }\n\n        let rgbx = new Uint8Array(4 * width * height);\n\n        let rgbxIndex = 0, dataIndex = 0;\n        let left = new Uint8Array(3);\n        for (let x = 0; x < width; x++) {\n            for (let c = 0; c < 3; c++) {\n                const prediction = left[c];\n                const value = data[dataIndex++] + prediction;\n                rgbx[rgbxIndex++] = value;\n                left[c] = value;\n            }\n            rgbx[rgbxIndex++] = 255;\n        }\n\n        let upperIndex = 0;\n        let upper = new Uint8Array(3),\n            upperleft = new Uint8Array(3);\n        for (let y = 1; y < height; y++) {\n            left.fill(0);\n            upperleft.fill(0);\n            for (let x = 0; x < width; x++) {\n                for (let c = 0; c < 3; c++) {\n                    upper[c] = rgbx[upperIndex++];\n                    let prediction = left[c] + upper[c] - upperleft[c];\n                    if (prediction < 0) {\n                        prediction = 0;\n                    } else if (prediction > 255) {\n                        prediction = 255;\n                    }\n                    const value = data[dataIndex++] + prediction;\n                    rgbx[rgbxIndex++] = value;\n                    upperleft[c] = upper[c];\n                    left[c] = value;\n                }\n                rgbx[rgbxIndex++] = 255;\n                upperIndex++;\n            }\n        }\n\n        display.blitImage(x, y, width, height, rgbx, 0, false);\n\n        return true;\n    }\n\n    _readData(sock) {\n        if (this._len === 0) {\n            if (sock.rQwait(\"TIGHT\", 3)) {\n                return null;\n            }\n\n            let byte;\n\n            byte = sock.rQshift8();\n            this._len = byte & 0x7f;\n            if (byte & 0x80) {\n                byte = sock.rQshift8();\n                this._len |= (byte & 0x7f) << 7;\n                if (byte & 0x80) {\n                    byte = sock.rQshift8();\n                    this._len |= byte << 14;\n                }\n            }\n        }\n\n        if (sock.rQwait(\"TIGHT\", this._len)) {\n            return null;\n        }\n\n        let data = sock.rQshiftBytes(this._len, false);\n        this._len = 0;\n\n        return data;\n    }\n\n    _getScratchBuffer(size) {\n        if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {\n            this._scratchBuffer = new Uint8Array(size);\n        }\n        return this._scratchBuffer;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/tightpng.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nimport TightDecoder from './tight.js';\n\nexport default class TightPNGDecoder extends TightDecoder {\n    _pngRect(x, y, width, height, sock, display, depth) {\n        let data = this._readData(sock);\n        if (data === null) {\n            return false;\n        }\n\n        display.imageRect(x, y, width, height, \"image/png\", data);\n\n        return true;\n    }\n\n    _basicRect(ctl, x, y, width, height, sock, display, depth) {\n        throw new Error(\"BasicCompression received in TightPNG rect\");\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/zlib.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2024 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nimport Inflator from \"../inflator.js\";\n\nexport default class ZlibDecoder {\n    constructor() {\n        this._zlib = new Inflator();\n        this._length = 0;\n    }\n\n    decodeRect(x, y, width, height, sock, display, depth) {\n        if ((width === 0) || (height === 0)) {\n            return true;\n        }\n\n        if (this._length === 0) {\n            if (sock.rQwait(\"ZLIB\", 4)) {\n                return false;\n            }\n\n            this._length = sock.rQshift32();\n        }\n\n        if (sock.rQwait(\"ZLIB\", this._length)) {\n            return false;\n        }\n\n        let data = new Uint8Array(sock.rQshiftBytes(this._length, false));\n        this._length = 0;\n\n        this._zlib.setInput(data);\n        data = this._zlib.inflate(width * height * 4);\n        this._zlib.setInput(null);\n\n        // Max sure the image is fully opaque\n        for (let i = 0; i < width * height; i++) {\n            data[i * 4 + 3] = 255;\n        }\n\n        display.blitImage(x, y, width, height, data, 0);\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/decoders/zrle.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2021 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nimport Inflate from \"../inflator.js\";\n\nconst ZRLE_TILE_WIDTH = 64;\nconst ZRLE_TILE_HEIGHT = 64;\n\nexport default class ZRLEDecoder {\n    constructor() {\n        this._length = 0;\n        this._inflator = new Inflate();\n\n        this._pixelBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);\n        this._tileBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);\n    }\n\n    decodeRect(x, y, width, height, sock, display, depth) {\n        if (this._length === 0) {\n            if (sock.rQwait(\"ZLib data length\", 4)) {\n                return false;\n            }\n            this._length = sock.rQshift32();\n        }\n        if (sock.rQwait(\"Zlib data\", this._length)) {\n            return false;\n        }\n\n        const data = sock.rQshiftBytes(this._length, false);\n\n        this._inflator.setInput(data);\n\n        for (let ty = y; ty < y + height; ty += ZRLE_TILE_HEIGHT) {\n            let th = Math.min(ZRLE_TILE_HEIGHT, y + height - ty);\n\n            for (let tx = x; tx < x + width; tx += ZRLE_TILE_WIDTH) {\n                let tw = Math.min(ZRLE_TILE_WIDTH, x + width - tx);\n\n                const tileSize = tw * th;\n                const subencoding = this._inflator.inflate(1)[0];\n                if (subencoding === 0) {\n                    // raw data\n                    const data = this._readPixels(tileSize);\n                    display.blitImage(tx, ty, tw, th, data, 0, false);\n                } else if (subencoding === 1) {\n                    // solid\n                    const background = this._readPixels(1);\n                    display.fillRect(tx, ty, tw, th, [background[0], background[1], background[2]]);\n                } else if (subencoding >= 2 && subencoding <= 16) {\n                    const data = this._decodePaletteTile(subencoding, tileSize, tw, th);\n                    display.blitImage(tx, ty, tw, th, data, 0, false);\n                } else if (subencoding === 128) {\n                    const data = this._decodeRLETile(tileSize);\n                    display.blitImage(tx, ty, tw, th, data, 0, false);\n                } else if (subencoding >= 130 && subencoding <= 255) {\n                    const data = this._decodeRLEPaletteTile(subencoding - 128, tileSize);\n                    display.blitImage(tx, ty, tw, th, data, 0, false);\n                } else {\n                    throw new Error('Unknown subencoding: ' + subencoding);\n                }\n            }\n        }\n        this._length = 0;\n        return true;\n    }\n\n    _getBitsPerPixelInPalette(paletteSize) {\n        if (paletteSize <= 2) {\n            return 1;\n        } else if (paletteSize <= 4) {\n            return 2;\n        } else if (paletteSize <= 16) {\n            return 4;\n        }\n    }\n\n    _readPixels(pixels) {\n        let data = this._pixelBuffer;\n        const buffer = this._inflator.inflate(3*pixels);\n        for (let i = 0, j = 0; i < pixels*4; i += 4, j += 3) {\n            data[i]     = buffer[j];\n            data[i + 1] = buffer[j + 1];\n            data[i + 2] = buffer[j + 2];\n            data[i + 3] = 255;  // Add the Alpha\n        }\n        return data;\n    }\n\n    _decodePaletteTile(paletteSize, tileSize, tilew, tileh) {\n        const data = this._tileBuffer;\n        const palette = this._readPixels(paletteSize);\n        const bitsPerPixel = this._getBitsPerPixelInPalette(paletteSize);\n        const mask = (1 << bitsPerPixel) - 1;\n\n        let offset = 0;\n        let encoded = this._inflator.inflate(1)[0];\n\n        for (let y=0; y<tileh; y++) {\n            let shift = 8-bitsPerPixel;\n            for (let x=0; x<tilew; x++) {\n                if (shift<0) {\n                    shift=8-bitsPerPixel;\n                    encoded = this._inflator.inflate(1)[0];\n                }\n                let indexInPalette = (encoded>>shift) & mask;\n\n                data[offset] = palette[indexInPalette * 4];\n                data[offset + 1] = palette[indexInPalette * 4 + 1];\n                data[offset + 2] = palette[indexInPalette * 4 + 2];\n                data[offset + 3] = palette[indexInPalette * 4 + 3];\n                offset += 4;\n                shift-=bitsPerPixel;\n            }\n            if (shift<8-bitsPerPixel && y<tileh-1) {\n                encoded =  this._inflator.inflate(1)[0];\n            }\n        }\n        return data;\n    }\n\n    _decodeRLETile(tileSize) {\n        const data = this._tileBuffer;\n        let i = 0;\n        while (i < tileSize) {\n            const pixel = this._readPixels(1);\n            const length = this._readRLELength();\n            for (let j = 0; j < length; j++) {\n                data[i * 4] = pixel[0];\n                data[i * 4 + 1] = pixel[1];\n                data[i * 4 + 2] = pixel[2];\n                data[i * 4 + 3] = pixel[3];\n                i++;\n            }\n        }\n        return data;\n    }\n\n    _decodeRLEPaletteTile(paletteSize, tileSize) {\n        const data = this._tileBuffer;\n\n        // palette\n        const palette = this._readPixels(paletteSize);\n\n        let offset = 0;\n        while (offset < tileSize) {\n            let indexInPalette = this._inflator.inflate(1)[0];\n            let length = 1;\n            if (indexInPalette >= 128) {\n                indexInPalette -= 128;\n                length = this._readRLELength();\n            }\n            if (indexInPalette > paletteSize) {\n                throw new Error('Too big index in palette: ' + indexInPalette + ', palette size: ' + paletteSize);\n            }\n            if (offset + length > tileSize) {\n                throw new Error('Too big rle length in palette mode: ' + length + ', allowed length is: ' + (tileSize - offset));\n            }\n\n            for (let j = 0; j < length; j++) {\n                data[offset * 4] = palette[indexInPalette * 4];\n                data[offset * 4 + 1] = palette[indexInPalette * 4 + 1];\n                data[offset * 4 + 2] = palette[indexInPalette * 4 + 2];\n                data[offset * 4 + 3] = palette[indexInPalette * 4 + 3];\n                offset++;\n            }\n        }\n        return data;\n    }\n\n    _readRLELength() {\n        let length = 0;\n        let current = 0;\n        do {\n            current = this._inflator.inflate(1)[0];\n            length += current;\n        } while (current === 255);\n        return length + 1;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/deflator.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\nimport { deflateInit, deflate } from \"../vendor/pako/lib/zlib/deflate.js\";\nimport { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from \"../vendor/pako/lib/zlib/deflate.js\";\nimport ZStream from \"../vendor/pako/lib/zlib/zstream.js\";\n\nexport default class Deflator {\n    constructor() {\n        this.strm = new ZStream();\n        this.chunkSize = 1024 * 10 * 10;\n        this.outputBuffer = new Uint8Array(this.chunkSize);\n\n        deflateInit(this.strm, Z_DEFAULT_COMPRESSION);\n    }\n\n    deflate(inData) {\n        /* eslint-disable camelcase */\n        this.strm.input = inData;\n        this.strm.avail_in = this.strm.input.length;\n        this.strm.next_in = 0;\n        this.strm.output = this.outputBuffer;\n        this.strm.avail_out = this.chunkSize;\n        this.strm.next_out = 0;\n        /* eslint-enable camelcase */\n\n        let lastRet = deflate(this.strm, Z_FULL_FLUSH);\n        let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);\n\n        if (lastRet < 0) {\n            throw new Error(\"zlib deflate failed\");\n        }\n\n        if (this.strm.avail_in > 0) {\n            // Read chunks until done\n\n            let chunks = [outData];\n            let totalLen = outData.length;\n            do {\n                /* eslint-disable camelcase */\n                this.strm.output = new Uint8Array(this.chunkSize);\n                this.strm.next_out = 0;\n                this.strm.avail_out = this.chunkSize;\n                /* eslint-enable camelcase */\n\n                lastRet = deflate(this.strm, Z_FULL_FLUSH);\n\n                if (lastRet < 0) {\n                    throw new Error(\"zlib deflate failed\");\n                }\n\n                let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);\n                totalLen += chunk.length;\n                chunks.push(chunk);\n            } while (this.strm.avail_in > 0);\n\n            // Combine chunks into a single data\n\n            let newData = new Uint8Array(totalLen);\n            let offset = 0;\n\n            for (let i = 0; i < chunks.length; i++) {\n                newData.set(chunks[i], offset);\n                offset += chunks[i].length;\n            }\n\n            outData = newData;\n        }\n\n        /* eslint-disable camelcase */\n        this.strm.input = null;\n        this.strm.avail_in = 0;\n        this.strm.next_in = 0;\n        /* eslint-enable camelcase */\n\n        return outData;\n    }\n\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/display.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\nimport * as Log from './util/logging.js';\nimport Base64 from \"./base64.js\";\nimport { toSigned32bit } from './util/int.js';\n\nexport default class Display {\n    constructor(target) {\n        this._drawCtx = null;\n\n        this._renderQ = [];  // queue drawing actions for in-oder rendering\n        this._flushPromise = null;\n\n        // the full frame buffer (logical canvas) size\n        this._fbWidth = 0;\n        this._fbHeight = 0;\n\n        this._prevDrawStyle = \"\";\n\n        Log.Debug(\">> Display.constructor\");\n\n        // The visible canvas\n        this._target = target;\n\n        if (!this._target) {\n            throw new Error(\"Target must be set\");\n        }\n\n        if (typeof this._target === 'string') {\n            throw new Error('target must be a DOM element');\n        }\n\n        if (!this._target.getContext) {\n            throw new Error(\"no getContext method\");\n        }\n\n        this._targetCtx = this._target.getContext('2d');\n\n        // the visible canvas viewport (i.e. what actually gets seen)\n        this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };\n\n        // The hidden canvas, where we do the actual rendering\n        this._backbuffer = document.createElement('canvas');\n        this._drawCtx = this._backbuffer.getContext('2d');\n\n        this._damageBounds = { left: 0, top: 0,\n                               right: this._backbuffer.width,\n                               bottom: this._backbuffer.height };\n\n        Log.Debug(\"User Agent: \" + navigator.userAgent);\n\n        Log.Debug(\"<< Display.constructor\");\n\n        // ===== PROPERTIES =====\n\n        this._scale = 1.0;\n        this._clipViewport = false;\n    }\n\n    // ===== PROPERTIES =====\n\n    get scale() { return this._scale; }\n    set scale(scale) {\n        this._rescale(scale);\n    }\n\n    get clipViewport() { return this._clipViewport; }\n    set clipViewport(viewport) {\n        this._clipViewport = viewport;\n        // May need to readjust the viewport dimensions\n        const vp = this._viewportLoc;\n        this.viewportChangeSize(vp.w, vp.h);\n        this.viewportChangePos(0, 0);\n    }\n\n    get width() {\n        return this._fbWidth;\n    }\n\n    get height() {\n        return this._fbHeight;\n    }\n\n    // ===== PUBLIC METHODS =====\n\n    viewportChangePos(deltaX, deltaY) {\n        const vp = this._viewportLoc;\n        deltaX = Math.floor(deltaX);\n        deltaY = Math.floor(deltaY);\n\n        if (!this._clipViewport) {\n            deltaX = -vp.w;  // clamped later of out of bounds\n            deltaY = -vp.h;\n        }\n\n        const vx2 = vp.x + vp.w - 1;\n        const vy2 = vp.y + vp.h - 1;\n\n        // Position change\n\n        if (deltaX < 0 && vp.x + deltaX < 0) {\n            deltaX = -vp.x;\n        }\n        if (vx2 + deltaX >= this._fbWidth) {\n            deltaX -= vx2 + deltaX - this._fbWidth + 1;\n        }\n\n        if (vp.y + deltaY < 0) {\n            deltaY = -vp.y;\n        }\n        if (vy2 + deltaY >= this._fbHeight) {\n            deltaY -= (vy2 + deltaY - this._fbHeight + 1);\n        }\n\n        if (deltaX === 0 && deltaY === 0) {\n            return;\n        }\n        Log.Debug(\"viewportChange deltaX: \" + deltaX + \", deltaY: \" + deltaY);\n\n        vp.x += deltaX;\n        vp.y += deltaY;\n\n        this._damage(vp.x, vp.y, vp.w, vp.h);\n\n        this.flip();\n    }\n\n    viewportChangeSize(width, height) {\n\n        if (!this._clipViewport ||\n            typeof(width) === \"undefined\" ||\n            typeof(height) === \"undefined\") {\n\n            Log.Debug(\"Setting viewport to full display region\");\n            width = this._fbWidth;\n            height = this._fbHeight;\n        }\n\n        width = Math.floor(width);\n        height = Math.floor(height);\n\n        if (width > this._fbWidth) {\n            width = this._fbWidth;\n        }\n        if (height > this._fbHeight) {\n            height = this._fbHeight;\n        }\n\n        const vp = this._viewportLoc;\n        if (vp.w !== width || vp.h !== height) {\n            vp.w = width;\n            vp.h = height;\n\n            const canvas = this._target;\n            canvas.width = width;\n            canvas.height = height;\n\n            // The position might need to be updated if we've grown\n            this.viewportChangePos(0, 0);\n\n            this._damage(vp.x, vp.y, vp.w, vp.h);\n            this.flip();\n\n            // Update the visible size of the target canvas\n            this._rescale(this._scale);\n        }\n    }\n\n    absX(x) {\n        if (this._scale === 0) {\n            return 0;\n        }\n        return toSigned32bit(x / this._scale + this._viewportLoc.x);\n    }\n\n    absY(y) {\n        if (this._scale === 0) {\n            return 0;\n        }\n        return toSigned32bit(y / this._scale + this._viewportLoc.y);\n    }\n\n    resize(width, height) {\n        this._prevDrawStyle = \"\";\n\n        this._fbWidth = width;\n        this._fbHeight = height;\n\n        const canvas = this._backbuffer;\n        if (canvas.width !== width || canvas.height !== height) {\n\n            // We have to save the canvas data since changing the size will clear it\n            let saveImg = null;\n            if (canvas.width > 0 && canvas.height > 0) {\n                saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height);\n            }\n\n            if (canvas.width !== width) {\n                canvas.width = width;\n            }\n            if (canvas.height !== height) {\n                canvas.height = height;\n            }\n\n            if (saveImg) {\n                this._drawCtx.putImageData(saveImg, 0, 0);\n            }\n        }\n\n        // Readjust the viewport as it may be incorrectly sized\n        // and positioned\n        const vp = this._viewportLoc;\n        this.viewportChangeSize(vp.w, vp.h);\n        this.viewportChangePos(0, 0);\n    }\n\n    getImageData() {\n        return this._drawCtx.getImageData(0, 0, this.width, this.height);\n    }\n\n    toDataURL(type, encoderOptions) {\n        return this._backbuffer.toDataURL(type, encoderOptions);\n    }\n\n    toBlob(callback, type, quality) {\n        return this._backbuffer.toBlob(callback, type, quality);\n    }\n\n    // Track what parts of the visible canvas that need updating\n    _damage(x, y, w, h) {\n        if (x < this._damageBounds.left) {\n            this._damageBounds.left = x;\n        }\n        if (y < this._damageBounds.top) {\n            this._damageBounds.top = y;\n        }\n        if ((x + w) > this._damageBounds.right) {\n            this._damageBounds.right = x + w;\n        }\n        if ((y + h) > this._damageBounds.bottom) {\n            this._damageBounds.bottom = y + h;\n        }\n    }\n\n    // Update the visible canvas with the contents of the\n    // rendering canvas\n    flip(fromQueue) {\n        if (this._renderQ.length !== 0 && !fromQueue) {\n            this._renderQPush({\n                'type': 'flip'\n            });\n        } else {\n            let x = this._damageBounds.left;\n            let y = this._damageBounds.top;\n            let w = this._damageBounds.right - x;\n            let h = this._damageBounds.bottom - y;\n\n            let vx = x - this._viewportLoc.x;\n            let vy = y - this._viewportLoc.y;\n\n            if (vx < 0) {\n                w += vx;\n                x -= vx;\n                vx = 0;\n            }\n            if (vy < 0) {\n                h += vy;\n                y -= vy;\n                vy = 0;\n            }\n\n            if ((vx + w) > this._viewportLoc.w) {\n                w = this._viewportLoc.w - vx;\n            }\n            if ((vy + h) > this._viewportLoc.h) {\n                h = this._viewportLoc.h - vy;\n            }\n\n            if ((w > 0) && (h > 0)) {\n                // FIXME: We may need to disable image smoothing here\n                //        as well (see copyImage()), but we haven't\n                //        noticed any problem yet.\n                this._targetCtx.drawImage(this._backbuffer,\n                                          x, y, w, h,\n                                          vx, vy, w, h);\n            }\n\n            this._damageBounds.left = this._damageBounds.top = 65535;\n            this._damageBounds.right = this._damageBounds.bottom = 0;\n        }\n    }\n\n    pending() {\n        return this._renderQ.length > 0;\n    }\n\n    flush() {\n        if (this._renderQ.length === 0) {\n            return Promise.resolve();\n        } else {\n            if (this._flushPromise === null) {\n                this._flushPromise = new Promise((resolve) => {\n                    this._flushResolve = resolve;\n                });\n            }\n            return this._flushPromise;\n        }\n    }\n\n    fillRect(x, y, width, height, color, fromQueue) {\n        if (this._renderQ.length !== 0 && !fromQueue) {\n            this._renderQPush({\n                'type': 'fill',\n                'x': x,\n                'y': y,\n                'width': width,\n                'height': height,\n                'color': color\n            });\n        } else {\n            this._setFillColor(color);\n            this._drawCtx.fillRect(x, y, width, height);\n            this._damage(x, y, width, height);\n        }\n    }\n\n    copyImage(oldX, oldY, newX, newY, w, h, fromQueue) {\n        if (this._renderQ.length !== 0 && !fromQueue) {\n            this._renderQPush({\n                'type': 'copy',\n                'oldX': oldX,\n                'oldY': oldY,\n                'x': newX,\n                'y': newY,\n                'width': w,\n                'height': h,\n            });\n        } else {\n            // Due to this bug among others [1] we need to disable the image-smoothing to\n            // avoid getting a blur effect when copying data.\n            //\n            // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719\n            //\n            // We need to set these every time since all properties are reset\n            // when the the size is changed\n            this._drawCtx.mozImageSmoothingEnabled = false;\n            this._drawCtx.webkitImageSmoothingEnabled = false;\n            this._drawCtx.msImageSmoothingEnabled = false;\n            this._drawCtx.imageSmoothingEnabled = false;\n\n            this._drawCtx.drawImage(this._backbuffer,\n                                    oldX, oldY, w, h,\n                                    newX, newY, w, h);\n            this._damage(newX, newY, w, h);\n        }\n    }\n\n    imageRect(x, y, width, height, mime, arr) {\n        /* The internal logic cannot handle empty images, so bail early */\n        if ((width === 0) || (height === 0)) {\n            return;\n        }\n\n        const img = new Image();\n        img.src = \"data: \" + mime + \";base64,\" + Base64.encode(arr);\n\n        this._renderQPush({\n            'type': 'img',\n            'img': img,\n            'x': x,\n            'y': y,\n            'width': width,\n            'height': height\n        });\n    }\n\n    videoFrame(x, y, width, height, frame) {\n        this._renderQPush({\n            'type': 'frame',\n            'frame': frame,\n            'x': x,\n            'y': y,\n            'width': width,\n            'height': height\n        });\n    }\n\n    blitImage(x, y, width, height, arr, offset, fromQueue) {\n        if (this._renderQ.length !== 0 && !fromQueue) {\n            // NB(directxman12): it's technically more performant here to use preallocated arrays,\n            // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,\n            // this probably isn't getting called *nearly* as much\n            const newArr = new Uint8Array(width * height * 4);\n            newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));\n            this._renderQPush({\n                'type': 'blit',\n                'data': newArr,\n                'x': x,\n                'y': y,\n                'width': width,\n                'height': height,\n            });\n        } else {\n            // NB(directxman12): arr must be an Type Array view\n            let data = new Uint8ClampedArray(arr.buffer,\n                                             arr.byteOffset + offset,\n                                             width * height * 4);\n            let img = new ImageData(data, width, height);\n            this._drawCtx.putImageData(img, x, y);\n            this._damage(x, y, width, height);\n        }\n    }\n\n    drawImage(img, ...args) {\n        this._drawCtx.drawImage(img, ...args);\n\n        if (args.length <= 4) {\n            const [x, y] = args;\n            this._damage(x, y, img.width, img.height);\n        } else {\n            const [,, sw, sh, dx, dy] = args;\n            this._damage(dx, dy, sw, sh);\n        }\n    }\n\n    autoscale(containerWidth, containerHeight) {\n        let scaleRatio;\n\n        if (containerWidth === 0 || containerHeight === 0) {\n            scaleRatio = 0;\n\n        } else {\n\n            const vp = this._viewportLoc;\n            const targetAspectRatio = containerWidth / containerHeight;\n            const fbAspectRatio = vp.w / vp.h;\n\n            if (fbAspectRatio >= targetAspectRatio) {\n                scaleRatio = containerWidth / vp.w;\n            } else {\n                scaleRatio = containerHeight / vp.h;\n            }\n        }\n\n        this._rescale(scaleRatio);\n    }\n\n    // ===== PRIVATE METHODS =====\n\n    _rescale(factor) {\n        this._scale = factor;\n        const vp = this._viewportLoc;\n\n        // NB(directxman12): If you set the width directly, or set the\n        //                   style width to a number, the canvas is cleared.\n        //                   However, if you set the style width to a string\n        //                   ('NNNpx'), the canvas is scaled without clearing.\n        const width = factor * vp.w + 'px';\n        const height = factor * vp.h + 'px';\n\n        if ((this._target.style.width !== width) ||\n            (this._target.style.height !== height)) {\n            this._target.style.width = width;\n            this._target.style.height = height;\n        }\n    }\n\n    _setFillColor(color) {\n        const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';\n        if (newStyle !== this._prevDrawStyle) {\n            this._drawCtx.fillStyle = newStyle;\n            this._prevDrawStyle = newStyle;\n        }\n    }\n\n    _renderQPush(action) {\n        this._renderQ.push(action);\n        if (this._renderQ.length === 1) {\n            // If this can be rendered immediately it will be, otherwise\n            // the scanner will wait for the relevant event\n            this._scanRenderQ();\n        }\n    }\n\n    _resumeRenderQ() {\n        // \"this\" is the object that is ready, not the\n        // display object\n        this.removeEventListener('load', this._noVNCDisplay._resumeRenderQ);\n        this._noVNCDisplay._scanRenderQ();\n    }\n\n    _scanRenderQ() {\n        let ready = true;\n        while (ready && this._renderQ.length > 0) {\n            const a = this._renderQ[0];\n            switch (a.type) {\n                case 'flip':\n                    this.flip(true);\n                    break;\n                case 'copy':\n                    this.copyImage(a.oldX, a.oldY, a.x, a.y, a.width, a.height, true);\n                    break;\n                case 'fill':\n                    this.fillRect(a.x, a.y, a.width, a.height, a.color, true);\n                    break;\n                case 'blit':\n                    this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);\n                    break;\n                case 'img':\n                    if (a.img.complete) {\n                        if (a.img.width !== a.width || a.img.height !== a.height) {\n                            Log.Error(\"Decoded image has incorrect dimensions. Got \" +\n                                      a.img.width + \"x\" + a.img.height + \". Expected \" +\n                                      a.width + \"x\" + a.height + \".\");\n                            return;\n                        }\n                        this.drawImage(a.img, a.x, a.y);\n                    } else {\n                        a.img._noVNCDisplay = this;\n                        a.img.addEventListener('load', this._resumeRenderQ);\n                        // We need to wait for this image to 'load'\n                        // to keep things in-order\n                        ready = false;\n                    }\n                    break;\n                case 'frame':\n                    if (a.frame.ready) {\n                        // The encoded frame may be larger than the rect due to\n                        // limitations of the encoder, so we need to crop the\n                        // frame.\n                        let frame = a.frame.frame;\n                        if (frame.codedWidth < a.width || frame.codedHeight < a.height) {\n                            Log.Warn(\"Decoded video frame does not cover its full rectangle area. Expecting at least \" +\n                                      a.width + \"x\" + a.height + \" but got \" +\n                                      frame.codedWidth + \"x\" + frame.codedHeight);\n                        }\n                        const sx = 0;\n                        const sy = 0;\n                        const sw = a.width;\n                        const sh = a.height;\n                        const dx = a.x;\n                        const dy = a.y;\n                        const dw = sw;\n                        const dh = sh;\n                        this.drawImage(frame, sx, sy, sw, sh, dx, dy, dw, dh);\n                        frame.close();\n                    } else {\n                        let display = this;\n                        a.frame.promise.then(() => {\n                            display._scanRenderQ();\n                        });\n                        ready = false;\n                    }\n                    break;\n            }\n\n            if (ready) {\n                this._renderQ.shift();\n            }\n        }\n\n        if (this._renderQ.length === 0 &&\n            this._flushPromise !== null) {\n            this._flushResolve();\n            this._flushPromise = null;\n            this._flushResolve = null;\n        }\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/encodings.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\nexport const encodings = {\n    encodingRaw: 0,\n    encodingCopyRect: 1,\n    encodingRRE: 2,\n    encodingHextile: 5,\n    encodingZlib: 6,\n    encodingTight: 7,\n    encodingZRLE: 16,\n    encodingTightPNG: -260,\n    encodingJPEG: 21,\n    encodingH264: 50,\n\n    pseudoEncodingQualityLevel9: -23,\n    pseudoEncodingQualityLevel0: -32,\n    pseudoEncodingDesktopSize: -223,\n    pseudoEncodingLastRect: -224,\n    pseudoEncodingCursor: -239,\n    pseudoEncodingQEMUExtendedKeyEvent: -258,\n    pseudoEncodingQEMULedEvent: -261,\n    pseudoEncodingDesktopName: -307,\n    pseudoEncodingExtendedDesktopSize: -308,\n    pseudoEncodingXvp: -309,\n    pseudoEncodingFence: -312,\n    pseudoEncodingContinuousUpdates: -313,\n    pseudoEncodingExtendedMouseButtons: -316,\n    pseudoEncodingCompressLevel9: -247,\n    pseudoEncodingCompressLevel0: -256,\n    pseudoEncodingVMwareCursor: 0x574d5664,\n    pseudoEncodingExtendedClipboard: 0xc0a1e5ce\n};\n\nexport function encodingName(num) {\n    switch (num) {\n        case encodings.encodingRaw:      return \"Raw\";\n        case encodings.encodingCopyRect: return \"CopyRect\";\n        case encodings.encodingRRE:      return \"RRE\";\n        case encodings.encodingHextile:  return \"Hextile\";\n        case encodings.encodingZlib:     return \"Zlib\";\n        case encodings.encodingTight:    return \"Tight\";\n        case encodings.encodingZRLE:     return \"ZRLE\";\n        case encodings.encodingTightPNG: return \"TightPNG\";\n        case encodings.encodingJPEG:     return \"JPEG\";\n        case encodings.encodingH264:     return \"H.264\";\n        default:                         return \"[unknown encoding \" + num + \"]\";\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/inflator.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\nimport { inflateInit, inflate, inflateReset } from \"../vendor/pako/lib/zlib/inflate.js\";\nimport ZStream from \"../vendor/pako/lib/zlib/zstream.js\";\n\nexport default class Inflate {\n    constructor() {\n        this.strm = new ZStream();\n        this.chunkSize = 1024 * 10 * 10;\n        this.strm.output = new Uint8Array(this.chunkSize);\n\n        inflateInit(this.strm);\n    }\n\n    setInput(data) {\n        if (!data) {\n            //FIXME: flush remaining data.\n            /* eslint-disable camelcase */\n            this.strm.input = null;\n            this.strm.avail_in = 0;\n            this.strm.next_in = 0;\n        } else {\n            this.strm.input = data;\n            this.strm.avail_in = this.strm.input.length;\n            this.strm.next_in = 0;\n            /* eslint-enable camelcase */\n        }\n    }\n\n    inflate(expected) {\n        // resize our output buffer if it's too small\n        // (we could just use multiple chunks, but that would cause an extra\n        // allocation each time to flatten the chunks)\n        if (expected > this.chunkSize) {\n            this.chunkSize = expected;\n            this.strm.output = new Uint8Array(this.chunkSize);\n        }\n\n        /* eslint-disable camelcase */\n        this.strm.next_out = 0;\n        this.strm.avail_out = expected;\n        /* eslint-enable camelcase */\n\n        let ret = inflate(this.strm, 0); // Flush argument not used.\n        if (ret < 0) {\n            throw new Error(\"zlib inflate failed\");\n        }\n\n        if (this.strm.next_out != expected) {\n            throw new Error(\"Incomplete zlib block\");\n        }\n\n        return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);\n    }\n\n    reset() {\n        inflateReset(this.strm);\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/input/domkeytable.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 or any later version (see LICENSE.txt)\n */\n\nimport KeyTable from \"./keysym.js\";\n\n/*\n * Mapping between HTML key values and VNC/X11 keysyms for \"special\"\n * keys that cannot be handled via their Unicode codepoint.\n *\n * See https://www.w3.org/TR/uievents-key/ for possible values.\n */\n\nconst DOMKeyTable = {};\n\nfunction addStandard(key, standard) {\n    if (standard === undefined) throw new Error(\"Undefined keysym for key \\\"\" + key + \"\\\"\");\n    if (key in DOMKeyTable) throw new Error(\"Duplicate entry for key \\\"\" + key + \"\\\"\");\n    DOMKeyTable[key] = [standard, standard, standard, standard];\n}\n\nfunction addLeftRight(key, left, right) {\n    if (left === undefined) throw new Error(\"Undefined keysym for key \\\"\" + key + \"\\\"\");\n    if (right === undefined) throw new Error(\"Undefined keysym for key \\\"\" + key + \"\\\"\");\n    if (key in DOMKeyTable) throw new Error(\"Duplicate entry for key \\\"\" + key + \"\\\"\");\n    DOMKeyTable[key] = [left, left, right, left];\n}\n\nfunction addNumpad(key, standard, numpad) {\n    if (standard === undefined) throw new Error(\"Undefined keysym for key \\\"\" + key + \"\\\"\");\n    if (numpad === undefined) throw new Error(\"Undefined keysym for key \\\"\" + key + \"\\\"\");\n    if (key in DOMKeyTable) throw new Error(\"Duplicate entry for key \\\"\" + key + \"\\\"\");\n    DOMKeyTable[key] = [standard, standard, standard, numpad];\n}\n\n// 3.2. Modifier Keys\n\naddLeftRight(\"Alt\", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R);\naddStandard(\"AltGraph\", KeyTable.XK_ISO_Level3_Shift);\naddStandard(\"CapsLock\", KeyTable.XK_Caps_Lock);\naddLeftRight(\"Control\", KeyTable.XK_Control_L, KeyTable.XK_Control_R);\n// - Fn\n// - FnLock\naddLeftRight(\"Meta\", KeyTable.XK_Super_L, KeyTable.XK_Super_R);\naddStandard(\"NumLock\", KeyTable.XK_Num_Lock);\naddStandard(\"ScrollLock\", KeyTable.XK_Scroll_Lock);\naddLeftRight(\"Shift\", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);\n// - Symbol\n// - SymbolLock\n// - Hyper\n// - Super\n\n// 3.3. Whitespace Keys\n\naddNumpad(\"Enter\", KeyTable.XK_Return, KeyTable.XK_KP_Enter);\naddStandard(\"Tab\", KeyTable.XK_Tab);\naddNumpad(\" \", KeyTable.XK_space, KeyTable.XK_KP_Space);\n\n// 3.4. Navigation Keys\n\naddNumpad(\"ArrowDown\", KeyTable.XK_Down, KeyTable.XK_KP_Down);\naddNumpad(\"ArrowLeft\", KeyTable.XK_Left, KeyTable.XK_KP_Left);\naddNumpad(\"ArrowRight\", KeyTable.XK_Right, KeyTable.XK_KP_Right);\naddNumpad(\"ArrowUp\", KeyTable.XK_Up, KeyTable.XK_KP_Up);\naddNumpad(\"End\", KeyTable.XK_End, KeyTable.XK_KP_End);\naddNumpad(\"Home\", KeyTable.XK_Home, KeyTable.XK_KP_Home);\naddNumpad(\"PageDown\", KeyTable.XK_Next, KeyTable.XK_KP_Next);\naddNumpad(\"PageUp\", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);\n\n// 3.5. Editing Keys\n\naddStandard(\"Backspace\", KeyTable.XK_BackSpace);\n// Browsers send \"Clear\" for the numpad 5 without NumLock because\n// Windows uses VK_Clear for that key. But Unix expects KP_Begin for\n// that scenario.\naddNumpad(\"Clear\", KeyTable.XK_Clear, KeyTable.XK_KP_Begin);\naddStandard(\"Copy\", KeyTable.XF86XK_Copy);\n// - CrSel\naddStandard(\"Cut\", KeyTable.XF86XK_Cut);\naddNumpad(\"Delete\", KeyTable.XK_Delete, KeyTable.XK_KP_Delete);\n// - EraseEof\n// - ExSel\naddNumpad(\"Insert\", KeyTable.XK_Insert, KeyTable.XK_KP_Insert);\naddStandard(\"Paste\", KeyTable.XF86XK_Paste);\naddStandard(\"Redo\", KeyTable.XK_Redo);\naddStandard(\"Undo\", KeyTable.XK_Undo);\n\n// 3.6. UI Keys\n\n// - Accept\n// - Again (could just be XK_Redo)\n// - Attn\naddStandard(\"Cancel\", KeyTable.XK_Cancel);\naddStandard(\"ContextMenu\", KeyTable.XK_Menu);\naddStandard(\"Escape\", KeyTable.XK_Escape);\naddStandard(\"Execute\", KeyTable.XK_Execute);\naddStandard(\"Find\", KeyTable.XK_Find);\naddStandard(\"Help\", KeyTable.XK_Help);\naddStandard(\"Pause\", KeyTable.XK_Pause);\n// - Play\n// - Props\naddStandard(\"Select\", KeyTable.XK_Select);\naddStandard(\"ZoomIn\", KeyTable.XF86XK_ZoomIn);\naddStandard(\"ZoomOut\", KeyTable.XF86XK_ZoomOut);\n\n// 3.7. Device Keys\n\naddStandard(\"BrightnessDown\", KeyTable.XF86XK_MonBrightnessDown);\naddStandard(\"BrightnessUp\", KeyTable.XF86XK_MonBrightnessUp);\naddStandard(\"Eject\", KeyTable.XF86XK_Eject);\naddStandard(\"LogOff\", KeyTable.XF86XK_LogOff);\naddStandard(\"Power\", KeyTable.XF86XK_PowerOff);\naddStandard(\"PowerOff\", KeyTable.XF86XK_PowerDown);\naddStandard(\"PrintScreen\", KeyTable.XK_Print);\naddStandard(\"Hibernate\", KeyTable.XF86XK_Hibernate);\naddStandard(\"Standby\", KeyTable.XF86XK_Standby);\naddStandard(\"WakeUp\", KeyTable.XF86XK_WakeUp);\n\n// 3.8. IME and Composition Keys\n\naddStandard(\"AllCandidates\", KeyTable.XK_MultipleCandidate);\naddStandard(\"Alphanumeric\", KeyTable.XK_Eisu_toggle);\naddStandard(\"CodeInput\", KeyTable.XK_Codeinput);\naddStandard(\"Compose\", KeyTable.XK_Multi_key);\naddStandard(\"Convert\", KeyTable.XK_Henkan);\n// - Dead\n// - FinalMode\naddStandard(\"GroupFirst\", KeyTable.XK_ISO_First_Group);\naddStandard(\"GroupLast\", KeyTable.XK_ISO_Last_Group);\naddStandard(\"GroupNext\", KeyTable.XK_ISO_Next_Group);\naddStandard(\"GroupPrevious\", KeyTable.XK_ISO_Prev_Group);\n// - ModeChange (XK_Mode_switch is often used for AltGr)\n// - NextCandidate\naddStandard(\"NonConvert\", KeyTable.XK_Muhenkan);\naddStandard(\"PreviousCandidate\", KeyTable.XK_PreviousCandidate);\n// - Process\naddStandard(\"SingleCandidate\", KeyTable.XK_SingleCandidate);\naddStandard(\"HangulMode\", KeyTable.XK_Hangul);\naddStandard(\"HanjaMode\", KeyTable.XK_Hangul_Hanja);\naddStandard(\"JunjaMode\", KeyTable.XK_Hangul_Jeonja);\naddStandard(\"Eisu\", KeyTable.XK_Eisu_toggle);\naddStandard(\"Hankaku\", KeyTable.XK_Hankaku);\naddStandard(\"Hiragana\", KeyTable.XK_Hiragana);\naddStandard(\"HiraganaKatakana\", KeyTable.XK_Hiragana_Katakana);\naddStandard(\"KanaMode\", KeyTable.XK_Kana_Shift); // could also be _Kana_Lock\naddStandard(\"KanjiMode\", KeyTable.XK_Kanji);\naddStandard(\"Katakana\", KeyTable.XK_Katakana);\naddStandard(\"Romaji\", KeyTable.XK_Romaji);\naddStandard(\"Zenkaku\", KeyTable.XK_Zenkaku);\naddStandard(\"ZenkakuHankaku\", KeyTable.XK_Zenkaku_Hankaku);\n\n// 3.9. General-Purpose Function Keys\n\naddStandard(\"F1\", KeyTable.XK_F1);\naddStandard(\"F2\", KeyTable.XK_F2);\naddStandard(\"F3\", KeyTable.XK_F3);\naddStandard(\"F4\", KeyTable.XK_F4);\naddStandard(\"F5\", KeyTable.XK_F5);\naddStandard(\"F6\", KeyTable.XK_F6);\naddStandard(\"F7\", KeyTable.XK_F7);\naddStandard(\"F8\", KeyTable.XK_F8);\naddStandard(\"F9\", KeyTable.XK_F9);\naddStandard(\"F10\", KeyTable.XK_F10);\naddStandard(\"F11\", KeyTable.XK_F11);\naddStandard(\"F12\", KeyTable.XK_F12);\naddStandard(\"F13\", KeyTable.XK_F13);\naddStandard(\"F14\", KeyTable.XK_F14);\naddStandard(\"F15\", KeyTable.XK_F15);\naddStandard(\"F16\", KeyTable.XK_F16);\naddStandard(\"F17\", KeyTable.XK_F17);\naddStandard(\"F18\", KeyTable.XK_F18);\naddStandard(\"F19\", KeyTable.XK_F19);\naddStandard(\"F20\", KeyTable.XK_F20);\naddStandard(\"F21\", KeyTable.XK_F21);\naddStandard(\"F22\", KeyTable.XK_F22);\naddStandard(\"F23\", KeyTable.XK_F23);\naddStandard(\"F24\", KeyTable.XK_F24);\naddStandard(\"F25\", KeyTable.XK_F25);\naddStandard(\"F26\", KeyTable.XK_F26);\naddStandard(\"F27\", KeyTable.XK_F27);\naddStandard(\"F28\", KeyTable.XK_F28);\naddStandard(\"F29\", KeyTable.XK_F29);\naddStandard(\"F30\", KeyTable.XK_F30);\naddStandard(\"F31\", KeyTable.XK_F31);\naddStandard(\"F32\", KeyTable.XK_F32);\naddStandard(\"F33\", KeyTable.XK_F33);\naddStandard(\"F34\", KeyTable.XK_F34);\naddStandard(\"F35\", KeyTable.XK_F35);\n// - Soft1...\n\n// 3.10. Multimedia Keys\n\n// - ChannelDown\n// - ChannelUp\naddStandard(\"Close\", KeyTable.XF86XK_Close);\naddStandard(\"MailForward\", KeyTable.XF86XK_MailForward);\naddStandard(\"MailReply\", KeyTable.XF86XK_Reply);\naddStandard(\"MailSend\", KeyTable.XF86XK_Send);\n// - MediaClose\naddStandard(\"MediaFastForward\", KeyTable.XF86XK_AudioForward);\naddStandard(\"MediaPause\", KeyTable.XF86XK_AudioPause);\naddStandard(\"MediaPlay\", KeyTable.XF86XK_AudioPlay);\n// - MediaPlayPause\naddStandard(\"MediaRecord\", KeyTable.XF86XK_AudioRecord);\naddStandard(\"MediaRewind\", KeyTable.XF86XK_AudioRewind);\naddStandard(\"MediaStop\", KeyTable.XF86XK_AudioStop);\naddStandard(\"MediaTrackNext\", KeyTable.XF86XK_AudioNext);\naddStandard(\"MediaTrackPrevious\", KeyTable.XF86XK_AudioPrev);\naddStandard(\"New\", KeyTable.XF86XK_New);\naddStandard(\"Open\", KeyTable.XF86XK_Open);\naddStandard(\"Print\", KeyTable.XK_Print);\naddStandard(\"Save\", KeyTable.XF86XK_Save);\naddStandard(\"SpellCheck\", KeyTable.XF86XK_Spell);\n\n// 3.11. Multimedia Numpad Keys\n\n// - Key11\n// - Key12\n\n// 3.12. Audio Keys\n\n// - AudioBalanceLeft\n// - AudioBalanceRight\n// - AudioBassBoostDown\n// - AudioBassBoostToggle\n// - AudioBassBoostUp\n// - AudioFaderFront\n// - AudioFaderRear\n// - AudioSurroundModeNext\n// - AudioTrebleDown\n// - AudioTrebleUp\naddStandard(\"AudioVolumeDown\", KeyTable.XF86XK_AudioLowerVolume);\naddStandard(\"AudioVolumeUp\", KeyTable.XF86XK_AudioRaiseVolume);\naddStandard(\"AudioVolumeMute\", KeyTable.XF86XK_AudioMute);\n// - MicrophoneToggle\n// - MicrophoneVolumeDown\n// - MicrophoneVolumeUp\naddStandard(\"MicrophoneVolumeMute\", KeyTable.XF86XK_AudioMicMute);\n\n// 3.13. Speech Keys\n\n// - SpeechCorrectionList\n// - SpeechInputToggle\n\n// 3.14. Application Keys\n\naddStandard(\"LaunchApplication1\", KeyTable.XF86XK_MyComputer);\naddStandard(\"LaunchApplication2\", KeyTable.XF86XK_Calculator);\naddStandard(\"LaunchCalendar\", KeyTable.XF86XK_Calendar);\n// - LaunchContacts\naddStandard(\"LaunchMail\", KeyTable.XF86XK_Mail);\naddStandard(\"LaunchMediaPlayer\", KeyTable.XF86XK_AudioMedia);\naddStandard(\"LaunchMusicPlayer\", KeyTable.XF86XK_Music);\naddStandard(\"LaunchPhone\", KeyTable.XF86XK_Phone);\naddStandard(\"LaunchScreenSaver\", KeyTable.XF86XK_ScreenSaver);\naddStandard(\"LaunchSpreadsheet\", KeyTable.XF86XK_Excel);\naddStandard(\"LaunchWebBrowser\", KeyTable.XF86XK_WWW);\naddStandard(\"LaunchWebCam\", KeyTable.XF86XK_WebCam);\naddStandard(\"LaunchWordProcessor\", KeyTable.XF86XK_Word);\n\n// 3.15. Browser Keys\n\naddStandard(\"BrowserBack\", KeyTable.XF86XK_Back);\naddStandard(\"BrowserFavorites\", KeyTable.XF86XK_Favorites);\naddStandard(\"BrowserForward\", KeyTable.XF86XK_Forward);\naddStandard(\"BrowserHome\", KeyTable.XF86XK_HomePage);\naddStandard(\"BrowserRefresh\", KeyTable.XF86XK_Refresh);\naddStandard(\"BrowserSearch\", KeyTable.XF86XK_Search);\naddStandard(\"BrowserStop\", KeyTable.XF86XK_Stop);\n\n// 3.16. Mobile Phone Keys\n\n// - A whole bunch...\n\n// 3.17. TV Keys\n\n// - A whole bunch...\n\n// 3.18. Media Controller Keys\n\n// - A whole bunch...\naddStandard(\"Dimmer\", KeyTable.XF86XK_BrightnessAdjust);\naddStandard(\"MediaAudioTrack\", KeyTable.XF86XK_AudioCycleTrack);\naddStandard(\"RandomToggle\", KeyTable.XF86XK_AudioRandomPlay);\naddStandard(\"SplitScreenToggle\", KeyTable.XF86XK_SplitScreen);\naddStandard(\"Subtitle\", KeyTable.XF86XK_Subtitle);\naddStandard(\"VideoModeNext\", KeyTable.XF86XK_Next_VMode);\n\n// Extra: Numpad\n\naddNumpad(\"=\", KeyTable.XK_equal, KeyTable.XK_KP_Equal);\naddNumpad(\"+\", KeyTable.XK_plus, KeyTable.XK_KP_Add);\naddNumpad(\"-\", KeyTable.XK_minus, KeyTable.XK_KP_Subtract);\naddNumpad(\"*\", KeyTable.XK_asterisk, KeyTable.XK_KP_Multiply);\naddNumpad(\"/\", KeyTable.XK_slash, KeyTable.XK_KP_Divide);\naddNumpad(\".\", KeyTable.XK_period, KeyTable.XK_KP_Decimal);\naddNumpad(\",\", KeyTable.XK_comma, KeyTable.XK_KP_Separator);\naddNumpad(\"0\", KeyTable.XK_0, KeyTable.XK_KP_0);\naddNumpad(\"1\", KeyTable.XK_1, KeyTable.XK_KP_1);\naddNumpad(\"2\", KeyTable.XK_2, KeyTable.XK_KP_2);\naddNumpad(\"3\", KeyTable.XK_3, KeyTable.XK_KP_3);\naddNumpad(\"4\", KeyTable.XK_4, KeyTable.XK_KP_4);\naddNumpad(\"5\", KeyTable.XK_5, KeyTable.XK_KP_5);\naddNumpad(\"6\", KeyTable.XK_6, KeyTable.XK_KP_6);\naddNumpad(\"7\", KeyTable.XK_7, KeyTable.XK_KP_7);\naddNumpad(\"8\", KeyTable.XK_8, KeyTable.XK_KP_8);\naddNumpad(\"9\", KeyTable.XK_9, KeyTable.XK_KP_9);\n\nexport default DOMKeyTable;\n"
  },
  {
    "path": "services/gateway/noVNC/core/input/fixedkeys.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 or any later version (see LICENSE.txt)\n */\n\n/*\n * Fallback mapping between HTML key codes (physical keys) and\n * HTML key values. This only works for keys that don't vary\n * between layouts. We also omit those who manage fine by mapping the\n * Unicode representation.\n *\n * See https://www.w3.org/TR/uievents-code/ for possible codes.\n * See https://www.w3.org/TR/uievents-key/ for possible values.\n */\n\n/* eslint-disable key-spacing */\n\nexport default {\n\n// 3.1.1.1. Writing System Keys\n\n    'Backspace':        'Backspace',\n\n// 3.1.1.2. Functional Keys\n\n    'AltLeft':          'Alt',\n    'AltRight':         'Alt', // This could also be 'AltGraph'\n    'CapsLock':         'CapsLock',\n    'ContextMenu':      'ContextMenu',\n    'ControlLeft':      'Control',\n    'ControlRight':     'Control',\n    'Enter':            'Enter',\n    'MetaLeft':         'Meta',\n    'MetaRight':        'Meta',\n    'ShiftLeft':        'Shift',\n    'ShiftRight':       'Shift',\n    'Tab':              'Tab',\n    // FIXME: Japanese/Korean keys\n\n// 3.1.2. Control Pad Section\n\n    'Delete':           'Delete',\n    'End':              'End',\n    'Help':             'Help',\n    'Home':             'Home',\n    'Insert':           'Insert',\n    'PageDown':         'PageDown',\n    'PageUp':           'PageUp',\n\n// 3.1.3. Arrow Pad Section\n\n    'ArrowDown':        'ArrowDown',\n    'ArrowLeft':        'ArrowLeft',\n    'ArrowRight':       'ArrowRight',\n    'ArrowUp':          'ArrowUp',\n\n// 3.1.4. Numpad Section\n\n    'NumLock':          'NumLock',\n    'NumpadBackspace':  'Backspace',\n    'NumpadClear':      'Clear',\n\n// 3.1.5. Function Section\n\n    'Escape':           'Escape',\n    'F1':               'F1',\n    'F2':               'F2',\n    'F3':               'F3',\n    'F4':               'F4',\n    'F5':               'F5',\n    'F6':               'F6',\n    'F7':               'F7',\n    'F8':               'F8',\n    'F9':               'F9',\n    'F10':              'F10',\n    'F11':              'F11',\n    'F12':              'F12',\n    'F13':              'F13',\n    'F14':              'F14',\n    'F15':              'F15',\n    'F16':              'F16',\n    'F17':              'F17',\n    'F18':              'F18',\n    'F19':              'F19',\n    'F20':              'F20',\n    'F21':              'F21',\n    'F22':              'F22',\n    'F23':              'F23',\n    'F24':              'F24',\n    'F25':              'F25',\n    'F26':              'F26',\n    'F27':              'F27',\n    'F28':              'F28',\n    'F29':              'F29',\n    'F30':              'F30',\n    'F31':              'F31',\n    'F32':              'F32',\n    'F33':              'F33',\n    'F34':              'F34',\n    'F35':              'F35',\n    'PrintScreen':      'PrintScreen',\n    'ScrollLock':       'ScrollLock',\n    'Pause':            'Pause',\n\n// 3.1.6. Media Keys\n\n    'BrowserBack':      'BrowserBack',\n    'BrowserFavorites': 'BrowserFavorites',\n    'BrowserForward':   'BrowserForward',\n    'BrowserHome':      'BrowserHome',\n    'BrowserRefresh':   'BrowserRefresh',\n    'BrowserSearch':    'BrowserSearch',\n    'BrowserStop':      'BrowserStop',\n    'Eject':            'Eject',\n    'LaunchApp1':       'LaunchMyComputer',\n    'LaunchApp2':       'LaunchCalendar',\n    'LaunchMail':       'LaunchMail',\n    'MediaPlayPause':   'MediaPlay',\n    'MediaStop':        'MediaStop',\n    'MediaTrackNext':   'MediaTrackNext',\n    'MediaTrackPrevious': 'MediaTrackPrevious',\n    'Power':            'Power',\n    'Sleep':            'Sleep',\n    'AudioVolumeDown':  'AudioVolumeDown',\n    'AudioVolumeMute':  'AudioVolumeMute',\n    'AudioVolumeUp':    'AudioVolumeUp',\n    'WakeUp':           'WakeUp',\n};\n"
  },
  {
    "path": "services/gateway/noVNC/core/input/gesturehandler.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nconst GH_NOGESTURE = 0;\nconst GH_ONETAP    = 1;\nconst GH_TWOTAP    = 2;\nconst GH_THREETAP  = 4;\nconst GH_DRAG      = 8;\nconst GH_LONGPRESS = 16;\nconst GH_TWODRAG   = 32;\nconst GH_PINCH     = 64;\n\nconst GH_INITSTATE = 127;\n\nconst GH_MOVE_THRESHOLD = 50;\nconst GH_ANGLE_THRESHOLD = 90; // Degrees\n\n// Timeout when waiting for gestures (ms)\nconst GH_MULTITOUCH_TIMEOUT = 250;\n\n// Maximum time between press and release for a tap (ms)\nconst GH_TAP_TIMEOUT = 1000;\n\n// Timeout when waiting for longpress (ms)\nconst GH_LONGPRESS_TIMEOUT = 1000;\n\n// Timeout when waiting to decide between PINCH and TWODRAG (ms)\nconst GH_TWOTOUCH_TIMEOUT = 50;\n\nexport default class GestureHandler {\n    constructor() {\n        this._target = null;\n\n        this._state = GH_INITSTATE;\n\n        this._tracked = [];\n        this._ignored = [];\n\n        this._waitingRelease = false;\n        this._releaseStart = 0.0;\n\n        this._longpressTimeoutId = null;\n        this._twoTouchTimeoutId = null;\n\n        this._boundEventHandler = this._eventHandler.bind(this);\n    }\n\n    attach(target) {\n        this.detach();\n\n        this._target = target;\n        this._target.addEventListener('touchstart',\n                                      this._boundEventHandler);\n        this._target.addEventListener('touchmove',\n                                      this._boundEventHandler);\n        this._target.addEventListener('touchend',\n                                      this._boundEventHandler);\n        this._target.addEventListener('touchcancel',\n                                      this._boundEventHandler);\n    }\n\n    detach() {\n        if (!this._target) {\n            return;\n        }\n\n        this._stopLongpressTimeout();\n        this._stopTwoTouchTimeout();\n\n        this._target.removeEventListener('touchstart',\n                                         this._boundEventHandler);\n        this._target.removeEventListener('touchmove',\n                                         this._boundEventHandler);\n        this._target.removeEventListener('touchend',\n                                         this._boundEventHandler);\n        this._target.removeEventListener('touchcancel',\n                                         this._boundEventHandler);\n        this._target = null;\n    }\n\n    _eventHandler(e) {\n        let fn;\n\n        e.stopPropagation();\n        e.preventDefault();\n\n        switch (e.type) {\n            case 'touchstart':\n                fn = this._touchStart;\n                break;\n            case 'touchmove':\n                fn = this._touchMove;\n                break;\n            case 'touchend':\n            case 'touchcancel':\n                fn = this._touchEnd;\n                break;\n        }\n\n        for (let i = 0; i < e.changedTouches.length; i++) {\n            let touch = e.changedTouches[i];\n            fn.call(this, touch.identifier, touch.clientX, touch.clientY);\n        }\n    }\n\n    _touchStart(id, x, y) {\n        // Ignore any new touches if there is already an active gesture,\n        // or we're in a cleanup state\n        if (this._hasDetectedGesture() || (this._state === GH_NOGESTURE)) {\n            this._ignored.push(id);\n            return;\n        }\n\n        // Did it take too long between touches that we should no longer\n        // consider this a single gesture?\n        if ((this._tracked.length > 0) &&\n            ((Date.now() - this._tracked[0].started) > GH_MULTITOUCH_TIMEOUT)) {\n            this._state = GH_NOGESTURE;\n            this._ignored.push(id);\n            return;\n        }\n\n        // If we're waiting for fingers to release then we should no longer\n        // recognize new touches\n        if (this._waitingRelease) {\n            this._state = GH_NOGESTURE;\n            this._ignored.push(id);\n            return;\n        }\n\n        this._tracked.push({\n            id: id,\n            started: Date.now(),\n            active: true,\n            firstX: x,\n            firstY: y,\n            lastX: x,\n            lastY: y,\n            angle: 0\n        });\n\n        switch (this._tracked.length) {\n            case 1:\n                this._startLongpressTimeout();\n                break;\n\n            case 2:\n                this._state &= ~(GH_ONETAP | GH_DRAG | GH_LONGPRESS);\n                this._stopLongpressTimeout();\n                break;\n\n            case 3:\n                this._state &= ~(GH_TWOTAP | GH_TWODRAG | GH_PINCH);\n                break;\n\n            default:\n                this._state = GH_NOGESTURE;\n        }\n    }\n\n    _touchMove(id, x, y) {\n        let touch = this._tracked.find(t => t.id === id);\n\n        // If this is an update for a touch we're not tracking, ignore it\n        if (touch === undefined) {\n            return;\n        }\n\n        // Update the touches last position with the event coordinates\n        touch.lastX = x;\n        touch.lastY = y;\n\n        let deltaX = x - touch.firstX;\n        let deltaY = y - touch.firstY;\n\n        // Update angle when the touch has moved\n        if ((touch.firstX !== touch.lastX) ||\n            (touch.firstY !== touch.lastY)) {\n            touch.angle = Math.atan2(deltaY, deltaX) * 180 / Math.PI;\n        }\n\n        if (!this._hasDetectedGesture()) {\n            // Ignore moves smaller than the minimum threshold\n            if (Math.hypot(deltaX, deltaY) < GH_MOVE_THRESHOLD) {\n                return;\n            }\n\n            // Can't be a tap or long press as we've seen movement\n            this._state &= ~(GH_ONETAP | GH_TWOTAP | GH_THREETAP | GH_LONGPRESS);\n            this._stopLongpressTimeout();\n\n            if (this._tracked.length !== 1) {\n                this._state &= ~(GH_DRAG);\n            }\n            if (this._tracked.length !== 2) {\n                this._state &= ~(GH_TWODRAG | GH_PINCH);\n            }\n\n            // We need to figure out which of our different two touch gestures\n            // this might be\n            if (this._tracked.length === 2) {\n\n                // The other touch is the one where the id doesn't match\n                let prevTouch = this._tracked.find(t => t.id !== id);\n\n                // How far the previous touch point has moved since start\n                let prevDeltaMove = Math.hypot(prevTouch.firstX - prevTouch.lastX,\n                                               prevTouch.firstY - prevTouch.lastY);\n\n                // We know that the current touch moved far enough,\n                // but unless both touches moved further than their\n                // threshold we don't want to disqualify any gestures\n                if (prevDeltaMove > GH_MOVE_THRESHOLD) {\n\n                    // The angle difference between the direction of the touch points\n                    let deltaAngle = Math.abs(touch.angle - prevTouch.angle);\n                    deltaAngle = Math.abs(((deltaAngle + 180) % 360) - 180);\n\n                    // PINCH or TWODRAG can be eliminated depending on the angle\n                    if (deltaAngle > GH_ANGLE_THRESHOLD) {\n                        this._state &= ~GH_TWODRAG;\n                    } else {\n                        this._state &= ~GH_PINCH;\n                    }\n\n                    if (this._isTwoTouchTimeoutRunning()) {\n                        this._stopTwoTouchTimeout();\n                    }\n                } else if (!this._isTwoTouchTimeoutRunning()) {\n                    // We can't determine the gesture right now, let's\n                    // wait and see if more events are on their way\n                    this._startTwoTouchTimeout();\n                }\n            }\n\n            if (!this._hasDetectedGesture()) {\n                return;\n            }\n\n            this._pushEvent('gesturestart');\n        }\n\n        this._pushEvent('gesturemove');\n    }\n\n    _touchEnd(id, x, y) {\n        // Check if this is an ignored touch\n        if (this._ignored.indexOf(id) !== -1) {\n            // Remove this touch from ignored\n            this._ignored.splice(this._ignored.indexOf(id), 1);\n\n            // And reset the state if there are no more touches\n            if ((this._ignored.length === 0) &&\n                (this._tracked.length === 0)) {\n                this._state = GH_INITSTATE;\n                this._waitingRelease = false;\n            }\n            return;\n        }\n\n        // We got a touchend before the timer triggered,\n        // this cannot result in a gesture anymore.\n        if (!this._hasDetectedGesture() &&\n            this._isTwoTouchTimeoutRunning()) {\n            this._stopTwoTouchTimeout();\n            this._state = GH_NOGESTURE;\n        }\n\n        // Some gestures don't trigger until a touch is released\n        if (!this._hasDetectedGesture()) {\n            // Can't be a gesture that relies on movement\n            this._state &= ~(GH_DRAG | GH_TWODRAG | GH_PINCH);\n            // Or something that relies on more time\n            this._state &= ~GH_LONGPRESS;\n            this._stopLongpressTimeout();\n\n            if (!this._waitingRelease) {\n                this._releaseStart = Date.now();\n                this._waitingRelease = true;\n\n                // Can't be a tap that requires more touches than we current have\n                switch (this._tracked.length) {\n                    case 1:\n                        this._state &= ~(GH_TWOTAP | GH_THREETAP);\n                        break;\n\n                    case 2:\n                        this._state &= ~(GH_ONETAP | GH_THREETAP);\n                        break;\n                }\n            }\n        }\n\n        // Waiting for all touches to release? (i.e. some tap)\n        if (this._waitingRelease) {\n            // Were all touches released at roughly the same time?\n            if ((Date.now() - this._releaseStart) > GH_MULTITOUCH_TIMEOUT) {\n                this._state = GH_NOGESTURE;\n            }\n\n            // Did too long time pass between press and release?\n            if (this._tracked.some(t => (Date.now() - t.started) > GH_TAP_TIMEOUT)) {\n                this._state = GH_NOGESTURE;\n            }\n\n            let touch = this._tracked.find(t => t.id === id);\n            touch.active = false;\n\n            // Are we still waiting for more releases?\n            if (this._hasDetectedGesture()) {\n                this._pushEvent('gesturestart');\n            } else {\n                // Have we reached a dead end?\n                if (this._state !== GH_NOGESTURE) {\n                    return;\n                }\n            }\n        }\n\n        if (this._hasDetectedGesture()) {\n            this._pushEvent('gestureend');\n        }\n\n        // Ignore any remaining touches until they are ended\n        for (let i = 0; i < this._tracked.length; i++) {\n            if (this._tracked[i].active) {\n                this._ignored.push(this._tracked[i].id);\n            }\n        }\n        this._tracked = [];\n\n        this._state = GH_NOGESTURE;\n\n        // Remove this touch from ignored if it's in there\n        if (this._ignored.indexOf(id) !== -1) {\n            this._ignored.splice(this._ignored.indexOf(id), 1);\n        }\n\n        // We reset the state if ignored is empty\n        if ((this._ignored.length === 0)) {\n            this._state = GH_INITSTATE;\n            this._waitingRelease = false;\n        }\n    }\n\n    _hasDetectedGesture() {\n        if (this._state === GH_NOGESTURE) {\n            return false;\n        }\n        // Check to see if the bitmask value is a power of 2\n        // (i.e. only one bit set). If it is, we have a state.\n        if (this._state & (this._state - 1)) {\n            return false;\n        }\n\n        // For taps we also need to have all touches released\n        // before we've fully detected the gesture\n        if (this._state & (GH_ONETAP | GH_TWOTAP | GH_THREETAP)) {\n            if (this._tracked.some(t => t.active)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    _startLongpressTimeout() {\n        this._stopLongpressTimeout();\n        this._longpressTimeoutId = setTimeout(() => this._longpressTimeout(),\n                                              GH_LONGPRESS_TIMEOUT);\n    }\n\n    _stopLongpressTimeout() {\n        clearTimeout(this._longpressTimeoutId);\n        this._longpressTimeoutId = null;\n    }\n\n    _longpressTimeout() {\n        if (this._hasDetectedGesture()) {\n            throw new Error(\"A longpress gesture failed, conflict with a different gesture\");\n        }\n\n        this._state = GH_LONGPRESS;\n        this._pushEvent('gesturestart');\n    }\n\n    _startTwoTouchTimeout() {\n        this._stopTwoTouchTimeout();\n        this._twoTouchTimeoutId = setTimeout(() => this._twoTouchTimeout(),\n                                             GH_TWOTOUCH_TIMEOUT);\n    }\n\n    _stopTwoTouchTimeout() {\n        clearTimeout(this._twoTouchTimeoutId);\n        this._twoTouchTimeoutId = null;\n    }\n\n    _isTwoTouchTimeoutRunning() {\n        return this._twoTouchTimeoutId !== null;\n    }\n\n    _twoTouchTimeout() {\n        if (this._tracked.length === 0) {\n            throw new Error(\"A pinch or two drag gesture failed, no tracked touches\");\n        }\n\n        // How far each touch point has moved since start\n        let avgM = this._getAverageMovement();\n        let avgMoveH = Math.abs(avgM.x);\n        let avgMoveV = Math.abs(avgM.y);\n\n        // The difference in the distance between where\n        // the touch points started and where they are now\n        let avgD = this._getAverageDistance();\n        let deltaTouchDistance = Math.abs(Math.hypot(avgD.first.x, avgD.first.y) -\n                                          Math.hypot(avgD.last.x, avgD.last.y));\n\n        if ((avgMoveV < deltaTouchDistance) &&\n            (avgMoveH < deltaTouchDistance)) {\n            this._state = GH_PINCH;\n        } else {\n            this._state = GH_TWODRAG;\n        }\n\n        this._pushEvent('gesturestart');\n        this._pushEvent('gesturemove');\n    }\n\n    _pushEvent(type) {\n        let detail = { type: this._stateToGesture(this._state) };\n\n        // For most gesture events the current (average) position is the\n        // most useful\n        let avg = this._getPosition();\n        let pos = avg.last;\n\n        // However we have a slight distance to detect gestures, so for the\n        // first gesture event we want to use the first positions we saw\n        if (type === 'gesturestart') {\n            pos = avg.first;\n        }\n\n        // For these gestures, we always want the event coordinates\n        // to be where the gesture began, not the current touch location.\n        switch (this._state) {\n            case GH_TWODRAG:\n            case GH_PINCH:\n                pos = avg.first;\n                break;\n        }\n\n        detail['clientX'] = pos.x;\n        detail['clientY'] = pos.y;\n\n        // FIXME: other coordinates?\n\n        // Some gestures also have a magnitude\n        if (this._state === GH_PINCH) {\n            let distance = this._getAverageDistance();\n            if (type === 'gesturestart') {\n                detail['magnitudeX'] = distance.first.x;\n                detail['magnitudeY'] = distance.first.y;\n            } else {\n                detail['magnitudeX'] = distance.last.x;\n                detail['magnitudeY'] = distance.last.y;\n            }\n        } else if (this._state === GH_TWODRAG) {\n            if (type === 'gesturestart') {\n                detail['magnitudeX'] = 0.0;\n                detail['magnitudeY'] = 0.0;\n            } else {\n                let movement = this._getAverageMovement();\n                detail['magnitudeX'] = movement.x;\n                detail['magnitudeY'] = movement.y;\n            }\n        }\n\n        let gev = new CustomEvent(type, { detail: detail });\n        this._target.dispatchEvent(gev);\n    }\n\n    _stateToGesture(state) {\n        switch (state) {\n            case GH_ONETAP:\n                return 'onetap';\n            case GH_TWOTAP:\n                return 'twotap';\n            case GH_THREETAP:\n                return 'threetap';\n            case GH_DRAG:\n                return 'drag';\n            case GH_LONGPRESS:\n                return 'longpress';\n            case GH_TWODRAG:\n                return 'twodrag';\n            case GH_PINCH:\n                return 'pinch';\n        }\n\n        throw new Error(\"Unknown gesture state: \" + state);\n    }\n\n    _getPosition() {\n        if (this._tracked.length === 0) {\n            throw new Error(\"Failed to get gesture position, no tracked touches\");\n        }\n\n        let size = this._tracked.length;\n        let fx = 0, fy = 0, lx = 0, ly = 0;\n\n        for (let i = 0; i < this._tracked.length; i++) {\n            fx += this._tracked[i].firstX;\n            fy += this._tracked[i].firstY;\n            lx += this._tracked[i].lastX;\n            ly += this._tracked[i].lastY;\n        }\n\n        return { first: { x: fx / size,\n                          y: fy / size },\n                 last: { x: lx / size,\n                         y: ly / size } };\n    }\n\n    _getAverageMovement() {\n        if (this._tracked.length === 0) {\n            throw new Error(\"Failed to get gesture movement, no tracked touches\");\n        }\n\n        let totalH, totalV;\n        totalH = totalV = 0;\n        let size = this._tracked.length;\n\n        for (let i = 0; i < this._tracked.length; i++) {\n            totalH += this._tracked[i].lastX - this._tracked[i].firstX;\n            totalV += this._tracked[i].lastY - this._tracked[i].firstY;\n        }\n\n        return { x: totalH / size,\n                 y: totalV / size };\n    }\n\n    _getAverageDistance() {\n        if (this._tracked.length === 0) {\n            throw new Error(\"Failed to get gesture distance, no tracked touches\");\n        }\n\n        // Distance between the first and last tracked touches\n\n        let first = this._tracked[0];\n        let last = this._tracked[this._tracked.length - 1];\n\n        let fdx = Math.abs(last.firstX - first.firstX);\n        let fdy = Math.abs(last.firstY - first.firstY);\n\n        let ldx = Math.abs(last.lastX - first.lastX);\n        let ldy = Math.abs(last.lastY - first.lastY);\n\n        return { first: { x: fdx, y: fdy },\n                 last: { x: ldx, y: ldy } };\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/input/keyboard.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 or any later version (see LICENSE.txt)\n */\n\nimport * as Log from '../util/logging.js';\nimport { stopEvent } from '../util/events.js';\nimport * as KeyboardUtil from \"./util.js\";\nimport KeyTable from \"./keysym.js\";\nimport * as browser from \"../util/browser.js\";\n\n//\n// Keyboard event handler\n//\n\nexport default class Keyboard {\n    constructor(target) {\n        this._target = target || null;\n\n        this._keyDownList = {};         // List of depressed keys\n                                        // (even if they are happy)\n        this._altGrArmed = false;       // Windows AltGr detection\n\n        // keep these here so we can refer to them later\n        this._eventHandlers = {\n            'keyup': this._handleKeyUp.bind(this),\n            'keydown': this._handleKeyDown.bind(this),\n            'blur': this._allKeysUp.bind(this),\n        };\n\n        // ===== EVENT HANDLERS =====\n\n        this.onkeyevent = () => {}; // Handler for key press/release\n    }\n\n    // ===== PRIVATE METHODS =====\n\n    _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {\n        if (down) {\n            this._keyDownList[code] = keysym;\n        } else {\n            // Do we really think this key is down?\n            if (!(code in this._keyDownList)) {\n                return;\n            }\n            delete this._keyDownList[code];\n        }\n\n        Log.Debug(\"onkeyevent \" + (down ? \"down\" : \"up\") +\n                  \", keysym: \" + keysym, \", code: \" + code +\n                  \", numlock: \" + numlock + \", capslock: \" + capslock);\n        this.onkeyevent(keysym, code, down, numlock, capslock);\n    }\n\n    _getKeyCode(e) {\n        const code = KeyboardUtil.getKeycode(e);\n        if (code !== 'Unidentified') {\n            return code;\n        }\n\n        // Unstable, but we don't have anything else to go on\n        if (e.keyCode) {\n            // 229 is used for composition events\n            if (e.keyCode !== 229) {\n                return 'Platform' + e.keyCode;\n            }\n        }\n\n        // A precursor to the final DOM3 standard. Unfortunately it\n        // is not layout independent, so it is as bad as using keyCode\n        if (e.keyIdentifier) {\n            // Non-character key?\n            if (e.keyIdentifier.substr(0, 2) !== 'U+') {\n                return e.keyIdentifier;\n            }\n\n            const codepoint = parseInt(e.keyIdentifier.substr(2), 16);\n            const char = String.fromCharCode(codepoint).toUpperCase();\n\n            return 'Platform' + char.charCodeAt();\n        }\n\n        return 'Unidentified';\n    }\n\n    _handleKeyDown(e) {\n        const code = this._getKeyCode(e);\n        let keysym = KeyboardUtil.getKeysym(e);\n        let numlock = e.getModifierState('NumLock');\n        let capslock = e.getModifierState('CapsLock');\n\n        // getModifierState for NumLock is not supported on mac and ios and always returns false.\n        // Set to null to indicate unknown/unsupported instead.\n        if (browser.isMac() || browser.isIOS()) {\n            numlock = null;\n        }\n\n        // Windows doesn't have a proper AltGr, but handles it using\n        // fake Ctrl+Alt. However the remote end might not be Windows,\n        // so we need to merge those in to a single AltGr event. We\n        // detect this case by seeing the two key events directly after\n        // each other with a very short time between them (<50ms).\n        if (this._altGrArmed) {\n            this._altGrArmed = false;\n            clearTimeout(this._altGrTimeout);\n\n            if ((code === \"AltRight\") &&\n                ((e.timeStamp - this._altGrCtrlTime) < 50)) {\n                // FIXME: We fail to detect this if either Ctrl key is\n                //        first manually pressed as Windows then no\n                //        longer sends the fake Ctrl down event. It\n                //        does however happily send real Ctrl events\n                //        even when AltGr is already down. Some\n                //        browsers detect this for us though and set the\n                //        key to \"AltGraph\".\n                keysym = KeyTable.XK_ISO_Level3_Shift;\n            } else {\n                this._sendKeyEvent(KeyTable.XK_Control_L, \"ControlLeft\", true, numlock, capslock);\n            }\n        }\n\n        // We cannot handle keys we cannot track, but we also need\n        // to deal with virtual keyboards which omit key info\n        if (code === 'Unidentified') {\n            if (keysym) {\n                // If it's a virtual keyboard then it should be\n                // sufficient to just send press and release right\n                // after each other\n                this._sendKeyEvent(keysym, code, true, numlock, capslock);\n                this._sendKeyEvent(keysym, code, false, numlock, capslock);\n            }\n\n            stopEvent(e);\n            return;\n        }\n\n        // Alt behaves more like AltGraph on macOS, so shuffle the\n        // keys around a bit to make things more sane for the remote\n        // server. This method is used by RealVNC and TigerVNC (and\n        // possibly others).\n        if (browser.isMac() || browser.isIOS()) {\n            switch (keysym) {\n                case KeyTable.XK_Super_L:\n                    keysym = KeyTable.XK_Alt_L;\n                    break;\n                case KeyTable.XK_Super_R:\n                    keysym = KeyTable.XK_Super_L;\n                    break;\n                case KeyTable.XK_Alt_L:\n                    keysym = KeyTable.XK_Mode_switch;\n                    break;\n                case KeyTable.XK_Alt_R:\n                    keysym = KeyTable.XK_ISO_Level3_Shift;\n                    break;\n            }\n        }\n\n        // Is this key already pressed? If so, then we must use the\n        // same keysym or we'll confuse the server\n        if (code in this._keyDownList) {\n            keysym = this._keyDownList[code];\n        }\n\n        // macOS doesn't send proper key releases if a key is pressed\n        // while meta is held down\n        if ((browser.isMac() || browser.isIOS()) &&\n            (e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) {\n            this._sendKeyEvent(keysym, code, true, numlock, capslock);\n            this._sendKeyEvent(keysym, code, false, numlock, capslock);\n            stopEvent(e);\n            return;\n        }\n\n        // macOS doesn't send proper key events for modifiers, only\n        // state change events. That gets extra confusing for CapsLock\n        // which toggles on each press, but not on release. So pretend\n        // it was a quick press and release of the button.\n        if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {\n            this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock);\n            this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock);\n            stopEvent(e);\n            return;\n        }\n\n        // Windows doesn't send proper key releases for a bunch of\n        // Japanese IM keys so we have to fake the release right away\n        const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku,\n                            KeyTable.XK_Eisu_toggle,\n                            KeyTable.XK_Katakana,\n                            KeyTable.XK_Hiragana,\n                            KeyTable.XK_Romaji ];\n        if (browser.isWindows() && jpBadKeys.includes(keysym)) {\n            this._sendKeyEvent(keysym, code, true, numlock, capslock);\n            this._sendKeyEvent(keysym, code, false, numlock, capslock);\n            stopEvent(e);\n            return;\n        }\n\n        stopEvent(e);\n\n        // Possible start of AltGr sequence? (see above)\n        if ((code === \"ControlLeft\") && browser.isWindows() &&\n            !(\"ControlLeft\" in this._keyDownList)) {\n            this._altGrArmed = true;\n            this._altGrTimeout = setTimeout(this._interruptAltGrSequence.bind(this), 100);\n            this._altGrCtrlTime = e.timeStamp;\n            return;\n        }\n\n        this._sendKeyEvent(keysym, code, true, numlock, capslock);\n    }\n\n    _handleKeyUp(e) {\n        stopEvent(e);\n\n        const code = this._getKeyCode(e);\n\n        // We can't get a release in the middle of an AltGr sequence, so\n        // abort that detection\n        this._interruptAltGrSequence();\n\n        // See comment in _handleKeyDown()\n        if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {\n            this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);\n            this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);\n            return;\n        }\n\n        this._sendKeyEvent(this._keyDownList[code], code, false);\n\n        // Windows has a rather nasty bug where it won't send key\n        // release events for a Shift button if the other Shift is still\n        // pressed\n        if (browser.isWindows() && ((code === 'ShiftLeft') ||\n                                    (code === 'ShiftRight'))) {\n            if ('ShiftRight' in this._keyDownList) {\n                this._sendKeyEvent(this._keyDownList['ShiftRight'],\n                                   'ShiftRight', false);\n            }\n            if ('ShiftLeft' in this._keyDownList) {\n                this._sendKeyEvent(this._keyDownList['ShiftLeft'],\n                                   'ShiftLeft', false);\n            }\n        }\n    }\n\n    _interruptAltGrSequence() {\n        if (this._altGrArmed) {\n            this._altGrArmed = false;\n            clearTimeout(this._altGrTimeout);\n            this._sendKeyEvent(KeyTable.XK_Control_L, \"ControlLeft\", true);\n        }\n    }\n\n    _allKeysUp() {\n        Log.Debug(\">> Keyboard.allKeysUp\");\n\n        // Prevent control key being processed after losing focus.\n        this._interruptAltGrSequence();\n\n        for (let code in this._keyDownList) {\n            this._sendKeyEvent(this._keyDownList[code], code, false);\n        }\n        Log.Debug(\"<< Keyboard.allKeysUp\");\n    }\n\n    // ===== PUBLIC METHODS =====\n\n    grab() {\n        //Log.Debug(\">> Keyboard.grab\");\n\n        this._target.addEventListener('keydown', this._eventHandlers.keydown);\n        this._target.addEventListener('keyup', this._eventHandlers.keyup);\n\n        // Release (key up) if window loses focus\n        window.addEventListener('blur', this._eventHandlers.blur);\n\n        //Log.Debug(\"<< Keyboard.grab\");\n    }\n\n    ungrab() {\n        //Log.Debug(\">> Keyboard.ungrab\");\n\n        this._target.removeEventListener('keydown', this._eventHandlers.keydown);\n        this._target.removeEventListener('keyup', this._eventHandlers.keyup);\n        window.removeEventListener('blur', this._eventHandlers.blur);\n\n        // Release (key up) all keys that are in a down state\n        this._allKeysUp();\n\n        //Log.Debug(\">> Keyboard.ungrab\");\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/input/keysym.js",
    "content": "/* eslint-disable key-spacing */\n\nexport default {\n    XK_VoidSymbol:                  0xffffff, /* Void symbol */\n\n    XK_BackSpace:                   0xff08, /* Back space, back char */\n    XK_Tab:                         0xff09,\n    XK_Linefeed:                    0xff0a, /* Linefeed, LF */\n    XK_Clear:                       0xff0b,\n    XK_Return:                      0xff0d, /* Return, enter */\n    XK_Pause:                       0xff13, /* Pause, hold */\n    XK_Scroll_Lock:                 0xff14,\n    XK_Sys_Req:                     0xff15,\n    XK_Escape:                      0xff1b,\n    XK_Delete:                      0xffff, /* Delete, rubout */\n\n    /* International & multi-key character composition */\n\n    XK_Multi_key:                   0xff20, /* Multi-key character compose */\n    XK_Codeinput:                   0xff37,\n    XK_SingleCandidate:             0xff3c,\n    XK_MultipleCandidate:           0xff3d,\n    XK_PreviousCandidate:           0xff3e,\n\n    /* Japanese keyboard support */\n\n    XK_Kanji:                       0xff21, /* Kanji, Kanji convert */\n    XK_Muhenkan:                    0xff22, /* Cancel Conversion */\n    XK_Henkan_Mode:                 0xff23, /* Start/Stop Conversion */\n    XK_Henkan:                      0xff23, /* Alias for Henkan_Mode */\n    XK_Romaji:                      0xff24, /* to Romaji */\n    XK_Hiragana:                    0xff25, /* to Hiragana */\n    XK_Katakana:                    0xff26, /* to Katakana */\n    XK_Hiragana_Katakana:           0xff27, /* Hiragana/Katakana toggle */\n    XK_Zenkaku:                     0xff28, /* to Zenkaku */\n    XK_Hankaku:                     0xff29, /* to Hankaku */\n    XK_Zenkaku_Hankaku:             0xff2a, /* Zenkaku/Hankaku toggle */\n    XK_Touroku:                     0xff2b, /* Add to Dictionary */\n    XK_Massyo:                      0xff2c, /* Delete from Dictionary */\n    XK_Kana_Lock:                   0xff2d, /* Kana Lock */\n    XK_Kana_Shift:                  0xff2e, /* Kana Shift */\n    XK_Eisu_Shift:                  0xff2f, /* Alphanumeric Shift */\n    XK_Eisu_toggle:                 0xff30, /* Alphanumeric toggle */\n    XK_Kanji_Bangou:                0xff37, /* Codeinput */\n    XK_Zen_Koho:                    0xff3d, /* Multiple/All Candidate(s) */\n    XK_Mae_Koho:                    0xff3e, /* Previous Candidate */\n\n    /* Cursor control & motion */\n\n    XK_Home:                        0xff50,\n    XK_Left:                        0xff51, /* Move left, left arrow */\n    XK_Up:                          0xff52, /* Move up, up arrow */\n    XK_Right:                       0xff53, /* Move right, right arrow */\n    XK_Down:                        0xff54, /* Move down, down arrow */\n    XK_Prior:                       0xff55, /* Prior, previous */\n    XK_Page_Up:                     0xff55,\n    XK_Next:                        0xff56, /* Next */\n    XK_Page_Down:                   0xff56,\n    XK_End:                         0xff57, /* EOL */\n    XK_Begin:                       0xff58, /* BOL */\n\n\n    /* Misc functions */\n\n    XK_Select:                      0xff60, /* Select, mark */\n    XK_Print:                       0xff61,\n    XK_Execute:                     0xff62, /* Execute, run, do */\n    XK_Insert:                      0xff63, /* Insert, insert here */\n    XK_Undo:                        0xff65,\n    XK_Redo:                        0xff66, /* Redo, again */\n    XK_Menu:                        0xff67,\n    XK_Find:                        0xff68, /* Find, search */\n    XK_Cancel:                      0xff69, /* Cancel, stop, abort, exit */\n    XK_Help:                        0xff6a, /* Help */\n    XK_Break:                       0xff6b,\n    XK_Mode_switch:                 0xff7e, /* Character set switch */\n    XK_script_switch:               0xff7e, /* Alias for mode_switch */\n    XK_Num_Lock:                    0xff7f,\n\n    /* Keypad functions, keypad numbers cleverly chosen to map to ASCII */\n\n    XK_KP_Space:                    0xff80, /* Space */\n    XK_KP_Tab:                      0xff89,\n    XK_KP_Enter:                    0xff8d, /* Enter */\n    XK_KP_F1:                       0xff91, /* PF1, KP_A, ... */\n    XK_KP_F2:                       0xff92,\n    XK_KP_F3:                       0xff93,\n    XK_KP_F4:                       0xff94,\n    XK_KP_Home:                     0xff95,\n    XK_KP_Left:                     0xff96,\n    XK_KP_Up:                       0xff97,\n    XK_KP_Right:                    0xff98,\n    XK_KP_Down:                     0xff99,\n    XK_KP_Prior:                    0xff9a,\n    XK_KP_Page_Up:                  0xff9a,\n    XK_KP_Next:                     0xff9b,\n    XK_KP_Page_Down:                0xff9b,\n    XK_KP_End:                      0xff9c,\n    XK_KP_Begin:                    0xff9d,\n    XK_KP_Insert:                   0xff9e,\n    XK_KP_Delete:                   0xff9f,\n    XK_KP_Equal:                    0xffbd, /* Equals */\n    XK_KP_Multiply:                 0xffaa,\n    XK_KP_Add:                      0xffab,\n    XK_KP_Separator:                0xffac, /* Separator, often comma */\n    XK_KP_Subtract:                 0xffad,\n    XK_KP_Decimal:                  0xffae,\n    XK_KP_Divide:                   0xffaf,\n\n    XK_KP_0:                        0xffb0,\n    XK_KP_1:                        0xffb1,\n    XK_KP_2:                        0xffb2,\n    XK_KP_3:                        0xffb3,\n    XK_KP_4:                        0xffb4,\n    XK_KP_5:                        0xffb5,\n    XK_KP_6:                        0xffb6,\n    XK_KP_7:                        0xffb7,\n    XK_KP_8:                        0xffb8,\n    XK_KP_9:                        0xffb9,\n\n    /*\n     * Auxiliary functions; note the duplicate definitions for left and right\n     * function keys;  Sun keyboards and a few other manufacturers have such\n     * function key groups on the left and/or right sides of the keyboard.\n     * We've not found a keyboard with more than 35 function keys total.\n     */\n\n    XK_F1:                          0xffbe,\n    XK_F2:                          0xffbf,\n    XK_F3:                          0xffc0,\n    XK_F4:                          0xffc1,\n    XK_F5:                          0xffc2,\n    XK_F6:                          0xffc3,\n    XK_F7:                          0xffc4,\n    XK_F8:                          0xffc5,\n    XK_F9:                          0xffc6,\n    XK_F10:                         0xffc7,\n    XK_F11:                         0xffc8,\n    XK_L1:                          0xffc8,\n    XK_F12:                         0xffc9,\n    XK_L2:                          0xffc9,\n    XK_F13:                         0xffca,\n    XK_L3:                          0xffca,\n    XK_F14:                         0xffcb,\n    XK_L4:                          0xffcb,\n    XK_F15:                         0xffcc,\n    XK_L5:                          0xffcc,\n    XK_F16:                         0xffcd,\n    XK_L6:                          0xffcd,\n    XK_F17:                         0xffce,\n    XK_L7:                          0xffce,\n    XK_F18:                         0xffcf,\n    XK_L8:                          0xffcf,\n    XK_F19:                         0xffd0,\n    XK_L9:                          0xffd0,\n    XK_F20:                         0xffd1,\n    XK_L10:                         0xffd1,\n    XK_F21:                         0xffd2,\n    XK_R1:                          0xffd2,\n    XK_F22:                         0xffd3,\n    XK_R2:                          0xffd3,\n    XK_F23:                         0xffd4,\n    XK_R3:                          0xffd4,\n    XK_F24:                         0xffd5,\n    XK_R4:                          0xffd5,\n    XK_F25:                         0xffd6,\n    XK_R5:                          0xffd6,\n    XK_F26:                         0xffd7,\n    XK_R6:                          0xffd7,\n    XK_F27:                         0xffd8,\n    XK_R7:                          0xffd8,\n    XK_F28:                         0xffd9,\n    XK_R8:                          0xffd9,\n    XK_F29:                         0xffda,\n    XK_R9:                          0xffda,\n    XK_F30:                         0xffdb,\n    XK_R10:                         0xffdb,\n    XK_F31:                         0xffdc,\n    XK_R11:                         0xffdc,\n    XK_F32:                         0xffdd,\n    XK_R12:                         0xffdd,\n    XK_F33:                         0xffde,\n    XK_R13:                         0xffde,\n    XK_F34:                         0xffdf,\n    XK_R14:                         0xffdf,\n    XK_F35:                         0xffe0,\n    XK_R15:                         0xffe0,\n\n    /* Modifiers */\n\n    XK_Shift_L:                     0xffe1, /* Left shift */\n    XK_Shift_R:                     0xffe2, /* Right shift */\n    XK_Control_L:                   0xffe3, /* Left control */\n    XK_Control_R:                   0xffe4, /* Right control */\n    XK_Caps_Lock:                   0xffe5, /* Caps lock */\n    XK_Shift_Lock:                  0xffe6, /* Shift lock */\n\n    XK_Meta_L:                      0xffe7, /* Left meta */\n    XK_Meta_R:                      0xffe8, /* Right meta */\n    XK_Alt_L:                       0xffe9, /* Left alt */\n    XK_Alt_R:                       0xffea, /* Right alt */\n    XK_Super_L:                     0xffeb, /* Left super */\n    XK_Super_R:                     0xffec, /* Right super */\n    XK_Hyper_L:                     0xffed, /* Left hyper */\n    XK_Hyper_R:                     0xffee, /* Right hyper */\n\n    /*\n     * Keyboard (XKB) Extension function and modifier keys\n     * (from Appendix C of \"The X Keyboard Extension: Protocol Specification\")\n     * Byte 3 = 0xfe\n     */\n\n    XK_ISO_Level3_Shift:            0xfe03, /* AltGr */\n    XK_ISO_Next_Group:              0xfe08,\n    XK_ISO_Prev_Group:              0xfe0a,\n    XK_ISO_First_Group:             0xfe0c,\n    XK_ISO_Last_Group:              0xfe0e,\n\n    /*\n     * Latin 1\n     * (ISO/IEC 8859-1: Unicode U+0020..U+00FF)\n     * Byte 3: 0\n     */\n\n    XK_space:                       0x0020, /* U+0020 SPACE */\n    XK_exclam:                      0x0021, /* U+0021 EXCLAMATION MARK */\n    XK_quotedbl:                    0x0022, /* U+0022 QUOTATION MARK */\n    XK_numbersign:                  0x0023, /* U+0023 NUMBER SIGN */\n    XK_dollar:                      0x0024, /* U+0024 DOLLAR SIGN */\n    XK_percent:                     0x0025, /* U+0025 PERCENT SIGN */\n    XK_ampersand:                   0x0026, /* U+0026 AMPERSAND */\n    XK_apostrophe:                  0x0027, /* U+0027 APOSTROPHE */\n    XK_quoteright:                  0x0027, /* deprecated */\n    XK_parenleft:                   0x0028, /* U+0028 LEFT PARENTHESIS */\n    XK_parenright:                  0x0029, /* U+0029 RIGHT PARENTHESIS */\n    XK_asterisk:                    0x002a, /* U+002A ASTERISK */\n    XK_plus:                        0x002b, /* U+002B PLUS SIGN */\n    XK_comma:                       0x002c, /* U+002C COMMA */\n    XK_minus:                       0x002d, /* U+002D HYPHEN-MINUS */\n    XK_period:                      0x002e, /* U+002E FULL STOP */\n    XK_slash:                       0x002f, /* U+002F SOLIDUS */\n    XK_0:                           0x0030, /* U+0030 DIGIT ZERO */\n    XK_1:                           0x0031, /* U+0031 DIGIT ONE */\n    XK_2:                           0x0032, /* U+0032 DIGIT TWO */\n    XK_3:                           0x0033, /* U+0033 DIGIT THREE */\n    XK_4:                           0x0034, /* U+0034 DIGIT FOUR */\n    XK_5:                           0x0035, /* U+0035 DIGIT FIVE */\n    XK_6:                           0x0036, /* U+0036 DIGIT SIX */\n    XK_7:                           0x0037, /* U+0037 DIGIT SEVEN */\n    XK_8:                           0x0038, /* U+0038 DIGIT EIGHT */\n    XK_9:                           0x0039, /* U+0039 DIGIT NINE */\n    XK_colon:                       0x003a, /* U+003A COLON */\n    XK_semicolon:                   0x003b, /* U+003B SEMICOLON */\n    XK_less:                        0x003c, /* U+003C LESS-THAN SIGN */\n    XK_equal:                       0x003d, /* U+003D EQUALS SIGN */\n    XK_greater:                     0x003e, /* U+003E GREATER-THAN SIGN */\n    XK_question:                    0x003f, /* U+003F QUESTION MARK */\n    XK_at:                          0x0040, /* U+0040 COMMERCIAL AT */\n    XK_A:                           0x0041, /* U+0041 LATIN CAPITAL LETTER A */\n    XK_B:                           0x0042, /* U+0042 LATIN CAPITAL LETTER B */\n    XK_C:                           0x0043, /* U+0043 LATIN CAPITAL LETTER C */\n    XK_D:                           0x0044, /* U+0044 LATIN CAPITAL LETTER D */\n    XK_E:                           0x0045, /* U+0045 LATIN CAPITAL LETTER E */\n    XK_F:                           0x0046, /* U+0046 LATIN CAPITAL LETTER F */\n    XK_G:                           0x0047, /* U+0047 LATIN CAPITAL LETTER G */\n    XK_H:                           0x0048, /* U+0048 LATIN CAPITAL LETTER H */\n    XK_I:                           0x0049, /* U+0049 LATIN CAPITAL LETTER I */\n    XK_J:                           0x004a, /* U+004A LATIN CAPITAL LETTER J */\n    XK_K:                           0x004b, /* U+004B LATIN CAPITAL LETTER K */\n    XK_L:                           0x004c, /* U+004C LATIN CAPITAL LETTER L */\n    XK_M:                           0x004d, /* U+004D LATIN CAPITAL LETTER M */\n    XK_N:                           0x004e, /* U+004E LATIN CAPITAL LETTER N */\n    XK_O:                           0x004f, /* U+004F LATIN CAPITAL LETTER O */\n    XK_P:                           0x0050, /* U+0050 LATIN CAPITAL LETTER P */\n    XK_Q:                           0x0051, /* U+0051 LATIN CAPITAL LETTER Q */\n    XK_R:                           0x0052, /* U+0052 LATIN CAPITAL LETTER R */\n    XK_S:                           0x0053, /* U+0053 LATIN CAPITAL LETTER S */\n    XK_T:                           0x0054, /* U+0054 LATIN CAPITAL LETTER T */\n    XK_U:                           0x0055, /* U+0055 LATIN CAPITAL LETTER U */\n    XK_V:                           0x0056, /* U+0056 LATIN CAPITAL LETTER V */\n    XK_W:                           0x0057, /* U+0057 LATIN CAPITAL LETTER W */\n    XK_X:                           0x0058, /* U+0058 LATIN CAPITAL LETTER X */\n    XK_Y:                           0x0059, /* U+0059 LATIN CAPITAL LETTER Y */\n    XK_Z:                           0x005a, /* U+005A LATIN CAPITAL LETTER Z */\n    XK_bracketleft:                 0x005b, /* U+005B LEFT SQUARE BRACKET */\n    XK_backslash:                   0x005c, /* U+005C REVERSE SOLIDUS */\n    XK_bracketright:                0x005d, /* U+005D RIGHT SQUARE BRACKET */\n    XK_asciicircum:                 0x005e, /* U+005E CIRCUMFLEX ACCENT */\n    XK_underscore:                  0x005f, /* U+005F LOW LINE */\n    XK_grave:                       0x0060, /* U+0060 GRAVE ACCENT */\n    XK_quoteleft:                   0x0060, /* deprecated */\n    XK_a:                           0x0061, /* U+0061 LATIN SMALL LETTER A */\n    XK_b:                           0x0062, /* U+0062 LATIN SMALL LETTER B */\n    XK_c:                           0x0063, /* U+0063 LATIN SMALL LETTER C */\n    XK_d:                           0x0064, /* U+0064 LATIN SMALL LETTER D */\n    XK_e:                           0x0065, /* U+0065 LATIN SMALL LETTER E */\n    XK_f:                           0x0066, /* U+0066 LATIN SMALL LETTER F */\n    XK_g:                           0x0067, /* U+0067 LATIN SMALL LETTER G */\n    XK_h:                           0x0068, /* U+0068 LATIN SMALL LETTER H */\n    XK_i:                           0x0069, /* U+0069 LATIN SMALL LETTER I */\n    XK_j:                           0x006a, /* U+006A LATIN SMALL LETTER J */\n    XK_k:                           0x006b, /* U+006B LATIN SMALL LETTER K */\n    XK_l:                           0x006c, /* U+006C LATIN SMALL LETTER L */\n    XK_m:                           0x006d, /* U+006D LATIN SMALL LETTER M */\n    XK_n:                           0x006e, /* U+006E LATIN SMALL LETTER N */\n    XK_o:                           0x006f, /* U+006F LATIN SMALL LETTER O */\n    XK_p:                           0x0070, /* U+0070 LATIN SMALL LETTER P */\n    XK_q:                           0x0071, /* U+0071 LATIN SMALL LETTER Q */\n    XK_r:                           0x0072, /* U+0072 LATIN SMALL LETTER R */\n    XK_s:                           0x0073, /* U+0073 LATIN SMALL LETTER S */\n    XK_t:                           0x0074, /* U+0074 LATIN SMALL LETTER T */\n    XK_u:                           0x0075, /* U+0075 LATIN SMALL LETTER U */\n    XK_v:                           0x0076, /* U+0076 LATIN SMALL LETTER V */\n    XK_w:                           0x0077, /* U+0077 LATIN SMALL LETTER W */\n    XK_x:                           0x0078, /* U+0078 LATIN SMALL LETTER X */\n    XK_y:                           0x0079, /* U+0079 LATIN SMALL LETTER Y */\n    XK_z:                           0x007a, /* U+007A LATIN SMALL LETTER Z */\n    XK_braceleft:                   0x007b, /* U+007B LEFT CURLY BRACKET */\n    XK_bar:                         0x007c, /* U+007C VERTICAL LINE */\n    XK_braceright:                  0x007d, /* U+007D RIGHT CURLY BRACKET */\n    XK_asciitilde:                  0x007e, /* U+007E TILDE */\n\n    XK_nobreakspace:                0x00a0, /* U+00A0 NO-BREAK SPACE */\n    XK_exclamdown:                  0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */\n    XK_cent:                        0x00a2, /* U+00A2 CENT SIGN */\n    XK_sterling:                    0x00a3, /* U+00A3 POUND SIGN */\n    XK_currency:                    0x00a4, /* U+00A4 CURRENCY SIGN */\n    XK_yen:                         0x00a5, /* U+00A5 YEN SIGN */\n    XK_brokenbar:                   0x00a6, /* U+00A6 BROKEN BAR */\n    XK_section:                     0x00a7, /* U+00A7 SECTION SIGN */\n    XK_diaeresis:                   0x00a8, /* U+00A8 DIAERESIS */\n    XK_copyright:                   0x00a9, /* U+00A9 COPYRIGHT SIGN */\n    XK_ordfeminine:                 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */\n    XK_guillemotleft:               0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */\n    XK_notsign:                     0x00ac, /* U+00AC NOT SIGN */\n    XK_hyphen:                      0x00ad, /* U+00AD SOFT HYPHEN */\n    XK_registered:                  0x00ae, /* U+00AE REGISTERED SIGN */\n    XK_macron:                      0x00af, /* U+00AF MACRON */\n    XK_degree:                      0x00b0, /* U+00B0 DEGREE SIGN */\n    XK_plusminus:                   0x00b1, /* U+00B1 PLUS-MINUS SIGN */\n    XK_twosuperior:                 0x00b2, /* U+00B2 SUPERSCRIPT TWO */\n    XK_threesuperior:               0x00b3, /* U+00B3 SUPERSCRIPT THREE */\n    XK_acute:                       0x00b4, /* U+00B4 ACUTE ACCENT */\n    XK_mu:                          0x00b5, /* U+00B5 MICRO SIGN */\n    XK_paragraph:                   0x00b6, /* U+00B6 PILCROW SIGN */\n    XK_periodcentered:              0x00b7, /* U+00B7 MIDDLE DOT */\n    XK_cedilla:                     0x00b8, /* U+00B8 CEDILLA */\n    XK_onesuperior:                 0x00b9, /* U+00B9 SUPERSCRIPT ONE */\n    XK_masculine:                   0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */\n    XK_guillemotright:              0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */\n    XK_onequarter:                  0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */\n    XK_onehalf:                     0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */\n    XK_threequarters:               0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */\n    XK_questiondown:                0x00bf, /* U+00BF INVERTED QUESTION MARK */\n    XK_Agrave:                      0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */\n    XK_Aacute:                      0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */\n    XK_Acircumflex:                 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */\n    XK_Atilde:                      0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */\n    XK_Adiaeresis:                  0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */\n    XK_Aring:                       0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */\n    XK_AE:                          0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */\n    XK_Ccedilla:                    0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */\n    XK_Egrave:                      0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */\n    XK_Eacute:                      0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */\n    XK_Ecircumflex:                 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */\n    XK_Ediaeresis:                  0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */\n    XK_Igrave:                      0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */\n    XK_Iacute:                      0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */\n    XK_Icircumflex:                 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */\n    XK_Idiaeresis:                  0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */\n    XK_ETH:                         0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */\n    XK_Eth:                         0x00d0, /* deprecated */\n    XK_Ntilde:                      0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */\n    XK_Ograve:                      0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */\n    XK_Oacute:                      0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */\n    XK_Ocircumflex:                 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */\n    XK_Otilde:                      0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */\n    XK_Odiaeresis:                  0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */\n    XK_multiply:                    0x00d7, /* U+00D7 MULTIPLICATION SIGN */\n    XK_Oslash:                      0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */\n    XK_Ooblique:                    0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */\n    XK_Ugrave:                      0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */\n    XK_Uacute:                      0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */\n    XK_Ucircumflex:                 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */\n    XK_Udiaeresis:                  0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */\n    XK_Yacute:                      0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */\n    XK_THORN:                       0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */\n    XK_Thorn:                       0x00de, /* deprecated */\n    XK_ssharp:                      0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */\n    XK_agrave:                      0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */\n    XK_aacute:                      0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */\n    XK_acircumflex:                 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */\n    XK_atilde:                      0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */\n    XK_adiaeresis:                  0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */\n    XK_aring:                       0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */\n    XK_ae:                          0x00e6, /* U+00E6 LATIN SMALL LETTER AE */\n    XK_ccedilla:                    0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */\n    XK_egrave:                      0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */\n    XK_eacute:                      0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */\n    XK_ecircumflex:                 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */\n    XK_ediaeresis:                  0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */\n    XK_igrave:                      0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */\n    XK_iacute:                      0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */\n    XK_icircumflex:                 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */\n    XK_idiaeresis:                  0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */\n    XK_eth:                         0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */\n    XK_ntilde:                      0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */\n    XK_ograve:                      0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */\n    XK_oacute:                      0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */\n    XK_ocircumflex:                 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */\n    XK_otilde:                      0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */\n    XK_odiaeresis:                  0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */\n    XK_division:                    0x00f7, /* U+00F7 DIVISION SIGN */\n    XK_oslash:                      0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */\n    XK_ooblique:                    0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */\n    XK_ugrave:                      0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */\n    XK_uacute:                      0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */\n    XK_ucircumflex:                 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */\n    XK_udiaeresis:                  0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */\n    XK_yacute:                      0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */\n    XK_thorn:                       0x00fe, /* U+00FE LATIN SMALL LETTER THORN */\n    XK_ydiaeresis:                  0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */\n\n    /*\n     * Korean\n     * Byte 3 = 0x0e\n     */\n\n    XK_Hangul:                      0xff31, /* Hangul start/stop(toggle) */\n    XK_Hangul_Hanja:                0xff34, /* Start Hangul->Hanja Conversion */\n    XK_Hangul_Jeonja:               0xff38, /* Jeonja mode */\n\n    /*\n     * XFree86 vendor specific keysyms.\n     *\n     * The XFree86 keysym range is 0x10080001 - 0x1008FFFF.\n     */\n\n    XF86XK_ModeLock:                0x1008FF01,\n    XF86XK_MonBrightnessUp:         0x1008FF02,\n    XF86XK_MonBrightnessDown:       0x1008FF03,\n    XF86XK_KbdLightOnOff:           0x1008FF04,\n    XF86XK_KbdBrightnessUp:         0x1008FF05,\n    XF86XK_KbdBrightnessDown:       0x1008FF06,\n    XF86XK_Standby:                 0x1008FF10,\n    XF86XK_AudioLowerVolume:        0x1008FF11,\n    XF86XK_AudioMute:               0x1008FF12,\n    XF86XK_AudioRaiseVolume:        0x1008FF13,\n    XF86XK_AudioPlay:               0x1008FF14,\n    XF86XK_AudioStop:               0x1008FF15,\n    XF86XK_AudioPrev:               0x1008FF16,\n    XF86XK_AudioNext:               0x1008FF17,\n    XF86XK_HomePage:                0x1008FF18,\n    XF86XK_Mail:                    0x1008FF19,\n    XF86XK_Start:                   0x1008FF1A,\n    XF86XK_Search:                  0x1008FF1B,\n    XF86XK_AudioRecord:             0x1008FF1C,\n    XF86XK_Calculator:              0x1008FF1D,\n    XF86XK_Memo:                    0x1008FF1E,\n    XF86XK_ToDoList:                0x1008FF1F,\n    XF86XK_Calendar:                0x1008FF20,\n    XF86XK_PowerDown:               0x1008FF21,\n    XF86XK_ContrastAdjust:          0x1008FF22,\n    XF86XK_RockerUp:                0x1008FF23,\n    XF86XK_RockerDown:              0x1008FF24,\n    XF86XK_RockerEnter:             0x1008FF25,\n    XF86XK_Back:                    0x1008FF26,\n    XF86XK_Forward:                 0x1008FF27,\n    XF86XK_Stop:                    0x1008FF28,\n    XF86XK_Refresh:                 0x1008FF29,\n    XF86XK_PowerOff:                0x1008FF2A,\n    XF86XK_WakeUp:                  0x1008FF2B,\n    XF86XK_Eject:                   0x1008FF2C,\n    XF86XK_ScreenSaver:             0x1008FF2D,\n    XF86XK_WWW:                     0x1008FF2E,\n    XF86XK_Sleep:                   0x1008FF2F,\n    XF86XK_Favorites:               0x1008FF30,\n    XF86XK_AudioPause:              0x1008FF31,\n    XF86XK_AudioMedia:              0x1008FF32,\n    XF86XK_MyComputer:              0x1008FF33,\n    XF86XK_VendorHome:              0x1008FF34,\n    XF86XK_LightBulb:               0x1008FF35,\n    XF86XK_Shop:                    0x1008FF36,\n    XF86XK_History:                 0x1008FF37,\n    XF86XK_OpenURL:                 0x1008FF38,\n    XF86XK_AddFavorite:             0x1008FF39,\n    XF86XK_HotLinks:                0x1008FF3A,\n    XF86XK_BrightnessAdjust:        0x1008FF3B,\n    XF86XK_Finance:                 0x1008FF3C,\n    XF86XK_Community:               0x1008FF3D,\n    XF86XK_AudioRewind:             0x1008FF3E,\n    XF86XK_BackForward:             0x1008FF3F,\n    XF86XK_Launch0:                 0x1008FF40,\n    XF86XK_Launch1:                 0x1008FF41,\n    XF86XK_Launch2:                 0x1008FF42,\n    XF86XK_Launch3:                 0x1008FF43,\n    XF86XK_Launch4:                 0x1008FF44,\n    XF86XK_Launch5:                 0x1008FF45,\n    XF86XK_Launch6:                 0x1008FF46,\n    XF86XK_Launch7:                 0x1008FF47,\n    XF86XK_Launch8:                 0x1008FF48,\n    XF86XK_Launch9:                 0x1008FF49,\n    XF86XK_LaunchA:                 0x1008FF4A,\n    XF86XK_LaunchB:                 0x1008FF4B,\n    XF86XK_LaunchC:                 0x1008FF4C,\n    XF86XK_LaunchD:                 0x1008FF4D,\n    XF86XK_LaunchE:                 0x1008FF4E,\n    XF86XK_LaunchF:                 0x1008FF4F,\n    XF86XK_ApplicationLeft:         0x1008FF50,\n    XF86XK_ApplicationRight:        0x1008FF51,\n    XF86XK_Book:                    0x1008FF52,\n    XF86XK_CD:                      0x1008FF53,\n    XF86XK_Calculater:              0x1008FF54,\n    XF86XK_Clear:                   0x1008FF55,\n    XF86XK_Close:                   0x1008FF56,\n    XF86XK_Copy:                    0x1008FF57,\n    XF86XK_Cut:                     0x1008FF58,\n    XF86XK_Display:                 0x1008FF59,\n    XF86XK_DOS:                     0x1008FF5A,\n    XF86XK_Documents:               0x1008FF5B,\n    XF86XK_Excel:                   0x1008FF5C,\n    XF86XK_Explorer:                0x1008FF5D,\n    XF86XK_Game:                    0x1008FF5E,\n    XF86XK_Go:                      0x1008FF5F,\n    XF86XK_iTouch:                  0x1008FF60,\n    XF86XK_LogOff:                  0x1008FF61,\n    XF86XK_Market:                  0x1008FF62,\n    XF86XK_Meeting:                 0x1008FF63,\n    XF86XK_MenuKB:                  0x1008FF65,\n    XF86XK_MenuPB:                  0x1008FF66,\n    XF86XK_MySites:                 0x1008FF67,\n    XF86XK_New:                     0x1008FF68,\n    XF86XK_News:                    0x1008FF69,\n    XF86XK_OfficeHome:              0x1008FF6A,\n    XF86XK_Open:                    0x1008FF6B,\n    XF86XK_Option:                  0x1008FF6C,\n    XF86XK_Paste:                   0x1008FF6D,\n    XF86XK_Phone:                   0x1008FF6E,\n    XF86XK_Q:                       0x1008FF70,\n    XF86XK_Reply:                   0x1008FF72,\n    XF86XK_Reload:                  0x1008FF73,\n    XF86XK_RotateWindows:           0x1008FF74,\n    XF86XK_RotationPB:              0x1008FF75,\n    XF86XK_RotationKB:              0x1008FF76,\n    XF86XK_Save:                    0x1008FF77,\n    XF86XK_ScrollUp:                0x1008FF78,\n    XF86XK_ScrollDown:              0x1008FF79,\n    XF86XK_ScrollClick:             0x1008FF7A,\n    XF86XK_Send:                    0x1008FF7B,\n    XF86XK_Spell:                   0x1008FF7C,\n    XF86XK_SplitScreen:             0x1008FF7D,\n    XF86XK_Support:                 0x1008FF7E,\n    XF86XK_TaskPane:                0x1008FF7F,\n    XF86XK_Terminal:                0x1008FF80,\n    XF86XK_Tools:                   0x1008FF81,\n    XF86XK_Travel:                  0x1008FF82,\n    XF86XK_UserPB:                  0x1008FF84,\n    XF86XK_User1KB:                 0x1008FF85,\n    XF86XK_User2KB:                 0x1008FF86,\n    XF86XK_Video:                   0x1008FF87,\n    XF86XK_WheelButton:             0x1008FF88,\n    XF86XK_Word:                    0x1008FF89,\n    XF86XK_Xfer:                    0x1008FF8A,\n    XF86XK_ZoomIn:                  0x1008FF8B,\n    XF86XK_ZoomOut:                 0x1008FF8C,\n    XF86XK_Away:                    0x1008FF8D,\n    XF86XK_Messenger:               0x1008FF8E,\n    XF86XK_WebCam:                  0x1008FF8F,\n    XF86XK_MailForward:             0x1008FF90,\n    XF86XK_Pictures:                0x1008FF91,\n    XF86XK_Music:                   0x1008FF92,\n    XF86XK_Battery:                 0x1008FF93,\n    XF86XK_Bluetooth:               0x1008FF94,\n    XF86XK_WLAN:                    0x1008FF95,\n    XF86XK_UWB:                     0x1008FF96,\n    XF86XK_AudioForward:            0x1008FF97,\n    XF86XK_AudioRepeat:             0x1008FF98,\n    XF86XK_AudioRandomPlay:         0x1008FF99,\n    XF86XK_Subtitle:                0x1008FF9A,\n    XF86XK_AudioCycleTrack:         0x1008FF9B,\n    XF86XK_CycleAngle:              0x1008FF9C,\n    XF86XK_FrameBack:               0x1008FF9D,\n    XF86XK_FrameForward:            0x1008FF9E,\n    XF86XK_Time:                    0x1008FF9F,\n    XF86XK_Select:                  0x1008FFA0,\n    XF86XK_View:                    0x1008FFA1,\n    XF86XK_TopMenu:                 0x1008FFA2,\n    XF86XK_Red:                     0x1008FFA3,\n    XF86XK_Green:                   0x1008FFA4,\n    XF86XK_Yellow:                  0x1008FFA5,\n    XF86XK_Blue:                    0x1008FFA6,\n    XF86XK_Suspend:                 0x1008FFA7,\n    XF86XK_Hibernate:               0x1008FFA8,\n    XF86XK_TouchpadToggle:          0x1008FFA9,\n    XF86XK_TouchpadOn:              0x1008FFB0,\n    XF86XK_TouchpadOff:             0x1008FFB1,\n    XF86XK_AudioMicMute:            0x1008FFB2,\n    XF86XK_Switch_VT_1:             0x1008FE01,\n    XF86XK_Switch_VT_2:             0x1008FE02,\n    XF86XK_Switch_VT_3:             0x1008FE03,\n    XF86XK_Switch_VT_4:             0x1008FE04,\n    XF86XK_Switch_VT_5:             0x1008FE05,\n    XF86XK_Switch_VT_6:             0x1008FE06,\n    XF86XK_Switch_VT_7:             0x1008FE07,\n    XF86XK_Switch_VT_8:             0x1008FE08,\n    XF86XK_Switch_VT_9:             0x1008FE09,\n    XF86XK_Switch_VT_10:            0x1008FE0A,\n    XF86XK_Switch_VT_11:            0x1008FE0B,\n    XF86XK_Switch_VT_12:            0x1008FE0C,\n    XF86XK_Ungrab:                  0x1008FE20,\n    XF86XK_ClearGrab:               0x1008FE21,\n    XF86XK_Next_VMode:              0x1008FE22,\n    XF86XK_Prev_VMode:              0x1008FE23,\n    XF86XK_LogWindowTree:           0x1008FE24,\n    XF86XK_LogGrabInfo:             0x1008FE25,\n};\n"
  },
  {
    "path": "services/gateway/noVNC/core/input/keysymdef.js",
    "content": "/*\n * Mapping from Unicode codepoints to X11/RFB keysyms\n *\n * This file was automatically generated from keysymdef.h\n * DO NOT EDIT!\n */\n\n/* Functions at the bottom */\n\nconst codepoints = {\n    0x0100: 0x03c0, // XK_Amacron\n    0x0101: 0x03e0, // XK_amacron\n    0x0102: 0x01c3, // XK_Abreve\n    0x0103: 0x01e3, // XK_abreve\n    0x0104: 0x01a1, // XK_Aogonek\n    0x0105: 0x01b1, // XK_aogonek\n    0x0106: 0x01c6, // XK_Cacute\n    0x0107: 0x01e6, // XK_cacute\n    0x0108: 0x02c6, // XK_Ccircumflex\n    0x0109: 0x02e6, // XK_ccircumflex\n    0x010a: 0x02c5, // XK_Cabovedot\n    0x010b: 0x02e5, // XK_cabovedot\n    0x010c: 0x01c8, // XK_Ccaron\n    0x010d: 0x01e8, // XK_ccaron\n    0x010e: 0x01cf, // XK_Dcaron\n    0x010f: 0x01ef, // XK_dcaron\n    0x0110: 0x01d0, // XK_Dstroke\n    0x0111: 0x01f0, // XK_dstroke\n    0x0112: 0x03aa, // XK_Emacron\n    0x0113: 0x03ba, // XK_emacron\n    0x0116: 0x03cc, // XK_Eabovedot\n    0x0117: 0x03ec, // XK_eabovedot\n    0x0118: 0x01ca, // XK_Eogonek\n    0x0119: 0x01ea, // XK_eogonek\n    0x011a: 0x01cc, // XK_Ecaron\n    0x011b: 0x01ec, // XK_ecaron\n    0x011c: 0x02d8, // XK_Gcircumflex\n    0x011d: 0x02f8, // XK_gcircumflex\n    0x011e: 0x02ab, // XK_Gbreve\n    0x011f: 0x02bb, // XK_gbreve\n    0x0120: 0x02d5, // XK_Gabovedot\n    0x0121: 0x02f5, // XK_gabovedot\n    0x0122: 0x03ab, // XK_Gcedilla\n    0x0123: 0x03bb, // XK_gcedilla\n    0x0124: 0x02a6, // XK_Hcircumflex\n    0x0125: 0x02b6, // XK_hcircumflex\n    0x0126: 0x02a1, // XK_Hstroke\n    0x0127: 0x02b1, // XK_hstroke\n    0x0128: 0x03a5, // XK_Itilde\n    0x0129: 0x03b5, // XK_itilde\n    0x012a: 0x03cf, // XK_Imacron\n    0x012b: 0x03ef, // XK_imacron\n    0x012e: 0x03c7, // XK_Iogonek\n    0x012f: 0x03e7, // XK_iogonek\n    0x0130: 0x02a9, // XK_Iabovedot\n    0x0131: 0x02b9, // XK_idotless\n    0x0134: 0x02ac, // XK_Jcircumflex\n    0x0135: 0x02bc, // XK_jcircumflex\n    0x0136: 0x03d3, // XK_Kcedilla\n    0x0137: 0x03f3, // XK_kcedilla\n    0x0138: 0x03a2, // XK_kra\n    0x0139: 0x01c5, // XK_Lacute\n    0x013a: 0x01e5, // XK_lacute\n    0x013b: 0x03a6, // XK_Lcedilla\n    0x013c: 0x03b6, // XK_lcedilla\n    0x013d: 0x01a5, // XK_Lcaron\n    0x013e: 0x01b5, // XK_lcaron\n    0x0141: 0x01a3, // XK_Lstroke\n    0x0142: 0x01b3, // XK_lstroke\n    0x0143: 0x01d1, // XK_Nacute\n    0x0144: 0x01f1, // XK_nacute\n    0x0145: 0x03d1, // XK_Ncedilla\n    0x0146: 0x03f1, // XK_ncedilla\n    0x0147: 0x01d2, // XK_Ncaron\n    0x0148: 0x01f2, // XK_ncaron\n    0x014a: 0x03bd, // XK_ENG\n    0x014b: 0x03bf, // XK_eng\n    0x014c: 0x03d2, // XK_Omacron\n    0x014d: 0x03f2, // XK_omacron\n    0x0150: 0x01d5, // XK_Odoubleacute\n    0x0151: 0x01f5, // XK_odoubleacute\n    0x0152: 0x13bc, // XK_OE\n    0x0153: 0x13bd, // XK_oe\n    0x0154: 0x01c0, // XK_Racute\n    0x0155: 0x01e0, // XK_racute\n    0x0156: 0x03a3, // XK_Rcedilla\n    0x0157: 0x03b3, // XK_rcedilla\n    0x0158: 0x01d8, // XK_Rcaron\n    0x0159: 0x01f8, // XK_rcaron\n    0x015a: 0x01a6, // XK_Sacute\n    0x015b: 0x01b6, // XK_sacute\n    0x015c: 0x02de, // XK_Scircumflex\n    0x015d: 0x02fe, // XK_scircumflex\n    0x015e: 0x01aa, // XK_Scedilla\n    0x015f: 0x01ba, // XK_scedilla\n    0x0160: 0x01a9, // XK_Scaron\n    0x0161: 0x01b9, // XK_scaron\n    0x0162: 0x01de, // XK_Tcedilla\n    0x0163: 0x01fe, // XK_tcedilla\n    0x0164: 0x01ab, // XK_Tcaron\n    0x0165: 0x01bb, // XK_tcaron\n    0x0166: 0x03ac, // XK_Tslash\n    0x0167: 0x03bc, // XK_tslash\n    0x0168: 0x03dd, // XK_Utilde\n    0x0169: 0x03fd, // XK_utilde\n    0x016a: 0x03de, // XK_Umacron\n    0x016b: 0x03fe, // XK_umacron\n    0x016c: 0x02dd, // XK_Ubreve\n    0x016d: 0x02fd, // XK_ubreve\n    0x016e: 0x01d9, // XK_Uring\n    0x016f: 0x01f9, // XK_uring\n    0x0170: 0x01db, // XK_Udoubleacute\n    0x0171: 0x01fb, // XK_udoubleacute\n    0x0172: 0x03d9, // XK_Uogonek\n    0x0173: 0x03f9, // XK_uogonek\n    0x0178: 0x13be, // XK_Ydiaeresis\n    0x0179: 0x01ac, // XK_Zacute\n    0x017a: 0x01bc, // XK_zacute\n    0x017b: 0x01af, // XK_Zabovedot\n    0x017c: 0x01bf, // XK_zabovedot\n    0x017d: 0x01ae, // XK_Zcaron\n    0x017e: 0x01be, // XK_zcaron\n    0x0192: 0x08f6, // XK_function\n    0x01d2: 0x10001d1, // XK_Ocaron\n    0x02c7: 0x01b7, // XK_caron\n    0x02d8: 0x01a2, // XK_breve\n    0x02d9: 0x01ff, // XK_abovedot\n    0x02db: 0x01b2, // XK_ogonek\n    0x02dd: 0x01bd, // XK_doubleacute\n    0x0385: 0x07ae, // XK_Greek_accentdieresis\n    0x0386: 0x07a1, // XK_Greek_ALPHAaccent\n    0x0388: 0x07a2, // XK_Greek_EPSILONaccent\n    0x0389: 0x07a3, // XK_Greek_ETAaccent\n    0x038a: 0x07a4, // XK_Greek_IOTAaccent\n    0x038c: 0x07a7, // XK_Greek_OMICRONaccent\n    0x038e: 0x07a8, // XK_Greek_UPSILONaccent\n    0x038f: 0x07ab, // XK_Greek_OMEGAaccent\n    0x0390: 0x07b6, // XK_Greek_iotaaccentdieresis\n    0x0391: 0x07c1, // XK_Greek_ALPHA\n    0x0392: 0x07c2, // XK_Greek_BETA\n    0x0393: 0x07c3, // XK_Greek_GAMMA\n    0x0394: 0x07c4, // XK_Greek_DELTA\n    0x0395: 0x07c5, // XK_Greek_EPSILON\n    0x0396: 0x07c6, // XK_Greek_ZETA\n    0x0397: 0x07c7, // XK_Greek_ETA\n    0x0398: 0x07c8, // XK_Greek_THETA\n    0x0399: 0x07c9, // XK_Greek_IOTA\n    0x039a: 0x07ca, // XK_Greek_KAPPA\n    0x039b: 0x07cb, // XK_Greek_LAMDA\n    0x039c: 0x07cc, // XK_Greek_MU\n    0x039d: 0x07cd, // XK_Greek_NU\n    0x039e: 0x07ce, // XK_Greek_XI\n    0x039f: 0x07cf, // XK_Greek_OMICRON\n    0x03a0: 0x07d0, // XK_Greek_PI\n    0x03a1: 0x07d1, // XK_Greek_RHO\n    0x03a3: 0x07d2, // XK_Greek_SIGMA\n    0x03a4: 0x07d4, // XK_Greek_TAU\n    0x03a5: 0x07d5, // XK_Greek_UPSILON\n    0x03a6: 0x07d6, // XK_Greek_PHI\n    0x03a7: 0x07d7, // XK_Greek_CHI\n    0x03a8: 0x07d8, // XK_Greek_PSI\n    0x03a9: 0x07d9, // XK_Greek_OMEGA\n    0x03aa: 0x07a5, // XK_Greek_IOTAdieresis\n    0x03ab: 0x07a9, // XK_Greek_UPSILONdieresis\n    0x03ac: 0x07b1, // XK_Greek_alphaaccent\n    0x03ad: 0x07b2, // XK_Greek_epsilonaccent\n    0x03ae: 0x07b3, // XK_Greek_etaaccent\n    0x03af: 0x07b4, // XK_Greek_iotaaccent\n    0x03b0: 0x07ba, // XK_Greek_upsilonaccentdieresis\n    0x03b1: 0x07e1, // XK_Greek_alpha\n    0x03b2: 0x07e2, // XK_Greek_beta\n    0x03b3: 0x07e3, // XK_Greek_gamma\n    0x03b4: 0x07e4, // XK_Greek_delta\n    0x03b5: 0x07e5, // XK_Greek_epsilon\n    0x03b6: 0x07e6, // XK_Greek_zeta\n    0x03b7: 0x07e7, // XK_Greek_eta\n    0x03b8: 0x07e8, // XK_Greek_theta\n    0x03b9: 0x07e9, // XK_Greek_iota\n    0x03ba: 0x07ea, // XK_Greek_kappa\n    0x03bb: 0x07eb, // XK_Greek_lamda\n    0x03bc: 0x07ec, // XK_Greek_mu\n    0x03bd: 0x07ed, // XK_Greek_nu\n    0x03be: 0x07ee, // XK_Greek_xi\n    0x03bf: 0x07ef, // XK_Greek_omicron\n    0x03c0: 0x07f0, // XK_Greek_pi\n    0x03c1: 0x07f1, // XK_Greek_rho\n    0x03c2: 0x07f3, // XK_Greek_finalsmallsigma\n    0x03c3: 0x07f2, // XK_Greek_sigma\n    0x03c4: 0x07f4, // XK_Greek_tau\n    0x03c5: 0x07f5, // XK_Greek_upsilon\n    0x03c6: 0x07f6, // XK_Greek_phi\n    0x03c7: 0x07f7, // XK_Greek_chi\n    0x03c8: 0x07f8, // XK_Greek_psi\n    0x03c9: 0x07f9, // XK_Greek_omega\n    0x03ca: 0x07b5, // XK_Greek_iotadieresis\n    0x03cb: 0x07b9, // XK_Greek_upsilondieresis\n    0x03cc: 0x07b7, // XK_Greek_omicronaccent\n    0x03cd: 0x07b8, // XK_Greek_upsilonaccent\n    0x03ce: 0x07bb, // XK_Greek_omegaaccent\n    0x0401: 0x06b3, // XK_Cyrillic_IO\n    0x0402: 0x06b1, // XK_Serbian_DJE\n    0x0403: 0x06b2, // XK_Macedonia_GJE\n    0x0404: 0x06b4, // XK_Ukrainian_IE\n    0x0405: 0x06b5, // XK_Macedonia_DSE\n    0x0406: 0x06b6, // XK_Ukrainian_I\n    0x0407: 0x06b7, // XK_Ukrainian_YI\n    0x0408: 0x06b8, // XK_Cyrillic_JE\n    0x0409: 0x06b9, // XK_Cyrillic_LJE\n    0x040a: 0x06ba, // XK_Cyrillic_NJE\n    0x040b: 0x06bb, // XK_Serbian_TSHE\n    0x040c: 0x06bc, // XK_Macedonia_KJE\n    0x040e: 0x06be, // XK_Byelorussian_SHORTU\n    0x040f: 0x06bf, // XK_Cyrillic_DZHE\n    0x0410: 0x06e1, // XK_Cyrillic_A\n    0x0411: 0x06e2, // XK_Cyrillic_BE\n    0x0412: 0x06f7, // XK_Cyrillic_VE\n    0x0413: 0x06e7, // XK_Cyrillic_GHE\n    0x0414: 0x06e4, // XK_Cyrillic_DE\n    0x0415: 0x06e5, // XK_Cyrillic_IE\n    0x0416: 0x06f6, // XK_Cyrillic_ZHE\n    0x0417: 0x06fa, // XK_Cyrillic_ZE\n    0x0418: 0x06e9, // XK_Cyrillic_I\n    0x0419: 0x06ea, // XK_Cyrillic_SHORTI\n    0x041a: 0x06eb, // XK_Cyrillic_KA\n    0x041b: 0x06ec, // XK_Cyrillic_EL\n    0x041c: 0x06ed, // XK_Cyrillic_EM\n    0x041d: 0x06ee, // XK_Cyrillic_EN\n    0x041e: 0x06ef, // XK_Cyrillic_O\n    0x041f: 0x06f0, // XK_Cyrillic_PE\n    0x0420: 0x06f2, // XK_Cyrillic_ER\n    0x0421: 0x06f3, // XK_Cyrillic_ES\n    0x0422: 0x06f4, // XK_Cyrillic_TE\n    0x0423: 0x06f5, // XK_Cyrillic_U\n    0x0424: 0x06e6, // XK_Cyrillic_EF\n    0x0425: 0x06e8, // XK_Cyrillic_HA\n    0x0426: 0x06e3, // XK_Cyrillic_TSE\n    0x0427: 0x06fe, // XK_Cyrillic_CHE\n    0x0428: 0x06fb, // XK_Cyrillic_SHA\n    0x0429: 0x06fd, // XK_Cyrillic_SHCHA\n    0x042a: 0x06ff, // XK_Cyrillic_HARDSIGN\n    0x042b: 0x06f9, // XK_Cyrillic_YERU\n    0x042c: 0x06f8, // XK_Cyrillic_SOFTSIGN\n    0x042d: 0x06fc, // XK_Cyrillic_E\n    0x042e: 0x06e0, // XK_Cyrillic_YU\n    0x042f: 0x06f1, // XK_Cyrillic_YA\n    0x0430: 0x06c1, // XK_Cyrillic_a\n    0x0431: 0x06c2, // XK_Cyrillic_be\n    0x0432: 0x06d7, // XK_Cyrillic_ve\n    0x0433: 0x06c7, // XK_Cyrillic_ghe\n    0x0434: 0x06c4, // XK_Cyrillic_de\n    0x0435: 0x06c5, // XK_Cyrillic_ie\n    0x0436: 0x06d6, // XK_Cyrillic_zhe\n    0x0437: 0x06da, // XK_Cyrillic_ze\n    0x0438: 0x06c9, // XK_Cyrillic_i\n    0x0439: 0x06ca, // XK_Cyrillic_shorti\n    0x043a: 0x06cb, // XK_Cyrillic_ka\n    0x043b: 0x06cc, // XK_Cyrillic_el\n    0x043c: 0x06cd, // XK_Cyrillic_em\n    0x043d: 0x06ce, // XK_Cyrillic_en\n    0x043e: 0x06cf, // XK_Cyrillic_o\n    0x043f: 0x06d0, // XK_Cyrillic_pe\n    0x0440: 0x06d2, // XK_Cyrillic_er\n    0x0441: 0x06d3, // XK_Cyrillic_es\n    0x0442: 0x06d4, // XK_Cyrillic_te\n    0x0443: 0x06d5, // XK_Cyrillic_u\n    0x0444: 0x06c6, // XK_Cyrillic_ef\n    0x0445: 0x06c8, // XK_Cyrillic_ha\n    0x0446: 0x06c3, // XK_Cyrillic_tse\n    0x0447: 0x06de, // XK_Cyrillic_che\n    0x0448: 0x06db, // XK_Cyrillic_sha\n    0x0449: 0x06dd, // XK_Cyrillic_shcha\n    0x044a: 0x06df, // XK_Cyrillic_hardsign\n    0x044b: 0x06d9, // XK_Cyrillic_yeru\n    0x044c: 0x06d8, // XK_Cyrillic_softsign\n    0x044d: 0x06dc, // XK_Cyrillic_e\n    0x044e: 0x06c0, // XK_Cyrillic_yu\n    0x044f: 0x06d1, // XK_Cyrillic_ya\n    0x0451: 0x06a3, // XK_Cyrillic_io\n    0x0452: 0x06a1, // XK_Serbian_dje\n    0x0453: 0x06a2, // XK_Macedonia_gje\n    0x0454: 0x06a4, // XK_Ukrainian_ie\n    0x0455: 0x06a5, // XK_Macedonia_dse\n    0x0456: 0x06a6, // XK_Ukrainian_i\n    0x0457: 0x06a7, // XK_Ukrainian_yi\n    0x0458: 0x06a8, // XK_Cyrillic_je\n    0x0459: 0x06a9, // XK_Cyrillic_lje\n    0x045a: 0x06aa, // XK_Cyrillic_nje\n    0x045b: 0x06ab, // XK_Serbian_tshe\n    0x045c: 0x06ac, // XK_Macedonia_kje\n    0x045e: 0x06ae, // XK_Byelorussian_shortu\n    0x045f: 0x06af, // XK_Cyrillic_dzhe\n    0x0490: 0x06bd, // XK_Ukrainian_GHE_WITH_UPTURN\n    0x0491: 0x06ad, // XK_Ukrainian_ghe_with_upturn\n    0x05d0: 0x0ce0, // XK_hebrew_aleph\n    0x05d1: 0x0ce1, // XK_hebrew_bet\n    0x05d2: 0x0ce2, // XK_hebrew_gimel\n    0x05d3: 0x0ce3, // XK_hebrew_dalet\n    0x05d4: 0x0ce4, // XK_hebrew_he\n    0x05d5: 0x0ce5, // XK_hebrew_waw\n    0x05d6: 0x0ce6, // XK_hebrew_zain\n    0x05d7: 0x0ce7, // XK_hebrew_chet\n    0x05d8: 0x0ce8, // XK_hebrew_tet\n    0x05d9: 0x0ce9, // XK_hebrew_yod\n    0x05da: 0x0cea, // XK_hebrew_finalkaph\n    0x05db: 0x0ceb, // XK_hebrew_kaph\n    0x05dc: 0x0cec, // XK_hebrew_lamed\n    0x05dd: 0x0ced, // XK_hebrew_finalmem\n    0x05de: 0x0cee, // XK_hebrew_mem\n    0x05df: 0x0cef, // XK_hebrew_finalnun\n    0x05e0: 0x0cf0, // XK_hebrew_nun\n    0x05e1: 0x0cf1, // XK_hebrew_samech\n    0x05e2: 0x0cf2, // XK_hebrew_ayin\n    0x05e3: 0x0cf3, // XK_hebrew_finalpe\n    0x05e4: 0x0cf4, // XK_hebrew_pe\n    0x05e5: 0x0cf5, // XK_hebrew_finalzade\n    0x05e6: 0x0cf6, // XK_hebrew_zade\n    0x05e7: 0x0cf7, // XK_hebrew_qoph\n    0x05e8: 0x0cf8, // XK_hebrew_resh\n    0x05e9: 0x0cf9, // XK_hebrew_shin\n    0x05ea: 0x0cfa, // XK_hebrew_taw\n    0x060c: 0x05ac, // XK_Arabic_comma\n    0x061b: 0x05bb, // XK_Arabic_semicolon\n    0x061f: 0x05bf, // XK_Arabic_question_mark\n    0x0621: 0x05c1, // XK_Arabic_hamza\n    0x0622: 0x05c2, // XK_Arabic_maddaonalef\n    0x0623: 0x05c3, // XK_Arabic_hamzaonalef\n    0x0624: 0x05c4, // XK_Arabic_hamzaonwaw\n    0x0625: 0x05c5, // XK_Arabic_hamzaunderalef\n    0x0626: 0x05c6, // XK_Arabic_hamzaonyeh\n    0x0627: 0x05c7, // XK_Arabic_alef\n    0x0628: 0x05c8, // XK_Arabic_beh\n    0x0629: 0x05c9, // XK_Arabic_tehmarbuta\n    0x062a: 0x05ca, // XK_Arabic_teh\n    0x062b: 0x05cb, // XK_Arabic_theh\n    0x062c: 0x05cc, // XK_Arabic_jeem\n    0x062d: 0x05cd, // XK_Arabic_hah\n    0x062e: 0x05ce, // XK_Arabic_khah\n    0x062f: 0x05cf, // XK_Arabic_dal\n    0x0630: 0x05d0, // XK_Arabic_thal\n    0x0631: 0x05d1, // XK_Arabic_ra\n    0x0632: 0x05d2, // XK_Arabic_zain\n    0x0633: 0x05d3, // XK_Arabic_seen\n    0x0634: 0x05d4, // XK_Arabic_sheen\n    0x0635: 0x05d5, // XK_Arabic_sad\n    0x0636: 0x05d6, // XK_Arabic_dad\n    0x0637: 0x05d7, // XK_Arabic_tah\n    0x0638: 0x05d8, // XK_Arabic_zah\n    0x0639: 0x05d9, // XK_Arabic_ain\n    0x063a: 0x05da, // XK_Arabic_ghain\n    0x0640: 0x05e0, // XK_Arabic_tatweel\n    0x0641: 0x05e1, // XK_Arabic_feh\n    0x0642: 0x05e2, // XK_Arabic_qaf\n    0x0643: 0x05e3, // XK_Arabic_kaf\n    0x0644: 0x05e4, // XK_Arabic_lam\n    0x0645: 0x05e5, // XK_Arabic_meem\n    0x0646: 0x05e6, // XK_Arabic_noon\n    0x0647: 0x05e7, // XK_Arabic_ha\n    0x0648: 0x05e8, // XK_Arabic_waw\n    0x0649: 0x05e9, // XK_Arabic_alefmaksura\n    0x064a: 0x05ea, // XK_Arabic_yeh\n    0x064b: 0x05eb, // XK_Arabic_fathatan\n    0x064c: 0x05ec, // XK_Arabic_dammatan\n    0x064d: 0x05ed, // XK_Arabic_kasratan\n    0x064e: 0x05ee, // XK_Arabic_fatha\n    0x064f: 0x05ef, // XK_Arabic_damma\n    0x0650: 0x05f0, // XK_Arabic_kasra\n    0x0651: 0x05f1, // XK_Arabic_shadda\n    0x0652: 0x05f2, // XK_Arabic_sukun\n    0x0e01: 0x0da1, // XK_Thai_kokai\n    0x0e02: 0x0da2, // XK_Thai_khokhai\n    0x0e03: 0x0da3, // XK_Thai_khokhuat\n    0x0e04: 0x0da4, // XK_Thai_khokhwai\n    0x0e05: 0x0da5, // XK_Thai_khokhon\n    0x0e06: 0x0da6, // XK_Thai_khorakhang\n    0x0e07: 0x0da7, // XK_Thai_ngongu\n    0x0e08: 0x0da8, // XK_Thai_chochan\n    0x0e09: 0x0da9, // XK_Thai_choching\n    0x0e0a: 0x0daa, // XK_Thai_chochang\n    0x0e0b: 0x0dab, // XK_Thai_soso\n    0x0e0c: 0x0dac, // XK_Thai_chochoe\n    0x0e0d: 0x0dad, // XK_Thai_yoying\n    0x0e0e: 0x0dae, // XK_Thai_dochada\n    0x0e0f: 0x0daf, // XK_Thai_topatak\n    0x0e10: 0x0db0, // XK_Thai_thothan\n    0x0e11: 0x0db1, // XK_Thai_thonangmontho\n    0x0e12: 0x0db2, // XK_Thai_thophuthao\n    0x0e13: 0x0db3, // XK_Thai_nonen\n    0x0e14: 0x0db4, // XK_Thai_dodek\n    0x0e15: 0x0db5, // XK_Thai_totao\n    0x0e16: 0x0db6, // XK_Thai_thothung\n    0x0e17: 0x0db7, // XK_Thai_thothahan\n    0x0e18: 0x0db8, // XK_Thai_thothong\n    0x0e19: 0x0db9, // XK_Thai_nonu\n    0x0e1a: 0x0dba, // XK_Thai_bobaimai\n    0x0e1b: 0x0dbb, // XK_Thai_popla\n    0x0e1c: 0x0dbc, // XK_Thai_phophung\n    0x0e1d: 0x0dbd, // XK_Thai_fofa\n    0x0e1e: 0x0dbe, // XK_Thai_phophan\n    0x0e1f: 0x0dbf, // XK_Thai_fofan\n    0x0e20: 0x0dc0, // XK_Thai_phosamphao\n    0x0e21: 0x0dc1, // XK_Thai_moma\n    0x0e22: 0x0dc2, // XK_Thai_yoyak\n    0x0e23: 0x0dc3, // XK_Thai_rorua\n    0x0e24: 0x0dc4, // XK_Thai_ru\n    0x0e25: 0x0dc5, // XK_Thai_loling\n    0x0e26: 0x0dc6, // XK_Thai_lu\n    0x0e27: 0x0dc7, // XK_Thai_wowaen\n    0x0e28: 0x0dc8, // XK_Thai_sosala\n    0x0e29: 0x0dc9, // XK_Thai_sorusi\n    0x0e2a: 0x0dca, // XK_Thai_sosua\n    0x0e2b: 0x0dcb, // XK_Thai_hohip\n    0x0e2c: 0x0dcc, // XK_Thai_lochula\n    0x0e2d: 0x0dcd, // XK_Thai_oang\n    0x0e2e: 0x0dce, // XK_Thai_honokhuk\n    0x0e2f: 0x0dcf, // XK_Thai_paiyannoi\n    0x0e30: 0x0dd0, // XK_Thai_saraa\n    0x0e31: 0x0dd1, // XK_Thai_maihanakat\n    0x0e32: 0x0dd2, // XK_Thai_saraaa\n    0x0e33: 0x0dd3, // XK_Thai_saraam\n    0x0e34: 0x0dd4, // XK_Thai_sarai\n    0x0e35: 0x0dd5, // XK_Thai_saraii\n    0x0e36: 0x0dd6, // XK_Thai_saraue\n    0x0e37: 0x0dd7, // XK_Thai_sarauee\n    0x0e38: 0x0dd8, // XK_Thai_sarau\n    0x0e39: 0x0dd9, // XK_Thai_sarauu\n    0x0e3a: 0x0dda, // XK_Thai_phinthu\n    0x0e3f: 0x0ddf, // XK_Thai_baht\n    0x0e40: 0x0de0, // XK_Thai_sarae\n    0x0e41: 0x0de1, // XK_Thai_saraae\n    0x0e42: 0x0de2, // XK_Thai_sarao\n    0x0e43: 0x0de3, // XK_Thai_saraaimaimuan\n    0x0e44: 0x0de4, // XK_Thai_saraaimaimalai\n    0x0e45: 0x0de5, // XK_Thai_lakkhangyao\n    0x0e46: 0x0de6, // XK_Thai_maiyamok\n    0x0e47: 0x0de7, // XK_Thai_maitaikhu\n    0x0e48: 0x0de8, // XK_Thai_maiek\n    0x0e49: 0x0de9, // XK_Thai_maitho\n    0x0e4a: 0x0dea, // XK_Thai_maitri\n    0x0e4b: 0x0deb, // XK_Thai_maichattawa\n    0x0e4c: 0x0dec, // XK_Thai_thanthakhat\n    0x0e4d: 0x0ded, // XK_Thai_nikhahit\n    0x0e50: 0x0df0, // XK_Thai_leksun\n    0x0e51: 0x0df1, // XK_Thai_leknung\n    0x0e52: 0x0df2, // XK_Thai_leksong\n    0x0e53: 0x0df3, // XK_Thai_leksam\n    0x0e54: 0x0df4, // XK_Thai_leksi\n    0x0e55: 0x0df5, // XK_Thai_lekha\n    0x0e56: 0x0df6, // XK_Thai_lekhok\n    0x0e57: 0x0df7, // XK_Thai_lekchet\n    0x0e58: 0x0df8, // XK_Thai_lekpaet\n    0x0e59: 0x0df9, // XK_Thai_lekkao\n    0x2002: 0x0aa2, // XK_enspace\n    0x2003: 0x0aa1, // XK_emspace\n    0x2004: 0x0aa3, // XK_em3space\n    0x2005: 0x0aa4, // XK_em4space\n    0x2007: 0x0aa5, // XK_digitspace\n    0x2008: 0x0aa6, // XK_punctspace\n    0x2009: 0x0aa7, // XK_thinspace\n    0x200a: 0x0aa8, // XK_hairspace\n    0x2012: 0x0abb, // XK_figdash\n    0x2013: 0x0aaa, // XK_endash\n    0x2014: 0x0aa9, // XK_emdash\n    0x2015: 0x07af, // XK_Greek_horizbar\n    0x2017: 0x0cdf, // XK_hebrew_doublelowline\n    0x2018: 0x0ad0, // XK_leftsinglequotemark\n    0x2019: 0x0ad1, // XK_rightsinglequotemark\n    0x201a: 0x0afd, // XK_singlelowquotemark\n    0x201c: 0x0ad2, // XK_leftdoublequotemark\n    0x201d: 0x0ad3, // XK_rightdoublequotemark\n    0x201e: 0x0afe, // XK_doublelowquotemark\n    0x2020: 0x0af1, // XK_dagger\n    0x2021: 0x0af2, // XK_doubledagger\n    0x2022: 0x0ae6, // XK_enfilledcircbullet\n    0x2025: 0x0aaf, // XK_doubbaselinedot\n    0x2026: 0x0aae, // XK_ellipsis\n    0x2030: 0x0ad5, // XK_permille\n    0x2032: 0x0ad6, // XK_minutes\n    0x2033: 0x0ad7, // XK_seconds\n    0x2038: 0x0afc, // XK_caret\n    0x203e: 0x047e, // XK_overline\n    0x20a9: 0x0eff, // XK_Korean_Won\n    0x20ac: 0x20ac, // XK_EuroSign\n    0x2105: 0x0ab8, // XK_careof\n    0x2116: 0x06b0, // XK_numerosign\n    0x2117: 0x0afb, // XK_phonographcopyright\n    0x211e: 0x0ad4, // XK_prescription\n    0x2122: 0x0ac9, // XK_trademark\n    0x2153: 0x0ab0, // XK_onethird\n    0x2154: 0x0ab1, // XK_twothirds\n    0x2155: 0x0ab2, // XK_onefifth\n    0x2156: 0x0ab3, // XK_twofifths\n    0x2157: 0x0ab4, // XK_threefifths\n    0x2158: 0x0ab5, // XK_fourfifths\n    0x2159: 0x0ab6, // XK_onesixth\n    0x215a: 0x0ab7, // XK_fivesixths\n    0x215b: 0x0ac3, // XK_oneeighth\n    0x215c: 0x0ac4, // XK_threeeighths\n    0x215d: 0x0ac5, // XK_fiveeighths\n    0x215e: 0x0ac6, // XK_seveneighths\n    0x2190: 0x08fb, // XK_leftarrow\n    0x2191: 0x08fc, // XK_uparrow\n    0x2192: 0x08fd, // XK_rightarrow\n    0x2193: 0x08fe, // XK_downarrow\n    0x21d2: 0x08ce, // XK_implies\n    0x21d4: 0x08cd, // XK_ifonlyif\n    0x2202: 0x08ef, // XK_partialderivative\n    0x2207: 0x08c5, // XK_nabla\n    0x2218: 0x0bca, // XK_jot\n    0x221a: 0x08d6, // XK_radical\n    0x221d: 0x08c1, // XK_variation\n    0x221e: 0x08c2, // XK_infinity\n    0x2227: 0x08de, // XK_logicaland\n    0x2228: 0x08df, // XK_logicalor\n    0x2229: 0x08dc, // XK_intersection\n    0x222a: 0x08dd, // XK_union\n    0x222b: 0x08bf, // XK_integral\n    0x2234: 0x08c0, // XK_therefore\n    0x223c: 0x08c8, // XK_approximate\n    0x2243: 0x08c9, // XK_similarequal\n    0x2245: 0x1002248, // XK_approxeq\n    0x2260: 0x08bd, // XK_notequal\n    0x2261: 0x08cf, // XK_identical\n    0x2264: 0x08bc, // XK_lessthanequal\n    0x2265: 0x08be, // XK_greaterthanequal\n    0x2282: 0x08da, // XK_includedin\n    0x2283: 0x08db, // XK_includes\n    0x22a2: 0x0bfc, // XK_righttack\n    0x22a3: 0x0bdc, // XK_lefttack\n    0x22a4: 0x0bc2, // XK_downtack\n    0x22a5: 0x0bce, // XK_uptack\n    0x2308: 0x0bd3, // XK_upstile\n    0x230a: 0x0bc4, // XK_downstile\n    0x2315: 0x0afa, // XK_telephonerecorder\n    0x2320: 0x08a4, // XK_topintegral\n    0x2321: 0x08a5, // XK_botintegral\n    0x2395: 0x0bcc, // XK_quad\n    0x239b: 0x08ab, // XK_topleftparens\n    0x239d: 0x08ac, // XK_botleftparens\n    0x239e: 0x08ad, // XK_toprightparens\n    0x23a0: 0x08ae, // XK_botrightparens\n    0x23a1: 0x08a7, // XK_topleftsqbracket\n    0x23a3: 0x08a8, // XK_botleftsqbracket\n    0x23a4: 0x08a9, // XK_toprightsqbracket\n    0x23a6: 0x08aa, // XK_botrightsqbracket\n    0x23a8: 0x08af, // XK_leftmiddlecurlybrace\n    0x23ac: 0x08b0, // XK_rightmiddlecurlybrace\n    0x23b7: 0x08a1, // XK_leftradical\n    0x23ba: 0x09ef, // XK_horizlinescan1\n    0x23bb: 0x09f0, // XK_horizlinescan3\n    0x23bc: 0x09f2, // XK_horizlinescan7\n    0x23bd: 0x09f3, // XK_horizlinescan9\n    0x2409: 0x09e2, // XK_ht\n    0x240a: 0x09e5, // XK_lf\n    0x240b: 0x09e9, // XK_vt\n    0x240c: 0x09e3, // XK_ff\n    0x240d: 0x09e4, // XK_cr\n    0x2423: 0x0aac, // XK_signifblank\n    0x2424: 0x09e8, // XK_nl\n    0x2500: 0x08a3, // XK_horizconnector\n    0x2502: 0x08a6, // XK_vertconnector\n    0x250c: 0x08a2, // XK_topleftradical\n    0x2510: 0x09eb, // XK_uprightcorner\n    0x2514: 0x09ed, // XK_lowleftcorner\n    0x2518: 0x09ea, // XK_lowrightcorner\n    0x251c: 0x09f4, // XK_leftt\n    0x2524: 0x09f5, // XK_rightt\n    0x252c: 0x09f7, // XK_topt\n    0x2534: 0x09f6, // XK_bott\n    0x253c: 0x09ee, // XK_crossinglines\n    0x2592: 0x09e1, // XK_checkerboard\n    0x25aa: 0x0ae7, // XK_enfilledsqbullet\n    0x25ab: 0x0ae1, // XK_enopensquarebullet\n    0x25ac: 0x0adb, // XK_filledrectbullet\n    0x25ad: 0x0ae2, // XK_openrectbullet\n    0x25ae: 0x0adf, // XK_emfilledrect\n    0x25af: 0x0acf, // XK_emopenrectangle\n    0x25b2: 0x0ae8, // XK_filledtribulletup\n    0x25b3: 0x0ae3, // XK_opentribulletup\n    0x25b6: 0x0add, // XK_filledrighttribullet\n    0x25b7: 0x0acd, // XK_rightopentriangle\n    0x25bc: 0x0ae9, // XK_filledtribulletdown\n    0x25bd: 0x0ae4, // XK_opentribulletdown\n    0x25c0: 0x0adc, // XK_filledlefttribullet\n    0x25c1: 0x0acc, // XK_leftopentriangle\n    0x25c6: 0x09e0, // XK_soliddiamond\n    0x25cb: 0x0ace, // XK_emopencircle\n    0x25cf: 0x0ade, // XK_emfilledcircle\n    0x25e6: 0x0ae0, // XK_enopencircbullet\n    0x2606: 0x0ae5, // XK_openstar\n    0x260e: 0x0af9, // XK_telephone\n    0x2613: 0x0aca, // XK_signaturemark\n    0x261c: 0x0aea, // XK_leftpointer\n    0x261e: 0x0aeb, // XK_rightpointer\n    0x2640: 0x0af8, // XK_femalesymbol\n    0x2642: 0x0af7, // XK_malesymbol\n    0x2663: 0x0aec, // XK_club\n    0x2665: 0x0aee, // XK_heart\n    0x2666: 0x0aed, // XK_diamond\n    0x266d: 0x0af6, // XK_musicalflat\n    0x266f: 0x0af5, // XK_musicalsharp\n    0x2713: 0x0af3, // XK_checkmark\n    0x2717: 0x0af4, // XK_ballotcross\n    0x271d: 0x0ad9, // XK_latincross\n    0x2720: 0x0af0, // XK_maltesecross\n    0x27e8: 0x0abc, // XK_leftanglebracket\n    0x27e9: 0x0abe, // XK_rightanglebracket\n    0x3001: 0x04a4, // XK_kana_comma\n    0x3002: 0x04a1, // XK_kana_fullstop\n    0x300c: 0x04a2, // XK_kana_openingbracket\n    0x300d: 0x04a3, // XK_kana_closingbracket\n    0x309b: 0x04de, // XK_voicedsound\n    0x309c: 0x04df, // XK_semivoicedsound\n    0x30a1: 0x04a7, // XK_kana_a\n    0x30a2: 0x04b1, // XK_kana_A\n    0x30a3: 0x04a8, // XK_kana_i\n    0x30a4: 0x04b2, // XK_kana_I\n    0x30a5: 0x04a9, // XK_kana_u\n    0x30a6: 0x04b3, // XK_kana_U\n    0x30a7: 0x04aa, // XK_kana_e\n    0x30a8: 0x04b4, // XK_kana_E\n    0x30a9: 0x04ab, // XK_kana_o\n    0x30aa: 0x04b5, // XK_kana_O\n    0x30ab: 0x04b6, // XK_kana_KA\n    0x30ad: 0x04b7, // XK_kana_KI\n    0x30af: 0x04b8, // XK_kana_KU\n    0x30b1: 0x04b9, // XK_kana_KE\n    0x30b3: 0x04ba, // XK_kana_KO\n    0x30b5: 0x04bb, // XK_kana_SA\n    0x30b7: 0x04bc, // XK_kana_SHI\n    0x30b9: 0x04bd, // XK_kana_SU\n    0x30bb: 0x04be, // XK_kana_SE\n    0x30bd: 0x04bf, // XK_kana_SO\n    0x30bf: 0x04c0, // XK_kana_TA\n    0x30c1: 0x04c1, // XK_kana_CHI\n    0x30c3: 0x04af, // XK_kana_tsu\n    0x30c4: 0x04c2, // XK_kana_TSU\n    0x30c6: 0x04c3, // XK_kana_TE\n    0x30c8: 0x04c4, // XK_kana_TO\n    0x30ca: 0x04c5, // XK_kana_NA\n    0x30cb: 0x04c6, // XK_kana_NI\n    0x30cc: 0x04c7, // XK_kana_NU\n    0x30cd: 0x04c8, // XK_kana_NE\n    0x30ce: 0x04c9, // XK_kana_NO\n    0x30cf: 0x04ca, // XK_kana_HA\n    0x30d2: 0x04cb, // XK_kana_HI\n    0x30d5: 0x04cc, // XK_kana_FU\n    0x30d8: 0x04cd, // XK_kana_HE\n    0x30db: 0x04ce, // XK_kana_HO\n    0x30de: 0x04cf, // XK_kana_MA\n    0x30df: 0x04d0, // XK_kana_MI\n    0x30e0: 0x04d1, // XK_kana_MU\n    0x30e1: 0x04d2, // XK_kana_ME\n    0x30e2: 0x04d3, // XK_kana_MO\n    0x30e3: 0x04ac, // XK_kana_ya\n    0x30e4: 0x04d4, // XK_kana_YA\n    0x30e5: 0x04ad, // XK_kana_yu\n    0x30e6: 0x04d5, // XK_kana_YU\n    0x30e7: 0x04ae, // XK_kana_yo\n    0x30e8: 0x04d6, // XK_kana_YO\n    0x30e9: 0x04d7, // XK_kana_RA\n    0x30ea: 0x04d8, // XK_kana_RI\n    0x30eb: 0x04d9, // XK_kana_RU\n    0x30ec: 0x04da, // XK_kana_RE\n    0x30ed: 0x04db, // XK_kana_RO\n    0x30ef: 0x04dc, // XK_kana_WA\n    0x30f2: 0x04a6, // XK_kana_WO\n    0x30f3: 0x04dd, // XK_kana_N\n    0x30fb: 0x04a5, // XK_kana_conjunctive\n    0x30fc: 0x04b0, // XK_prolongedsound\n};\n\nexport default {\n    lookup(u) {\n        // Latin-1 is one-to-one mapping\n        if ((u >= 0x20) && (u <= 0xff)) {\n            return u;\n        }\n\n        // Lookup table (fairly random)\n        const keysym = codepoints[u];\n        if (keysym !== undefined) {\n            return keysym;\n        }\n\n        // General mapping as final fallback\n        return 0x01000000 | u;\n    },\n};\n"
  },
  {
    "path": "services/gateway/noVNC/core/input/util.js",
    "content": "import KeyTable from \"./keysym.js\";\nimport keysyms from \"./keysymdef.js\";\nimport vkeys from \"./vkeys.js\";\nimport fixedkeys from \"./fixedkeys.js\";\nimport DOMKeyTable from \"./domkeytable.js\";\nimport * as browser from \"../util/browser.js\";\n\n// Get 'KeyboardEvent.code', handling legacy browsers\nexport function getKeycode(evt) {\n    // Are we getting proper key identifiers?\n    // (unfortunately Firefox and Chrome are crappy here and gives\n    // us an empty string on some platforms, rather than leaving it\n    // undefined)\n    if (evt.code) {\n        // Mozilla isn't fully in sync with the spec yet\n        switch (evt.code) {\n            case 'OSLeft': return 'MetaLeft';\n            case 'OSRight': return 'MetaRight';\n        }\n\n        return evt.code;\n    }\n\n    // The de-facto standard is to use Windows Virtual-Key codes\n    // in the 'keyCode' field for non-printable characters\n    if (evt.keyCode in vkeys) {\n        let code = vkeys[evt.keyCode];\n\n        // macOS has messed up this code for some reason\n        if (browser.isMac() && (code === 'ContextMenu')) {\n            code = 'MetaRight';\n        }\n\n        // The keyCode doesn't distinguish between left and right\n        // for the standard modifiers\n        if (evt.location === 2) {\n            switch (code) {\n                case 'ShiftLeft': return 'ShiftRight';\n                case 'ControlLeft': return 'ControlRight';\n                case 'AltLeft': return 'AltRight';\n            }\n        }\n\n        // Nor a bunch of the numpad keys\n        if (evt.location === 3) {\n            switch (code) {\n                case 'Delete': return 'NumpadDecimal';\n                case 'Insert': return 'Numpad0';\n                case 'End': return 'Numpad1';\n                case 'ArrowDown': return 'Numpad2';\n                case 'PageDown': return 'Numpad3';\n                case 'ArrowLeft': return 'Numpad4';\n                case 'ArrowRight': return 'Numpad6';\n                case 'Home': return 'Numpad7';\n                case 'ArrowUp': return 'Numpad8';\n                case 'PageUp': return 'Numpad9';\n                case 'Enter': return 'NumpadEnter';\n            }\n        }\n\n        return code;\n    }\n\n    return 'Unidentified';\n}\n\n// Get 'KeyboardEvent.key', handling legacy browsers\nexport function getKey(evt) {\n    // Are we getting a proper key value?\n    if ((evt.key !== undefined) && (evt.key !== 'Unidentified')) {\n        // Mozilla isn't fully in sync with the spec yet\n        switch (evt.key) {\n            case 'OS': return 'Meta';\n            case 'LaunchMyComputer': return 'LaunchApplication1';\n            case 'LaunchCalculator': return 'LaunchApplication2';\n        }\n\n        // iOS leaks some OS names\n        switch (evt.key) {\n            case 'UIKeyInputUpArrow': return 'ArrowUp';\n            case 'UIKeyInputDownArrow': return 'ArrowDown';\n            case 'UIKeyInputLeftArrow': return 'ArrowLeft';\n            case 'UIKeyInputRightArrow': return 'ArrowRight';\n            case 'UIKeyInputEscape': return 'Escape';\n        }\n\n        // Broken behaviour in Chrome\n        if ((evt.key === '\\x00') && (evt.code === 'NumpadDecimal')) {\n            return 'Delete';\n        }\n\n        return evt.key;\n    }\n\n    // Try to deduce it based on the physical key\n    const code = getKeycode(evt);\n    if (code in fixedkeys) {\n        return fixedkeys[code];\n    }\n\n    // If that failed, then see if we have a printable character\n    if (evt.charCode) {\n        return String.fromCharCode(evt.charCode);\n    }\n\n    // At this point we have nothing left to go on\n    return 'Unidentified';\n}\n\n// Get the most reliable keysym value we can get from a key event\nexport function getKeysym(evt) {\n    const key = getKey(evt);\n\n    if (key === 'Unidentified') {\n        return null;\n    }\n\n    // First look up special keys\n    if (key in DOMKeyTable) {\n        let location = evt.location;\n\n        // Safari screws up location for the right cmd key\n        if ((key === 'Meta') && (location === 0)) {\n            location = 2;\n        }\n\n        // And for Clear\n        if ((key === 'Clear') && (location === 3)) {\n            let code = getKeycode(evt);\n            if (code === 'NumLock') {\n                location = 0;\n            }\n        }\n\n        if ((location === undefined) || (location > 3)) {\n            location = 0;\n        }\n\n        // The original Meta key now gets confused with the Windows key\n        // https://bugs.chromium.org/p/chromium/issues/detail?id=1020141\n        // https://bugzilla.mozilla.org/show_bug.cgi?id=1232918\n        if (key === 'Meta') {\n            let code = getKeycode(evt);\n            if (code === 'AltLeft') {\n                return KeyTable.XK_Meta_L;\n            } else if (code === 'AltRight') {\n                return KeyTable.XK_Meta_R;\n            }\n        }\n\n        // macOS has Clear instead of NumLock, but the remote system is\n        // probably not macOS, so lying here is probably best...\n        if (key === 'Clear') {\n            let code = getKeycode(evt);\n            if (code === 'NumLock') {\n                return KeyTable.XK_Num_Lock;\n            }\n        }\n\n        // Windows sends alternating symbols for some keys when using a\n        // Japanese layout. We have no way of synchronising with the IM\n        // running on the remote system, so we send some combined keysym\n        // instead and hope for the best.\n        if (browser.isWindows()) {\n            switch (key) {\n                case 'Zenkaku':\n                case 'Hankaku':\n                    return KeyTable.XK_Zenkaku_Hankaku;\n                case 'Romaji':\n                case 'KanaMode':\n                    return KeyTable.XK_Romaji;\n            }\n        }\n\n        return DOMKeyTable[key][location];\n    }\n\n    // Now we need to look at the Unicode symbol instead\n\n    // Special key? (FIXME: Should have been caught earlier)\n    if (key.length !== 1) {\n        return null;\n    }\n\n    const codepoint = key.charCodeAt();\n    if (codepoint) {\n        return keysyms.lookup(codepoint);\n    }\n\n    return null;\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/input/vkeys.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 or any later version (see LICENSE.txt)\n */\n\n/*\n * Mapping between Microsoft® Windows® Virtual-Key codes and\n * HTML key codes.\n */\n\nexport default {\n    0x08: 'Backspace',\n    0x09: 'Tab',\n    0x0a: 'NumpadClear',\n    0x0d: 'Enter',\n    0x10: 'ShiftLeft',\n    0x11: 'ControlLeft',\n    0x12: 'AltLeft',\n    0x13: 'Pause',\n    0x14: 'CapsLock',\n    0x15: 'Lang1',\n    0x19: 'Lang2',\n    0x1b: 'Escape',\n    0x1c: 'Convert',\n    0x1d: 'NonConvert',\n    0x20: 'Space',\n    0x21: 'PageUp',\n    0x22: 'PageDown',\n    0x23: 'End',\n    0x24: 'Home',\n    0x25: 'ArrowLeft',\n    0x26: 'ArrowUp',\n    0x27: 'ArrowRight',\n    0x28: 'ArrowDown',\n    0x29: 'Select',\n    0x2c: 'PrintScreen',\n    0x2d: 'Insert',\n    0x2e: 'Delete',\n    0x2f: 'Help',\n    0x30: 'Digit0',\n    0x31: 'Digit1',\n    0x32: 'Digit2',\n    0x33: 'Digit3',\n    0x34: 'Digit4',\n    0x35: 'Digit5',\n    0x36: 'Digit6',\n    0x37: 'Digit7',\n    0x38: 'Digit8',\n    0x39: 'Digit9',\n    0x5b: 'MetaLeft',\n    0x5c: 'MetaRight',\n    0x5d: 'ContextMenu',\n    0x5f: 'Sleep',\n    0x60: 'Numpad0',\n    0x61: 'Numpad1',\n    0x62: 'Numpad2',\n    0x63: 'Numpad3',\n    0x64: 'Numpad4',\n    0x65: 'Numpad5',\n    0x66: 'Numpad6',\n    0x67: 'Numpad7',\n    0x68: 'Numpad8',\n    0x69: 'Numpad9',\n    0x6a: 'NumpadMultiply',\n    0x6b: 'NumpadAdd',\n    0x6c: 'NumpadDecimal',\n    0x6d: 'NumpadSubtract',\n    0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows\n    0x6f: 'NumpadDivide',\n    0x70: 'F1',\n    0x71: 'F2',\n    0x72: 'F3',\n    0x73: 'F4',\n    0x74: 'F5',\n    0x75: 'F6',\n    0x76: 'F7',\n    0x77: 'F8',\n    0x78: 'F9',\n    0x79: 'F10',\n    0x7a: 'F11',\n    0x7b: 'F12',\n    0x7c: 'F13',\n    0x7d: 'F14',\n    0x7e: 'F15',\n    0x7f: 'F16',\n    0x80: 'F17',\n    0x81: 'F18',\n    0x82: 'F19',\n    0x83: 'F20',\n    0x84: 'F21',\n    0x85: 'F22',\n    0x86: 'F23',\n    0x87: 'F24',\n    0x90: 'NumLock',\n    0x91: 'ScrollLock',\n    0xa6: 'BrowserBack',\n    0xa7: 'BrowserForward',\n    0xa8: 'BrowserRefresh',\n    0xa9: 'BrowserStop',\n    0xaa: 'BrowserSearch',\n    0xab: 'BrowserFavorites',\n    0xac: 'BrowserHome',\n    0xad: 'AudioVolumeMute',\n    0xae: 'AudioVolumeDown',\n    0xaf: 'AudioVolumeUp',\n    0xb0: 'MediaTrackNext',\n    0xb1: 'MediaTrackPrevious',\n    0xb2: 'MediaStop',\n    0xb3: 'MediaPlayPause',\n    0xb4: 'LaunchMail',\n    0xb5: 'MediaSelect',\n    0xb6: 'LaunchApp1',\n    0xb7: 'LaunchApp2',\n    0xe1: 'AltRight', // Only when it is AltGraph\n};\n"
  },
  {
    "path": "services/gateway/noVNC/core/input/xtscancodes.js",
    "content": "/*\n * This file is auto-generated from keymaps.csv\n * Database checksum sha256(76d68c10e97d37fe2ea459e210125ae41796253fb217e900bf2983ade13a7920)\n * To re-generate, run:\n *   keymap-gen code-map --lang=js keymaps.csv html atset1\n*/\nexport default {\n  \"Again\": 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */\n  \"AltLeft\": 0x38, /* html:AltLeft (AltLeft) -> linux:56 (KEY_LEFTALT) -> atset1:56 */\n  \"AltRight\": 0xe038, /* html:AltRight (AltRight) -> linux:100 (KEY_RIGHTALT) -> atset1:57400 */\n  \"ArrowDown\": 0xe050, /* html:ArrowDown (ArrowDown) -> linux:108 (KEY_DOWN) -> atset1:57424 */\n  \"ArrowLeft\": 0xe04b, /* html:ArrowLeft (ArrowLeft) -> linux:105 (KEY_LEFT) -> atset1:57419 */\n  \"ArrowRight\": 0xe04d, /* html:ArrowRight (ArrowRight) -> linux:106 (KEY_RIGHT) -> atset1:57421 */\n  \"ArrowUp\": 0xe048, /* html:ArrowUp (ArrowUp) -> linux:103 (KEY_UP) -> atset1:57416 */\n  \"AudioVolumeDown\": 0xe02e, /* html:AudioVolumeDown (AudioVolumeDown) -> linux:114 (KEY_VOLUMEDOWN) -> atset1:57390 */\n  \"AudioVolumeMute\": 0xe020, /* html:AudioVolumeMute (AudioVolumeMute) -> linux:113 (KEY_MUTE) -> atset1:57376 */\n  \"AudioVolumeUp\": 0xe030, /* html:AudioVolumeUp (AudioVolumeUp) -> linux:115 (KEY_VOLUMEUP) -> atset1:57392 */\n  \"Backquote\": 0x29, /* html:Backquote (Backquote) -> linux:41 (KEY_GRAVE) -> atset1:41 */\n  \"Backslash\": 0x2b, /* html:Backslash (Backslash) -> linux:43 (KEY_BACKSLASH) -> atset1:43 */\n  \"Backspace\": 0xe, /* html:Backspace (Backspace) -> linux:14 (KEY_BACKSPACE) -> atset1:14 */\n  \"BracketLeft\": 0x1a, /* html:BracketLeft (BracketLeft) -> linux:26 (KEY_LEFTBRACE) -> atset1:26 */\n  \"BracketRight\": 0x1b, /* html:BracketRight (BracketRight) -> linux:27 (KEY_RIGHTBRACE) -> atset1:27 */\n  \"BrowserBack\": 0xe06a, /* html:BrowserBack (BrowserBack) -> linux:158 (KEY_BACK) -> atset1:57450 */\n  \"BrowserFavorites\": 0xe066, /* html:BrowserFavorites (BrowserFavorites) -> linux:156 (KEY_BOOKMARKS) -> atset1:57446 */\n  \"BrowserForward\": 0xe069, /* html:BrowserForward (BrowserForward) -> linux:159 (KEY_FORWARD) -> atset1:57449 */\n  \"BrowserHome\": 0xe032, /* html:BrowserHome (BrowserHome) -> linux:172 (KEY_HOMEPAGE) -> atset1:57394 */\n  \"BrowserRefresh\": 0xe067, /* html:BrowserRefresh (BrowserRefresh) -> linux:173 (KEY_REFRESH) -> atset1:57447 */\n  \"BrowserSearch\": 0xe065, /* html:BrowserSearch (BrowserSearch) -> linux:217 (KEY_SEARCH) -> atset1:57445 */\n  \"BrowserStop\": 0xe068, /* html:BrowserStop (BrowserStop) -> linux:128 (KEY_STOP) -> atset1:57448 */\n  \"CapsLock\": 0x3a, /* html:CapsLock (CapsLock) -> linux:58 (KEY_CAPSLOCK) -> atset1:58 */\n  \"Comma\": 0x33, /* html:Comma (Comma) -> linux:51 (KEY_COMMA) -> atset1:51 */\n  \"ContextMenu\": 0xe05d, /* html:ContextMenu (ContextMenu) -> linux:127 (KEY_COMPOSE) -> atset1:57437 */\n  \"ControlLeft\": 0x1d, /* html:ControlLeft (ControlLeft) -> linux:29 (KEY_LEFTCTRL) -> atset1:29 */\n  \"ControlRight\": 0xe01d, /* html:ControlRight (ControlRight) -> linux:97 (KEY_RIGHTCTRL) -> atset1:57373 */\n  \"Convert\": 0x79, /* html:Convert (Convert) -> linux:92 (KEY_HENKAN) -> atset1:121 */\n  \"Copy\": 0xe078, /* html:Copy (Copy) -> linux:133 (KEY_COPY) -> atset1:57464 */\n  \"Cut\": 0xe03c, /* html:Cut (Cut) -> linux:137 (KEY_CUT) -> atset1:57404 */\n  \"Delete\": 0xe053, /* html:Delete (Delete) -> linux:111 (KEY_DELETE) -> atset1:57427 */\n  \"Digit0\": 0xb, /* html:Digit0 (Digit0) -> linux:11 (KEY_0) -> atset1:11 */\n  \"Digit1\": 0x2, /* html:Digit1 (Digit1) -> linux:2 (KEY_1) -> atset1:2 */\n  \"Digit2\": 0x3, /* html:Digit2 (Digit2) -> linux:3 (KEY_2) -> atset1:3 */\n  \"Digit3\": 0x4, /* html:Digit3 (Digit3) -> linux:4 (KEY_3) -> atset1:4 */\n  \"Digit4\": 0x5, /* html:Digit4 (Digit4) -> linux:5 (KEY_4) -> atset1:5 */\n  \"Digit5\": 0x6, /* html:Digit5 (Digit5) -> linux:6 (KEY_5) -> atset1:6 */\n  \"Digit6\": 0x7, /* html:Digit6 (Digit6) -> linux:7 (KEY_6) -> atset1:7 */\n  \"Digit7\": 0x8, /* html:Digit7 (Digit7) -> linux:8 (KEY_7) -> atset1:8 */\n  \"Digit8\": 0x9, /* html:Digit8 (Digit8) -> linux:9 (KEY_8) -> atset1:9 */\n  \"Digit9\": 0xa, /* html:Digit9 (Digit9) -> linux:10 (KEY_9) -> atset1:10 */\n  \"Eject\": 0xe07d, /* html:Eject (Eject) -> linux:162 (KEY_EJECTCLOSECD) -> atset1:57469 */\n  \"End\": 0xe04f, /* html:End (End) -> linux:107 (KEY_END) -> atset1:57423 */\n  \"Enter\": 0x1c, /* html:Enter (Enter) -> linux:28 (KEY_ENTER) -> atset1:28 */\n  \"Equal\": 0xd, /* html:Equal (Equal) -> linux:13 (KEY_EQUAL) -> atset1:13 */\n  \"Escape\": 0x1, /* html:Escape (Escape) -> linux:1 (KEY_ESC) -> atset1:1 */\n  \"F1\": 0x3b, /* html:F1 (F1) -> linux:59 (KEY_F1) -> atset1:59 */\n  \"F10\": 0x44, /* html:F10 (F10) -> linux:68 (KEY_F10) -> atset1:68 */\n  \"F11\": 0x57, /* html:F11 (F11) -> linux:87 (KEY_F11) -> atset1:87 */\n  \"F12\": 0x58, /* html:F12 (F12) -> linux:88 (KEY_F12) -> atset1:88 */\n  \"F13\": 0x5d, /* html:F13 (F13) -> linux:183 (KEY_F13) -> atset1:93 */\n  \"F14\": 0x5e, /* html:F14 (F14) -> linux:184 (KEY_F14) -> atset1:94 */\n  \"F15\": 0x5f, /* html:F15 (F15) -> linux:185 (KEY_F15) -> atset1:95 */\n  \"F16\": 0x55, /* html:F16 (F16) -> linux:186 (KEY_F16) -> atset1:85 */\n  \"F17\": 0xe003, /* html:F17 (F17) -> linux:187 (KEY_F17) -> atset1:57347 */\n  \"F18\": 0xe077, /* html:F18 (F18) -> linux:188 (KEY_F18) -> atset1:57463 */\n  \"F19\": 0xe004, /* html:F19 (F19) -> linux:189 (KEY_F19) -> atset1:57348 */\n  \"F2\": 0x3c, /* html:F2 (F2) -> linux:60 (KEY_F2) -> atset1:60 */\n  \"F20\": 0x5a, /* html:F20 (F20) -> linux:190 (KEY_F20) -> atset1:90 */\n  \"F21\": 0x74, /* html:F21 (F21) -> linux:191 (KEY_F21) -> atset1:116 */\n  \"F22\": 0xe079, /* html:F22 (F22) -> linux:192 (KEY_F22) -> atset1:57465 */\n  \"F23\": 0x6d, /* html:F23 (F23) -> linux:193 (KEY_F23) -> atset1:109 */\n  \"F24\": 0x6f, /* html:F24 (F24) -> linux:194 (KEY_F24) -> atset1:111 */\n  \"F3\": 0x3d, /* html:F3 (F3) -> linux:61 (KEY_F3) -> atset1:61 */\n  \"F4\": 0x3e, /* html:F4 (F4) -> linux:62 (KEY_F4) -> atset1:62 */\n  \"F5\": 0x3f, /* html:F5 (F5) -> linux:63 (KEY_F5) -> atset1:63 */\n  \"F6\": 0x40, /* html:F6 (F6) -> linux:64 (KEY_F6) -> atset1:64 */\n  \"F7\": 0x41, /* html:F7 (F7) -> linux:65 (KEY_F7) -> atset1:65 */\n  \"F8\": 0x42, /* html:F8 (F8) -> linux:66 (KEY_F8) -> atset1:66 */\n  \"F9\": 0x43, /* html:F9 (F9) -> linux:67 (KEY_F9) -> atset1:67 */\n  \"Find\": 0xe041, /* html:Find (Find) -> linux:136 (KEY_FIND) -> atset1:57409 */\n  \"Help\": 0xe075, /* html:Help (Help) -> linux:138 (KEY_HELP) -> atset1:57461 */\n  \"Hiragana\": 0x77, /* html:Hiragana (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */\n  \"Home\": 0xe047, /* html:Home (Home) -> linux:102 (KEY_HOME) -> atset1:57415 */\n  \"Insert\": 0xe052, /* html:Insert (Insert) -> linux:110 (KEY_INSERT) -> atset1:57426 */\n  \"IntlBackslash\": 0x56, /* html:IntlBackslash (IntlBackslash) -> linux:86 (KEY_102ND) -> atset1:86 */\n  \"IntlRo\": 0x73, /* html:IntlRo (IntlRo) -> linux:89 (KEY_RO) -> atset1:115 */\n  \"IntlYen\": 0x7d, /* html:IntlYen (IntlYen) -> linux:124 (KEY_YEN) -> atset1:125 */\n  \"KanaMode\": 0x70, /* html:KanaMode (KanaMode) -> linux:93 (KEY_KATAKANAHIRAGANA) -> atset1:112 */\n  \"Katakana\": 0x78, /* html:Katakana (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */\n  \"KeyA\": 0x1e, /* html:KeyA (KeyA) -> linux:30 (KEY_A) -> atset1:30 */\n  \"KeyB\": 0x30, /* html:KeyB (KeyB) -> linux:48 (KEY_B) -> atset1:48 */\n  \"KeyC\": 0x2e, /* html:KeyC (KeyC) -> linux:46 (KEY_C) -> atset1:46 */\n  \"KeyD\": 0x20, /* html:KeyD (KeyD) -> linux:32 (KEY_D) -> atset1:32 */\n  \"KeyE\": 0x12, /* html:KeyE (KeyE) -> linux:18 (KEY_E) -> atset1:18 */\n  \"KeyF\": 0x21, /* html:KeyF (KeyF) -> linux:33 (KEY_F) -> atset1:33 */\n  \"KeyG\": 0x22, /* html:KeyG (KeyG) -> linux:34 (KEY_G) -> atset1:34 */\n  \"KeyH\": 0x23, /* html:KeyH (KeyH) -> linux:35 (KEY_H) -> atset1:35 */\n  \"KeyI\": 0x17, /* html:KeyI (KeyI) -> linux:23 (KEY_I) -> atset1:23 */\n  \"KeyJ\": 0x24, /* html:KeyJ (KeyJ) -> linux:36 (KEY_J) -> atset1:36 */\n  \"KeyK\": 0x25, /* html:KeyK (KeyK) -> linux:37 (KEY_K) -> atset1:37 */\n  \"KeyL\": 0x26, /* html:KeyL (KeyL) -> linux:38 (KEY_L) -> atset1:38 */\n  \"KeyM\": 0x32, /* html:KeyM (KeyM) -> linux:50 (KEY_M) -> atset1:50 */\n  \"KeyN\": 0x31, /* html:KeyN (KeyN) -> linux:49 (KEY_N) -> atset1:49 */\n  \"KeyO\": 0x18, /* html:KeyO (KeyO) -> linux:24 (KEY_O) -> atset1:24 */\n  \"KeyP\": 0x19, /* html:KeyP (KeyP) -> linux:25 (KEY_P) -> atset1:25 */\n  \"KeyQ\": 0x10, /* html:KeyQ (KeyQ) -> linux:16 (KEY_Q) -> atset1:16 */\n  \"KeyR\": 0x13, /* html:KeyR (KeyR) -> linux:19 (KEY_R) -> atset1:19 */\n  \"KeyS\": 0x1f, /* html:KeyS (KeyS) -> linux:31 (KEY_S) -> atset1:31 */\n  \"KeyT\": 0x14, /* html:KeyT (KeyT) -> linux:20 (KEY_T) -> atset1:20 */\n  \"KeyU\": 0x16, /* html:KeyU (KeyU) -> linux:22 (KEY_U) -> atset1:22 */\n  \"KeyV\": 0x2f, /* html:KeyV (KeyV) -> linux:47 (KEY_V) -> atset1:47 */\n  \"KeyW\": 0x11, /* html:KeyW (KeyW) -> linux:17 (KEY_W) -> atset1:17 */\n  \"KeyX\": 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */\n  \"KeyY\": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */\n  \"KeyZ\": 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */\n  \"Lang1\": 0x72, /* html:Lang1 (Lang1) -> linux:122 (KEY_HANGEUL) -> atset1:114 */\n  \"Lang2\": 0x71, /* html:Lang2 (Lang2) -> linux:123 (KEY_HANJA) -> atset1:113 */\n  \"Lang3\": 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */\n  \"Lang4\": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */\n  \"Lang5\": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */\n  \"LaunchApp1\": 0xe06b, /* html:LaunchApp1 (LaunchApp1) -> linux:157 (KEY_COMPUTER) -> atset1:57451 */\n  \"LaunchApp2\": 0xe021, /* html:LaunchApp2 (LaunchApp2) -> linux:140 (KEY_CALC) -> atset1:57377 */\n  \"LaunchMail\": 0xe06c, /* html:LaunchMail (LaunchMail) -> linux:155 (KEY_MAIL) -> atset1:57452 */\n  \"MediaPlayPause\": 0xe022, /* html:MediaPlayPause (MediaPlayPause) -> linux:164 (KEY_PLAYPAUSE) -> atset1:57378 */\n  \"MediaSelect\": 0xe06d, /* html:MediaSelect (MediaSelect) -> linux:226 (KEY_MEDIA) -> atset1:57453 */\n  \"MediaStop\": 0xe024, /* html:MediaStop (MediaStop) -> linux:166 (KEY_STOPCD) -> atset1:57380 */\n  \"MediaTrackNext\": 0xe019, /* html:MediaTrackNext (MediaTrackNext) -> linux:163 (KEY_NEXTSONG) -> atset1:57369 */\n  \"MediaTrackPrevious\": 0xe010, /* html:MediaTrackPrevious (MediaTrackPrevious) -> linux:165 (KEY_PREVIOUSSONG) -> atset1:57360 */\n  \"MetaLeft\": 0xe05b, /* html:MetaLeft (MetaLeft) -> linux:125 (KEY_LEFTMETA) -> atset1:57435 */\n  \"MetaRight\": 0xe05c, /* html:MetaRight (MetaRight) -> linux:126 (KEY_RIGHTMETA) -> atset1:57436 */\n  \"Minus\": 0xc, /* html:Minus (Minus) -> linux:12 (KEY_MINUS) -> atset1:12 */\n  \"NonConvert\": 0x7b, /* html:NonConvert (NonConvert) -> linux:94 (KEY_MUHENKAN) -> atset1:123 */\n  \"NumLock\": 0x45, /* html:NumLock (NumLock) -> linux:69 (KEY_NUMLOCK) -> atset1:69 */\n  \"Numpad0\": 0x52, /* html:Numpad0 (Numpad0) -> linux:82 (KEY_KP0) -> atset1:82 */\n  \"Numpad1\": 0x4f, /* html:Numpad1 (Numpad1) -> linux:79 (KEY_KP1) -> atset1:79 */\n  \"Numpad2\": 0x50, /* html:Numpad2 (Numpad2) -> linux:80 (KEY_KP2) -> atset1:80 */\n  \"Numpad3\": 0x51, /* html:Numpad3 (Numpad3) -> linux:81 (KEY_KP3) -> atset1:81 */\n  \"Numpad4\": 0x4b, /* html:Numpad4 (Numpad4) -> linux:75 (KEY_KP4) -> atset1:75 */\n  \"Numpad5\": 0x4c, /* html:Numpad5 (Numpad5) -> linux:76 (KEY_KP5) -> atset1:76 */\n  \"Numpad6\": 0x4d, /* html:Numpad6 (Numpad6) -> linux:77 (KEY_KP6) -> atset1:77 */\n  \"Numpad7\": 0x47, /* html:Numpad7 (Numpad7) -> linux:71 (KEY_KP7) -> atset1:71 */\n  \"Numpad8\": 0x48, /* html:Numpad8 (Numpad8) -> linux:72 (KEY_KP8) -> atset1:72 */\n  \"Numpad9\": 0x49, /* html:Numpad9 (Numpad9) -> linux:73 (KEY_KP9) -> atset1:73 */\n  \"NumpadAdd\": 0x4e, /* html:NumpadAdd (NumpadAdd) -> linux:78 (KEY_KPPLUS) -> atset1:78 */\n  \"NumpadComma\": 0x7e, /* html:NumpadComma (NumpadComma) -> linux:121 (KEY_KPCOMMA) -> atset1:126 */\n  \"NumpadDecimal\": 0x53, /* html:NumpadDecimal (NumpadDecimal) -> linux:83 (KEY_KPDOT) -> atset1:83 */\n  \"NumpadDivide\": 0xe035, /* html:NumpadDivide (NumpadDivide) -> linux:98 (KEY_KPSLASH) -> atset1:57397 */\n  \"NumpadEnter\": 0xe01c, /* html:NumpadEnter (NumpadEnter) -> linux:96 (KEY_KPENTER) -> atset1:57372 */\n  \"NumpadEqual\": 0x59, /* html:NumpadEqual (NumpadEqual) -> linux:117 (KEY_KPEQUAL) -> atset1:89 */\n  \"NumpadMultiply\": 0x37, /* html:NumpadMultiply (NumpadMultiply) -> linux:55 (KEY_KPASTERISK) -> atset1:55 */\n  \"NumpadParenLeft\": 0xe076, /* html:NumpadParenLeft (NumpadParenLeft) -> linux:179 (KEY_KPLEFTPAREN) -> atset1:57462 */\n  \"NumpadParenRight\": 0xe07b, /* html:NumpadParenRight (NumpadParenRight) -> linux:180 (KEY_KPRIGHTPAREN) -> atset1:57467 */\n  \"NumpadSubtract\": 0x4a, /* html:NumpadSubtract (NumpadSubtract) -> linux:74 (KEY_KPMINUS) -> atset1:74 */\n  \"Open\": 0x64, /* html:Open (Open) -> linux:134 (KEY_OPEN) -> atset1:100 */\n  \"PageDown\": 0xe051, /* html:PageDown (PageDown) -> linux:109 (KEY_PAGEDOWN) -> atset1:57425 */\n  \"PageUp\": 0xe049, /* html:PageUp (PageUp) -> linux:104 (KEY_PAGEUP) -> atset1:57417 */\n  \"Paste\": 0x65, /* html:Paste (Paste) -> linux:135 (KEY_PASTE) -> atset1:101 */\n  \"Pause\": 0xe046, /* html:Pause (Pause) -> linux:119 (KEY_PAUSE) -> atset1:57414 */\n  \"Period\": 0x34, /* html:Period (Period) -> linux:52 (KEY_DOT) -> atset1:52 */\n  \"Power\": 0xe05e, /* html:Power (Power) -> linux:116 (KEY_POWER) -> atset1:57438 */\n  \"PrintScreen\": 0x54, /* html:PrintScreen (PrintScreen) -> linux:99 (KEY_SYSRQ) -> atset1:84 */\n  \"Props\": 0xe006, /* html:Props (Props) -> linux:130 (KEY_PROPS) -> atset1:57350 */\n  \"Quote\": 0x28, /* html:Quote (Quote) -> linux:40 (KEY_APOSTROPHE) -> atset1:40 */\n  \"ScrollLock\": 0x46, /* html:ScrollLock (ScrollLock) -> linux:70 (KEY_SCROLLLOCK) -> atset1:70 */\n  \"Semicolon\": 0x27, /* html:Semicolon (Semicolon) -> linux:39 (KEY_SEMICOLON) -> atset1:39 */\n  \"ShiftLeft\": 0x2a, /* html:ShiftLeft (ShiftLeft) -> linux:42 (KEY_LEFTSHIFT) -> atset1:42 */\n  \"ShiftRight\": 0x36, /* html:ShiftRight (ShiftRight) -> linux:54 (KEY_RIGHTSHIFT) -> atset1:54 */\n  \"Slash\": 0x35, /* html:Slash (Slash) -> linux:53 (KEY_SLASH) -> atset1:53 */\n  \"Sleep\": 0xe05f, /* html:Sleep (Sleep) -> linux:142 (KEY_SLEEP) -> atset1:57439 */\n  \"Space\": 0x39, /* html:Space (Space) -> linux:57 (KEY_SPACE) -> atset1:57 */\n  \"Suspend\": 0xe025, /* html:Suspend (Suspend) -> linux:205 (KEY_SUSPEND) -> atset1:57381 */\n  \"Tab\": 0xf, /* html:Tab (Tab) -> linux:15 (KEY_TAB) -> atset1:15 */\n  \"Undo\": 0xe007, /* html:Undo (Undo) -> linux:131 (KEY_UNDO) -> atset1:57351 */\n  \"WakeUp\": 0xe063, /* html:WakeUp (WakeUp) -> linux:143 (KEY_WAKEUP) -> atset1:57443 */\n};\n"
  },
  {
    "path": "services/gateway/noVNC/core/ra2.js",
    "content": "import { encodeUTF8 } from './util/strings.js';\nimport EventTargetMixin from './util/eventtarget.js';\nimport legacyCrypto from './crypto/crypto.js';\n\nclass RA2Cipher {\n    constructor() {\n        this._cipher = null;\n        this._counter = new Uint8Array(16);\n    }\n\n    async setKey(key) {\n        this._cipher = await legacyCrypto.importKey(\n            \"raw\", key, { name: \"AES-EAX\" }, false, [\"encrypt, decrypt\"]);\n    }\n\n    async makeMessage(message) {\n        const ad = new Uint8Array([(message.length & 0xff00) >>> 8, message.length & 0xff]);\n        const encrypted = await legacyCrypto.encrypt({\n            name: \"AES-EAX\",\n            iv: this._counter,\n            additionalData: ad,\n        }, this._cipher, message);\n        for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);\n        const res = new Uint8Array(message.length + 2 + 16);\n        res.set(ad);\n        res.set(encrypted, 2);\n        return res;\n    }\n\n    async receiveMessage(length, encrypted) {\n        const ad = new Uint8Array([(length & 0xff00) >>> 8, length & 0xff]);\n        const res = await legacyCrypto.decrypt({\n            name: \"AES-EAX\",\n            iv: this._counter,\n            additionalData: ad,\n        }, this._cipher, encrypted);\n        for (let i = 0; i < 16 && this._counter[i]++ === 255; i++);\n        return res;\n    }\n}\n\nexport default class RSAAESAuthenticationState extends EventTargetMixin {\n    constructor(sock, getCredentials) {\n        super();\n        this._hasStarted = false;\n        this._checkSock = null;\n        this._checkCredentials = null;\n        this._approveServerResolve = null;\n        this._sockReject = null;\n        this._credentialsReject = null;\n        this._approveServerReject = null;\n        this._sock = sock;\n        this._getCredentials = getCredentials;\n    }\n\n    _waitSockAsync(len) {\n        return new Promise((resolve, reject) => {\n            const hasData = () => !this._sock.rQwait('RA2', len);\n            if (hasData()) {\n                resolve();\n            } else {\n                this._checkSock = () => {\n                    if (hasData()) {\n                        resolve();\n                        this._checkSock = null;\n                        this._sockReject = null;\n                    }\n                };\n                this._sockReject = reject;\n            }\n        });\n    }\n\n    _waitApproveKeyAsync() {\n        return new Promise((resolve, reject) => {\n            this._approveServerResolve = resolve;\n            this._approveServerReject = reject;\n        });\n    }\n\n    _waitCredentialsAsync(subtype) {\n        const hasCredentials = () => {\n            if (subtype === 1 && this._getCredentials().username !== undefined &&\n                this._getCredentials().password !== undefined) {\n                return true;\n            } else if (subtype === 2 && this._getCredentials().password !== undefined) {\n                return true;\n            }\n            return false;\n        };\n        return new Promise((resolve, reject) => {\n            if (hasCredentials()) {\n                resolve();\n            } else {\n                this._checkCredentials = () => {\n                    if (hasCredentials()) {\n                        resolve();\n                        this._checkCredentials = null;\n                        this._credentialsReject = null;\n                    }\n                };\n                this._credentialsReject = reject;\n            }\n        });\n    }\n\n    checkInternalEvents() {\n        if (this._checkSock !== null) {\n            this._checkSock();\n        }\n        if (this._checkCredentials !== null) {\n            this._checkCredentials();\n        }\n    }\n\n    approveServer() {\n        if (this._approveServerResolve !== null) {\n            this._approveServerResolve();\n            this._approveServerResolve = null;\n        }\n    }\n\n    disconnect() {\n        if (this._sockReject !== null) {\n            this._sockReject(new Error(\"disconnect normally\"));\n            this._sockReject = null;\n        }\n        if (this._credentialsReject !== null) {\n            this._credentialsReject(new Error(\"disconnect normally\"));\n            this._credentialsReject = null;\n        }\n        if (this._approveServerReject !== null) {\n            this._approveServerReject(new Error(\"disconnect normally\"));\n            this._approveServerReject = null;\n        }\n    }\n\n    async negotiateRA2neAuthAsync() {\n        this._hasStarted = true;\n        // 1: Receive server public key\n        await this._waitSockAsync(4);\n        const serverKeyLengthBuffer = this._sock.rQpeekBytes(4);\n        const serverKeyLength = this._sock.rQshift32();\n        if (serverKeyLength < 1024) {\n            throw new Error(\"RA2: server public key is too short: \" + serverKeyLength);\n        } else if (serverKeyLength > 8192) {\n            throw new Error(\"RA2: server public key is too long: \" + serverKeyLength);\n        }\n        const serverKeyBytes = Math.ceil(serverKeyLength / 8);\n        await this._waitSockAsync(serverKeyBytes * 2);\n        const serverN = this._sock.rQshiftBytes(serverKeyBytes);\n        const serverE = this._sock.rQshiftBytes(serverKeyBytes);\n        const serverRSACipher = await legacyCrypto.importKey(\n            \"raw\", { n: serverN, e: serverE }, { name: \"RSA-PKCS1-v1_5\" }, false, [\"encrypt\"]);\n        const serverPublickey = new Uint8Array(4 + serverKeyBytes * 2);\n        serverPublickey.set(serverKeyLengthBuffer);\n        serverPublickey.set(serverN, 4);\n        serverPublickey.set(serverE, 4 + serverKeyBytes);\n\n        // verify server public key\n        let approveKey = this._waitApproveKeyAsync();\n        this.dispatchEvent(new CustomEvent(\"serververification\", {\n            detail: { type: \"RSA\", publickey: serverPublickey }\n        }));\n        await approveKey;\n\n        // 2: Send client public key\n        const clientKeyLength = 2048;\n        const clientKeyBytes = Math.ceil(clientKeyLength / 8);\n        const clientRSACipher = (await legacyCrypto.generateKey({\n            name: \"RSA-PKCS1-v1_5\",\n            modulusLength: clientKeyLength,\n            publicExponent: new Uint8Array([1, 0, 1]),\n        }, true, [\"encrypt\"])).privateKey;\n        const clientExportedRSAKey = await legacyCrypto.exportKey(\"raw\", clientRSACipher);\n        const clientN = clientExportedRSAKey.n;\n        const clientE = clientExportedRSAKey.e;\n        const clientPublicKey = new Uint8Array(4 + clientKeyBytes * 2);\n        clientPublicKey[0] = (clientKeyLength & 0xff000000) >>> 24;\n        clientPublicKey[1] = (clientKeyLength & 0xff0000) >>> 16;\n        clientPublicKey[2] = (clientKeyLength & 0xff00) >>> 8;\n        clientPublicKey[3] = clientKeyLength & 0xff;\n        clientPublicKey.set(clientN, 4);\n        clientPublicKey.set(clientE, 4 + clientKeyBytes);\n        this._sock.sQpushBytes(clientPublicKey);\n        this._sock.flush();\n\n        // 3: Send client random\n        const clientRandom = new Uint8Array(16);\n        window.crypto.getRandomValues(clientRandom);\n        const clientEncryptedRandom = await legacyCrypto.encrypt(\n            { name: \"RSA-PKCS1-v1_5\" }, serverRSACipher, clientRandom);\n        const clientRandomMessage = new Uint8Array(2 + serverKeyBytes);\n        clientRandomMessage[0] = (serverKeyBytes & 0xff00) >>> 8;\n        clientRandomMessage[1] = serverKeyBytes & 0xff;\n        clientRandomMessage.set(clientEncryptedRandom, 2);\n        this._sock.sQpushBytes(clientRandomMessage);\n        this._sock.flush();\n\n        // 4: Receive server random\n        await this._waitSockAsync(2);\n        if (this._sock.rQshift16() !== clientKeyBytes) {\n            throw new Error(\"RA2: wrong encrypted message length\");\n        }\n        const serverEncryptedRandom = this._sock.rQshiftBytes(clientKeyBytes);\n        const serverRandom = await legacyCrypto.decrypt(\n            { name: \"RSA-PKCS1-v1_5\" }, clientRSACipher, serverEncryptedRandom);\n        if (serverRandom === null || serverRandom.length !== 16) {\n            throw new Error(\"RA2: corrupted server encrypted random\");\n        }\n\n        // 5: Compute session keys and set ciphers\n        let clientSessionKey = new Uint8Array(32);\n        let serverSessionKey = new Uint8Array(32);\n        clientSessionKey.set(serverRandom);\n        clientSessionKey.set(clientRandom, 16);\n        serverSessionKey.set(clientRandom);\n        serverSessionKey.set(serverRandom, 16);\n        clientSessionKey = await window.crypto.subtle.digest(\"SHA-1\", clientSessionKey);\n        clientSessionKey = new Uint8Array(clientSessionKey).slice(0, 16);\n        serverSessionKey = await window.crypto.subtle.digest(\"SHA-1\", serverSessionKey);\n        serverSessionKey = new Uint8Array(serverSessionKey).slice(0, 16);\n        const clientCipher = new RA2Cipher();\n        await clientCipher.setKey(clientSessionKey);\n        const serverCipher = new RA2Cipher();\n        await serverCipher.setKey(serverSessionKey);\n\n        // 6: Compute and exchange hashes\n        let serverHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);\n        let clientHash = new Uint8Array(8 + serverKeyBytes * 2 + clientKeyBytes * 2);\n        serverHash.set(serverPublickey);\n        serverHash.set(clientPublicKey, 4 + serverKeyBytes * 2);\n        clientHash.set(clientPublicKey);\n        clientHash.set(serverPublickey, 4 + clientKeyBytes * 2);\n        serverHash = await window.crypto.subtle.digest(\"SHA-1\", serverHash);\n        clientHash = await window.crypto.subtle.digest(\"SHA-1\", clientHash);\n        serverHash = new Uint8Array(serverHash);\n        clientHash = new Uint8Array(clientHash);\n        this._sock.sQpushBytes(await clientCipher.makeMessage(clientHash));\n        this._sock.flush();\n        await this._waitSockAsync(2 + 20 + 16);\n        if (this._sock.rQshift16() !== 20) {\n            throw new Error(\"RA2: wrong server hash\");\n        }\n        const serverHashReceived = await serverCipher.receiveMessage(\n            20, this._sock.rQshiftBytes(20 + 16));\n        if (serverHashReceived === null) {\n            throw new Error(\"RA2: failed to authenticate the message\");\n        }\n        for (let i = 0; i < 20; i++) {\n            if (serverHashReceived[i] !== serverHash[i]) {\n                throw new Error(\"RA2: wrong server hash\");\n            }\n        }\n\n        // 7: Receive subtype\n        await this._waitSockAsync(2 + 1 + 16);\n        if (this._sock.rQshift16() !== 1) {\n            throw new Error(\"RA2: wrong subtype\");\n        }\n        let subtype = (await serverCipher.receiveMessage(\n            1, this._sock.rQshiftBytes(1 + 16)));\n        if (subtype === null) {\n            throw new Error(\"RA2: failed to authenticate the message\");\n        }\n        subtype = subtype[0];\n        let waitCredentials = this._waitCredentialsAsync(subtype);\n        if (subtype === 1) {\n            if (this._getCredentials().username === undefined ||\n                this._getCredentials().password === undefined) {\n                this.dispatchEvent(new CustomEvent(\n                    \"credentialsrequired\",\n                    { detail: { types: [\"username\", \"password\"] } }));\n            }\n        } else if (subtype === 2) {\n            if (this._getCredentials().password === undefined) {\n                this.dispatchEvent(new CustomEvent(\n                    \"credentialsrequired\",\n                    { detail: { types: [\"password\"] } }));\n            }\n        } else {\n            throw new Error(\"RA2: wrong subtype\");\n        }\n        await waitCredentials;\n        let username;\n        if (subtype === 1) {\n            username = encodeUTF8(this._getCredentials().username).slice(0, 255);\n        } else {\n            username = \"\";\n        }\n        const password = encodeUTF8(this._getCredentials().password).slice(0, 255);\n        const credentials = new Uint8Array(username.length + password.length + 2);\n        credentials[0] = username.length;\n        credentials[username.length + 1] = password.length;\n        for (let i = 0; i < username.length; i++) {\n            credentials[i + 1] = username.charCodeAt(i);\n        }\n        for (let i = 0; i < password.length; i++) {\n            credentials[username.length + 2 + i] = password.charCodeAt(i);\n        }\n        this._sock.sQpushBytes(await clientCipher.makeMessage(credentials));\n        this._sock.flush();\n    }\n\n    get hasStarted() {\n        return this._hasStarted;\n    }\n\n    set hasStarted(s) {\n        this._hasStarted = s;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/rfb.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n */\n\nimport { toUnsigned32bit, toSigned32bit } from './util/int.js';\nimport * as Log from './util/logging.js';\nimport { encodeUTF8, decodeUTF8 } from './util/strings.js';\nimport { dragThreshold, supportsWebCodecsH264Decode } from './util/browser.js';\nimport { clientToElement } from './util/element.js';\nimport { setCapture } from './util/events.js';\nimport EventTargetMixin from './util/eventtarget.js';\nimport Display from \"./display.js\";\nimport Inflator from \"./inflator.js\";\nimport Deflator from \"./deflator.js\";\nimport Keyboard from \"./input/keyboard.js\";\nimport GestureHandler from \"./input/gesturehandler.js\";\nimport Cursor from \"./util/cursor.js\";\nimport Websock from \"./websock.js\";\nimport KeyTable from \"./input/keysym.js\";\nimport XtScancode from \"./input/xtscancodes.js\";\nimport { encodings } from \"./encodings.js\";\nimport RSAAESAuthenticationState from \"./ra2.js\";\nimport legacyCrypto from \"./crypto/crypto.js\";\n\nimport RawDecoder from \"./decoders/raw.js\";\nimport CopyRectDecoder from \"./decoders/copyrect.js\";\nimport RREDecoder from \"./decoders/rre.js\";\nimport HextileDecoder from \"./decoders/hextile.js\";\nimport ZlibDecoder from './decoders/zlib.js';\nimport TightDecoder from \"./decoders/tight.js\";\nimport TightPNGDecoder from \"./decoders/tightpng.js\";\nimport ZRLEDecoder from \"./decoders/zrle.js\";\nimport JPEGDecoder from \"./decoders/jpeg.js\";\nimport H264Decoder from \"./decoders/h264.js\";\n\n// How many seconds to wait for a disconnect to finish\nconst DISCONNECT_TIMEOUT = 3;\nconst DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';\n\n// Minimum wait (ms) between two mouse moves\nconst MOUSE_MOVE_DELAY = 17;\n\n// Wheel thresholds\nconst WHEEL_STEP = 50; // Pixels needed for one step\nconst WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step\n\n// Gesture thresholds\nconst GESTURE_ZOOMSENS = 75;\nconst GESTURE_SCRLSENS = 50;\nconst DOUBLE_TAP_TIMEOUT = 1000;\nconst DOUBLE_TAP_THRESHOLD = 50;\n\n// Security types\nconst securityTypeNone              = 1;\nconst securityTypeVNCAuth           = 2;\nconst securityTypeRA2ne             = 6;\nconst securityTypeTight             = 16;\nconst securityTypeVeNCrypt          = 19;\nconst securityTypeXVP               = 22;\nconst securityTypeARD               = 30;\nconst securityTypeMSLogonII         = 113;\n\n// Special Tight security types\nconst securityTypeUnixLogon         = 129;\n\n// VeNCrypt security types\nconst securityTypePlain             = 256;\n\n// Extended clipboard pseudo-encoding formats\nconst extendedClipboardFormatText   = 1;\n/*eslint-disable no-unused-vars */\nconst extendedClipboardFormatRtf    = 1 << 1;\nconst extendedClipboardFormatHtml   = 1 << 2;\nconst extendedClipboardFormatDib    = 1 << 3;\nconst extendedClipboardFormatFiles  = 1 << 4;\n/*eslint-enable */\n\n// Extended clipboard pseudo-encoding actions\nconst extendedClipboardActionCaps    = 1 << 24;\nconst extendedClipboardActionRequest = 1 << 25;\nconst extendedClipboardActionPeek    = 1 << 26;\nconst extendedClipboardActionNotify  = 1 << 27;\nconst extendedClipboardActionProvide = 1 << 28;\n\nexport default class RFB extends EventTargetMixin {\n    constructor(target, urlOrChannel, options) {\n        if (!target) {\n            throw new Error(\"Must specify target\");\n        }\n        if (!urlOrChannel) {\n            throw new Error(\"Must specify URL, WebSocket or RTCDataChannel\");\n        }\n\n        // We rely on modern APIs which might not be available in an\n        // insecure context\n        if (!window.isSecureContext) {\n            Log.Error(\"noVNC requires a secure context (TLS). Expect crashes!\");\n        }\n\n        super();\n\n        this._target = target;\n\n        if (typeof urlOrChannel === \"string\") {\n            this._url = urlOrChannel;\n        } else {\n            this._url = null;\n            this._rawChannel = urlOrChannel;\n        }\n\n        // Connection details\n        options = options || {};\n        this._rfbCredentials = options.credentials || {};\n        this._shared = 'shared' in options ? !!options.shared : true;\n        this._repeaterID = options.repeaterID || '';\n        this._wsProtocols = options.wsProtocols || [];\n\n        // Internal state\n        this._rfbConnectionState = '';\n        this._rfbInitState = '';\n        this._rfbAuthScheme = -1;\n        this._rfbCleanDisconnect = true;\n        this._rfbRSAAESAuthenticationState = null;\n\n        // Server capabilities\n        this._rfbVersion = 0;\n        this._rfbMaxVersion = 3.8;\n        this._rfbTightVNC = false;\n        this._rfbVeNCryptState = 0;\n        this._rfbXvpVer = 0;\n\n        this._fbWidth = 0;\n        this._fbHeight = 0;\n\n        this._fbName = \"\";\n\n        this._capabilities = { power: false };\n\n        this._supportsFence = false;\n\n        this._supportsContinuousUpdates = false;\n        this._enabledContinuousUpdates = false;\n\n        this._supportsSetDesktopSize = false;\n        this._screenID = 0;\n        this._screenFlags = 0;\n        this._pendingRemoteResize = false;\n        this._lastResize = 0;\n\n        this._qemuExtKeyEventSupported = false;\n\n        this._extendedPointerEventSupported = false;\n\n        this._clipboardText = null;\n        this._clipboardServerCapabilitiesActions = {};\n        this._clipboardServerCapabilitiesFormats = {};\n\n        // Internal objects\n        this._sock = null;              // Websock object\n        this._display = null;           // Display object\n        this._flushing = false;         // Display flushing state\n        this._keyboard = null;          // Keyboard input handler object\n        this._gestures = null;          // Gesture input handler object\n        this._resizeObserver = null;    // Resize observer object\n\n        // Timers\n        this._disconnTimer = null;      // disconnection timer\n        this._resizeTimeout = null;     // resize rate limiting\n        this._mouseMoveTimer = null;\n\n        // Decoder states\n        this._decoders = {};\n\n        this._FBU = {\n            rects: 0,\n            x: 0,\n            y: 0,\n            width: 0,\n            height: 0,\n            encoding: null,\n        };\n\n        // Mouse state\n        this._mousePos = {};\n        this._mouseButtonMask = 0;\n        this._mouseLastMoveTime = 0;\n        this._viewportDragging = false;\n        this._viewportDragPos = {};\n        this._viewportHasMoved = false;\n        this._accumulatedWheelDeltaX = 0;\n        this._accumulatedWheelDeltaY = 0;\n\n        // Gesture state\n        this._gestureLastTapTime = null;\n        this._gestureFirstDoubleTapEv = null;\n        this._gestureLastMagnitudeX = 0;\n        this._gestureLastMagnitudeY = 0;\n\n        // Bound event handlers\n        this._eventHandlers = {\n            focusCanvas: this._focusCanvas.bind(this),\n            handleResize: this._handleResize.bind(this),\n            handleMouse: this._handleMouse.bind(this),\n            handleWheel: this._handleWheel.bind(this),\n            handleGesture: this._handleGesture.bind(this),\n            handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),\n            handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),\n        };\n\n        // main setup\n        Log.Debug(\">> RFB.constructor\");\n\n        // Create DOM elements\n        this._screen = document.createElement('div');\n        this._screen.style.display = 'flex';\n        this._screen.style.width = '100%';\n        this._screen.style.height = '100%';\n        this._screen.style.overflow = 'auto';\n        this._screen.style.background = DEFAULT_BACKGROUND;\n        this._canvas = document.createElement('canvas');\n        this._canvas.style.margin = 'auto';\n        // Some browsers add an outline on focus\n        this._canvas.style.outline = 'none';\n        this._canvas.width = 0;\n        this._canvas.height = 0;\n        this._canvas.tabIndex = -1;\n        this._screen.appendChild(this._canvas);\n\n        // Cursor\n        this._cursor = new Cursor();\n\n        // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes\n        // it. Result: no cursor at all until a window border or an edit field\n        // is hit blindly. But there are also VNC servers that draw the cursor\n        // in the framebuffer and don't send the empty local cursor. There is\n        // no way to satisfy both sides.\n        //\n        // The spec is unclear on this \"initial cursor\" issue. Many other\n        // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the\n        // initial cursor instead.\n        this._cursorImage = RFB.cursors.none;\n\n        // populate decoder array with objects\n        this._decoders[encodings.encodingRaw] = new RawDecoder();\n        this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();\n        this._decoders[encodings.encodingRRE] = new RREDecoder();\n        this._decoders[encodings.encodingHextile] = new HextileDecoder();\n        this._decoders[encodings.encodingZlib] = new ZlibDecoder();\n        this._decoders[encodings.encodingTight] = new TightDecoder();\n        this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();\n        this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();\n        this._decoders[encodings.encodingJPEG] = new JPEGDecoder();\n        this._decoders[encodings.encodingH264] = new H264Decoder();\n\n        // NB: nothing that needs explicit teardown should be done\n        // before this point, since this can throw an exception\n        try {\n            this._display = new Display(this._canvas);\n        } catch (exc) {\n            Log.Error(\"Display exception: \" + exc);\n            throw exc;\n        }\n\n        this._keyboard = new Keyboard(this._canvas);\n        this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);\n        this._remoteCapsLock = null; // Null indicates unknown or irrelevant\n        this._remoteNumLock = null;\n\n        this._gestures = new GestureHandler();\n\n        this._sock = new Websock();\n        this._sock.on('open', this._socketOpen.bind(this));\n        this._sock.on('close', this._socketClose.bind(this));\n        this._sock.on('message', this._handleMessage.bind(this));\n        this._sock.on('error', this._socketError.bind(this));\n\n        this._expectedClientWidth = null;\n        this._expectedClientHeight = null;\n        this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);\n\n        // All prepared, kick off the connection\n        this._updateConnectionState('connecting');\n\n        Log.Debug(\"<< RFB.constructor\");\n\n        // ===== PROPERTIES =====\n\n        this.dragViewport = false;\n        this.focusOnClick = true;\n\n        this._viewOnly = false;\n        this._clipViewport = false;\n        this._clippingViewport = false;\n        this._scaleViewport = false;\n        this._resizeSession = false;\n\n        this._showDotCursor = false;\n        if (options.showDotCursor !== undefined) {\n            Log.Warn(\"Specifying showDotCursor as a RFB constructor argument is deprecated\");\n            this._showDotCursor = options.showDotCursor;\n        }\n\n        this._qualityLevel = 6;\n        this._compressionLevel = 2;\n    }\n\n    // ===== PROPERTIES =====\n\n    get viewOnly() { return this._viewOnly; }\n    set viewOnly(viewOnly) {\n        this._viewOnly = viewOnly;\n\n        if (this._rfbConnectionState === \"connecting\" ||\n            this._rfbConnectionState === \"connected\") {\n            if (viewOnly) {\n                this._keyboard.ungrab();\n            } else {\n                this._keyboard.grab();\n            }\n        }\n    }\n\n    get capabilities() { return this._capabilities; }\n\n    get clippingViewport() { return this._clippingViewport; }\n    _setClippingViewport(on) {\n        if (on === this._clippingViewport) {\n            return;\n        }\n        this._clippingViewport = on;\n        this.dispatchEvent(new CustomEvent(\"clippingviewport\",\n                                           { detail: this._clippingViewport }));\n    }\n\n    get touchButton() { return 0; }\n    set touchButton(button) { Log.Warn(\"Using old API!\"); }\n\n    get clipViewport() { return this._clipViewport; }\n    set clipViewport(viewport) {\n        this._clipViewport = viewport;\n        this._updateClip();\n    }\n\n    get scaleViewport() { return this._scaleViewport; }\n    set scaleViewport(scale) {\n        this._scaleViewport = scale;\n        // Scaling trumps clipping, so we may need to adjust\n        // clipping when enabling or disabling scaling\n        if (scale && this._clipViewport) {\n            this._updateClip();\n        }\n        this._updateScale();\n        if (!scale && this._clipViewport) {\n            this._updateClip();\n        }\n    }\n\n    get resizeSession() { return this._resizeSession; }\n    set resizeSession(resize) {\n        this._resizeSession = resize;\n        if (resize) {\n            this._requestRemoteResize();\n        }\n    }\n\n    get showDotCursor() { return this._showDotCursor; }\n    set showDotCursor(show) {\n        this._showDotCursor = show;\n        this._refreshCursor();\n    }\n\n    get background() { return this._screen.style.background; }\n    set background(cssValue) { this._screen.style.background = cssValue; }\n\n    get qualityLevel() {\n        return this._qualityLevel;\n    }\n    set qualityLevel(qualityLevel) {\n        if (!Number.isInteger(qualityLevel) || qualityLevel < 0 || qualityLevel > 9) {\n            Log.Error(\"qualityLevel must be an integer between 0 and 9\");\n            return;\n        }\n\n        if (this._qualityLevel === qualityLevel) {\n            return;\n        }\n\n        this._qualityLevel = qualityLevel;\n\n        if (this._rfbConnectionState === 'connected') {\n            this._sendEncodings();\n        }\n    }\n\n    get compressionLevel() {\n        return this._compressionLevel;\n    }\n    set compressionLevel(compressionLevel) {\n        if (!Number.isInteger(compressionLevel) || compressionLevel < 0 || compressionLevel > 9) {\n            Log.Error(\"compressionLevel must be an integer between 0 and 9\");\n            return;\n        }\n\n        if (this._compressionLevel === compressionLevel) {\n            return;\n        }\n\n        this._compressionLevel = compressionLevel;\n\n        if (this._rfbConnectionState === 'connected') {\n            this._sendEncodings();\n        }\n    }\n\n    // ===== PUBLIC METHODS =====\n\n    disconnect() {\n        this._updateConnectionState('disconnecting');\n        this._sock.off('error');\n        this._sock.off('message');\n        this._sock.off('open');\n        if (this._rfbRSAAESAuthenticationState !== null) {\n            this._rfbRSAAESAuthenticationState.disconnect();\n        }\n    }\n\n    approveServer() {\n        if (this._rfbRSAAESAuthenticationState !== null) {\n            this._rfbRSAAESAuthenticationState.approveServer();\n        }\n    }\n\n    sendCredentials(creds) {\n        this._rfbCredentials = creds;\n        this._resumeAuthentication();\n    }\n\n    sendCtrlAltDel() {\n        if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }\n        Log.Info(\"Sending Ctrl-Alt-Del\");\n\n        this.sendKey(KeyTable.XK_Control_L, \"ControlLeft\", true);\n        this.sendKey(KeyTable.XK_Alt_L, \"AltLeft\", true);\n        this.sendKey(KeyTable.XK_Delete, \"Delete\", true);\n        this.sendKey(KeyTable.XK_Delete, \"Delete\", false);\n        this.sendKey(KeyTable.XK_Alt_L, \"AltLeft\", false);\n        this.sendKey(KeyTable.XK_Control_L, \"ControlLeft\", false);\n    }\n\n    machineShutdown() {\n        this._xvpOp(1, 2);\n    }\n\n    machineReboot() {\n        this._xvpOp(1, 3);\n    }\n\n    machineReset() {\n        this._xvpOp(1, 4);\n    }\n\n    // Send a key press. If 'down' is not specified then send a down key\n    // followed by an up key.\n    sendKey(keysym, code, down) {\n        if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }\n\n        if (down === undefined) {\n            this.sendKey(keysym, code, true);\n            this.sendKey(keysym, code, false);\n            return;\n        }\n\n        const scancode = XtScancode[code];\n\n        if (this._qemuExtKeyEventSupported && scancode) {\n            // 0 is NoSymbol\n            keysym = keysym || 0;\n\n            Log.Info(\"Sending key (\" + (down ? \"down\" : \"up\") + \"): keysym \" + keysym + \", scancode \" + scancode);\n\n            RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);\n        } else {\n            if (!keysym) {\n                return;\n            }\n            Log.Info(\"Sending keysym (\" + (down ? \"down\" : \"up\") + \"): \" + keysym);\n            RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);\n        }\n    }\n\n    focus(options) {\n        this._canvas.focus(options);\n    }\n\n    blur() {\n        this._canvas.blur();\n    }\n\n    clipboardPasteFrom(text) {\n        if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }\n\n        if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] &&\n            this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {\n\n            this._clipboardText = text;\n            RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);\n        } else {\n            let length, i;\n            let data;\n\n            length = 0;\n            // eslint-disable-next-line no-unused-vars\n            for (let codePoint of text) {\n                length++;\n            }\n\n            data = new Uint8Array(length);\n\n            i = 0;\n            for (let codePoint of text) {\n                let code = codePoint.codePointAt(0);\n\n                /* Only ISO 8859-1 is supported */\n                if (code > 0xff) {\n                    code = 0x3f; // '?'\n                }\n\n                data[i++] = code;\n            }\n\n            RFB.messages.clientCutText(this._sock, data);\n        }\n    }\n\n    getImageData() {\n        return this._display.getImageData();\n    }\n\n    toDataURL(type, encoderOptions) {\n        return this._display.toDataURL(type, encoderOptions);\n    }\n\n    toBlob(callback, type, quality) {\n        return this._display.toBlob(callback, type, quality);\n    }\n\n    // ===== PRIVATE METHODS =====\n\n    _connect() {\n        Log.Debug(\">> RFB.connect\");\n\n        if (this._url) {\n            Log.Info(`connecting to ${this._url}`);\n            this._sock.open(this._url, this._wsProtocols);\n        } else {\n            Log.Info(`attaching ${this._rawChannel} to Websock`);\n            this._sock.attach(this._rawChannel);\n\n            if (this._sock.readyState === 'closed') {\n                throw Error(\"Cannot use already closed WebSocket/RTCDataChannel\");\n            }\n\n            if (this._sock.readyState === 'open') {\n                // FIXME: _socketOpen() can in theory call _fail(), which\n                //        isn't allowed this early, but I'm not sure that can\n                //        happen without a bug messing up our state variables\n                this._socketOpen();\n            }\n        }\n\n        // Make our elements part of the page\n        this._target.appendChild(this._screen);\n\n        this._gestures.attach(this._canvas);\n\n        this._cursor.attach(this._canvas);\n        this._refreshCursor();\n\n        // Monitor size changes of the screen element\n        this._resizeObserver.observe(this._screen);\n\n        // Always grab focus on some kind of click event\n        this._canvas.addEventListener(\"mousedown\", this._eventHandlers.focusCanvas);\n        this._canvas.addEventListener(\"touchstart\", this._eventHandlers.focusCanvas);\n\n        // Mouse events\n        this._canvas.addEventListener('mousedown', this._eventHandlers.handleMouse);\n        this._canvas.addEventListener('mouseup', this._eventHandlers.handleMouse);\n        this._canvas.addEventListener('mousemove', this._eventHandlers.handleMouse);\n        // Prevent middle-click pasting (see handler for why we bind to document)\n        this._canvas.addEventListener('click', this._eventHandlers.handleMouse);\n        // preventDefault() on mousedown doesn't stop this event for some\n        // reason so we have to explicitly block it\n        this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);\n\n        // Wheel events\n        this._canvas.addEventListener(\"wheel\", this._eventHandlers.handleWheel);\n\n        // Gesture events\n        this._canvas.addEventListener(\"gesturestart\", this._eventHandlers.handleGesture);\n        this._canvas.addEventListener(\"gesturemove\", this._eventHandlers.handleGesture);\n        this._canvas.addEventListener(\"gestureend\", this._eventHandlers.handleGesture);\n\n        Log.Debug(\"<< RFB.connect\");\n    }\n\n    _disconnect() {\n        Log.Debug(\">> RFB.disconnect\");\n        this._cursor.detach();\n        this._canvas.removeEventListener(\"gesturestart\", this._eventHandlers.handleGesture);\n        this._canvas.removeEventListener(\"gesturemove\", this._eventHandlers.handleGesture);\n        this._canvas.removeEventListener(\"gestureend\", this._eventHandlers.handleGesture);\n        this._canvas.removeEventListener(\"wheel\", this._eventHandlers.handleWheel);\n        this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse);\n        this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse);\n        this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);\n        this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);\n        this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);\n        this._canvas.removeEventListener(\"mousedown\", this._eventHandlers.focusCanvas);\n        this._canvas.removeEventListener(\"touchstart\", this._eventHandlers.focusCanvas);\n        this._resizeObserver.disconnect();\n        this._keyboard.ungrab();\n        this._gestures.detach();\n        this._sock.close();\n        try {\n            this._target.removeChild(this._screen);\n        } catch (e) {\n            if (e.name === 'NotFoundError') {\n                // Some cases where the initial connection fails\n                // can disconnect before the _screen is created\n            } else {\n                throw e;\n            }\n        }\n        clearTimeout(this._resizeTimeout);\n        clearTimeout(this._mouseMoveTimer);\n        Log.Debug(\"<< RFB.disconnect\");\n    }\n\n    _socketOpen() {\n        if ((this._rfbConnectionState === 'connecting') &&\n            (this._rfbInitState === '')) {\n            this._rfbInitState = 'ProtocolVersion';\n            Log.Debug(\"Starting VNC handshake\");\n        } else {\n            this._fail(\"Unexpected server connection while \" +\n                       this._rfbConnectionState);\n        }\n    }\n\n    _socketClose(e) {\n        Log.Debug(\"WebSocket on-close event\");\n        let msg = \"\";\n        if (e.code) {\n            msg = \"(code: \" + e.code;\n            if (e.reason) {\n                msg += \", reason: \" + e.reason;\n            }\n            msg += \")\";\n        }\n        switch (this._rfbConnectionState) {\n            case 'connecting':\n                this._fail(\"Connection closed \" + msg);\n                break;\n            case 'connected':\n                // Handle disconnects that were initiated server-side\n                this._updateConnectionState('disconnecting');\n                this._updateConnectionState('disconnected');\n                break;\n            case 'disconnecting':\n                // Normal disconnection path\n                this._updateConnectionState('disconnected');\n                break;\n            case 'disconnected':\n                this._fail(\"Unexpected server disconnect \" +\n                           \"when already disconnected \" + msg);\n                break;\n            default:\n                this._fail(\"Unexpected server disconnect before connecting \" +\n                           msg);\n                break;\n        }\n        this._sock.off('close');\n        // Delete reference to raw channel to allow cleanup.\n        this._rawChannel = null;\n    }\n\n    _socketError(e) {\n        Log.Warn(\"WebSocket on-error event\");\n    }\n\n    _focusCanvas(event) {\n        if (!this.focusOnClick) {\n            return;\n        }\n\n        this.focus({ preventScroll: true });\n    }\n\n    _setDesktopName(name) {\n        this._fbName = name;\n        this.dispatchEvent(new CustomEvent(\n            \"desktopname\",\n            { detail: { name: this._fbName } }));\n    }\n\n    _saveExpectedClientSize() {\n        this._expectedClientWidth = this._screen.clientWidth;\n        this._expectedClientHeight = this._screen.clientHeight;\n    }\n\n    _currentClientSize() {\n        return [this._screen.clientWidth, this._screen.clientHeight];\n    }\n\n    _clientHasExpectedSize() {\n        const [currentWidth, currentHeight] = this._currentClientSize();\n        return currentWidth == this._expectedClientWidth &&\n            currentHeight == this._expectedClientHeight;\n    }\n\n    // Handle browser window resizes\n    _handleResize() {\n        // Don't change anything if the client size is already as expected\n        if (this._clientHasExpectedSize()) {\n            return;\n        }\n        // If the window resized then our screen element might have\n        // as well. Update the viewport dimensions.\n        window.requestAnimationFrame(() => {\n            this._updateClip();\n            this._updateScale();\n            this._saveExpectedClientSize();\n        });\n\n        // Request changing the resolution of the remote display to\n        // the size of the local browser viewport.\n        this._requestRemoteResize();\n    }\n\n    // Update state of clipping in Display object, and make sure the\n    // configured viewport matches the current screen size\n    _updateClip() {\n        const curClip = this._display.clipViewport;\n        let newClip = this._clipViewport;\n\n        if (this._scaleViewport) {\n            // Disable viewport clipping if we are scaling\n            newClip = false;\n        }\n\n        if (curClip !== newClip) {\n            this._display.clipViewport = newClip;\n        }\n\n        if (newClip) {\n            // When clipping is enabled, the screen is limited to\n            // the size of the container.\n            const size = this._screenSize();\n            this._display.viewportChangeSize(size.w, size.h);\n            this._fixScrollbars();\n            this._setClippingViewport(size.w < this._display.width ||\n                                      size.h < this._display.height);\n        } else {\n            this._setClippingViewport(false);\n        }\n\n        // When changing clipping we might show or hide scrollbars.\n        // This causes the expected client dimensions to change.\n        if (curClip !== newClip) {\n            this._saveExpectedClientSize();\n        }\n    }\n\n    _updateScale() {\n        if (!this._scaleViewport) {\n            this._display.scale = 1.0;\n        } else {\n            const size = this._screenSize();\n            this._display.autoscale(size.w, size.h);\n        }\n        this._fixScrollbars();\n    }\n\n    // Requests a change of remote desktop size. This message is an extension\n    // and may only be sent if we have received an ExtendedDesktopSize message\n    _requestRemoteResize() {\n        if (!this._resizeSession) {\n            return;\n        }\n        if (this._viewOnly) {\n            return;\n        }\n        if (!this._supportsSetDesktopSize) {\n            return;\n        }\n\n        // Rate limit to one pending resize at a time\n        if (this._pendingRemoteResize) {\n            return;\n        }\n\n        // And no more than once every 100ms\n        if ((Date.now() - this._lastResize) < 100) {\n            clearTimeout(this._resizeTimeout);\n            this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this),\n                                             100 - (Date.now() - this._lastResize));\n            return;\n        }\n        this._resizeTimeout = null;\n\n        const size = this._screenSize();\n\n        // Do we actually change anything?\n        if (size.w === this._fbWidth && size.h === this._fbHeight) {\n            return;\n        }\n\n        this._pendingRemoteResize = true;\n        this._lastResize = Date.now();\n        RFB.messages.setDesktopSize(this._sock,\n                                    Math.floor(size.w), Math.floor(size.h),\n                                    this._screenID, this._screenFlags);\n\n        Log.Debug('Requested new desktop size: ' +\n                   size.w + 'x' + size.h);\n    }\n\n    // Gets the the size of the available screen\n    _screenSize() {\n        let r = this._screen.getBoundingClientRect();\n        return { w: r.width, h: r.height };\n    }\n\n    _fixScrollbars() {\n        // This is a hack because Safari on macOS screws up the calculation\n        // for when scrollbars are needed. We get scrollbars when making the\n        // browser smaller, despite remote resize being enabled. So to fix it\n        // we temporarily toggle them off and on.\n        const orig = this._screen.style.overflow;\n        this._screen.style.overflow = 'hidden';\n        // Force Safari to recalculate the layout by asking for\n        // an element's dimensions\n        this._screen.getBoundingClientRect();\n        this._screen.style.overflow = orig;\n    }\n\n    /*\n     * Connection states:\n     *   connecting\n     *   connected\n     *   disconnecting\n     *   disconnected - permanent state\n     */\n    _updateConnectionState(state) {\n        const oldstate = this._rfbConnectionState;\n\n        if (state === oldstate) {\n            Log.Debug(\"Already in state '\" + state + \"', ignoring\");\n            return;\n        }\n\n        // The 'disconnected' state is permanent for each RFB object\n        if (oldstate === 'disconnected') {\n            Log.Error(\"Tried changing state of a disconnected RFB object\");\n            return;\n        }\n\n        // Ensure proper transitions before doing anything\n        switch (state) {\n            case 'connected':\n                if (oldstate !== 'connecting') {\n                    Log.Error(\"Bad transition to connected state, \" +\n                               \"previous connection state: \" + oldstate);\n                    return;\n                }\n                break;\n\n            case 'disconnected':\n                if (oldstate !== 'disconnecting') {\n                    Log.Error(\"Bad transition to disconnected state, \" +\n                               \"previous connection state: \" + oldstate);\n                    return;\n                }\n                break;\n\n            case 'connecting':\n                if (oldstate !== '') {\n                    Log.Error(\"Bad transition to connecting state, \" +\n                               \"previous connection state: \" + oldstate);\n                    return;\n                }\n                break;\n\n            case 'disconnecting':\n                if (oldstate !== 'connected' && oldstate !== 'connecting') {\n                    Log.Error(\"Bad transition to disconnecting state, \" +\n                               \"previous connection state: \" + oldstate);\n                    return;\n                }\n                break;\n\n            default:\n                Log.Error(\"Unknown connection state: \" + state);\n                return;\n        }\n\n        // State change actions\n\n        this._rfbConnectionState = state;\n\n        Log.Debug(\"New state '\" + state + \"', was '\" + oldstate + \"'.\");\n\n        if (this._disconnTimer && state !== 'disconnecting') {\n            Log.Debug(\"Clearing disconnect timer\");\n            clearTimeout(this._disconnTimer);\n            this._disconnTimer = null;\n\n            // make sure we don't get a double event\n            this._sock.off('close');\n        }\n\n        switch (state) {\n            case 'connecting':\n                this._connect();\n                break;\n\n            case 'connected':\n                this.dispatchEvent(new CustomEvent(\"connect\", { detail: {} }));\n                break;\n\n            case 'disconnecting':\n                this._disconnect();\n\n                this._disconnTimer = setTimeout(() => {\n                    Log.Error(\"Disconnection timed out.\");\n                    this._updateConnectionState('disconnected');\n                }, DISCONNECT_TIMEOUT * 1000);\n                break;\n\n            case 'disconnected':\n                this.dispatchEvent(new CustomEvent(\n                    \"disconnect\", { detail:\n                                    { clean: this._rfbCleanDisconnect } }));\n                break;\n        }\n    }\n\n    /* Print errors and disconnect\n     *\n     * The parameter 'details' is used for information that\n     * should be logged but not sent to the user interface.\n     */\n    _fail(details) {\n        switch (this._rfbConnectionState) {\n            case 'disconnecting':\n                Log.Error(\"Failed when disconnecting: \" + details);\n                break;\n            case 'connected':\n                Log.Error(\"Failed while connected: \" + details);\n                break;\n            case 'connecting':\n                Log.Error(\"Failed when connecting: \" + details);\n                break;\n            default:\n                Log.Error(\"RFB failure: \" + details);\n                break;\n        }\n        this._rfbCleanDisconnect = false; //This is sent to the UI\n\n        // Transition to disconnected without waiting for socket to close\n        this._updateConnectionState('disconnecting');\n        this._updateConnectionState('disconnected');\n\n        return false;\n    }\n\n    _setCapability(cap, val) {\n        this._capabilities[cap] = val;\n        this.dispatchEvent(new CustomEvent(\"capabilities\",\n                                           { detail: { capabilities: this._capabilities } }));\n    }\n\n    _handleMessage() {\n        if (this._sock.rQwait(\"message\", 1)) {\n            Log.Warn(\"handleMessage called on an empty receive queue\");\n            return;\n        }\n\n        switch (this._rfbConnectionState) {\n            case 'disconnected':\n                Log.Error(\"Got data while disconnected\");\n                break;\n            case 'connected':\n                while (true) {\n                    if (this._flushing) {\n                        break;\n                    }\n                    if (!this._normalMsg()) {\n                        break;\n                    }\n                    if (this._sock.rQwait(\"message\", 1)) {\n                        break;\n                    }\n                }\n                break;\n            case 'connecting':\n                while (this._rfbConnectionState === 'connecting') {\n                    if (!this._initMsg()) {\n                        break;\n                    }\n                }\n                break;\n            default:\n                Log.Error(\"Got data while in an invalid state\");\n                break;\n        }\n    }\n\n    _handleKeyEvent(keysym, code, down, numlock, capslock) {\n        // If remote state of capslock is known, and it doesn't match the local led state of\n        // the keyboard, we send a capslock keypress first to bring it into sync.\n        // If we just pressed CapsLock, or we toggled it remotely due to it being out of sync\n        // we clear the remote state so that we don't send duplicate or spurious fixes,\n        // since it may take some time to receive the new remote CapsLock state.\n        if (code == 'CapsLock' && down) {\n            this._remoteCapsLock = null;\n        }\n        if (this._remoteCapsLock !== null && capslock !== null && this._remoteCapsLock !== capslock && down) {\n            Log.Debug(\"Fixing remote caps lock\");\n\n            this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', true);\n            this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', false);\n            // We clear the remote capsLock state when we do this to prevent issues with doing this twice\n            // before we receive an update of the the remote state.\n            this._remoteCapsLock = null;\n        }\n\n        // Logic for numlock is exactly the same.\n        if (code == 'NumLock' && down) {\n            this._remoteNumLock = null;\n        }\n        if (this._remoteNumLock !== null && numlock !== null && this._remoteNumLock !== numlock && down) {\n            Log.Debug(\"Fixing remote num lock\");\n            this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', true);\n            this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', false);\n            this._remoteNumLock = null;\n        }\n        this.sendKey(keysym, code, down);\n    }\n\n    static _convertButtonMask(buttons) {\n        /* The bits in MouseEvent.buttons property correspond\n         * to the following mouse buttons:\n         *     0: Left\n         *     1: Right\n         *     2: Middle\n         *     3: Back\n         *     4: Forward\n         *\n         * These bits needs to be converted to what they are defined as\n         * in the RFB protocol.\n         */\n\n        const buttonMaskMap = {\n            0: 1 << 0, // Left\n            1: 1 << 2, // Right\n            2: 1 << 1, // Middle\n            3: 1 << 7, // Back\n            4: 1 << 8, // Forward\n        };\n\n        let bmask = 0;\n        for (let i = 0; i < 5; i++) {\n            if (buttons & (1 << i)) {\n                bmask |= buttonMaskMap[i];\n            }\n        }\n        return bmask;\n    }\n\n    _handleMouse(ev) {\n        /*\n         * We don't check connection status or viewOnly here as the\n         * mouse events might be used to control the viewport\n         */\n\n        if (ev.type === 'click') {\n            /*\n             * Note: This is only needed for the 'click' event as it fails\n             *       to fire properly for the target element so we have\n             *       to listen on the document element instead.\n             */\n            if (ev.target !== this._canvas) {\n                return;\n            }\n        }\n\n        // FIXME: if we're in view-only and not dragging,\n        //        should we stop events?\n        ev.stopPropagation();\n        ev.preventDefault();\n\n        if ((ev.type === 'click') || (ev.type === 'contextmenu')) {\n            return;\n        }\n\n        let pos = clientToElement(ev.clientX, ev.clientY,\n                                  this._canvas);\n\n        let bmask = RFB._convertButtonMask(ev.buttons);\n\n        let down = ev.type == 'mousedown';\n        switch (ev.type) {\n            case 'mousedown':\n            case 'mouseup':\n                if (this.dragViewport) {\n                    if (down && !this._viewportDragging) {\n                        this._viewportDragging = true;\n                        this._viewportDragPos = {'x': pos.x, 'y': pos.y};\n                        this._viewportHasMoved = false;\n\n                        this._flushMouseMoveTimer(pos.x, pos.y);\n\n                        // Skip sending mouse events, instead save the current\n                        // mouse mask so we can send it later.\n                        this._mouseButtonMask = bmask;\n                        break;\n                    } else {\n                        this._viewportDragging = false;\n\n                        // If we actually performed a drag then we are done\n                        // here and should not send any mouse events\n                        if (this._viewportHasMoved) {\n                            this._mouseButtonMask = bmask;\n                            break;\n                        }\n                        // Otherwise we treat this as a mouse click event.\n                        // Send the previously saved button mask, followed\n                        // by the current button mask at the end of this\n                        // function.\n                        this._sendMouse(pos.x, pos.y,  this._mouseButtonMask);\n                    }\n                }\n                if (down) {\n                    setCapture(this._canvas);\n                }\n                this._handleMouseButton(pos.x, pos.y, bmask);\n                break;\n            case 'mousemove':\n                if (this._viewportDragging) {\n                    const deltaX = this._viewportDragPos.x - pos.x;\n                    const deltaY = this._viewportDragPos.y - pos.y;\n\n                    if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||\n                                                   Math.abs(deltaY) > dragThreshold)) {\n                        this._viewportHasMoved = true;\n\n                        this._viewportDragPos = {'x': pos.x, 'y': pos.y};\n                        this._display.viewportChangePos(deltaX, deltaY);\n                    }\n\n                    // Skip sending mouse events\n                    break;\n                }\n                this._handleMouseMove(pos.x, pos.y);\n                break;\n        }\n    }\n\n    _handleMouseButton(x, y, bmask) {\n        // Flush waiting move event first\n        this._flushMouseMoveTimer(x, y);\n\n        this._mouseButtonMask = bmask;\n        this._sendMouse(x, y, this._mouseButtonMask);\n    }\n\n    _handleMouseMove(x, y) {\n        this._mousePos = { 'x': x, 'y': y };\n\n        // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms\n        if (this._mouseMoveTimer == null) {\n\n            const timeSinceLastMove = Date.now() - this._mouseLastMoveTime;\n            if (timeSinceLastMove > MOUSE_MOVE_DELAY) {\n                this._sendMouse(x, y, this._mouseButtonMask);\n                this._mouseLastMoveTime = Date.now();\n            } else {\n                // Too soon since the latest move, wait the remaining time\n                this._mouseMoveTimer = setTimeout(() => {\n                    this._handleDelayedMouseMove();\n                }, MOUSE_MOVE_DELAY - timeSinceLastMove);\n            }\n        }\n    }\n\n    _handleDelayedMouseMove() {\n        this._mouseMoveTimer = null;\n        this._sendMouse(this._mousePos.x, this._mousePos.y,\n                        this._mouseButtonMask);\n        this._mouseLastMoveTime = Date.now();\n    }\n\n    _sendMouse(x, y, mask) {\n        if (this._rfbConnectionState !== 'connected') { return; }\n        if (this._viewOnly) { return; } // View only, skip mouse events\n\n        // Highest bit in mask is never sent to the server\n        if (mask & 0x8000) {\n            throw new Error(\"Illegal mouse button mask (mask: \" + mask + \")\");\n        }\n\n        let extendedMouseButtons = mask & 0x7f80;\n\n        if (this._extendedPointerEventSupported && extendedMouseButtons) {\n            RFB.messages.extendedPointerEvent(this._sock, this._display.absX(x),\n                                              this._display.absY(y), mask);\n        } else {\n            RFB.messages.pointerEvent(this._sock, this._display.absX(x),\n                                      this._display.absY(y), mask);\n        }\n    }\n\n    _handleWheel(ev) {\n        if (this._rfbConnectionState !== 'connected') { return; }\n        if (this._viewOnly) { return; } // View only, skip mouse events\n\n        ev.stopPropagation();\n        ev.preventDefault();\n\n        let pos = clientToElement(ev.clientX, ev.clientY,\n                                  this._canvas);\n\n        let bmask = RFB._convertButtonMask(ev.buttons);\n        let dX = ev.deltaX;\n        let dY = ev.deltaY;\n\n        // Pixel units unless it's non-zero.\n        // Note that if deltamode is line or page won't matter since we aren't\n        // sending the mouse wheel delta to the server anyway.\n        // The difference between pixel and line can be important however since\n        // we have a threshold that can be smaller than the line height.\n        if (ev.deltaMode !== 0) {\n            dX *= WHEEL_LINE_HEIGHT;\n            dY *= WHEEL_LINE_HEIGHT;\n        }\n\n        // Mouse wheel events are sent in steps over VNC. This means that the VNC\n        // protocol can't handle a wheel event with specific distance or speed.\n        // Therefor, if we get a lot of small mouse wheel events we combine them.\n        this._accumulatedWheelDeltaX += dX;\n        this._accumulatedWheelDeltaY += dY;\n\n\n        // Generate a mouse wheel step event when the accumulated delta\n        // for one of the axes is large enough.\n        if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) {\n            if (this._accumulatedWheelDeltaX < 0) {\n                this._handleMouseButton(pos.x, pos.y, bmask | 1 << 5);\n                this._handleMouseButton(pos.x, pos.y, bmask);\n            } else if (this._accumulatedWheelDeltaX > 0) {\n                this._handleMouseButton(pos.x, pos.y, bmask | 1 << 6);\n                this._handleMouseButton(pos.x, pos.y, bmask);\n            }\n\n            this._accumulatedWheelDeltaX = 0;\n        }\n        if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) {\n            if (this._accumulatedWheelDeltaY < 0) {\n                this._handleMouseButton(pos.x, pos.y, bmask | 1 << 3);\n                this._handleMouseButton(pos.x, pos.y, bmask);\n            } else if (this._accumulatedWheelDeltaY > 0) {\n                this._handleMouseButton(pos.x, pos.y, bmask | 1 << 4);\n                this._handleMouseButton(pos.x, pos.y, bmask);\n            }\n\n            this._accumulatedWheelDeltaY = 0;\n        }\n    }\n\n    _fakeMouseMove(ev, elementX, elementY) {\n        this._handleMouseMove(elementX, elementY);\n        this._cursor.move(ev.detail.clientX, ev.detail.clientY);\n    }\n\n    _handleTapEvent(ev, bmask) {\n        let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,\n                                  this._canvas);\n\n        // If the user quickly taps multiple times we assume they meant to\n        // hit the same spot, so slightly adjust coordinates\n\n        if ((this._gestureLastTapTime !== null) &&\n            ((Date.now() - this._gestureLastTapTime) < DOUBLE_TAP_TIMEOUT) &&\n            (this._gestureFirstDoubleTapEv.detail.type === ev.detail.type)) {\n            let dx = this._gestureFirstDoubleTapEv.detail.clientX - ev.detail.clientX;\n            let dy = this._gestureFirstDoubleTapEv.detail.clientY - ev.detail.clientY;\n            let distance = Math.hypot(dx, dy);\n\n            if (distance < DOUBLE_TAP_THRESHOLD) {\n                pos = clientToElement(this._gestureFirstDoubleTapEv.detail.clientX,\n                                      this._gestureFirstDoubleTapEv.detail.clientY,\n                                      this._canvas);\n            } else {\n                this._gestureFirstDoubleTapEv = ev;\n            }\n        } else {\n            this._gestureFirstDoubleTapEv = ev;\n        }\n        this._gestureLastTapTime = Date.now();\n\n        this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y);\n        this._handleMouseButton(pos.x, pos.y, bmask);\n        this._handleMouseButton(pos.x, pos.y, 0x0);\n    }\n\n    _handleGesture(ev) {\n        let magnitude;\n\n        let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,\n                                  this._canvas);\n        switch (ev.type) {\n            case 'gesturestart':\n                switch (ev.detail.type) {\n                    case 'onetap':\n                        this._handleTapEvent(ev, 0x1);\n                        break;\n                    case 'twotap':\n                        this._handleTapEvent(ev, 0x4);\n                        break;\n                    case 'threetap':\n                        this._handleTapEvent(ev, 0x2);\n                        break;\n                    case 'drag':\n                        if (this.dragViewport) {\n                            this._viewportHasMoved = false;\n                            this._viewportDragging = true;\n                            this._viewportDragPos = {'x': pos.x, 'y': pos.y};\n                        } else {\n                            this._fakeMouseMove(ev, pos.x, pos.y);\n                            this._handleMouseButton(pos.x, pos.y, 0x1);\n                        }\n                        break;\n                    case 'longpress':\n                        if (this.dragViewport) {\n                            // If dragViewport is true, we need to wait to see\n                            // if we have dragged outside the threshold before\n                            // sending any events to the server.\n                            this._viewportHasMoved = false;\n                            this._viewportDragPos = {'x': pos.x, 'y': pos.y};\n                        } else {\n                            this._fakeMouseMove(ev, pos.x, pos.y);\n                            this._handleMouseButton(pos.x, pos.y, 0x4);\n                        }\n                        break;\n                    case 'twodrag':\n                        this._gestureLastMagnitudeX = ev.detail.magnitudeX;\n                        this._gestureLastMagnitudeY = ev.detail.magnitudeY;\n                        this._fakeMouseMove(ev, pos.x, pos.y);\n                        break;\n                    case 'pinch':\n                        this._gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX,\n                                                                 ev.detail.magnitudeY);\n                        this._fakeMouseMove(ev, pos.x, pos.y);\n                        break;\n                }\n                break;\n\n            case 'gesturemove':\n                switch (ev.detail.type) {\n                    case 'onetap':\n                    case 'twotap':\n                    case 'threetap':\n                        break;\n                    case 'drag':\n                    case 'longpress':\n                        if (this.dragViewport) {\n                            this._viewportDragging = true;\n                            const deltaX = this._viewportDragPos.x - pos.x;\n                            const deltaY = this._viewportDragPos.y - pos.y;\n\n                            if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||\n                                                           Math.abs(deltaY) > dragThreshold)) {\n                                this._viewportHasMoved = true;\n\n                                this._viewportDragPos = {'x': pos.x, 'y': pos.y};\n                                this._display.viewportChangePos(deltaX, deltaY);\n                            }\n                        } else {\n                            this._fakeMouseMove(ev, pos.x, pos.y);\n                        }\n                        break;\n                    case 'twodrag':\n                        // Always scroll in the same position.\n                        // We don't know if the mouse was moved so we need to move it\n                        // every update.\n                        this._fakeMouseMove(ev, pos.x, pos.y);\n                        while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) {\n                            this._handleMouseButton(pos.x, pos.y, 0x8);\n                            this._handleMouseButton(pos.x, pos.y, 0x0);\n                            this._gestureLastMagnitudeY += GESTURE_SCRLSENS;\n                        }\n                        while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) {\n                            this._handleMouseButton(pos.x, pos.y, 0x10);\n                            this._handleMouseButton(pos.x, pos.y, 0x0);\n                            this._gestureLastMagnitudeY -= GESTURE_SCRLSENS;\n                        }\n                        while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) {\n                            this._handleMouseButton(pos.x, pos.y, 0x20);\n                            this._handleMouseButton(pos.x, pos.y, 0x0);\n                            this._gestureLastMagnitudeX += GESTURE_SCRLSENS;\n                        }\n                        while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) {\n                            this._handleMouseButton(pos.x, pos.y, 0x40);\n                            this._handleMouseButton(pos.x, pos.y, 0x0);\n                            this._gestureLastMagnitudeX -= GESTURE_SCRLSENS;\n                        }\n                        break;\n                    case 'pinch':\n                        // Always scroll in the same position.\n                        // We don't know if the mouse was moved so we need to move it\n                        // every update.\n                        this._fakeMouseMove(ev, pos.x, pos.y);\n                        magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY);\n                        if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {\n                            this._handleKeyEvent(KeyTable.XK_Control_L, \"ControlLeft\", true);\n                            while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {\n                                this._handleMouseButton(pos.x, pos.y, 0x8);\n                                this._handleMouseButton(pos.x, pos.y, 0x0);\n                                this._gestureLastMagnitudeX += GESTURE_ZOOMSENS;\n                            }\n                            while ((magnitude -  this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) {\n                                this._handleMouseButton(pos.x, pos.y, 0x10);\n                                this._handleMouseButton(pos.x, pos.y, 0x0);\n                                this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS;\n                            }\n                        }\n                        this._handleKeyEvent(KeyTable.XK_Control_L, \"ControlLeft\", false);\n                        break;\n                }\n                break;\n\n            case 'gestureend':\n                switch (ev.detail.type) {\n                    case 'onetap':\n                    case 'twotap':\n                    case 'threetap':\n                    case 'pinch':\n                    case 'twodrag':\n                        break;\n                    case 'drag':\n                        if (this.dragViewport) {\n                            this._viewportDragging = false;\n                        } else {\n                            this._fakeMouseMove(ev, pos.x, pos.y);\n                            this._handleMouseButton(pos.x, pos.y, 0x0);\n                        }\n                        break;\n                    case 'longpress':\n                        if (this._viewportHasMoved) {\n                            // We don't want to send any events if we have moved\n                            // our viewport\n                            break;\n                        }\n\n                        if (this.dragViewport && !this._viewportHasMoved) {\n                            this._fakeMouseMove(ev, pos.x, pos.y);\n                            // If dragViewport is true, we need to wait to see\n                            // if we have dragged outside the threshold before\n                            // sending any events to the server.\n                            this._handleMouseButton(pos.x, pos.y, 0x4);\n                            this._handleMouseButton(pos.x, pos.y, 0x0);\n                            this._viewportDragging = false;\n                        } else {\n                            this._fakeMouseMove(ev, pos.x, pos.y);\n                            this._handleMouseButton(pos.x, pos.y, 0x0);\n                        }\n                        break;\n                }\n                break;\n        }\n    }\n\n    _flushMouseMoveTimer(x, y) {\n        if (this._mouseMoveTimer !== null) {\n            clearTimeout(this._mouseMoveTimer);\n            this._mouseMoveTimer = null;\n            this._sendMouse(x, y, this._mouseButtonMask);\n        }\n    }\n\n    // Message handlers\n\n    _negotiateProtocolVersion() {\n        if (this._sock.rQwait(\"version\", 12)) {\n            return false;\n        }\n\n        const sversion = this._sock.rQshiftStr(12).substr(4, 7);\n        Log.Info(\"Server ProtocolVersion: \" + sversion);\n        let isRepeater = 0;\n        switch (sversion) {\n            case \"000.000\":  // UltraVNC repeater\n                isRepeater = 1;\n                break;\n            case \"003.003\":\n            case \"003.006\":  // UltraVNC\n                this._rfbVersion = 3.3;\n                break;\n            case \"003.007\":\n                this._rfbVersion = 3.7;\n                break;\n            case \"003.008\":\n            case \"003.889\":  // Apple Remote Desktop\n            case \"004.000\":  // Intel AMT KVM\n            case \"004.001\":  // RealVNC 4.6\n            case \"005.000\":  // RealVNC 5.3\n                this._rfbVersion = 3.8;\n                break;\n            default:\n                return this._fail(\"Invalid server version \" + sversion);\n        }\n\n        if (isRepeater) {\n            let repeaterID = \"ID:\" + this._repeaterID;\n            while (repeaterID.length < 250) {\n                repeaterID += \"\\0\";\n            }\n            this._sock.sQpushString(repeaterID);\n            this._sock.flush();\n            return true;\n        }\n\n        if (this._rfbVersion > this._rfbMaxVersion) {\n            this._rfbVersion = this._rfbMaxVersion;\n        }\n\n        const cversion = \"00\" + parseInt(this._rfbVersion, 10) +\n                       \".00\" + ((this._rfbVersion * 10) % 10);\n        this._sock.sQpushString(\"RFB \" + cversion + \"\\n\");\n        this._sock.flush();\n        Log.Debug('Sent ProtocolVersion: ' + cversion);\n\n        this._rfbInitState = 'Security';\n    }\n\n    _isSupportedSecurityType(type) {\n        const clientTypes = [\n            securityTypeNone,\n            securityTypeVNCAuth,\n            securityTypeRA2ne,\n            securityTypeTight,\n            securityTypeVeNCrypt,\n            securityTypeXVP,\n            securityTypeARD,\n            securityTypeMSLogonII,\n            securityTypePlain,\n        ];\n\n        return clientTypes.includes(type);\n    }\n\n    _negotiateSecurity() {\n        if (this._rfbVersion >= 3.7) {\n            // Server sends supported list, client decides\n            const numTypes = this._sock.rQshift8();\n            if (this._sock.rQwait(\"security type\", numTypes, 1)) { return false; }\n\n            if (numTypes === 0) {\n                this._rfbInitState = \"SecurityReason\";\n                this._securityContext = \"no security types\";\n                this._securityStatus = 1;\n                return true;\n            }\n\n            const types = this._sock.rQshiftBytes(numTypes);\n            Log.Debug(\"Server security types: \" + types);\n\n            // Look for a matching security type in the order that the\n            // server prefers\n            this._rfbAuthScheme = -1;\n            for (let type of types) {\n                if (this._isSupportedSecurityType(type)) {\n                    this._rfbAuthScheme = type;\n                    break;\n                }\n            }\n\n            if (this._rfbAuthScheme === -1) {\n                return this._fail(\"Unsupported security types (types: \" + types + \")\");\n            }\n\n            this._sock.sQpush8(this._rfbAuthScheme);\n            this._sock.flush();\n        } else {\n            // Server decides\n            if (this._sock.rQwait(\"security scheme\", 4)) { return false; }\n            this._rfbAuthScheme = this._sock.rQshift32();\n\n            if (this._rfbAuthScheme == 0) {\n                this._rfbInitState = \"SecurityReason\";\n                this._securityContext = \"authentication scheme\";\n                this._securityStatus = 1;\n                return true;\n            }\n        }\n\n        this._rfbInitState = 'Authentication';\n        Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);\n\n        return true;\n    }\n\n    _handleSecurityReason() {\n        if (this._sock.rQwait(\"reason length\", 4)) {\n            return false;\n        }\n        const strlen = this._sock.rQshift32();\n        let reason = \"\";\n\n        if (strlen > 0) {\n            if (this._sock.rQwait(\"reason\", strlen, 4)) { return false; }\n            reason = this._sock.rQshiftStr(strlen);\n        }\n\n        if (reason !== \"\") {\n            this.dispatchEvent(new CustomEvent(\n                \"securityfailure\",\n                { detail: { status: this._securityStatus,\n                            reason: reason } }));\n\n            return this._fail(\"Security negotiation failed on \" +\n                              this._securityContext +\n                              \" (reason: \" + reason + \")\");\n        } else {\n            this.dispatchEvent(new CustomEvent(\n                \"securityfailure\",\n                { detail: { status: this._securityStatus } }));\n\n            return this._fail(\"Security negotiation failed on \" +\n                              this._securityContext);\n        }\n    }\n\n    // authentication\n    _negotiateXvpAuth() {\n        if (this._rfbCredentials.username === undefined ||\n            this._rfbCredentials.password === undefined ||\n            this._rfbCredentials.target === undefined) {\n            this.dispatchEvent(new CustomEvent(\n                \"credentialsrequired\",\n                { detail: { types: [\"username\", \"password\", \"target\"] } }));\n            return false;\n        }\n\n        this._sock.sQpush8(this._rfbCredentials.username.length);\n        this._sock.sQpush8(this._rfbCredentials.target.length);\n        this._sock.sQpushString(this._rfbCredentials.username);\n        this._sock.sQpushString(this._rfbCredentials.target);\n\n        this._sock.flush();\n\n        this._rfbAuthScheme = securityTypeVNCAuth;\n\n        return this._negotiateAuthentication();\n    }\n\n    // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype\n    _negotiateVeNCryptAuth() {\n\n        // waiting for VeNCrypt version\n        if (this._rfbVeNCryptState == 0) {\n            if (this._sock.rQwait(\"vencrypt version\", 2)) { return false; }\n\n            const major = this._sock.rQshift8();\n            const minor = this._sock.rQshift8();\n\n            if (!(major == 0 && minor == 2)) {\n                return this._fail(\"Unsupported VeNCrypt version \" + major + \".\" + minor);\n            }\n\n            this._sock.sQpush8(0);\n            this._sock.sQpush8(2);\n            this._sock.flush();\n            this._rfbVeNCryptState = 1;\n        }\n\n        // waiting for ACK\n        if (this._rfbVeNCryptState == 1) {\n            if (this._sock.rQwait(\"vencrypt ack\", 1)) { return false; }\n\n            const res = this._sock.rQshift8();\n\n            if (res != 0) {\n                return this._fail(\"VeNCrypt failure \" + res);\n            }\n\n            this._rfbVeNCryptState = 2;\n        }\n        // must fall through here (i.e. no \"else if\"), beacause we may have already received\n        // the subtypes length and won't be called again\n\n        if (this._rfbVeNCryptState == 2) { // waiting for subtypes length\n            if (this._sock.rQwait(\"vencrypt subtypes length\", 1)) { return false; }\n\n            const subtypesLength = this._sock.rQshift8();\n            if (subtypesLength < 1) {\n                return this._fail(\"VeNCrypt subtypes empty\");\n            }\n\n            this._rfbVeNCryptSubtypesLength = subtypesLength;\n            this._rfbVeNCryptState = 3;\n        }\n\n        // waiting for subtypes list\n        if (this._rfbVeNCryptState == 3) {\n            if (this._sock.rQwait(\"vencrypt subtypes\", 4 * this._rfbVeNCryptSubtypesLength)) { return false; }\n\n            const subtypes = [];\n            for (let i = 0; i < this._rfbVeNCryptSubtypesLength; i++) {\n                subtypes.push(this._sock.rQshift32());\n            }\n\n            // Look for a matching security type in the order that the\n            // server prefers\n            this._rfbAuthScheme = -1;\n            for (let type of subtypes) {\n                // Avoid getting in to a loop\n                if (type === securityTypeVeNCrypt) {\n                    continue;\n                }\n\n                if (this._isSupportedSecurityType(type)) {\n                    this._rfbAuthScheme = type;\n                    break;\n                }\n            }\n\n            if (this._rfbAuthScheme === -1) {\n                return this._fail(\"Unsupported security types (types: \" + subtypes + \")\");\n            }\n\n            this._sock.sQpush32(this._rfbAuthScheme);\n            this._sock.flush();\n\n            this._rfbVeNCryptState = 4;\n            return true;\n        }\n    }\n\n    _negotiatePlainAuth() {\n        if (this._rfbCredentials.username === undefined ||\n            this._rfbCredentials.password === undefined) {\n            this.dispatchEvent(new CustomEvent(\n                \"credentialsrequired\",\n                { detail: { types: [\"username\", \"password\"] } }));\n            return false;\n        }\n\n        const user = encodeUTF8(this._rfbCredentials.username);\n        const pass = encodeUTF8(this._rfbCredentials.password);\n\n        this._sock.sQpush32(user.length);\n        this._sock.sQpush32(pass.length);\n        this._sock.sQpushString(user);\n        this._sock.sQpushString(pass);\n        this._sock.flush();\n\n        this._rfbInitState = \"SecurityResult\";\n        return true;\n    }\n\n    _negotiateStdVNCAuth() {\n        if (this._sock.rQwait(\"auth challenge\", 16)) { return false; }\n\n        if (this._rfbCredentials.password === undefined) {\n            this.dispatchEvent(new CustomEvent(\n                \"credentialsrequired\",\n                { detail: { types: [\"password\"] } }));\n            return false;\n        }\n\n        // TODO(directxman12): make genDES not require an Array\n        const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));\n        const response = RFB.genDES(this._rfbCredentials.password, challenge);\n        this._sock.sQpushBytes(response);\n        this._sock.flush();\n        this._rfbInitState = \"SecurityResult\";\n        return true;\n    }\n\n    _negotiateARDAuth() {\n\n        if (this._rfbCredentials.username === undefined ||\n            this._rfbCredentials.password === undefined) {\n            this.dispatchEvent(new CustomEvent(\n                \"credentialsrequired\",\n                { detail: { types: [\"username\", \"password\"] } }));\n            return false;\n        }\n\n        if (this._rfbCredentials.ardPublicKey != undefined &&\n            this._rfbCredentials.ardCredentials != undefined) {\n            // if the async web crypto is done return the results\n            this._sock.sQpushBytes(this._rfbCredentials.ardCredentials);\n            this._sock.sQpushBytes(this._rfbCredentials.ardPublicKey);\n            this._sock.flush();\n            this._rfbCredentials.ardCredentials = null;\n            this._rfbCredentials.ardPublicKey = null;\n            this._rfbInitState = \"SecurityResult\";\n            return true;\n        }\n\n        if (this._sock.rQwait(\"read ard\", 4)) { return false; }\n\n        let generator = this._sock.rQshiftBytes(2);   // DH base generator value\n\n        let keyLength = this._sock.rQshift16();\n\n        if (this._sock.rQwait(\"read ard keylength\", keyLength*2, 4)) { return false; }\n\n        // read the server values\n        let prime = this._sock.rQshiftBytes(keyLength);  // predetermined prime modulus\n        let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key\n\n        let clientKey = legacyCrypto.generateKey(\n            { name: \"DH\", g: generator, p: prime }, false, [\"deriveBits\"]);\n        this._negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey);\n\n        return false;\n    }\n\n    async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {\n        const clientPublicKey = legacyCrypto.exportKey(\"raw\", clientKey.publicKey);\n        const sharedKey = legacyCrypto.deriveBits(\n            { name: \"DH\", public: serverPublicKey }, clientKey.privateKey, keyLength * 8);\n\n        const username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);\n        const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);\n\n        const credentials = window.crypto.getRandomValues(new Uint8Array(128));\n        for (let i = 0; i < username.length; i++) {\n            credentials[i] = username.charCodeAt(i);\n        }\n        credentials[username.length] = 0;\n        for (let i = 0; i < password.length; i++) {\n            credentials[64 + i] = password.charCodeAt(i);\n        }\n        credentials[64 + password.length] = 0;\n\n        const key = await legacyCrypto.digest(\"MD5\", sharedKey);\n        const cipher = await legacyCrypto.importKey(\n            \"raw\", key, { name: \"AES-ECB\" }, false, [\"encrypt\"]);\n        const encrypted = await legacyCrypto.encrypt({ name: \"AES-ECB\" }, cipher, credentials);\n\n        this._rfbCredentials.ardCredentials = encrypted;\n        this._rfbCredentials.ardPublicKey = clientPublicKey;\n\n        this._resumeAuthentication();\n    }\n\n    _negotiateTightUnixAuth() {\n        if (this._rfbCredentials.username === undefined ||\n            this._rfbCredentials.password === undefined) {\n            this.dispatchEvent(new CustomEvent(\n                \"credentialsrequired\",\n                { detail: { types: [\"username\", \"password\"] } }));\n            return false;\n        }\n\n        this._sock.sQpush32(this._rfbCredentials.username.length);\n        this._sock.sQpush32(this._rfbCredentials.password.length);\n        this._sock.sQpushString(this._rfbCredentials.username);\n        this._sock.sQpushString(this._rfbCredentials.password);\n        this._sock.flush();\n\n        this._rfbInitState = \"SecurityResult\";\n        return true;\n    }\n\n    _negotiateTightTunnels(numTunnels) {\n        const clientSupportedTunnelTypes = {\n            0: { vendor: 'TGHT', signature: 'NOTUNNEL' }\n        };\n        const serverSupportedTunnelTypes = {};\n        // receive tunnel capabilities\n        for (let i = 0; i < numTunnels; i++) {\n            const capCode = this._sock.rQshift32();\n            const capVendor = this._sock.rQshiftStr(4);\n            const capSignature = this._sock.rQshiftStr(8);\n            serverSupportedTunnelTypes[capCode] = { vendor: capVendor, signature: capSignature };\n        }\n\n        Log.Debug(\"Server Tight tunnel types: \" + serverSupportedTunnelTypes);\n\n        // Siemens touch panels have a VNC server that supports NOTUNNEL,\n        // but forgets to advertise it. Try to detect such servers by\n        // looking for their custom tunnel type.\n        if (serverSupportedTunnelTypes[1] &&\n            (serverSupportedTunnelTypes[1].vendor === \"SICR\") &&\n            (serverSupportedTunnelTypes[1].signature === \"SCHANNEL\")) {\n            Log.Debug(\"Detected Siemens server. Assuming NOTUNNEL support.\");\n            serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };\n        }\n\n        // choose the notunnel type\n        if (serverSupportedTunnelTypes[0]) {\n            if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||\n                serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {\n                return this._fail(\"Client's tunnel type had the incorrect \" +\n                                  \"vendor or signature\");\n            }\n            Log.Debug(\"Selected tunnel type: \" + clientSupportedTunnelTypes[0]);\n            this._sock.sQpush32(0); // use NOTUNNEL\n            this._sock.flush();\n            return false; // wait until we receive the sub auth count to continue\n        } else {\n            return this._fail(\"Server wanted tunnels, but doesn't support \" +\n                              \"the notunnel type\");\n        }\n    }\n\n    _negotiateTightAuth() {\n        if (!this._rfbTightVNC) {  // first pass, do the tunnel negotiation\n            if (this._sock.rQwait(\"num tunnels\", 4)) { return false; }\n            const numTunnels = this._sock.rQshift32();\n            if (numTunnels > 0 && this._sock.rQwait(\"tunnel capabilities\", 16 * numTunnels, 4)) { return false; }\n\n            this._rfbTightVNC = true;\n\n            if (numTunnels > 0) {\n                this._negotiateTightTunnels(numTunnels);\n                return false;  // wait until we receive the sub auth to continue\n            }\n        }\n\n        // second pass, do the sub-auth negotiation\n        if (this._sock.rQwait(\"sub auth count\", 4)) { return false; }\n        const subAuthCount = this._sock.rQshift32();\n        if (subAuthCount === 0) {  // empty sub-auth list received means 'no auth' subtype selected\n            this._rfbInitState = 'SecurityResult';\n            return true;\n        }\n\n        if (this._sock.rQwait(\"sub auth capabilities\", 16 * subAuthCount, 4)) { return false; }\n\n        const clientSupportedTypes = {\n            'STDVNOAUTH__': 1,\n            'STDVVNCAUTH_': 2,\n            'TGHTULGNAUTH': 129\n        };\n\n        const serverSupportedTypes = [];\n\n        for (let i = 0; i < subAuthCount; i++) {\n            this._sock.rQshift32(); // capNum\n            const capabilities = this._sock.rQshiftStr(12);\n            serverSupportedTypes.push(capabilities);\n        }\n\n        Log.Debug(\"Server Tight authentication types: \" + serverSupportedTypes);\n\n        for (let authType in clientSupportedTypes) {\n            if (serverSupportedTypes.indexOf(authType) != -1) {\n                this._sock.sQpush32(clientSupportedTypes[authType]);\n                this._sock.flush();\n                Log.Debug(\"Selected authentication type: \" + authType);\n\n                switch (authType) {\n                    case 'STDVNOAUTH__':  // no auth\n                        this._rfbInitState = 'SecurityResult';\n                        return true;\n                    case 'STDVVNCAUTH_':\n                        this._rfbAuthScheme = securityTypeVNCAuth;\n                        return true;\n                    case 'TGHTULGNAUTH':\n                        this._rfbAuthScheme = securityTypeUnixLogon;\n                        return true;\n                    default:\n                        return this._fail(\"Unsupported tiny auth scheme \" +\n                                          \"(scheme: \" + authType + \")\");\n                }\n            }\n        }\n\n        return this._fail(\"No supported sub-auth types!\");\n    }\n\n    _handleRSAAESCredentialsRequired(event) {\n        this.dispatchEvent(event);\n    }\n\n    _handleRSAAESServerVerification(event) {\n        this.dispatchEvent(event);\n    }\n\n    _negotiateRA2neAuth() {\n        if (this._rfbRSAAESAuthenticationState === null) {\n            this._rfbRSAAESAuthenticationState = new RSAAESAuthenticationState(this._sock, () => this._rfbCredentials);\n            this._rfbRSAAESAuthenticationState.addEventListener(\n                \"serververification\", this._eventHandlers.handleRSAAESServerVerification);\n            this._rfbRSAAESAuthenticationState.addEventListener(\n                \"credentialsrequired\", this._eventHandlers.handleRSAAESCredentialsRequired);\n        }\n        this._rfbRSAAESAuthenticationState.checkInternalEvents();\n        if (!this._rfbRSAAESAuthenticationState.hasStarted) {\n            this._rfbRSAAESAuthenticationState.negotiateRA2neAuthAsync()\n                .catch((e) => {\n                    if (e.message !== \"disconnect normally\") {\n                        this._fail(e.message);\n                    }\n                })\n                .then(() => {\n                    this._rfbInitState = \"SecurityResult\";\n                    return true;\n                }).finally(() => {\n                    this._rfbRSAAESAuthenticationState.removeEventListener(\n                        \"serververification\", this._eventHandlers.handleRSAAESServerVerification);\n                    this._rfbRSAAESAuthenticationState.removeEventListener(\n                        \"credentialsrequired\", this._eventHandlers.handleRSAAESCredentialsRequired);\n                    this._rfbRSAAESAuthenticationState = null;\n                });\n        }\n        return false;\n    }\n\n    _negotiateMSLogonIIAuth() {\n        if (this._sock.rQwait(\"mslogonii dh param\", 24)) { return false; }\n\n        if (this._rfbCredentials.username === undefined ||\n            this._rfbCredentials.password === undefined) {\n            this.dispatchEvent(new CustomEvent(\n                \"credentialsrequired\",\n                { detail: { types: [\"username\", \"password\"] } }));\n            return false;\n        }\n\n        const g = this._sock.rQshiftBytes(8);\n        const p = this._sock.rQshiftBytes(8);\n        const A = this._sock.rQshiftBytes(8);\n        const dhKey = legacyCrypto.generateKey({ name: \"DH\", g: g, p: p }, true, [\"deriveBits\"]);\n        const B = legacyCrypto.exportKey(\"raw\", dhKey.publicKey);\n        const secret = legacyCrypto.deriveBits({ name: \"DH\", public: A }, dhKey.privateKey, 64);\n\n        const key = legacyCrypto.importKey(\"raw\", secret, { name: \"DES-CBC\" }, false, [\"encrypt\"]);\n        const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);\n        const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);\n        let usernameBytes = new Uint8Array(256);\n        let passwordBytes = new Uint8Array(64);\n        window.crypto.getRandomValues(usernameBytes);\n        window.crypto.getRandomValues(passwordBytes);\n        for (let i = 0; i < username.length; i++) {\n            usernameBytes[i] = username.charCodeAt(i);\n        }\n        usernameBytes[username.length] = 0;\n        for (let i = 0; i < password.length; i++) {\n            passwordBytes[i] = password.charCodeAt(i);\n        }\n        passwordBytes[password.length] = 0;\n        usernameBytes = legacyCrypto.encrypt({ name: \"DES-CBC\", iv: secret }, key, usernameBytes);\n        passwordBytes = legacyCrypto.encrypt({ name: \"DES-CBC\", iv: secret }, key, passwordBytes);\n        this._sock.sQpushBytes(B);\n        this._sock.sQpushBytes(usernameBytes);\n        this._sock.sQpushBytes(passwordBytes);\n        this._sock.flush();\n        this._rfbInitState = \"SecurityResult\";\n        return true;\n    }\n\n    _negotiateAuthentication() {\n        switch (this._rfbAuthScheme) {\n            case securityTypeNone:\n                if (this._rfbVersion >= 3.8) {\n                    this._rfbInitState = 'SecurityResult';\n                } else {\n                    this._rfbInitState = 'ClientInitialisation';\n                }\n                return true;\n\n            case securityTypeXVP:\n                return this._negotiateXvpAuth();\n\n            case securityTypeARD:\n                return this._negotiateARDAuth();\n\n            case securityTypeVNCAuth:\n                return this._negotiateStdVNCAuth();\n\n            case securityTypeTight:\n                return this._negotiateTightAuth();\n\n            case securityTypeVeNCrypt:\n                return this._negotiateVeNCryptAuth();\n\n            case securityTypePlain:\n                return this._negotiatePlainAuth();\n\n            case securityTypeUnixLogon:\n                return this._negotiateTightUnixAuth();\n\n            case securityTypeRA2ne:\n                return this._negotiateRA2neAuth();\n\n            case securityTypeMSLogonII:\n                return this._negotiateMSLogonIIAuth();\n\n            default:\n                return this._fail(\"Unsupported auth scheme (scheme: \" +\n                                  this._rfbAuthScheme + \")\");\n        }\n    }\n\n    _handleSecurityResult() {\n        if (this._sock.rQwait('VNC auth response ', 4)) { return false; }\n\n        const status = this._sock.rQshift32();\n\n        if (status === 0) { // OK\n            this._rfbInitState = 'ClientInitialisation';\n            Log.Debug('Authentication OK');\n            return true;\n        } else {\n            if (this._rfbVersion >= 3.8) {\n                this._rfbInitState = \"SecurityReason\";\n                this._securityContext = \"security result\";\n                this._securityStatus = status;\n                return true;\n            } else {\n                this.dispatchEvent(new CustomEvent(\n                    \"securityfailure\",\n                    { detail: { status: status } }));\n\n                return this._fail(\"Security handshake failed\");\n            }\n        }\n    }\n\n    _negotiateServerInit() {\n        if (this._sock.rQwait(\"server initialization\", 24)) { return false; }\n\n        /* Screen size */\n        const width = this._sock.rQshift16();\n        const height = this._sock.rQshift16();\n\n        /* PIXEL_FORMAT */\n        const bpp         = this._sock.rQshift8();\n        const depth       = this._sock.rQshift8();\n        const bigEndian  = this._sock.rQshift8();\n        const trueColor  = this._sock.rQshift8();\n\n        const redMax     = this._sock.rQshift16();\n        const greenMax   = this._sock.rQshift16();\n        const blueMax    = this._sock.rQshift16();\n        const redShift   = this._sock.rQshift8();\n        const greenShift = this._sock.rQshift8();\n        const blueShift  = this._sock.rQshift8();\n        this._sock.rQskipBytes(3);  // padding\n\n        // NB(directxman12): we don't want to call any callbacks or print messages until\n        //                   *after* we're past the point where we could backtrack\n\n        /* Connection name/title */\n        const nameLength = this._sock.rQshift32();\n        if (this._sock.rQwait('server init name', nameLength, 24)) { return false; }\n        let name = this._sock.rQshiftStr(nameLength);\n        name = decodeUTF8(name, true);\n\n        if (this._rfbTightVNC) {\n            if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; }\n            // In TightVNC mode, ServerInit message is extended\n            const numServerMessages = this._sock.rQshift16();\n            const numClientMessages = this._sock.rQshift16();\n            const numEncodings = this._sock.rQshift16();\n            this._sock.rQskipBytes(2);  // padding\n\n            const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;\n            if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; }\n\n            // we don't actually do anything with the capability information that TIGHT sends,\n            // so we just skip the all of this.\n\n            // TIGHT server message capabilities\n            this._sock.rQskipBytes(16 * numServerMessages);\n\n            // TIGHT client message capabilities\n            this._sock.rQskipBytes(16 * numClientMessages);\n\n            // TIGHT encoding capabilities\n            this._sock.rQskipBytes(16 * numEncodings);\n        }\n\n        // NB(directxman12): these are down here so that we don't run them multiple times\n        //                   if we backtrack\n        Log.Info(\"Screen: \" + width + \"x\" + height +\n                  \", bpp: \" + bpp + \", depth: \" + depth +\n                  \", bigEndian: \" + bigEndian +\n                  \", trueColor: \" + trueColor +\n                  \", redMax: \" + redMax +\n                  \", greenMax: \" + greenMax +\n                  \", blueMax: \" + blueMax +\n                  \", redShift: \" + redShift +\n                  \", greenShift: \" + greenShift +\n                  \", blueShift: \" + blueShift);\n\n        // we're past the point where we could backtrack, so it's safe to call this\n        this._setDesktopName(name);\n        this._resize(width, height);\n\n        if (!this._viewOnly) { this._keyboard.grab(); }\n\n        this._fbDepth = 24;\n\n        if (this._fbName === \"Intel(r) AMT KVM\") {\n            Log.Warn(\"Intel AMT KVM only supports 8/16 bit depths. Using low color mode.\");\n            this._fbDepth = 8;\n        }\n\n        RFB.messages.pixelFormat(this._sock, this._fbDepth, true);\n        this._sendEncodings();\n        RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);\n\n        this._updateConnectionState('connected');\n        return true;\n    }\n\n    _sendEncodings() {\n        const encs = [];\n\n        // In preference order\n        encs.push(encodings.encodingCopyRect);\n        // Only supported with full depth support\n        if (this._fbDepth == 24) {\n            if (supportsWebCodecsH264Decode) {\n                encs.push(encodings.encodingH264);\n            }\n            encs.push(encodings.encodingTight);\n            encs.push(encodings.encodingTightPNG);\n            encs.push(encodings.encodingZRLE);\n            encs.push(encodings.encodingJPEG);\n            encs.push(encodings.encodingHextile);\n            encs.push(encodings.encodingRRE);\n            encs.push(encodings.encodingZlib);\n        }\n        encs.push(encodings.encodingRaw);\n\n        // Psuedo-encoding settings\n        encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);\n        encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);\n\n        encs.push(encodings.pseudoEncodingDesktopSize);\n        encs.push(encodings.pseudoEncodingLastRect);\n        encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);\n        encs.push(encodings.pseudoEncodingQEMULedEvent);\n        encs.push(encodings.pseudoEncodingExtendedDesktopSize);\n        encs.push(encodings.pseudoEncodingXvp);\n        encs.push(encodings.pseudoEncodingFence);\n        encs.push(encodings.pseudoEncodingContinuousUpdates);\n        encs.push(encodings.pseudoEncodingDesktopName);\n        encs.push(encodings.pseudoEncodingExtendedClipboard);\n        encs.push(encodings.pseudoEncodingExtendedMouseButtons);\n\n        if (this._fbDepth == 24) {\n            encs.push(encodings.pseudoEncodingVMwareCursor);\n            encs.push(encodings.pseudoEncodingCursor);\n        }\n\n        RFB.messages.clientEncodings(this._sock, encs);\n    }\n\n    /* RFB protocol initialization states:\n     *   ProtocolVersion\n     *   Security\n     *   Authentication\n     *   SecurityResult\n     *   ClientInitialization - not triggered by server message\n     *   ServerInitialization\n     */\n    _initMsg() {\n        switch (this._rfbInitState) {\n            case 'ProtocolVersion':\n                return this._negotiateProtocolVersion();\n\n            case 'Security':\n                return this._negotiateSecurity();\n\n            case 'Authentication':\n                return this._negotiateAuthentication();\n\n            case 'SecurityResult':\n                return this._handleSecurityResult();\n\n            case 'SecurityReason':\n                return this._handleSecurityReason();\n\n            case 'ClientInitialisation':\n                this._sock.sQpush8(this._shared ? 1 : 0); // ClientInitialisation\n                this._sock.flush();\n                this._rfbInitState = 'ServerInitialisation';\n                return true;\n\n            case 'ServerInitialisation':\n                return this._negotiateServerInit();\n\n            default:\n                return this._fail(\"Unknown init state (state: \" +\n                                  this._rfbInitState + \")\");\n        }\n    }\n\n    // Resume authentication handshake after it was paused for some\n    // reason, e.g. waiting for a password from the user\n    _resumeAuthentication() {\n        // We use setTimeout() so it's run in its own context, just like\n        // it originally did via the WebSocket's event handler\n        setTimeout(this._initMsg.bind(this), 0);\n    }\n\n    _handleSetColourMapMsg() {\n        Log.Debug(\"SetColorMapEntries\");\n\n        return this._fail(\"Unexpected SetColorMapEntries message\");\n    }\n\n    _handleServerCutText() {\n        Log.Debug(\"ServerCutText\");\n\n        if (this._sock.rQwait(\"ServerCutText header\", 7, 1)) { return false; }\n\n        this._sock.rQskipBytes(3);  // Padding\n\n        let length = this._sock.rQshift32();\n        length = toSigned32bit(length);\n\n        if (this._sock.rQwait(\"ServerCutText content\", Math.abs(length), 8)) { return false; }\n\n        if (length >= 0) {\n            //Standard msg\n            const text = this._sock.rQshiftStr(length);\n            if (this._viewOnly) {\n                return true;\n            }\n\n            this.dispatchEvent(new CustomEvent(\n                \"clipboard\",\n                { detail: { text: text } }));\n\n        } else {\n            //Extended msg.\n            length = Math.abs(length);\n            const flags = this._sock.rQshift32();\n            let formats = flags & 0x0000FFFF;\n            let actions = flags & 0xFF000000;\n\n            let isCaps = (!!(actions & extendedClipboardActionCaps));\n            if (isCaps) {\n                this._clipboardServerCapabilitiesFormats = {};\n                this._clipboardServerCapabilitiesActions = {};\n\n                // Update our server capabilities for Formats\n                for (let i = 0; i <= 15; i++) {\n                    let index = 1 << i;\n\n                    // Check if format flag is set.\n                    if ((formats & index)) {\n                        this._clipboardServerCapabilitiesFormats[index] = true;\n                        // We don't send unsolicited clipboard, so we\n                        // ignore the size\n                        this._sock.rQshift32();\n                    }\n                }\n\n                // Update our server capabilities for Actions\n                for (let i = 24; i <= 31; i++) {\n                    let index = 1 << i;\n                    this._clipboardServerCapabilitiesActions[index] = !!(actions & index);\n                }\n\n                /*  Caps handling done, send caps with the clients\n                    capabilities set as a response */\n                let clientActions = [\n                    extendedClipboardActionCaps,\n                    extendedClipboardActionRequest,\n                    extendedClipboardActionPeek,\n                    extendedClipboardActionNotify,\n                    extendedClipboardActionProvide\n                ];\n                RFB.messages.extendedClipboardCaps(this._sock, clientActions, {extendedClipboardFormatText: 0});\n\n            } else if (actions === extendedClipboardActionRequest) {\n                if (this._viewOnly) {\n                    return true;\n                }\n\n                // Check if server has told us it can handle Provide and there is clipboard data to send.\n                if (this._clipboardText != null &&\n                    this._clipboardServerCapabilitiesActions[extendedClipboardActionProvide]) {\n\n                    if (formats & extendedClipboardFormatText) {\n                        RFB.messages.extendedClipboardProvide(this._sock, [extendedClipboardFormatText], [this._clipboardText]);\n                    }\n                }\n\n            } else if (actions === extendedClipboardActionPeek) {\n                if (this._viewOnly) {\n                    return true;\n                }\n\n                if (this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {\n\n                    if (this._clipboardText != null) {\n                        RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);\n                    } else {\n                        RFB.messages.extendedClipboardNotify(this._sock, []);\n                    }\n                }\n\n            } else if (actions === extendedClipboardActionNotify) {\n                if (this._viewOnly) {\n                    return true;\n                }\n\n                if (this._clipboardServerCapabilitiesActions[extendedClipboardActionRequest]) {\n\n                    if (formats & extendedClipboardFormatText) {\n                        RFB.messages.extendedClipboardRequest(this._sock, [extendedClipboardFormatText]);\n                    }\n                }\n\n            } else if (actions === extendedClipboardActionProvide) {\n                if (this._viewOnly) {\n                    return true;\n                }\n\n                if (!(formats & extendedClipboardFormatText)) {\n                    return true;\n                }\n                // Ignore what we had in our clipboard client side.\n                this._clipboardText = null;\n\n                // FIXME: Should probably verify that this data was actually requested\n                let zlibStream = this._sock.rQshiftBytes(length - 4);\n                let streamInflator = new Inflator();\n                let textData = null;\n\n                streamInflator.setInput(zlibStream);\n                for (let i = 0; i <= 15; i++) {\n                    let format = 1 << i;\n\n                    if (formats & format) {\n\n                        let size = 0x00;\n                        let sizeArray = streamInflator.inflate(4);\n\n                        size |= (sizeArray[0] << 24);\n                        size |= (sizeArray[1] << 16);\n                        size |= (sizeArray[2] << 8);\n                        size |= (sizeArray[3]);\n                        let chunk = streamInflator.inflate(size);\n\n                        if (format === extendedClipboardFormatText) {\n                            textData = chunk;\n                        }\n                    }\n                }\n                streamInflator.setInput(null);\n\n                if (textData !== null) {\n                    let tmpText = \"\";\n                    for (let i = 0; i < textData.length; i++) {\n                        tmpText += String.fromCharCode(textData[i]);\n                    }\n                    textData = tmpText;\n\n                    textData = decodeUTF8(textData);\n                    if ((textData.length > 0) && \"\\0\" === textData.charAt(textData.length - 1)) {\n                        textData = textData.slice(0, -1);\n                    }\n\n                    textData = textData.replaceAll(\"\\r\\n\", \"\\n\");\n\n                    this.dispatchEvent(new CustomEvent(\n                        \"clipboard\",\n                        { detail: { text: textData } }));\n                }\n            } else {\n                return this._fail(\"Unexpected action in extended clipboard message: \" + actions);\n            }\n        }\n        return true;\n    }\n\n    _handleServerFenceMsg() {\n        if (this._sock.rQwait(\"ServerFence header\", 8, 1)) { return false; }\n        this._sock.rQskipBytes(3); // Padding\n        let flags = this._sock.rQshift32();\n        let length = this._sock.rQshift8();\n\n        if (this._sock.rQwait(\"ServerFence payload\", length, 9)) { return false; }\n\n        if (length > 64) {\n            Log.Warn(\"Bad payload length (\" + length + \") in fence response\");\n            length = 64;\n        }\n\n        const payload = this._sock.rQshiftStr(length);\n\n        this._supportsFence = true;\n\n        /*\n         * Fence flags\n         *\n         *  (1<<0)  - BlockBefore\n         *  (1<<1)  - BlockAfter\n         *  (1<<2)  - SyncNext\n         *  (1<<31) - Request\n         */\n\n        if (!(flags & (1<<31))) {\n            return this._fail(\"Unexpected fence response\");\n        }\n\n        // Filter out unsupported flags\n        // FIXME: support syncNext\n        flags &= (1<<0) | (1<<1);\n\n        // BlockBefore and BlockAfter are automatically handled by\n        // the fact that we process each incoming message\n        // synchronuosly.\n        RFB.messages.clientFence(this._sock, flags, payload);\n\n        return true;\n    }\n\n    _handleXvpMsg() {\n        if (this._sock.rQwait(\"XVP version and message\", 3, 1)) { return false; }\n        this._sock.rQskipBytes(1);  // Padding\n        const xvpVer = this._sock.rQshift8();\n        const xvpMsg = this._sock.rQshift8();\n\n        switch (xvpMsg) {\n            case 0:  // XVP_FAIL\n                Log.Error(\"XVP operation failed\");\n                break;\n            case 1:  // XVP_INIT\n                this._rfbXvpVer = xvpVer;\n                Log.Info(\"XVP extensions enabled (version \" + this._rfbXvpVer + \")\");\n                this._setCapability(\"power\", true);\n                break;\n            default:\n                this._fail(\"Illegal server XVP message (msg: \" + xvpMsg + \")\");\n                break;\n        }\n\n        return true;\n    }\n\n    _normalMsg() {\n        let msgType;\n        if (this._FBU.rects > 0) {\n            msgType = 0;\n        } else {\n            msgType = this._sock.rQshift8();\n        }\n\n        let first, ret;\n        switch (msgType) {\n            case 0:  // FramebufferUpdate\n                ret = this._framebufferUpdate();\n                if (ret && !this._enabledContinuousUpdates) {\n                    RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,\n                                                 this._fbWidth, this._fbHeight);\n                }\n                return ret;\n\n            case 1:  // SetColorMapEntries\n                return this._handleSetColourMapMsg();\n\n            case 2:  // Bell\n                Log.Debug(\"Bell\");\n                this.dispatchEvent(new CustomEvent(\n                    \"bell\",\n                    { detail: {} }));\n                return true;\n\n            case 3:  // ServerCutText\n                return this._handleServerCutText();\n\n            case 150: // EndOfContinuousUpdates\n                first = !this._supportsContinuousUpdates;\n                this._supportsContinuousUpdates = true;\n                this._enabledContinuousUpdates = false;\n                if (first) {\n                    this._enabledContinuousUpdates = true;\n                    this._updateContinuousUpdates();\n                    Log.Info(\"Enabling continuous updates.\");\n                } else {\n                    // FIXME: We need to send a framebufferupdaterequest here\n                    // if we add support for turning off continuous updates\n                }\n                return true;\n\n            case 248: // ServerFence\n                return this._handleServerFenceMsg();\n\n            case 250:  // XVP\n                return this._handleXvpMsg();\n\n            default:\n                this._fail(\"Unexpected server message (type \" + msgType + \")\");\n                Log.Debug(\"sock.rQpeekBytes(30): \" + this._sock.rQpeekBytes(30));\n                return true;\n        }\n    }\n\n    _framebufferUpdate() {\n        if (this._FBU.rects === 0) {\n            if (this._sock.rQwait(\"FBU header\", 3, 1)) { return false; }\n            this._sock.rQskipBytes(1);  // Padding\n            this._FBU.rects = this._sock.rQshift16();\n\n            // Make sure the previous frame is fully rendered first\n            // to avoid building up an excessive queue\n            if (this._display.pending()) {\n                this._flushing = true;\n                this._display.flush()\n                    .then(() => {\n                        this._flushing = false;\n                        // Resume processing\n                        if (!this._sock.rQwait(\"message\", 1)) {\n                            this._handleMessage();\n                        }\n                    });\n                return false;\n            }\n        }\n\n        while (this._FBU.rects > 0) {\n            if (this._FBU.encoding === null) {\n                if (this._sock.rQwait(\"rect header\", 12)) { return false; }\n                /* New FramebufferUpdate */\n\n                this._FBU.x = this._sock.rQshift16();\n                this._FBU.y = this._sock.rQshift16();\n                this._FBU.width = this._sock.rQshift16();\n                this._FBU.height = this._sock.rQshift16();\n                this._FBU.encoding = this._sock.rQshift32();\n                /* Encodings are signed */\n                this._FBU.encoding >>= 0;\n            }\n\n            if (!this._handleRect()) {\n                return false;\n            }\n\n            this._FBU.rects--;\n            this._FBU.encoding = null;\n        }\n\n        this._display.flip();\n\n        return true;  // We finished this FBU\n    }\n\n    _handleRect() {\n        switch (this._FBU.encoding) {\n            case encodings.pseudoEncodingLastRect:\n                this._FBU.rects = 1; // Will be decreased when we return\n                return true;\n\n            case encodings.pseudoEncodingVMwareCursor:\n                return this._handleVMwareCursor();\n\n            case encodings.pseudoEncodingCursor:\n                return this._handleCursor();\n\n            case encodings.pseudoEncodingQEMUExtendedKeyEvent:\n                this._qemuExtKeyEventSupported = true;\n                return true;\n\n            case encodings.pseudoEncodingDesktopName:\n                return this._handleDesktopName();\n\n            case encodings.pseudoEncodingDesktopSize:\n                this._resize(this._FBU.width, this._FBU.height);\n                return true;\n\n            case encodings.pseudoEncodingExtendedDesktopSize:\n                return this._handleExtendedDesktopSize();\n\n            case encodings.pseudoEncodingExtendedMouseButtons:\n                this._extendedPointerEventSupported = true;\n                return true;\n\n            case encodings.pseudoEncodingQEMULedEvent:\n                return this._handleLedEvent();\n\n            default:\n                return this._handleDataRect();\n        }\n    }\n\n    _handleVMwareCursor() {\n        const hotx = this._FBU.x;  // hotspot-x\n        const hoty = this._FBU.y;  // hotspot-y\n        const w = this._FBU.width;\n        const h = this._FBU.height;\n        if (this._sock.rQwait(\"VMware cursor encoding\", 1)) {\n            return false;\n        }\n\n        const cursorType = this._sock.rQshift8();\n\n        this._sock.rQshift8(); //Padding\n\n        let rgba;\n        const bytesPerPixel = 4;\n\n        //Classic cursor\n        if (cursorType == 0) {\n            //Used to filter away unimportant bits.\n            //OR is used for correct conversion in js.\n            const PIXEL_MASK = 0xffffff00 | 0;\n            rgba = new Array(w * h * bytesPerPixel);\n\n            if (this._sock.rQwait(\"VMware cursor classic encoding\",\n                                  (w * h * bytesPerPixel) * 2, 2)) {\n                return false;\n            }\n\n            let andMask = new Array(w * h);\n            for (let pixel = 0; pixel < (w * h); pixel++) {\n                andMask[pixel] = this._sock.rQshift32();\n            }\n\n            let xorMask = new Array(w * h);\n            for (let pixel = 0; pixel < (w * h); pixel++) {\n                xorMask[pixel] = this._sock.rQshift32();\n            }\n\n            for (let pixel = 0; pixel < (w * h); pixel++) {\n                if (andMask[pixel] == 0) {\n                    //Fully opaque pixel\n                    let bgr = xorMask[pixel];\n                    let r   = bgr >> 8  & 0xff;\n                    let g   = bgr >> 16 & 0xff;\n                    let b   = bgr >> 24 & 0xff;\n\n                    rgba[(pixel * bytesPerPixel)     ] = r;    //r\n                    rgba[(pixel * bytesPerPixel) + 1 ] = g;    //g\n                    rgba[(pixel * bytesPerPixel) + 2 ] = b;    //b\n                    rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a\n\n                } else if ((andMask[pixel] & PIXEL_MASK) ==\n                           PIXEL_MASK) {\n                    //Only screen value matters, no mouse colouring\n                    if (xorMask[pixel] == 0) {\n                        //Transparent pixel\n                        rgba[(pixel * bytesPerPixel)     ] = 0x00;\n                        rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;\n                        rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;\n                        rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;\n\n                    } else if ((xorMask[pixel] & PIXEL_MASK) ==\n                               PIXEL_MASK) {\n                        //Inverted pixel, not supported in browsers.\n                        //Fully opaque instead.\n                        rgba[(pixel * bytesPerPixel)     ] = 0x00;\n                        rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;\n                        rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;\n                        rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;\n\n                    } else {\n                        //Unhandled xorMask\n                        rgba[(pixel * bytesPerPixel)     ] = 0x00;\n                        rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;\n                        rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;\n                        rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;\n                    }\n\n                } else {\n                    //Unhandled andMask\n                    rgba[(pixel * bytesPerPixel)     ] = 0x00;\n                    rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;\n                    rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;\n                    rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;\n                }\n            }\n\n        //Alpha cursor.\n        } else if (cursorType == 1) {\n            if (this._sock.rQwait(\"VMware cursor alpha encoding\",\n                                  (w * h * 4), 2)) {\n                return false;\n            }\n\n            rgba = new Array(w * h * bytesPerPixel);\n\n            for (let pixel = 0; pixel < (w * h); pixel++) {\n                let data = this._sock.rQshift32();\n\n                rgba[(pixel * 4)     ] = data >> 24 & 0xff; //r\n                rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g\n                rgba[(pixel * 4) + 2 ] = data >> 8 & 0xff;  //b\n                rgba[(pixel * 4) + 3 ] = data & 0xff;       //a\n            }\n\n        } else {\n            Log.Warn(\"The given cursor type is not supported: \"\n                      + cursorType + \" given.\");\n            return false;\n        }\n\n        this._updateCursor(rgba, hotx, hoty, w, h);\n\n        return true;\n    }\n\n    _handleCursor() {\n        const hotx = this._FBU.x;  // hotspot-x\n        const hoty = this._FBU.y;  // hotspot-y\n        const w = this._FBU.width;\n        const h = this._FBU.height;\n\n        const pixelslength = w * h * 4;\n        const masklength = Math.ceil(w / 8) * h;\n\n        let bytes = pixelslength + masklength;\n        if (this._sock.rQwait(\"cursor encoding\", bytes)) {\n            return false;\n        }\n\n        // Decode from BGRX pixels + bit mask to RGBA\n        const pixels = this._sock.rQshiftBytes(pixelslength);\n        const mask = this._sock.rQshiftBytes(masklength);\n        let rgba = new Uint8Array(w * h * 4);\n\n        let pixIdx = 0;\n        for (let y = 0; y < h; y++) {\n            for (let x = 0; x < w; x++) {\n                let maskIdx = y * Math.ceil(w / 8) + Math.floor(x / 8);\n                let alpha = (mask[maskIdx] << (x % 8)) & 0x80 ? 255 : 0;\n                rgba[pixIdx    ] = pixels[pixIdx + 2];\n                rgba[pixIdx + 1] = pixels[pixIdx + 1];\n                rgba[pixIdx + 2] = pixels[pixIdx];\n                rgba[pixIdx + 3] = alpha;\n                pixIdx += 4;\n            }\n        }\n\n        this._updateCursor(rgba, hotx, hoty, w, h);\n\n        return true;\n    }\n\n    _handleDesktopName() {\n        if (this._sock.rQwait(\"DesktopName\", 4)) {\n            return false;\n        }\n\n        let length = this._sock.rQshift32();\n\n        if (this._sock.rQwait(\"DesktopName\", length, 4)) {\n            return false;\n        }\n\n        let name = this._sock.rQshiftStr(length);\n        name = decodeUTF8(name, true);\n\n        this._setDesktopName(name);\n\n        return true;\n    }\n\n    _handleLedEvent() {\n        if (this._sock.rQwait(\"LED status\", 1)) {\n            return false;\n        }\n\n        let data = this._sock.rQshift8();\n        // ScrollLock state can be retrieved with data & 1. This is currently not needed.\n        let numLock = data & 2 ? true : false;\n        let capsLock = data & 4 ? true : false;\n        this._remoteCapsLock = capsLock;\n        this._remoteNumLock = numLock;\n\n        return true;\n    }\n\n    _handleExtendedDesktopSize() {\n        if (this._sock.rQwait(\"ExtendedDesktopSize\", 4)) {\n            return false;\n        }\n\n        const numberOfScreens = this._sock.rQpeek8();\n\n        let bytes = 4 + (numberOfScreens * 16);\n        if (this._sock.rQwait(\"ExtendedDesktopSize\", bytes)) {\n            return false;\n        }\n\n        const firstUpdate = !this._supportsSetDesktopSize;\n        this._supportsSetDesktopSize = true;\n\n        this._sock.rQskipBytes(1);  // number-of-screens\n        this._sock.rQskipBytes(3);  // padding\n\n        for (let i = 0; i < numberOfScreens; i += 1) {\n            // Save the id and flags of the first screen\n            if (i === 0) {\n                this._screenID = this._sock.rQshift32();    // id\n                this._sock.rQskipBytes(2);                  // x-position\n                this._sock.rQskipBytes(2);                  // y-position\n                this._sock.rQskipBytes(2);                  // width\n                this._sock.rQskipBytes(2);                  // height\n                this._screenFlags = this._sock.rQshift32(); // flags\n            } else {\n                this._sock.rQskipBytes(16);\n            }\n        }\n\n        /*\n         * The x-position indicates the reason for the change:\n         *\n         *  0 - server resized on its own\n         *  1 - this client requested the resize\n         *  2 - another client requested the resize\n         */\n\n        if (this._FBU.x === 1) {\n            this._pendingRemoteResize = false;\n        }\n\n        // We need to handle errors when we requested the resize.\n        if (this._FBU.x === 1 && this._FBU.y !== 0) {\n            let msg = \"\";\n            // The y-position indicates the status code from the server\n            switch (this._FBU.y) {\n                case 1:\n                    msg = \"Resize is administratively prohibited\";\n                    break;\n                case 2:\n                    msg = \"Out of resources\";\n                    break;\n                case 3:\n                    msg = \"Invalid screen layout\";\n                    break;\n                default:\n                    msg = \"Unknown reason\";\n                    break;\n            }\n            Log.Warn(\"Server did not accept the resize request: \"\n                     + msg);\n        } else {\n            this._resize(this._FBU.width, this._FBU.height);\n        }\n\n        // Normally we only apply the current resize mode after a\n        // window resize event. However there is no such trigger on the\n        // initial connect. And we don't know if the server supports\n        // resizing until we've gotten here.\n        if (firstUpdate) {\n            this._requestRemoteResize();\n        }\n\n        if (this._FBU.x === 1 && this._FBU.y === 0) {\n            // We might have resized again whilst waiting for the\n            // previous request, so check if we are in sync\n            this._requestRemoteResize();\n        }\n\n        return true;\n    }\n\n    _handleDataRect() {\n        let decoder = this._decoders[this._FBU.encoding];\n        if (!decoder) {\n            this._fail(\"Unsupported encoding (encoding: \" +\n                       this._FBU.encoding + \")\");\n            return false;\n        }\n\n        try {\n            return decoder.decodeRect(this._FBU.x, this._FBU.y,\n                                      this._FBU.width, this._FBU.height,\n                                      this._sock, this._display,\n                                      this._fbDepth);\n        } catch (err) {\n            this._fail(\"Error decoding rect: \" + err);\n            return false;\n        }\n    }\n\n    _updateContinuousUpdates() {\n        if (!this._enabledContinuousUpdates) { return; }\n\n        RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,\n                                             this._fbWidth, this._fbHeight);\n    }\n\n    // Handle resize-messages from the server\n    _resize(width, height) {\n        this._fbWidth = width;\n        this._fbHeight = height;\n\n        this._display.resize(this._fbWidth, this._fbHeight);\n\n        // Adjust the visible viewport based on the new dimensions\n        this._updateClip();\n        this._updateScale();\n\n        this._updateContinuousUpdates();\n\n        // Keep this size until browser client size changes\n        this._saveExpectedClientSize();\n    }\n\n    _xvpOp(ver, op) {\n        if (this._rfbXvpVer < ver) { return; }\n        Log.Info(\"Sending XVP operation \" + op + \" (version \" + ver + \")\");\n        RFB.messages.xvpOp(this._sock, ver, op);\n    }\n\n    _updateCursor(rgba, hotx, hoty, w, h) {\n        this._cursorImage = {\n            rgbaPixels: rgba,\n            hotx: hotx, hoty: hoty, w: w, h: h,\n        };\n        this._refreshCursor();\n    }\n\n    _shouldShowDotCursor() {\n        // Called when this._cursorImage is updated\n        if (!this._showDotCursor) {\n            // User does not want to see the dot, so...\n            return false;\n        }\n\n        // The dot should not be shown if the cursor is already visible,\n        // i.e. contains at least one not-fully-transparent pixel.\n        // So iterate through all alpha bytes in rgba and stop at the\n        // first non-zero.\n        for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {\n            if (this._cursorImage.rgbaPixels[i]) {\n                return false;\n            }\n        }\n\n        // At this point, we know that the cursor is fully transparent, and\n        // the user wants to see the dot instead of this.\n        return true;\n    }\n\n    _refreshCursor() {\n        if (this._rfbConnectionState !== \"connecting\" &&\n            this._rfbConnectionState !== \"connected\") {\n            return;\n        }\n        const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;\n        this._cursor.change(image.rgbaPixels,\n                            image.hotx, image.hoty,\n                            image.w, image.h\n        );\n    }\n\n    static genDES(password, challenge) {\n        const passwordChars = password.split('').map(c => c.charCodeAt(0));\n        const key = legacyCrypto.importKey(\n            \"raw\", passwordChars, { name: \"DES-ECB\" }, false, [\"encrypt\"]);\n        return legacyCrypto.encrypt({ name: \"DES-ECB\" }, key, challenge);\n    }\n}\n\n// Class Methods\nRFB.messages = {\n    keyEvent(sock, keysym, down) {\n        sock.sQpush8(4); // msg-type\n        sock.sQpush8(down);\n\n        sock.sQpush16(0);\n\n        sock.sQpush32(keysym);\n\n        sock.flush();\n    },\n\n    QEMUExtendedKeyEvent(sock, keysym, down, keycode) {\n        function getRFBkeycode(xtScanCode) {\n            const upperByte = (keycode >> 8);\n            const lowerByte = (keycode & 0x00ff);\n            if (upperByte === 0xe0 && lowerByte < 0x7f) {\n                return lowerByte | 0x80;\n            }\n            return xtScanCode;\n        }\n\n        sock.sQpush8(255); // msg-type\n        sock.sQpush8(0); // sub msg-type\n\n        sock.sQpush16(down);\n\n        sock.sQpush32(keysym);\n\n        const RFBkeycode = getRFBkeycode(keycode);\n\n        sock.sQpush32(RFBkeycode);\n\n        sock.flush();\n    },\n\n    pointerEvent(sock, x, y, mask) {\n        sock.sQpush8(5); // msg-type\n\n        // Marker bit must be set to 0, otherwise the server might\n        // confuse the marker bit with the highest bit in a normal\n        // PointerEvent message.\n        mask = mask & 0x7f;\n        sock.sQpush8(mask);\n\n        sock.sQpush16(x);\n        sock.sQpush16(y);\n\n        sock.flush();\n    },\n\n    extendedPointerEvent(sock, x, y, mask) {\n        sock.sQpush8(5); // msg-type\n\n        let higherBits = (mask >> 7) & 0xff;\n\n        // Bits 2-7 are reserved\n        if (higherBits & 0xfc) {\n            throw new Error(\"Invalid mouse button mask: \" + mask);\n        }\n\n        let lowerBits = mask & 0x7f;\n        lowerBits |= 0x80; // Set marker bit to 1\n\n        sock.sQpush8(lowerBits);\n        sock.sQpush16(x);\n        sock.sQpush16(y);\n        sock.sQpush8(higherBits);\n\n        sock.flush();\n    },\n\n    // Used to build Notify and Request data.\n    _buildExtendedClipboardFlags(actions, formats) {\n        let data = new Uint8Array(4);\n        let formatFlag = 0x00000000;\n        let actionFlag = 0x00000000;\n\n        for (let i = 0; i < actions.length; i++) {\n            actionFlag |= actions[i];\n        }\n\n        for (let i = 0; i < formats.length; i++) {\n            formatFlag |= formats[i];\n        }\n\n        data[0] = actionFlag >> 24; // Actions\n        data[1] = 0x00;             // Reserved\n        data[2] = 0x00;             // Reserved\n        data[3] = formatFlag;       // Formats\n\n        return data;\n    },\n\n    extendedClipboardProvide(sock, formats, inData) {\n        // Deflate incomming data and their sizes\n        let deflator = new Deflator();\n        let dataToDeflate = [];\n\n        for (let i = 0; i < formats.length; i++) {\n            // We only support the format Text at this time\n            if (formats[i] != extendedClipboardFormatText) {\n                throw new Error(\"Unsupported extended clipboard format for Provide message.\");\n            }\n\n            // Change lone \\r or \\n into \\r\\n as defined in rfbproto\n            inData[i] = inData[i].replace(/\\r\\n|\\r|\\n/gm, \"\\r\\n\");\n\n            // Check if it already has \\0\n            let text = encodeUTF8(inData[i] + \"\\0\");\n\n            dataToDeflate.push( (text.length >> 24) & 0xFF,\n                                (text.length >> 16) & 0xFF,\n                                (text.length >>  8) & 0xFF,\n                                (text.length & 0xFF));\n\n            for (let j = 0; j < text.length; j++) {\n                dataToDeflate.push(text.charCodeAt(j));\n            }\n        }\n\n        let deflatedData = deflator.deflate(new Uint8Array(dataToDeflate));\n\n        // Build data  to send\n        let data = new Uint8Array(4 + deflatedData.length);\n        data.set(RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionProvide],\n                                                           formats));\n        data.set(deflatedData, 4);\n\n        RFB.messages.clientCutText(sock, data, true);\n    },\n\n    extendedClipboardNotify(sock, formats) {\n        let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionNotify],\n                                                              formats);\n        RFB.messages.clientCutText(sock, flags, true);\n    },\n\n    extendedClipboardRequest(sock, formats) {\n        let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionRequest],\n                                                              formats);\n        RFB.messages.clientCutText(sock, flags, true);\n    },\n\n    extendedClipboardCaps(sock, actions, formats) {\n        let formatKeys = Object.keys(formats);\n        let data  = new Uint8Array(4 + (4 * formatKeys.length));\n\n        formatKeys.map(x => parseInt(x));\n        formatKeys.sort((a, b) =>  a - b);\n\n        data.set(RFB.messages._buildExtendedClipboardFlags(actions, []));\n\n        let loopOffset = 4;\n        for (let i = 0; i < formatKeys.length; i++) {\n            data[loopOffset]     = formats[formatKeys[i]] >> 24;\n            data[loopOffset + 1] = formats[formatKeys[i]] >> 16;\n            data[loopOffset + 2] = formats[formatKeys[i]] >> 8;\n            data[loopOffset + 3] = formats[formatKeys[i]] >> 0;\n\n            loopOffset += 4;\n            data[3] |= (1 << formatKeys[i]); // Update our format flags\n        }\n\n        RFB.messages.clientCutText(sock, data, true);\n    },\n\n    clientCutText(sock, data, extended = false) {\n        sock.sQpush8(6); // msg-type\n\n        sock.sQpush8(0); // padding\n        sock.sQpush8(0); // padding\n        sock.sQpush8(0); // padding\n\n        let length;\n        if (extended) {\n            length = toUnsigned32bit(-data.length);\n        } else {\n            length = data.length;\n        }\n\n        sock.sQpush32(length);\n        sock.sQpushBytes(data);\n        sock.flush();\n    },\n\n    setDesktopSize(sock, width, height, id, flags) {\n        sock.sQpush8(251); // msg-type\n\n        sock.sQpush8(0); // padding\n\n        sock.sQpush16(width);\n        sock.sQpush16(height);\n\n        sock.sQpush8(1); // number-of-screens\n\n        sock.sQpush8(0); // padding\n\n        // screen array\n        sock.sQpush32(id);\n        sock.sQpush16(0); // x-position\n        sock.sQpush16(0); // y-position\n        sock.sQpush16(width);\n        sock.sQpush16(height);\n        sock.sQpush32(flags);\n\n        sock.flush();\n    },\n\n    clientFence(sock, flags, payload) {\n        sock.sQpush8(248); // msg-type\n\n        sock.sQpush8(0); // padding\n        sock.sQpush8(0); // padding\n        sock.sQpush8(0); // padding\n\n        sock.sQpush32(flags);\n\n        sock.sQpush8(payload.length);\n        sock.sQpushString(payload);\n\n        sock.flush();\n    },\n\n    enableContinuousUpdates(sock, enable, x, y, width, height) {\n        sock.sQpush8(150); // msg-type\n\n        sock.sQpush8(enable);\n\n        sock.sQpush16(x);\n        sock.sQpush16(y);\n        sock.sQpush16(width);\n        sock.sQpush16(height);\n\n        sock.flush();\n    },\n\n    pixelFormat(sock, depth, trueColor) {\n        let bpp;\n\n        if (depth > 16) {\n            bpp = 32;\n        } else if (depth > 8) {\n            bpp = 16;\n        } else {\n            bpp = 8;\n        }\n\n        const bits = Math.floor(depth/3);\n\n        sock.sQpush8(0); // msg-type\n\n        sock.sQpush8(0); // padding\n        sock.sQpush8(0); // padding\n        sock.sQpush8(0); // padding\n\n        sock.sQpush8(bpp);\n        sock.sQpush8(depth);\n        sock.sQpush8(0); // little-endian\n        sock.sQpush8(trueColor ? 1 : 0);\n\n        sock.sQpush16((1 << bits) - 1); // red-max\n        sock.sQpush16((1 << bits) - 1); // green-max\n        sock.sQpush16((1 << bits) - 1); // blue-max\n\n        sock.sQpush8(bits * 0); // red-shift\n        sock.sQpush8(bits * 1); // green-shift\n        sock.sQpush8(bits * 2); // blue-shift\n\n        sock.sQpush8(0); // padding\n        sock.sQpush8(0); // padding\n        sock.sQpush8(0); // padding\n\n        sock.flush();\n    },\n\n    clientEncodings(sock, encodings) {\n        sock.sQpush8(2); // msg-type\n\n        sock.sQpush8(0); // padding\n\n        sock.sQpush16(encodings.length);\n        for (let i = 0; i < encodings.length; i++) {\n            sock.sQpush32(encodings[i]);\n        }\n\n        sock.flush();\n    },\n\n    fbUpdateRequest(sock, incremental, x, y, w, h) {\n        if (typeof(x) === \"undefined\") { x = 0; }\n        if (typeof(y) === \"undefined\") { y = 0; }\n\n        sock.sQpush8(3); // msg-type\n\n        sock.sQpush8(incremental ? 1 : 0);\n\n        sock.sQpush16(x);\n        sock.sQpush16(y);\n        sock.sQpush16(w);\n        sock.sQpush16(h);\n\n        sock.flush();\n    },\n\n    xvpOp(sock, ver, op) {\n        sock.sQpush8(250); // msg-type\n\n        sock.sQpush8(0); // padding\n\n        sock.sQpush8(ver);\n        sock.sQpush8(op);\n\n        sock.flush();\n    }\n};\n\nRFB.cursors = {\n    none: {\n        rgbaPixels: new Uint8Array(),\n        w: 0, h: 0,\n        hotx: 0, hoty: 0,\n    },\n\n    dot: {\n        /* eslint-disable indent */\n        rgbaPixels: new Uint8Array([\n            255, 255, 255, 255,   0,   0,   0, 255, 255, 255, 255, 255,\n              0,   0,   0, 255,   0,   0,   0,   0,   0,   0,  0,  255,\n            255, 255, 255, 255,   0,   0,   0, 255, 255, 255, 255, 255,\n        ]),\n        /* eslint-enable indent */\n        w: 3, h: 3,\n        hotx: 1, hoty: 1,\n    }\n};\n"
  },
  {
    "path": "services/gateway/noVNC/core/util/browser.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n *\n * Browser feature support detection\n */\n\nimport * as Log from './logging.js';\nimport Base64 from '../base64.js';\n\n// Touch detection\nexport let isTouchDevice = ('ontouchstart' in document.documentElement) ||\n                                 // required for Chrome debugger\n                                 (document.ontouchstart !== undefined) ||\n                                 // required for MS Surface\n                                 (navigator.maxTouchPoints > 0) ||\n                                 (navigator.msMaxTouchPoints > 0);\nwindow.addEventListener('touchstart', function onFirstTouch() {\n    isTouchDevice = true;\n    window.removeEventListener('touchstart', onFirstTouch, false);\n}, false);\n\n\n// The goal is to find a certain physical width, the devicePixelRatio\n// brings us a bit closer but is not optimal.\nexport let dragThreshold = 10 * (window.devicePixelRatio || 1);\n\nlet _supportsCursorURIs = false;\n\ntry {\n    const target = document.createElement('canvas');\n    target.style.cursor = 'url(\"data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==\") 2 2, default';\n\n    if (target.style.cursor.indexOf(\"url\") === 0) {\n        Log.Info(\"Data URI scheme cursor supported\");\n        _supportsCursorURIs = true;\n    } else {\n        Log.Warn(\"Data URI scheme cursor not supported\");\n    }\n} catch (exc) {\n    Log.Error(\"Data URI scheme cursor test exception: \" + exc);\n}\n\nexport const supportsCursorURIs = _supportsCursorURIs;\n\nlet _hasScrollbarGutter = true;\ntry {\n    // Create invisible container\n    const container = document.createElement('div');\n    container.style.visibility = 'hidden';\n    container.style.overflow = 'scroll'; // forcing scrollbars\n    document.body.appendChild(container);\n\n    // Create a div and place it in the container\n    const child = document.createElement('div');\n    container.appendChild(child);\n\n    // Calculate the difference between the container's full width\n    // and the child's width - the difference is the scrollbars\n    const scrollbarWidth = (container.offsetWidth - child.offsetWidth);\n\n    // Clean up\n    container.parentNode.removeChild(container);\n\n    _hasScrollbarGutter = scrollbarWidth != 0;\n} catch (exc) {\n    Log.Error(\"Scrollbar test exception: \" + exc);\n}\nexport const hasScrollbarGutter = _hasScrollbarGutter;\n\nexport let supportsWebCodecsH264Decode = false;\n\nasync function _checkWebCodecsH264DecodeSupport() {\n    if (!('VideoDecoder' in window)) {\n        return false;\n    }\n\n    // We'll need to make do with some placeholders here\n    const config = {\n        codec: 'avc1.42401f',\n        codedWidth: 1920,\n        codedHeight: 1080,\n        optimizeForLatency: true,\n    };\n\n    let support = await VideoDecoder.isConfigSupported(config);\n    if (!support.supported) {\n        return false;\n    }\n\n    // Firefox incorrectly reports supports for H.264 under some\n    // circumstances, so we need to actually test a real frame\n    // https://bugzilla.mozilla.org/show_bug.cgi?id=1932392\n\n    const data = new Uint8Array(Base64.decode(\n        'AAAAAWdCwBTZnpuAgICgAAADACAAAAZB4oVNAAAAAWjJYyyAAAABBgX//4Hc' +\n        'Rem95tlIt5Ys2CDZI+7veDI2NCAtIGNvcmUgMTY0IHIzMTA4IDMxZTE5Zjkg' +\n        'LSBILjI2NC9NUEVHLTQgQVZDIGNvZGVjIC0gQ29weWxlZnQgMjAwMy0yMDIz' +\n        'IC0gaHR0cDovL3d3dy52aWRlb2xhbi5vcmcveDI2NC5odG1sIC0gb3B0aW9u' +\n        'czogY2FiYWM9MCByZWY9NSBkZWJsb2NrPTE6MDowIGFuYWx5c2U9MHgxOjB4' +\n        'MTExIG1lPWhleCBzdWJtZT04IHBzeT0xIHBzeV9yZD0xLjAwOjAuMDAgbWl4' +\n        'ZWRfcmVmPTEgbWVfcmFuZ2U9MTYgY2hyb21hX21lPTEgdHJlbGxpcz0yIDh4' +\n        'OGRjdD0wIGNxbT0wIGRlYWR6b25lPTIxLDExIGZhc3RfcHNraXA9MSBjaHJv' +\n        'bWFfcXBfb2Zmc2V0PS0yIHRocmVhZHM9MSBsb29rYWhlYWRfdGhyZWFkcz0x' +\n        'IHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9' +\n        'MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVz' +\n        'PTAgd2VpZ2h0cD0wIGtleWludD1pbmZpbml0ZSBrZXlpbnRfbWluPTI1IHNj' +\n        'ZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NTAgcmM9' +\n        'YWJyIG1idHJlZT0xIGJpdHJhdGU9NDAwIHJhdGV0b2w9MS4wIHFjb21wPTAu' +\n        'NjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFx' +\n        'PTE6MS4wMACAAAABZYiEBrxmKAAPVccAAS044AA5DRJMnkycJk4TPw=='));\n\n    let gotframe = false;\n    let error = null;\n\n    let decoder = new VideoDecoder({\n        output: (frame) => { gotframe = true; frame.close(); },\n        error: (e) => { error = e; },\n    });\n    let chunk = new EncodedVideoChunk({\n        timestamp: 0,\n        type: 'key',\n        data: data,\n    });\n\n    decoder.configure(config);\n    decoder.decode(chunk);\n    try {\n        await decoder.flush();\n    } catch (e) {\n        // Firefox incorrectly throws an exception here\n        // https://bugzilla.mozilla.org/show_bug.cgi?id=1932566\n        error = e;\n    }\n\n    // Firefox fails to deliver the error on Windows, so we need to\n    // check if we got a frame instead\n    // https://bugzilla.mozilla.org/show_bug.cgi?id=1932579\n    if (!gotframe) {\n        return false;\n    }\n\n    if (error !== null) {\n        return false;\n    }\n\n    return true;\n}\nsupportsWebCodecsH264Decode = await _checkWebCodecsH264DecodeSupport();\n\n/*\n * The functions for detection of platforms and browsers below are exported\n * but the use of these should be minimized as much as possible.\n *\n * It's better to use feature detection than platform detection.\n */\n\n/* OS */\n\nexport function isMac() {\n    return !!(/mac/i).exec(navigator.platform);\n}\n\nexport function isWindows() {\n    return !!(/win/i).exec(navigator.platform);\n}\n\nexport function isIOS() {\n    return (!!(/ipad/i).exec(navigator.platform) ||\n            !!(/iphone/i).exec(navigator.platform) ||\n            !!(/ipod/i).exec(navigator.platform));\n}\n\nexport function isAndroid() {\n    /* Android sets navigator.platform to Linux :/ */\n    return !!navigator.userAgent.match('Android ');\n}\n\nexport function isChromeOS() {\n    /* ChromeOS sets navigator.platform to Linux :/ */\n    return !!navigator.userAgent.match(' CrOS ');\n}\n\n/* Browser */\n\nexport function isSafari() {\n    return !!navigator.userAgent.match('Safari/...') &&\n           !navigator.userAgent.match('Chrome/...') &&\n           !navigator.userAgent.match('Chromium/...') &&\n           !navigator.userAgent.match('Epiphany/...');\n}\n\nexport function isFirefox() {\n    return !!navigator.userAgent.match('Firefox/...') &&\n           !navigator.userAgent.match('Seamonkey/...');\n}\n\nexport function isChrome() {\n    return !!navigator.userAgent.match('Chrome/...') &&\n           !navigator.userAgent.match('Chromium/...') &&\n           !navigator.userAgent.match('Edg/...') &&\n           !navigator.userAgent.match('OPR/...');\n}\n\nexport function isChromium() {\n    return !!navigator.userAgent.match('Chromium/...');\n}\n\nexport function isOpera() {\n    return !!navigator.userAgent.match('OPR/...');\n}\n\nexport function isEdge() {\n    return !!navigator.userAgent.match('Edg/...');\n}\n\n/* Engine */\n\nexport function isGecko() {\n    return !!navigator.userAgent.match('Gecko/...');\n}\n\nexport function isWebKit() {\n    return !!navigator.userAgent.match('AppleWebKit/...') &&\n           !navigator.userAgent.match('Chrome/...');\n}\n\nexport function isBlink() {\n    return !!navigator.userAgent.match('Chrome/...');\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/util/cursor.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 or any later version (see LICENSE.txt)\n */\n\nimport { supportsCursorURIs, isTouchDevice } from './browser.js';\n\nconst useFallback = !supportsCursorURIs || isTouchDevice;\n\nexport default class Cursor {\n    constructor() {\n        this._target = null;\n\n        this._canvas = document.createElement('canvas');\n\n        if (useFallback) {\n            this._canvas.style.position = 'fixed';\n            this._canvas.style.zIndex = '65535';\n            this._canvas.style.pointerEvents = 'none';\n            // Safari on iOS can select the cursor image\n            // https://bugs.webkit.org/show_bug.cgi?id=249223\n            this._canvas.style.userSelect = 'none';\n            this._canvas.style.WebkitUserSelect = 'none';\n            // Can't use \"display\" because of Firefox bug #1445997\n            this._canvas.style.visibility = 'hidden';\n        }\n\n        this._position = { x: 0, y: 0 };\n        this._hotSpot = { x: 0, y: 0 };\n\n        this._eventHandlers = {\n            'mouseover': this._handleMouseOver.bind(this),\n            'mouseleave': this._handleMouseLeave.bind(this),\n            'mousemove': this._handleMouseMove.bind(this),\n            'mouseup': this._handleMouseUp.bind(this),\n        };\n    }\n\n    attach(target) {\n        if (this._target) {\n            this.detach();\n        }\n\n        this._target = target;\n\n        if (useFallback) {\n            document.body.appendChild(this._canvas);\n\n            const options = { capture: true, passive: true };\n            this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);\n            this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);\n            this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);\n            this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);\n        }\n\n        this.clear();\n    }\n\n    detach() {\n        if (!this._target) {\n            return;\n        }\n\n        if (useFallback) {\n            const options = { capture: true, passive: true };\n            this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);\n            this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);\n            this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);\n            this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);\n\n            if (document.contains(this._canvas)) {\n                document.body.removeChild(this._canvas);\n            }\n        }\n\n        this._target = null;\n    }\n\n    change(rgba, hotx, hoty, w, h) {\n        if ((w === 0) || (h === 0)) {\n            this.clear();\n            return;\n        }\n\n        this._position.x = this._position.x + this._hotSpot.x - hotx;\n        this._position.y = this._position.y + this._hotSpot.y - hoty;\n        this._hotSpot.x = hotx;\n        this._hotSpot.y = hoty;\n\n        let ctx = this._canvas.getContext('2d');\n\n        this._canvas.width = w;\n        this._canvas.height = h;\n\n        let img = new ImageData(new Uint8ClampedArray(rgba), w, h);\n        ctx.clearRect(0, 0, w, h);\n        ctx.putImageData(img, 0, 0);\n\n        if (useFallback) {\n            this._updatePosition();\n        } else {\n            let url = this._canvas.toDataURL();\n            this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';\n        }\n    }\n\n    clear() {\n        this._target.style.cursor = 'none';\n        this._canvas.width = 0;\n        this._canvas.height = 0;\n        this._position.x = this._position.x + this._hotSpot.x;\n        this._position.y = this._position.y + this._hotSpot.y;\n        this._hotSpot.x = 0;\n        this._hotSpot.y = 0;\n    }\n\n    // Mouse events might be emulated, this allows\n    // moving the cursor in such cases\n    move(clientX, clientY) {\n        if (!useFallback) {\n            return;\n        }\n        // clientX/clientY are relative the _visual viewport_,\n        // but our position is relative the _layout viewport_,\n        // so try to compensate when we can\n        if (window.visualViewport) {\n            this._position.x = clientX + window.visualViewport.offsetLeft;\n            this._position.y = clientY + window.visualViewport.offsetTop;\n        } else {\n            this._position.x = clientX;\n            this._position.y = clientY;\n        }\n        this._updatePosition();\n        let target = document.elementFromPoint(clientX, clientY);\n        this._updateVisibility(target);\n    }\n\n    _handleMouseOver(event) {\n        // This event could be because we're entering the target, or\n        // moving around amongst its sub elements. Let the move handler\n        // sort things out.\n        this._handleMouseMove(event);\n    }\n\n    _handleMouseLeave(event) {\n        // Check if we should show the cursor on the element we are leaving to\n        this._updateVisibility(event.relatedTarget);\n    }\n\n    _handleMouseMove(event) {\n        this._updateVisibility(event.target);\n\n        this._position.x = event.clientX - this._hotSpot.x;\n        this._position.y = event.clientY - this._hotSpot.y;\n\n        this._updatePosition();\n    }\n\n    _handleMouseUp(event) {\n        // We might get this event because of a drag operation that\n        // moved outside of the target. Check what's under the cursor\n        // now and adjust visibility based on that.\n        let target = document.elementFromPoint(event.clientX, event.clientY);\n        this._updateVisibility(target);\n\n        // Captures end with a mouseup but we can't know the event order of\n        // mouseup vs releaseCapture.\n        //\n        // In the cases when releaseCapture comes first, the code above is\n        // enough.\n        //\n        // In the cases when the mouseup comes first, we need wait for the\n        // browser to flush all events and then check again if the cursor\n        // should be visible.\n        if (this._captureIsActive()) {\n            window.setTimeout(() => {\n                // We might have detached at this point\n                if (!this._target) {\n                    return;\n                }\n                // Refresh the target from elementFromPoint since queued events\n                // might have altered the DOM\n                target = document.elementFromPoint(event.clientX,\n                                                   event.clientY);\n                this._updateVisibility(target);\n            }, 0);\n        }\n    }\n\n    _showCursor() {\n        if (this._canvas.style.visibility === 'hidden') {\n            this._canvas.style.visibility = '';\n        }\n    }\n\n    _hideCursor() {\n        if (this._canvas.style.visibility !== 'hidden') {\n            this._canvas.style.visibility = 'hidden';\n        }\n    }\n\n    // Should we currently display the cursor?\n    // (i.e. are we over the target, or a child of the target without a\n    // different cursor set)\n    _shouldShowCursor(target) {\n        if (!target) {\n            return false;\n        }\n        // Easy case\n        if (target === this._target) {\n            return true;\n        }\n        // Other part of the DOM?\n        if (!this._target.contains(target)) {\n            return false;\n        }\n        // Has the child its own cursor?\n        // FIXME: How can we tell that a sub element has an\n        //        explicit \"cursor: none;\"?\n        if (window.getComputedStyle(target).cursor !== 'none') {\n            return false;\n        }\n        return true;\n    }\n\n    _updateVisibility(target) {\n        // When the cursor target has capture we want to show the cursor.\n        // So, if a capture is active - look at the captured element instead.\n        if (this._captureIsActive()) {\n            target = document.captureElement;\n        }\n        if (this._shouldShowCursor(target)) {\n            this._showCursor();\n        } else {\n            this._hideCursor();\n        }\n    }\n\n    _updatePosition() {\n        this._canvas.style.left = this._position.x + \"px\";\n        this._canvas.style.top = this._position.y + \"px\";\n    }\n\n    _captureIsActive() {\n        return document.captureElement &&\n            document.documentElement.contains(document.captureElement);\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/util/element.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\n/*\n * HTML element utility functions\n */\n\nexport function clientToElement(x, y, elem) {\n    const bounds = elem.getBoundingClientRect();\n    let pos = { x: 0, y: 0 };\n    // Clip to target bounds\n    if (x < bounds.left) {\n        pos.x = 0;\n    } else if (x >= bounds.right) {\n        pos.x = bounds.width - 1;\n    } else {\n        pos.x = x - bounds.left;\n    }\n    if (y < bounds.top) {\n        pos.y = 0;\n    } else if (y >= bounds.bottom) {\n        pos.y = bounds.height - 1;\n    } else {\n        pos.y = y - bounds.top;\n    }\n    return pos;\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/util/events.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\n/*\n * Cross-browser event and position routines\n */\n\nexport function getPointerEvent(e) {\n    return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e;\n}\n\nexport function stopEvent(e) {\n    e.stopPropagation();\n    e.preventDefault();\n}\n\n// Emulate Element.setCapture() when not supported\nlet _captureRecursion = false;\nlet _elementForUnflushedEvents = null;\ndocument.captureElement = null;\nfunction _captureProxy(e) {\n    // Recursion protection as we'll see our own event\n    if (_captureRecursion) return;\n\n    // Clone the event as we cannot dispatch an already dispatched event\n    const newEv = new e.constructor(e.type, e);\n\n    _captureRecursion = true;\n    if (document.captureElement) {\n        document.captureElement.dispatchEvent(newEv);\n    } else {\n        _elementForUnflushedEvents.dispatchEvent(newEv);\n    }\n    _captureRecursion = false;\n\n    // Avoid double events\n    e.stopPropagation();\n\n    // Respect the wishes of the redirected event handlers\n    if (newEv.defaultPrevented) {\n        e.preventDefault();\n    }\n\n    // Implicitly release the capture on button release\n    if (e.type === \"mouseup\") {\n        releaseCapture();\n    }\n}\n\n// Follow cursor style of target element\nfunction _capturedElemChanged() {\n    const proxyElem = document.getElementById(\"noVNC_mouse_capture_elem\");\n    proxyElem.style.cursor = window.getComputedStyle(document.captureElement).cursor;\n}\n\nconst _captureObserver = new MutationObserver(_capturedElemChanged);\n\nexport function setCapture(target) {\n    if (target.setCapture) {\n\n        target.setCapture();\n        document.captureElement = target;\n    } else {\n        // Release any existing capture in case this method is\n        // called multiple times without coordination\n        releaseCapture();\n\n        let proxyElem = document.getElementById(\"noVNC_mouse_capture_elem\");\n\n        if (proxyElem === null) {\n            proxyElem = document.createElement(\"div\");\n            proxyElem.id = \"noVNC_mouse_capture_elem\";\n            proxyElem.style.position = \"fixed\";\n            proxyElem.style.top = \"0px\";\n            proxyElem.style.left = \"0px\";\n            proxyElem.style.width = \"100%\";\n            proxyElem.style.height = \"100%\";\n            proxyElem.style.zIndex = 10000;\n            proxyElem.style.display = \"none\";\n            document.body.appendChild(proxyElem);\n\n            // This is to make sure callers don't get confused by having\n            // our blocking element as the target\n            proxyElem.addEventListener('contextmenu', _captureProxy);\n\n            proxyElem.addEventListener('mousemove', _captureProxy);\n            proxyElem.addEventListener('mouseup', _captureProxy);\n        }\n\n        document.captureElement = target;\n\n        // Track cursor and get initial cursor\n        _captureObserver.observe(target, {attributes: true});\n        _capturedElemChanged();\n\n        proxyElem.style.display = \"\";\n\n        // We listen to events on window in order to keep tracking if it\n        // happens to leave the viewport\n        window.addEventListener('mousemove', _captureProxy);\n        window.addEventListener('mouseup', _captureProxy);\n    }\n}\n\nexport function releaseCapture() {\n    if (document.releaseCapture) {\n\n        document.releaseCapture();\n        document.captureElement = null;\n\n    } else {\n        if (!document.captureElement) {\n            return;\n        }\n\n        // There might be events already queued. The event proxy needs\n        // access to the captured element for these queued events.\n        // E.g. contextmenu (right-click) in Microsoft Edge\n        //\n        // Before removing the capturedElem pointer we save it to a\n        // temporary variable that the unflushed events can use.\n        _elementForUnflushedEvents = document.captureElement;\n        document.captureElement = null;\n\n        _captureObserver.disconnect();\n\n        const proxyElem = document.getElementById(\"noVNC_mouse_capture_elem\");\n        proxyElem.style.display = \"none\";\n\n        window.removeEventListener('mousemove', _captureProxy);\n        window.removeEventListener('mouseup', _captureProxy);\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/util/eventtarget.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\nexport default class EventTargetMixin {\n    constructor() {\n        this._listeners = new Map();\n    }\n\n    addEventListener(type, callback) {\n        if (!this._listeners.has(type)) {\n            this._listeners.set(type, new Set());\n        }\n        this._listeners.get(type).add(callback);\n    }\n\n    removeEventListener(type, callback) {\n        if (this._listeners.has(type)) {\n            this._listeners.get(type).delete(callback);\n        }\n    }\n\n    dispatchEvent(event) {\n        if (!this._listeners.has(event.type)) {\n            return true;\n        }\n        this._listeners.get(event.type)\n            .forEach(callback => callback.call(this, event));\n        return !event.defaultPrevented;\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/util/int.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\nexport function toUnsigned32bit(toConvert) {\n    return toConvert >>> 0;\n}\n\nexport function toSigned32bit(toConvert) {\n    return toConvert | 0;\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/util/logging.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\n/*\n * Logging/debug routines\n */\n\nlet _logLevel = 'warn';\n\nlet Debug = () => {};\nlet Info = () => {};\nlet Warn = () => {};\nlet Error = () => {};\n\nexport function initLogging(level) {\n    if (typeof level === 'undefined') {\n        level = _logLevel;\n    } else {\n        _logLevel = level;\n    }\n\n    Debug = Info = Warn = Error = () => {};\n\n    if (typeof window.console !== \"undefined\") {\n        /* eslint-disable no-console, no-fallthrough */\n        switch (level) {\n            case 'debug':\n                Debug = console.debug.bind(window.console);\n            case 'info':\n                Info  = console.info.bind(window.console);\n            case 'warn':\n                Warn  = console.warn.bind(window.console);\n            case 'error':\n                Error = console.error.bind(window.console);\n            case 'none':\n                break;\n            default:\n                throw new window.Error(\"invalid logging type '\" + level + \"'\");\n        }\n        /* eslint-enable no-console, no-fallthrough */\n    }\n}\n\nexport function getLogging() {\n    return _logLevel;\n}\n\nexport { Debug, Info, Warn, Error };\n\n// Initialize logging level\ninitLogging();\n"
  },
  {
    "path": "services/gateway/noVNC/core/util/strings.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * See README.md for usage and integration instructions.\n */\n\n// Decode from UTF-8\nexport function decodeUTF8(utf8string, allowLatin1=false) {\n    try {\n        return decodeURIComponent(escape(utf8string));\n    } catch (e) {\n        if (e instanceof URIError) {\n            if (allowLatin1) {\n                // If we allow Latin1 we can ignore any decoding fails\n                // and in these cases return the original string\n                return utf8string;\n            }\n        }\n        throw e;\n    }\n}\n\n// Encode to UTF-8\nexport function encodeUTF8(DOMString) {\n    return unescape(encodeURIComponent(DOMString));\n}\n"
  },
  {
    "path": "services/gateway/noVNC/core/websock.js",
    "content": "/*\n * Websock: high-performance buffering wrapper\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * Websock is similar to the standard WebSocket / RTCDataChannel object\n * but with extra buffer handling.\n *\n * Websock has built-in receive queue buffering; the message event\n * does not contain actual data but is simply a notification that\n * there is new data available. Several rQ* methods are available to\n * read binary data off of the receive queue.\n */\n\nimport * as Log from './util/logging.js';\n\n// this has performance issues in some versions Chromium, and\n// doesn't gain a tremendous amount of performance increase in Firefox\n// at the moment.  It may be valuable to turn it on in the future.\nconst MAX_RQ_GROW_SIZE = 40 * 1024 * 1024;  // 40 MiB\n\n// Constants pulled from RTCDataChannelState enum\n// https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/readyState#RTCDataChannelState_enum\nconst DataChannel = {\n    CONNECTING: \"connecting\",\n    OPEN: \"open\",\n    CLOSING: \"closing\",\n    CLOSED: \"closed\"\n};\n\nconst ReadyStates = {\n    CONNECTING: [WebSocket.CONNECTING, DataChannel.CONNECTING],\n    OPEN: [WebSocket.OPEN, DataChannel.OPEN],\n    CLOSING: [WebSocket.CLOSING, DataChannel.CLOSING],\n    CLOSED: [WebSocket.CLOSED, DataChannel.CLOSED],\n};\n\n// Properties a raw channel must have, WebSocket and RTCDataChannel are two examples\nconst rawChannelProps = [\n    \"send\",\n    \"close\",\n    \"binaryType\",\n    \"onerror\",\n    \"onmessage\",\n    \"onopen\",\n    \"protocol\",\n    \"readyState\",\n];\n\nexport default class Websock {\n    constructor() {\n        this._websocket = null;  // WebSocket or RTCDataChannel object\n\n        this._rQi = 0;           // Receive queue index\n        this._rQlen = 0;         // Next write position in the receive queue\n        this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)\n        // called in init: this._rQ = new Uint8Array(this._rQbufferSize);\n        this._rQ = null; // Receive queue\n\n        this._sQbufferSize = 1024 * 10;  // 10 KiB\n        // called in init: this._sQ = new Uint8Array(this._sQbufferSize);\n        this._sQlen = 0;\n        this._sQ = null;  // Send queue\n\n        this._eventHandlers = {\n            message: () => {},\n            open: () => {},\n            close: () => {},\n            error: () => {}\n        };\n    }\n\n    // Getters and setters\n\n    get readyState() {\n        let subState;\n\n        if (this._websocket === null) {\n            return \"unused\";\n        }\n\n        subState = this._websocket.readyState;\n\n        if (ReadyStates.CONNECTING.includes(subState)) {\n            return \"connecting\";\n        } else if (ReadyStates.OPEN.includes(subState)) {\n            return \"open\";\n        } else if (ReadyStates.CLOSING.includes(subState)) {\n            return \"closing\";\n        } else if (ReadyStates.CLOSED.includes(subState)) {\n            return \"closed\";\n        }\n\n        return \"unknown\";\n    }\n\n    // Receive queue\n    rQpeek8() {\n        return this._rQ[this._rQi];\n    }\n\n    rQskipBytes(bytes) {\n        this._rQi += bytes;\n    }\n\n    rQshift8() {\n        return this._rQshift(1);\n    }\n\n    rQshift16() {\n        return this._rQshift(2);\n    }\n\n    rQshift32() {\n        return this._rQshift(4);\n    }\n\n    // TODO(directxman12): test performance with these vs a DataView\n    _rQshift(bytes) {\n        let res = 0;\n        for (let byte = bytes - 1; byte >= 0; byte--) {\n            res += this._rQ[this._rQi++] << (byte * 8);\n        }\n        return res >>> 0;\n    }\n\n    rQshiftStr(len) {\n        let str = \"\";\n        // Handle large arrays in steps to avoid long strings on the stack\n        for (let i = 0; i < len; i += 4096) {\n            let part = this.rQshiftBytes(Math.min(4096, len - i), false);\n            str += String.fromCharCode.apply(null, part);\n        }\n        return str;\n    }\n\n    rQshiftBytes(len, copy=true) {\n        this._rQi += len;\n        if (copy) {\n            return this._rQ.slice(this._rQi - len, this._rQi);\n        } else {\n            return this._rQ.subarray(this._rQi - len, this._rQi);\n        }\n    }\n\n    rQshiftTo(target, len) {\n        // TODO: make this just use set with views when using a ArrayBuffer to store the rQ\n        target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));\n        this._rQi += len;\n    }\n\n    rQpeekBytes(len, copy=true) {\n        if (copy) {\n            return this._rQ.slice(this._rQi, this._rQi + len);\n        } else {\n            return this._rQ.subarray(this._rQi, this._rQi + len);\n        }\n    }\n\n    // Check to see if we must wait for 'num' bytes (default to FBU.bytes)\n    // to be available in the receive queue. Return true if we need to\n    // wait (and possibly print a debug message), otherwise false.\n    rQwait(msg, num, goback) {\n        if (this._rQlen - this._rQi < num) {\n            if (goback) {\n                if (this._rQi < goback) {\n                    throw new Error(\"rQwait cannot backup \" + goback + \" bytes\");\n                }\n                this._rQi -= goback;\n            }\n            return true; // true means need more data\n        }\n        return false;\n    }\n\n    // Send queue\n\n    sQpush8(num) {\n        this._sQensureSpace(1);\n        this._sQ[this._sQlen++] = num;\n    }\n\n    sQpush16(num) {\n        this._sQensureSpace(2);\n        this._sQ[this._sQlen++] = (num >> 8) & 0xff;\n        this._sQ[this._sQlen++] = (num >> 0) & 0xff;\n    }\n\n    sQpush32(num) {\n        this._sQensureSpace(4);\n        this._sQ[this._sQlen++] = (num >> 24) & 0xff;\n        this._sQ[this._sQlen++] = (num >> 16) & 0xff;\n        this._sQ[this._sQlen++] = (num >>  8) & 0xff;\n        this._sQ[this._sQlen++] = (num >>  0) & 0xff;\n    }\n\n    sQpushString(str) {\n        let bytes = str.split('').map(chr => chr.charCodeAt(0));\n        this.sQpushBytes(new Uint8Array(bytes));\n    }\n\n    sQpushBytes(bytes) {\n        for (let offset = 0;offset < bytes.length;) {\n            this._sQensureSpace(1);\n\n            let chunkSize = this._sQbufferSize - this._sQlen;\n            if (chunkSize > bytes.length - offset) {\n                chunkSize = bytes.length - offset;\n            }\n\n            this._sQ.set(bytes.subarray(offset, offset + chunkSize), this._sQlen);\n            this._sQlen += chunkSize;\n            offset += chunkSize;\n        }\n    }\n\n    flush() {\n        if (this._sQlen > 0 && this.readyState === 'open') {\n            this._websocket.send(new Uint8Array(this._sQ.buffer, 0, this._sQlen));\n            this._sQlen = 0;\n        }\n    }\n\n    _sQensureSpace(bytes) {\n        if (this._sQbufferSize - this._sQlen < bytes) {\n            this.flush();\n        }\n    }\n\n    // Event handlers\n    off(evt) {\n        this._eventHandlers[evt] = () => {};\n    }\n\n    on(evt, handler) {\n        this._eventHandlers[evt] = handler;\n    }\n\n    _allocateBuffers() {\n        this._rQ = new Uint8Array(this._rQbufferSize);\n        this._sQ = new Uint8Array(this._sQbufferSize);\n    }\n\n    init() {\n        this._allocateBuffers();\n        this._rQi = 0;\n        this._websocket = null;\n    }\n\n    open(uri, protocols) {\n        this.attach(new WebSocket(uri, protocols));\n    }\n\n    attach(rawChannel) {\n        this.init();\n\n        // Must get object and class methods to be compatible with the tests.\n        const channelProps = [...Object.keys(rawChannel), ...Object.getOwnPropertyNames(Object.getPrototypeOf(rawChannel))];\n        for (let i = 0; i < rawChannelProps.length; i++) {\n            const prop = rawChannelProps[i];\n            if (channelProps.indexOf(prop) < 0) {\n                throw new Error('Raw channel missing property: ' + prop);\n            }\n        }\n\n        this._websocket = rawChannel;\n        this._websocket.binaryType = \"arraybuffer\";\n        this._websocket.onmessage = this._recvMessage.bind(this);\n\n        this._websocket.onopen = () => {\n            Log.Debug('>> WebSock.onopen');\n            if (this._websocket.protocol) {\n                Log.Info(\"Server choose sub-protocol: \" + this._websocket.protocol);\n            }\n\n            this._eventHandlers.open();\n            Log.Debug(\"<< WebSock.onopen\");\n        };\n\n        this._websocket.onclose = (e) => {\n            Log.Debug(\">> WebSock.onclose\");\n            this._eventHandlers.close(e);\n            Log.Debug(\"<< WebSock.onclose\");\n        };\n\n        this._websocket.onerror = (e) => {\n            Log.Debug(\">> WebSock.onerror: \" + e);\n            this._eventHandlers.error(e);\n            Log.Debug(\"<< WebSock.onerror: \" + e);\n        };\n    }\n\n    close() {\n        if (this._websocket) {\n            if (this.readyState === 'connecting' ||\n                this.readyState === 'open') {\n                Log.Info(\"Closing WebSocket connection\");\n                this._websocket.close();\n            }\n\n            this._websocket.onmessage = () => {};\n        }\n    }\n\n    // private methods\n\n    // We want to move all the unread data to the start of the queue,\n    // e.g. compacting.\n    // The function also expands the receive que if needed, and for\n    // performance reasons we combine these two actions to avoid\n    // unnecessary copying.\n    _expandCompactRQ(minFit) {\n        // if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place\n        // instead of resizing\n        const requiredBufferSize =  (this._rQlen - this._rQi + minFit) * 8;\n        const resizeNeeded = this._rQbufferSize < requiredBufferSize;\n\n        if (resizeNeeded) {\n            // Make sure we always *at least* double the buffer size, and have at least space for 8x\n            // the current amount of data\n            this._rQbufferSize = Math.max(this._rQbufferSize * 2, requiredBufferSize);\n        }\n\n        // we don't want to grow unboundedly\n        if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {\n            this._rQbufferSize = MAX_RQ_GROW_SIZE;\n            if (this._rQbufferSize - (this._rQlen - this._rQi) < minFit) {\n                throw new Error(\"Receive queue buffer exceeded \" + MAX_RQ_GROW_SIZE + \" bytes, and the new message could not fit\");\n            }\n        }\n\n        if (resizeNeeded) {\n            const oldRQbuffer = this._rQ.buffer;\n            this._rQ = new Uint8Array(this._rQbufferSize);\n            this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));\n        } else {\n            this._rQ.copyWithin(0, this._rQi, this._rQlen);\n        }\n\n        this._rQlen = this._rQlen - this._rQi;\n        this._rQi = 0;\n    }\n\n    // push arraybuffer values onto the end of the receive que\n    _recvMessage(e) {\n        if (this._rQlen == this._rQi) {\n            // All data has now been processed, this means we\n            // can reset the receive queue.\n            this._rQlen = 0;\n            this._rQi = 0;\n        }\n        const u8 = new Uint8Array(e.data);\n        if (u8.length > this._rQbufferSize - this._rQlen) {\n            this._expandCompactRQ(u8.length);\n        }\n        this._rQ.set(u8, this._rQlen);\n        this._rQlen += u8.length;\n\n        if (this._rQlen - this._rQi > 0) {\n            this._eventHandlers.message();\n        } else {\n            Log.Debug(\"Ignoring empty message\");\n        }\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/defaults.json",
    "content": "{}\n"
  },
  {
    "path": "services/gateway/noVNC/docs/API-internal.md",
    "content": "# 1. Internal modules\n\nThe noVNC client is composed of several internal modules that handle\nrendering, input, networking, etc. Each of the modules is designed to\nbe cross-browser and independent from each other.\n\nNote however that the API of these modules is not guaranteed to be\nstable, and this documentation is not maintained as well as the\nofficial external API.\n\n\n## 1.1 Module list\n\n* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with\nnon-US keyboard support. Translates keyDown and keyUp events to X11\nkeysym values.\n\n* __Display__ (core/display.js): Efficient 2D rendering abstraction\nlayered on the HTML5 canvas element.\n\n* __Websock__ (core/websock.js): Websock client from websockify\nwith transparent binary data support.\n[Websock API](https://github.com/novnc/websockify-js/wiki/websock.js) wiki page.\n\n\n## 1.2 Callbacks\n\nFor the Mouse, Keyboard and Display objects the callback functions are\nassigned to configuration attributes, just as for the RFB object. The\nWebSock module has a method named 'on' that takes two parameters: the\ncallback event name, and the callback function.\n\n## 2. Modules\n\n## 2.1 Keyboard module\n\n### 2.1.1 Configuration attributes\n\nNone\n\n### 2.1.2 Methods\n\n| name   | parameters | description\n| ------ | ---------- | ------------\n| grab   | ()         | Begin capturing keyboard events\n| ungrab | ()         | Stop capturing keyboard events\n\n### 2.1.3 Callbacks\n\n| name       | parameters           | description\n| ---------- | -------------------- | ------------\n| onkeypress | (keysym, code, down) | Handler for key press/release\n\n\n## 2.2 Display module\n\n### 2.2.1 Configuration attributes\n\n| name         | type  | mode | default | description\n| ------------ | ----- | ---- | ------- | ------------\n| scale        | float | RW   | 1.0     | Display area scale factor 0.0 - 1.0\n| clipViewport | bool  | RW   | false   | Use viewport clipping\n| width        | int   | RO   |         | Display area width\n| height       | int   | RO   |         | Display area height\n\n### 2.2.2 Methods\n\n| name               | parameters                                              | description\n| ------------------ | ------------------------------------------------------- | ------------\n| viewportChangePos  | (deltaX, deltaY)                                        | Move the viewport relative to the current location\n| viewportChangeSize | (width, height)                                         | Change size of the viewport\n| absX               | (x)                                                     | Return X relative to the remote display\n| absY               | (y)                                                     | Return Y relative to the remote display\n| resize             | (width, height)                                         | Set width and height\n| flip               | (from_queue)                                            | Update the visible canvas with the contents of the rendering canvas\n| pending            | ()                                                      | Check if there are waiting items in the render queue\n| flush              | ()                                                      | Resume processing the render queue unless it's empty\n| fillRect           | (x, y, width, height, color, from_queue)                | Draw a filled in rectangle\n| copyImage          | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area\n| imageRect          | (x, y, width, height, mime, arr)                        | Draw a rectangle with an image\n| blitImage          | (x, y, width, height, arr, offset, from_queue)          | Blit pixels (of R,G,B,A) to the display\n| drawImage          | (img, x, y)                                             | Draw image and track damage\n| autoscale          | (containerWidth, containerHeight)                       | Scale the display\n"
  },
  {
    "path": "services/gateway/noVNC/docs/API.md",
    "content": "# noVNC API\n\nThe interface of the noVNC client consists of a single RFB object that\nis instantiated once per connection.\n\n## RFB\n\nThe `RFB` object represents a single connection to a VNC server. It\ncommunicates using a WebSocket that must provide a standard RFB\nprotocol stream.\n\n### Constructor\n\n[`RFB()`](#rfb-1)\n  - Creates and returns a new `RFB` object.\n\n### Properties\n\n`background`\n  - Is a valid CSS [background][mdn-bg] style value indicating which\n    background style should be applied to the element containing the\n    remote session screen. The default value is `rgb(40, 40, 40)` (solid\n    gray color).\n\n[mdn-bg]: https://developer.mozilla.org/en-US/docs/Web/CSS/background\n\n`capabilities` *Read only*\n  - Is an `Object` indicating which optional extensions are available\n    on the server. Some methods may only be called if the corresponding\n    capability is set. The following capabilities are defined:\n\n    | name     | type      | description\n    | -------- | --------- | -----------\n    | `power`  | `boolean` | Machine power control is available\n\n`clippingViewport` *Read only*\n  - Is a `boolean` indicating if the remote session is currently being\n    clipped to its container. Only relevant if `clipViewport` is\n    enabled.\n\n`clipViewport`\n  - Is a `boolean` indicating if the remote session should be clipped\n    to its container. When disabled scrollbars will be shown to handle\n    the resulting overflow. Disabled by default.\n\n`compressionLevel`\n  - Is an `int` in range `[0-9]` controlling the desired compression\n    level. Value `0` means no compression. Level 1 uses a minimum of CPU\n    resources and achieves weak compression ratios, while level 9 offers\n    best compression but is slow in terms of CPU consumption on the server\n    side. Use high levels with very slow network connections.\n    Default value is `2`.\n\n`dragViewport`\n  - Is a `boolean` indicating if mouse events should control the\n    relative position of a clipped remote session. Only relevant if\n    `clipViewport` is enabled. Disabled by default.\n\n`focusOnClick`\n  - Is a `boolean` indicating if keyboard focus should automatically be\n    moved to the remote session when a `mousedown` or `touchstart`\n    event is received. Enabled by default.\n\n`qualityLevel`\n  - Is an `int` in range `[0-9]` controlling the desired JPEG quality.\n    Value `0` implies low quality and `9` implies high quality.\n    Default value is `6`.\n\n`resizeSession`\n  - Is a `boolean` indicating if a request to resize the remote session\n    should be sent whenever the container changes dimensions. Disabled\n    by default.\n\n`scaleViewport`\n  - Is a `boolean` indicating if the remote session should be scaled\n    locally so it fits its container. When disabled it will be centered\n    if the remote session is smaller than its container, or handled\n    according to `clipViewport` if it is larger. Disabled by default.\n\n`showDotCursor`\n  - Is a `boolean` indicating whether a dot cursor should be shown\n    instead of a zero-sized or fully-transparent cursor if the server\n    sets such invisible cursor. Disabled by default.\n\n`viewOnly`\n  - Is a `boolean` indicating if any events (e.g. key presses or mouse\n    movement) should be prevented from being sent to the server.\n    Disabled by default.\n\n### Events\n\n[`bell`](#bell)\n  - The `bell` event is fired when a audible bell request is received\n    from the server.\n\n[`capabilities`](#capabilities)\n  - The `capabilities` event is fired when `RFB.capabilities` is\n    updated.\n\n[`clipboard`](#clipboard)\n  - The `clipboard` event is fired when clipboard data is received from\n    the server.\n\n[`clippingviewport`](#clippingviewport)\n  - The `clippingviewport` event is fired when `RFB.clippingViewport` is\n    updated.\n\n[`connect`](#connect)\n  - The `connect` event is fired when the `RFB` object has completed\n    the connection and handshaking with the server.\n\n[`credentialsrequired`](#credentialsrequired)\n  - The `credentialsrequired` event is fired when more credentials must\n    be given to continue.\n\n[`desktopname`](#desktopname)\n  - The `desktopname` event is fired when the remote desktop name\n    changes.\n\n[`disconnect`](#disconnect)\n  - The `disconnect` event is fired when the `RFB` object disconnects.\n\n[`securityfailure`](#securityfailure)\n  - The `securityfailure` event is fired when the security negotiation\n    with the server fails.\n\n[`serververification`](#serververification)\n  - The `serververification` event is fired when the server identity\n    must be confirmed by the user.\n\n### Methods\n\n[`RFB.approveServer()`](#rfbapproveserver)\n  - Proceed connecting to the server. Should be called after the\n    [`serververification`](#serververification) event has fired and the\n    user has verified the identity of the server.\n\n[`RFB.blur()`](#rfbblur)\n  - Remove keyboard focus from the remote session.\n\n[`RFB.clipboardPasteFrom()`](#rfbclipboardpastefrom)\n  - Send clipboard contents to server.\n\n[`RFB.disconnect()`](#rfbdisconnect)\n  - Disconnect from the server.\n\n[`RFB.focus()`](#rfbfocus)\n  - Move keyboard focus to the remote session.\n\n[`RFB.getImageData()`](#rfbgetimagedata)\n  - Return the current content of the screen as an ImageData array.\n\n[`RFB.machineReboot()`](#rfbmachinereboot)\n  - Request a reboot of the remote machine.\n\n[`RFB.machineReset()`](#rfbmachinereset)\n  - Request a reset of the remote machine.\n\n[`RFB.machineShutdown()`](#rfbmachineshutdown)\n  - Request a shutdown of the remote machine.\n\n[`RFB.sendCredentials()`](#rfbsendcredentials)\n  - Send credentials to server. Should be called after the\n    [`credentialsrequired`](#credentialsrequired) event has fired.\n\n[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)\n  - Send Ctrl-Alt-Del key sequence.\n\n[`RFB.sendKey()`](#rfbsendkey)\n  - Send a key event.\n\n[`RFB.toBlob()`](#rfbtoblob)\n  - Return the current content of the screen as Blob encoded image file.\n\n[`RFB.toDataURL()`](#rfbtodataurl)\n  - Return the current content of the screen as data-url encoded image file.\n\n### Details\n\n#### RFB()\n\nThe `RFB()` constructor returns a new `RFB` object and initiates a new\nconnection to a specified VNC server.\n\n##### Syntax\n\n```js\nnew RFB(target, urlOrChannel);\nnew RFB(target, urlOrChannel, options);\n```\n\n###### Parameters\n\n**`target`**\n  - A block [`HTMLElement`][mdn-elem] that specifies where the `RFB`\n    object should attach itself. The existing contents of the\n    `HTMLElement` will be untouched, but new elements will be added\n    during the lifetime of the `RFB` object.\n\n[mdn-elem]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement\n\n**`urlOrChannel`**\n  - A `DOMString` specifying the VNC server to connect to. This must be\n    a valid WebSocket URL. This can also be a `WebSocket` or `RTCDataChannel`.\n\n**`options`** *Optional*\n  - An `Object` specifying extra details about how the connection\n    should be made.\n\n    Possible options:\n\n    `shared`\n      - A `boolean` indicating if the remote server should be shared or\n        if any other connected clients should be disconnected. Enabled\n        by default.\n\n    `credentials`\n      - An `Object` specifying the credentials to provide to the server\n        when authenticating. The following credentials are possible:\n\n        | name         | type        | description\n        | ------------ | ----------- | -----------\n        | `\"username\"` | `DOMString` | The user that authenticates\n        | `\"password\"` | `DOMString` | Password for the user\n        | `\"target\"`   | `DOMString` | Target machine or session\n\n    `repeaterID`\n      - A `DOMString` specifying the ID to provide to any VNC repeater\n        encountered.\n\n    `wsProtocols`\n      - An `Array` of `DOMString`s specifying the sub-protocols to use\n        in the WebSocket connection. Empty by default.\n\n#### bell\n\nThe `bell` event is fired when the server has requested an audible\nbell.\n\n#### capabilities\n\nThe `capabilities` event is fired whenever an entry is added or removed\nfrom `RFB.capabilities`. The `detail` property is an `Object` with the\nproperty `capabilities` containing the new value of `RFB.capabilities`.\n\n#### clippingviewport\n\nThe `clippingviewport` event is fired whenever `RFB.clippingViewport`\nchanges between `true` and `false`. The `detail` property is a `boolean`\nwith the new value of `RFB.clippingViewport`.\n\n#### clipboard\n\nThe `clipboard` event is fired when the server has sent clipboard data.\nThe `detail` property is an `Object` containing the property `text`\nwhich is a `DOMString` with the clipboard data.\n\n#### credentialsrequired\n\nThe `credentialsrequired` event is fired when the server requests more\ncredentials than were specified to [`RFB()`](#rfb-1). The `detail`\nproperty is an `Object` containing the property `types` which is an\n`Array` of `DOMString` listing the credentials that are required.\n\n#### connect\n\nThe `connect` event is fired after all the handshaking with the server\nis completed and the connection is fully established. After this event\nthe `RFB` object is ready to recieve graphics updates and to send input.\n\n#### desktopname\n\nThe `desktopname` event is fired when the name of the remote desktop\nchanges. The `detail` property is an `Object` with the property `name`\nwhich is a `DOMString` specifying the new name.\n\n#### disconnect\n\nThe `disconnect` event is fired when the connection has been\nterminated. The `detail` property is an `Object` that contains the\nproperty `clean`. `clean` is a `boolean` indicating if the termination\nwas clean or not. In the event of an unexpected termination or an error\n`clean` will be set to false.\n\n#### securityfailure\n\nThe `securityfailure` event is fired when the handshaking process with\nthe server fails during the security negotiation step. The `detail`\nproperty is an `Object` containing the following properties:\n\n| Property | Type        | Description\n| -------- | ----------- | -----------\n| `status` | `long`      | The failure status code\n| `reason` | `DOMString` | The **optional** reason for the failure\n\nThe property `status` corresponds to the [SecurityResult][rfb-secresult]\nstatus code in cases of failure. A status of zero will not be sent in\nthis event since that indicates a successful security handshaking\nprocess. The optional property `reason` is provided by the server and\nthus the language of the string is not known. However most servers will\nprobably send English strings. The server can choose to not send a\nreason and in these cases the `reason` property will be omitted.\n\n[rfb-secresult]: https://github.com/rfbproto/rfbproto/blob/master/rfbproto.rst#securityresult\n\n#### serververification\n\nThe `serververification` event is fired when the server provides\ninformation that allows the user to verify that it is the correct server\nand protect against a man-in-the-middle attack. The `detail` property is\nan `Object` containing the property `type` which is a `DOMString`\nspecifying which type of information the server has provided. Other\nproperties are also available, depending on the value of `type`:\n\n`\"RSA\"`\n - The server identity is verified using just a RSA key. The property\n   `publickey` is a `Uint8Array` containing the public key in a unsigned\n   big endian representation.\n\n#### RFB.approveServer()\n\nThe `RFB.approveServer()` method is used to signal that the user has\nverified the server identity provided in a `serververification` event\nand that the connection can continue.\n\n##### Syntax\n\n```js\nRFB.approveServer();\n```\n\n#### RFB.blur()\n\nThe `RFB.blur()` method remove keyboard focus on the remote session.\nKeyboard events will no longer be sent to the remote server after this\npoint.\n\n##### Syntax\n\n```js\nRFB.blur();\n```\n\n#### RFB.clipboardPasteFrom()\n\nThe `RFB.clipboardPasteFrom()` method is used to send clipboard data\nto the remote server.\n\n##### Syntax\n\n```js\nRFB.clipboardPasteFrom(text);\n```\n\n###### Parameters\n\n**`text`**\n  - A `DOMString` specifying the clipboard data to send.\n\n#### RFB.disconnect()\n\nThe `RFB.disconnect()` method is used to disconnect from the currently\nconnected server.\n\n##### Syntax\n\n```js\nRFB.disconnect();\n```\n\n#### RFB.focus()\n\nThe `RFB.focus()` method sets the keyboard focus on the remote session.\nKeyboard events will be sent to the remote server after this point.\n\n##### Syntax\n\n```js\nRFB.focus();\nRFB.focus(options);\n```\n\n###### Parameters\n\n**`options`** *Optional*\n  - A `object` providing options to control how the focus will be\n    performed. Please see [`HTMLElement.focus()`][mdn-focus] for\n    available options.\n\n[mdn-focus]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus\n\n#### RFB.getImageData()\n\nThe `RFB.getImageData()` method is used to return the current content of\nthe screen encoded as [`ImageData`][mdn-imagedata].\n\n[mdn-imagedata]: https://developer.mozilla.org/en-US/docs/Web/API/ImageData\n\n##### Syntax\n\n```js\nRFB.getImageData();\n```\n\n#### RFB.machineReboot()\n\nThe `RFB.machineReboot()` method is used to request a clean reboot of\nthe remote machine. The capability `power` must be set for this method\nto have any effect.\n\n##### Syntax\n\n```js\nRFB.machineReboot();\n```\n\n#### RFB.machineReset()\n\nThe `RFB.machineReset()` method is used to request a forced reset of\nthe remote machine. The capability `power` must be set for this method\nto have any effect.\n\n##### Syntax\n\n```js\nRFB.machineReset();\n```\n\n#### RFB.machineShutdown()\n\nThe `RFB.machineShutdown()` method is used to request to shut down the\nremote machine. The capability `power` must be set for this method to\nhave any effect.\n\n##### Syntax\n\n```js\nRFB.machineShutdown();\n```\n\n#### RFB.sendCredentials()\n\nThe `RFB.sendCredentials()` method is used to provide the missing\ncredentials after a `credentialsrequired` event has been fired.\n\n##### Syntax\n\n```js\nRFB.sendCredentials(credentials);\n```\n\n###### Parameters\n\n**`credentials`**\n  - An `Object` specifying the credentials to provide to the server\n    when authenticating. See [`RFB()`](#rfb-1) for details.\n\n#### RFB.sendCtrlAltDel()\n\nThe `RFB.sendCtrlAltDel()` method is used to send the key sequence\n*left Control*, *left Alt*, *Delete*. This is a convenience wrapper\naround [`RFB.sendKey()`](#rfbsendkey).\n\n##### Syntax\n\n```js\nRFB.sendCtrlAltDel();\n```\n\n#### RFB.sendKey()\n\nThe `RFB.sendKey()` method is used to send a key event to the server.\n\n##### Syntax\n\n```js\nRFB.sendKey(keysym, code);\nRFB.sendKey(keysym, code, down);\n```\n\n###### Parameters\n\n**`keysym`**\n  - A `long` specifying the RFB keysym to send. Can be `0` if a valid\n    **`code`** is specified.\n\n**`code`**\n  - A `DOMString` specifying the physical key to send. Valid values are\n    those that can be specified to [`KeyboardEvent.code`][mdn-keycode].\n    If the physical key cannot be determined then `null` shall be\n    specified.\n\n[mdn-keycode]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code\n\n**`down`** *Optional*\n  - A `boolean` specifying if a press or a release event should be\n    sent. If omitted then both a press and release event are sent.\n\n#### RFB.toBlob()\n\nThe `RFB.toBlob()` method is used to return the current content of the\nscreen encoded as [`Blob`][mdn-blob].\n\n[mdn-blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob\n\n##### Syntax\n\n```js\nRFB.toBlob(callback);\nRFB.toBlob(callback, type);\nRFB.toBlob(callback, type, quality);\n```\n\n###### Parameters\n\n**`callback`**\n  - A callback function which will receive the resulting\n    [`Blob`][mdn-blob] as the single argument\n\n**`type`** *Optional*\n  - A string indicating the requested MIME type of the image\n\n**`quality`** *Optional*\n  - A number between 0 and 1 indicating the image quality.\n\n#### RFB.toDataURL()\n\nThe `RFB.toDataURL()` method is used to return the current content of the\nscreen encoded as a data URL that could for example be put in the `src` attribute\nof an `img` tag.\n\n##### Syntax\n\n```js\nRFB.toDataURL();\nRFB.toDataURL(type);\nRFB.toDataURL(type, encoderOptions);\n```\n\n###### Parameters\n\n**`type`** *Optional*\n  - A string indicating the requested MIME type of the image\n\n**`encoderOptions`** *Optional*\n  - A number between 0 and 1 indicating the image quality.\n"
  },
  {
    "path": "services/gateway/noVNC/docs/EMBEDDING.md",
    "content": "# Embedding and deploying noVNC application\n\nThis document describes how to embed and deploy the noVNC application, which\nincludes settings and a full user interface. If you are looking for\ndocumentation on how to use the core noVNC library in your own application,\nthen please see our [library documentation](LIBRARY.md).\n\n## Files\n\nThe noVNC application consists of the following files and directories:\n\n* `vnc.html` - The main page for the application and where users should go. It\n  is possible to rename this file.\n\n* `app/` - Support files for the application. Contains code, images, styles and\n  translations.\n\n* `core/` - The core noVNC library.\n\n* `vendor/` - Third party support libraries used by the application and the\n  core library.\n\nThe most basic deployment consists of simply serving these files from a web\nserver and setting up a WebSocket proxy to the VNC server.\n\n## Parameters\n\nThe noVNC application can be controlled by including certain settings in the\nquery string. Currently the following options are available:\n\n* `autoconnect` - Automatically connect as soon as the page has finished\n  loading.\n\n* `reconnect` - If noVNC should automatically reconnect if the connection is\n  dropped.\n\n* `reconnect_delay` - How long to wait in milliseconds before attempting to\n  reconnect.\n\n* `host` - The WebSocket host to connect to.\n\n* `port` - The WebSocket port to connect to.\n\n* `encrypt` - If TLS should be used for the WebSocket connection.\n\n* `path` - The WebSocket path to use.\n\n* `password` - The password sent to the server, if required.\n\n* `repeaterID` - The repeater ID to use if a VNC repeater is detected.\n\n* `shared` - If other VNC clients should be disconnected when noVNC connects.\n\n* `bell` - If the keyboard bell should be enabled or not.\n\n* `view_only` - If the remote session should be in non-interactive mode.\n\n* `view_clip` - If the remote session should be clipped or use scrollbars if\n  it cannot fit in the browser.\n\n* `resize` - How to resize the remote session if it is not the same size as\n  the browser window. Can be one of `off`, `scale` and `remote`.\n\n* `quality` - The session JPEG quality level. Can be `0` to `9`.\n\n* `compression` - The session compression level. Can be `0` to `9`.\n\n* `show_dot` - If a dot cursor should be shown when the remote server provides\n  no local cursor, or provides a fully-transparent (invisible) cursor.\n\n* `logging` - The console log level. Can be one of `error`, `warn`, `info` or\n  `debug`.\n\n## HTTP serving considerations\n### Browser cache issue\n\nIf you serve noVNC files using a web server that provides an ETag header, and\ninclude any options in the query string, a nasty browser cache issue can bite\nyou on upgrade, resulting in a red error box. The issue is caused by a mismatch\nbetween the new vnc.html (which is reloaded because the user has used it with\nnew query string after the upgrade) and the old javascript files (that the\nbrowser reuses from its cache). To avoid this issue, the browser must be told\nto always revalidate cached files using conditional requests. The correct\nsemantics are achieved via the (confusingly named) `Cache-Control: no-cache`\nheader that needs to be provided in the web server responses.\n\n### Example server configurations\n\nApache:\n\n```\n    # In the main configuration file\n    # (Debian/Ubuntu users: use \"a2enmod headers\" instead)\n    LoadModule headers_module modules/mod_headers.so\n\n    # In the <Directory> or <Location> block related to noVNC\n    Header set Cache-Control \"no-cache\"\n```\n\nNginx:\n\n```\n    # In the location block related to noVNC\n    add_header Cache-Control no-cache;\n```\n"
  },
  {
    "path": "services/gateway/noVNC/docs/LIBRARY.md",
    "content": "# Using the noVNC JavaScript library\n\nThis document describes how to make use of the noVNC JavaScript library for\nintegration in your own VNC client application. If you wish to embed the more\ncomplete noVNC application with its included user interface then please see\nour [embedding documentation](EMBEDDING.md).\n\n## API\n\nThe API of noVNC consists of a single object called `RFB`. The formal\ndocumentation for that object can be found in our [API documentation](API.md).\n\n## Example\n\nnoVNC includes a small example application called `vnc_lite.html`. This does\nnot make use of all the features of noVNC, but is a good start to see how to\ndo things.\n\n## Conversion of modules\n\nnoVNC is written using ECMAScript 6 modules. This is not supported by older\nversions of Node.js. To use noVNC with those older versions of Node.js the\nlibrary must first be converted.\n\nFortunately noVNC includes a script to handle this conversion. Please follow\nthe following steps:\n\n 1. Install Node.js\n 2. Run `npm install` in the noVNC directory\n\nThe result of the conversion is available in the `lib/` directory.\n"
  },
  {
    "path": "services/gateway/noVNC/docs/LICENSE.BSD-2-Clause",
    "content": "Copyright (c) <year>, <copyright holder>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "services/gateway/noVNC/docs/LICENSE.BSD-3-Clause",
    "content": "Copyright (c) <year>, <copyright holder>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the <organization> nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "services/gateway/noVNC/docs/LICENSE.MPL-2.0",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in \n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "services/gateway/noVNC/docs/LICENSE.OFL-1.1",
    "content": "This Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at:\nhttp://scripts.sil.org/OFL\n\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded, \nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "services/gateway/noVNC/docs/flash_policy.txt",
    "content": "Manual setup:\n\nDATA=\"echo \\'<cross-domain-policy><allow-access-from domain=\\\\\\\"*\\\\\\\" to-ports=\\\\\\\"*\\\\\\\" /></cross-domain-policy>\\'\"\n/usr/bin/socat -T 1 TCP-L:843,reuseaddr,fork,crlf SYSTEM:\"$DATA\"\n"
  },
  {
    "path": "services/gateway/noVNC/docs/links",
    "content": "New tight PNG protocol:\n    http://wiki.qemu.org/VNC_Tight_PNG\n    http://xf.iksaif.net/blog/index.php?post/2010/06/14/QEMU:-Tight-PNG-and-some-profiling\n\nRFB protocol and extensions:\n    http://tigervnc.org/cgi-bin/rfbproto\n\nCanvas Browser Compatibility:\n    http://philip.html5.org/tests/canvas/suite/tests/results.html\n\nWebSockets API standard:\n    http://www.whatwg.org/specs/web-apps/current-work/complete.html#websocket\n    http://dev.w3.org/html5/websockets/\n    http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt\n\nBrowser Keyboard Events detailed:\n    http://unixpapa.com/js/key.html\n\nActionScript (Flash) WebSocket implementation:\n    http://github.com/gimite/web-socket-js\n\nActionScript (Flash) crypto/TLS library:\n    http://code.google.com/p/as3crypto\n    http://github.com/lyokato/as3crypto_patched\n\nTLS Protocol:\n    http://en.wikipedia.org/wiki/Transport_Layer_Security\n\nGenerate self-signed certificate:\n    http://docs.python.org/dev/library/ssl.html#certificates\n\nCursor appearance/style (for Cursor pseudo-encoding):\n    http://en.wikipedia.org/wiki/ICO_(file_format)\n    http://www.daubnet.com/en/file-format-cur\n    https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property\n    http://www.fileformat.info/format/bmp/egff.htm\n\nIcon/Cursor file format:\n    http://msdn.microsoft.com/en-us/library/ms997538\n    http://msdn.microsoft.com/en-us/library/aa921550.aspx\n    http://msdn.microsoft.com/en-us/library/aa930622.aspx\n\n\nRDP Protocol specification:\n    http://msdn.microsoft.com/en-us/library/cc240445(v=PROT.10).aspx\n\n\nRelated projects:\n    \n    guacamole: http://guacamole.sourceforge.net/\n\n        - Web client, but Java servlet does pre-processing\n\n    jsvnc: http://code.google.com/p/jsvnc/\n\n        - No releases\n\n    webvnc: http://code.google.com/p/webvnc/\n\n        - Jetty web server gateway, no updates since April 2008.\n\n    RealVNC Java applet: http://www.realvnc.com/support/javavncviewer.html\n\n        - Java applet\n\n    Flashlight-VNC: http://www.wizhelp.com/flashlight-vnc/\n\n        - Adobe Flash implementation\n\n    FVNC: http://osflash.org/fvnc\n\n        - Adbove Flash implementation\n\n    CanVNC: http://canvnc.sourceforge.net/\n\n        - HTML client with REST to VNC python proxy. Mostly vapor.\n"
  },
  {
    "path": "services/gateway/noVNC/docs/notes",
    "content": "Rebuilding inflator.js\n\n- Download pako from npm\n- Install browserify using npm\n- browserify core/inflator.mod.js -o core/inflator.js -s Inflator\n"
  },
  {
    "path": "services/gateway/noVNC/docs/novnc_proxy.1",
    "content": ".TH novnc_proxy 1  \"June 25, 2020\" \"version 1.2.0\" \"USER COMMANDS\"\n\n.SH NAME\nnovnc_proxy - noVNC proxy server\n.SH SYNOPSIS\n.B novnc_proxy [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]\n\nStarts the WebSockets proxy and a mini-webserver and\nprovides a cut-and-paste URL to go to.\n\n    --listen [HOST:]PORT  Port for proxy/webserver to listen on\n                          Default: 6080 (on all interfaces)\n    --vnc VNC_HOST:PORT   VNC server host:port proxy target\n                          Default: localhost:5900\n    --cert CERT           Path to combined cert/key file, or just\n                          the cert file if used with --key\n                          Default: self.pem\n    --key KEY             Path to key file, when not combined with cert\n    --web WEB             Path to web files (e.g. vnc.html)\n                          Default: ./\n    --ssl-only            Disable non-https connections.\n\n    --record FILE         Record traffic to FILE.session.js\n\n    --syslog SERVER       Can be local socket such as /dev/log, or a UDP host:port pair.\n\n    --heartbeat SEC       send a ping to the client every SEC seconds\n    --timeout SEC         after SEC seconds exit when not connected\n    --idle-timeout SEC    server exits after SEC seconds if there are no\n                          active connections\n\n.SH AUTHOR\nThe noVNC authors\nhttps://github.com/novnc/noVNC\n\n.SH SEE ALSO\nwebsockify(1), nova-novncproxy(1)\n"
  },
  {
    "path": "services/gateway/noVNC/docs/rfb_notes",
    "content": "5.1.1 ProtocolVersion: 12, 12 bytes\n\n    - Sent by server, max supported\n        12 ascii - \"RFB 003.008\\n\"\n    - Response by client, version to use\n        12 ascii - \"RFB 003.003\\n\"\n\n5.1.2 Authentication: >=4, [16, 4] bytes\n\n    - Sent by server\n        CARD32 - authentication-scheme\n                0 - connection failed\n                    CARD32 - length\n                    length - reason\n                1 - no authentication\n\n                2 - VNC authentication\n                    16 CARD8 - challenge (random bytes)\n\n    - Response by client (if VNC authentication)\n        16 CARD8 - client encrypts the challenge with DES, using user\n                   password as key, sends resulting 16 byte response\n\n    - Response by server (if VNC authentication) \n        CARD32 - 0 - OK\n                 1 - failed\n                 2 - too-many\n\n5.1.3 ClientInitialisation: 1 byte\n    - Sent by client\n        CARD8 - shared-flag, 0 exclusive, non-zero shared\n\n5.1.4 ServerInitialisation: >=24 bytes\n    - Sent by server\n        CARD16 - framebuffer-width\n        CARD16 - framebuffer-height\n        16 byte PIXEL_FORMAT - server-pixel-format\n            CARD8 - bits-per-pixel\n            CARD8 - depth\n            CARD8 - big-endian-flag, non-zero is big endian\n            CARD8 - true-color-flag, non-zero then next 6 apply\n            CARD16 - red-max\n            CARD16 - green-max\n            CARD16 - blue-max\n            CARD8 - red-shift\n            CARD8 - green-shift\n            CARD8 - blue-shift\n            3 bytes - padding\n        CARD32 - name-length\n\n        CARD8[length] - name-string\n\n\n\nClient to Server Messages:\n\n5.2.1 SetPixelFormat: 20 bytes\n    CARD8: 0 - message-type\n    ...\n\n5.2.2 FixColourMapEntries: >=6 bytes\n    CARD8: 1 - message-type\n    ...\n\n5.2.3 SetEncodings: >=8 bytes\n    CARD8: 2 - message-type\n    CARD8    - padding\n    CARD16   - numer-of-encodings\n\n    CARD32   - encoding-type in preference order\n        0 - raw\n        1 - copy-rectangle\n        2 - RRE\n        4 - CoRRE\n        5 - hextile\n\n5.2.4 FramebufferUpdateRequest (10 bytes)\n    CARD8: 3 - message-type\n    CARD8    - incremental (0 for full-update, non-zero for incremental)\n    CARD16   - x-position\n    CARD16   - y-position\n    CARD16   - width\n    CARD16   - height\n\n\n5.2.5 KeyEvent: 8 bytes\n    CARD8: 4 - message-type\n    CARD8    - down-flag\n    2 bytes  - padding\n    CARD32   - key (X-Windows keysym values)\n\n5.2.6 PointerEvent: 6 bytes\n    CARD8: 5 - message-type\n    CARD8    - button-mask\n    CARD16   - x-position\n    CARD16   - y-position\n\n5.2.7 ClientCutText: >=9 bytes\n    CARD8: 6 - message-type\n    ...\n\n\nServer to Client Messages:\n\n5.3.1 FramebufferUpdate\n    CARD8: 0 - message-type\n    1 byte   - padding\n    CARD16   - number-of-rectangles\n\n    CARD16   - x-position\n    CARD16   - y-position\n    CARD16   - width\n    CARD16   - height\n    CARD16   - encoding-type:\n        0 - raw\n        1 - copy rectangle\n        2 - RRE\n        4 - CoRRE\n        5 - hextile\n\n        raw:\n            - width x height pixel values\n\n        copy rectangle: \n            CARD16 - src-x-position\n            CARD16 - src-y-position\n\n        RRE:\n            CARD32  - N number-of-subrectangles\n            Nxd bytes - background-pixel-value (d bits-per-pixel)\n\n        ...\n\n5.3.2 SetColourMapEntries (no support)\n    CARD8: 1 - message-type\n    ...\n\n5.3.3 Bell\n    CARD8: 2 - message-type\n\n5.3.4 ServerCutText\n    CARD8: 3 - message-type\n\n\n\n\n    \n"
  },
  {
    "path": "services/gateway/noVNC/eslint.config.mjs",
    "content": "import globals from \"globals\";\nimport js from \"@eslint/js\";\n\nexport default [\n    js.configs.recommended,\n    {\n        languageOptions: {\n            ecmaVersion: 2022,\n            sourceType: \"module\",\n            globals: {\n                ...globals.browser,\n                ...globals.es2022,\n            }\n        },\n        ignores: [\"**/xtscancodes.js\"],\n        rules: {\n            // Unsafe or confusing stuff that we forbid\n\n            \"no-unused-vars\": [\"error\", { \"vars\": \"all\",\n                                          \"args\": \"none\",\n                                          \"ignoreRestSiblings\": true,\n                                          \"caughtErrors\": \"none\" }],\n            \"no-constant-condition\": [\"error\", { \"checkLoops\": false }],\n            \"no-var\": \"error\",\n            \"no-useless-constructor\": \"error\",\n            \"object-shorthand\": [\"error\", \"methods\", { \"avoidQuotes\": true }],\n            \"prefer-arrow-callback\": \"error\",\n            \"arrow-body-style\": [\"error\", \"as-needed\", { \"requireReturnForObjectLiteral\": false } ],\n            \"arrow-parens\": [\"error\", \"as-needed\", { \"requireForBlockBody\": true }],\n            \"arrow-spacing\": [\"error\"],\n            \"no-confusing-arrow\": [\"error\", { \"allowParens\": true }],\n\n            // Enforced coding style\n\n            \"brace-style\": [\"error\", \"1tbs\", { \"allowSingleLine\": true }],\n            \"indent\": [\"error\", 4, { \"SwitchCase\": 1,\n                                     \"VariableDeclarator\": \"first\",\n                                     \"FunctionDeclaration\": { \"parameters\": \"first\" },\n                                     \"FunctionExpression\": { \"parameters\": \"first\" },\n                                     \"CallExpression\": { \"arguments\": \"first\" },\n                                     \"ArrayExpression\": \"first\",\n                                     \"ObjectExpression\": \"first\",\n                                     \"ImportDeclaration\": \"first\",\n                                     \"ignoreComments\": true }],\n            \"comma-spacing\": [\"error\"],\n            \"comma-style\": [\"error\"],\n            \"curly\": [\"error\", \"multi-line\"],\n            \"func-call-spacing\": [\"error\"],\n            \"func-names\": [\"error\"],\n            \"func-style\": [\"error\", \"declaration\", { \"allowArrowFunctions\": true }],\n            \"key-spacing\": [\"error\"],\n            \"keyword-spacing\": [\"error\"],\n            \"no-trailing-spaces\": [\"error\"],\n            \"semi\": [\"error\"],\n            \"space-before-blocks\": [\"error\"],\n            \"space-before-function-paren\": [\"error\", { \"anonymous\": \"always\",\n                                                       \"named\": \"never\",\n                                                       \"asyncArrow\": \"always\" }],\n            \"switch-colon-spacing\": [\"error\"],\n            \"camelcase\": [\"error\", { \"allow\": [\"^XK_\", \"^XF86XK_\"] }],\n            \"no-console\": [\"error\"],\n        }\n    },\n    {\n        files: [\"po/po2js\", \"po/xgettext-html\"],\n        languageOptions: {\n            globals: {\n                ...globals.node,\n            }\n        },\n        rules: {\n            \"no-console\": 0,\n        },\n    },\n    {\n        files: [\"tests/*\"],\n        languageOptions: {\n            globals: {\n                ...globals.node,\n                ...globals.mocha,\n                sinon: false,\n                expect: false,\n            }\n        },\n        rules: {\n            \"prefer-arrow-callback\": 0,\n            // Too many anonymous callbacks\n            \"func-names\": \"off\",\n        },\n    },\n    {\n        files: [\"utils/*\"],\n        languageOptions: {\n            globals: {\n                ...globals.node,\n            }\n        },\n        rules: {\n            \"no-console\": 0,\n        },\n    },\n];\n"
  },
  {
    "path": "services/gateway/noVNC/karma.conf.js",
    "content": "// Karma configuration\n\n// The Safari launcher is broken, so construct our own\nfunction SafariBrowser(id, baseBrowserDecorator, args) {\n  baseBrowserDecorator(this);\n\n  this._start = function(url) {\n    this._execCommand('/usr/bin/open', ['-W', '-n', '-a', 'Safari', url]);\n  }\n}\n\nSafariBrowser.prototype = {\n  name: 'Safari'\n}\n\nmodule.exports = (config) => {\n  let browsers = [];\n\n  if (process.env.TEST_BROWSER_NAME) {\n    browsers = process.env.TEST_BROWSER_NAME.split(',');\n  }\n\n  const my_conf = {\n\n    // base path that will be used to resolve all patterns (eg. files, exclude)\n    basePath: '',\n\n    // frameworks to use\n    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter\n    frameworks: ['mocha'],\n\n    // list of files / patterns to load in the browser\n    files: [\n      // node modules\n      { pattern: 'node_modules/chai/**', included: false },\n      { pattern: 'node_modules/sinon/**', included: false },\n      { pattern: 'node_modules/sinon-chai/**', included: false },\n      // modules to test\n      { pattern: 'app/localization.js', included: false, type: 'module' },\n      { pattern: 'app/webutil.js', included: false, type: 'module' },\n      { pattern: 'core/**/*.js', included: false, type: 'module' },\n      { pattern: 'vendor/pako/**/*.js', included: false, type: 'module' },\n      // tests\n      { pattern: 'tests/test.*.js', type: 'module' },\n      // test support files\n      { pattern: 'tests/fake.*.js', included: false, type: 'module' },\n      { pattern: 'tests/assertions.js', type: 'module' },\n    ],\n\n    client: {\n      mocha: {\n        // replace Karma debug page with mocha display\n        'reporter': 'html',\n        'ui': 'bdd'\n      }\n    },\n\n    // list of files to exclude\n    exclude: [\n    ],\n\n    plugins: [\n      'karma-*',\n      '@chiragrupani/karma-chromium-edge-launcher',\n      { 'launcher:Safari': [ 'type', SafariBrowser ] },\n    ],\n\n    // start these browsers\n    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher\n    browsers: browsers,\n\n    // test results reporter to use\n    // possible values: 'dots', 'progress'\n    // available reporters: https://npmjs.org/browse/keyword/karma-reporter\n    reporters: ['mocha'],\n\n\n    // level of logging\n    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n    logLevel: config.LOG_INFO,\n\n\n    // enable / disable watching file and executing tests whenever any file changes\n    autoWatch: false,\n\n    // Continuous Integration mode\n    // if true, Karma captures browsers, runs the tests and exits\n    singleRun: true,\n  };\n\n  config.set(my_conf);\n};\n"
  },
  {
    "path": "services/gateway/noVNC/mandatory.json",
    "content": "{}\n"
  },
  {
    "path": "services/gateway/noVNC/package.json",
    "content": "{\n  \"name\": \"@novnc/novnc\",\n  \"version\": \"1.6.0\",\n  \"description\": \"An HTML5 VNC client\",\n  \"browser\": \"lib/rfb\",\n  \"directories\": {\n    \"lib\": \"lib\",\n    \"doc\": \"docs\",\n    \"test\": \"tests\"\n  },\n  \"files\": [\n    \"lib\",\n    \"AUTHORS\",\n    \"VERSION\",\n    \"docs/API.md\",\n    \"docs/LIBRARY.md\",\n    \"docs/LICENSE*\"\n  ],\n  \"scripts\": {\n    \"lint\": \"eslint app core po/po2js po/xgettext-html tests utils\",\n    \"test\": \"karma start karma.conf.js\",\n    \"prepublish\": \"node ./utils/convert.js --clean\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/novnc/noVNC.git\"\n  },\n  \"author\": \"Joel Martin <github@martintribe.org> (https://github.com/kanaka)\",\n  \"contributors\": [\n    \"Samuel Mannehed <samuel@cendio.se> (https://github.com/samhed)\",\n    \"Pierre Ossman <ossman@cendio.se> (https://github.com/CendioOssman)\"\n  ],\n  \"license\": \"MPL-2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/novnc/noVNC/issues\"\n  },\n  \"homepage\": \"https://github.com/novnc/noVNC\",\n  \"devDependencies\": {\n    \"@babel/core\": \"latest\",\n    \"@babel/preset-env\": \"latest\",\n    \"babel-plugin-import-redirect\": \"latest\",\n    \"browserify\": \"latest\",\n    \"chai\": \"latest\",\n    \"commander\": \"latest\",\n    \"eslint\": \"latest\",\n    \"fs-extra\": \"latest\",\n    \"globals\": \"latest\",\n    \"jsdom\": \"latest\",\n    \"karma\": \"latest\",\n    \"karma-mocha\": \"latest\",\n    \"karma-chrome-launcher\": \"latest\",\n    \"@chiragrupani/karma-chromium-edge-launcher\": \"latest\",\n    \"karma-firefox-launcher\": \"latest\",\n    \"karma-ie-launcher\": \"latest\",\n    \"karma-mocha-reporter\": \"latest\",\n    \"karma-safari-launcher\": \"latest\",\n    \"karma-script-launcher\": \"latest\",\n    \"mocha\": \"latest\",\n    \"pofile\": \"latest\",\n    \"sinon\": \"latest\",\n    \"sinon-chai\": \"latest\"\n  },\n  \"dependencies\": {},\n  \"keywords\": [\n    \"vnc\",\n    \"rfb\",\n    \"novnc\",\n    \"websockify\"\n  ]\n}\n"
  },
  {
    "path": "services/gateway/noVNC/po/Makefile",
    "content": "all:\n.PHONY: update-po update-js update-pot\n.PHONY: FORCE\n\nLINGUAS := cs de el es fr it ja ko nl pl pt_BR ru sv tr zh_CN zh_TW\n\nVERSION := $(shell grep '\"version\"' ../package.json | cut -d '\"' -f 4)\n\nPOFILES := $(addsuffix .po,$(LINGUAS))\nJSONFILES := $(addprefix ../app/locale/,$(addsuffix .json,$(LINGUAS)))\n\nupdate-po: $(POFILES)\nupdate-js: $(JSONFILES)\n\n%.po: FORCE\n\tmsgmerge --update --lang=$* $@ noVNC.pot\n../app/locale/%.json: FORCE\n\t./po2js $*.po $@\n\nupdate-pot:\n\txgettext --output=noVNC.js.pot \\\n\t\t--copyright-holder=\"The noVNC authors\" \\\n\t\t--package-name=\"noVNC\" \\\n\t\t--package-version=\"$(VERSION)\" \\\n\t\t--msgid-bugs-address=\"novnc@googlegroups.com\" \\\n\t\t--add-comments=TRANSLATORS: \\\n\t\t--from-code=UTF-8 \\\n\t\t--sort-by-file \\\n\t\t../app/*.js \\\n\t\t../core/*.js \\\n\t\t../core/input/*.js\n\t./xgettext-html --output=noVNC.html.pot \\\n\t\t../vnc.html\n\tmsgcat --output-file=noVNC.pot \\\n\t\t--sort-by-file noVNC.js.pot noVNC.html.pot\n\trm -f noVNC.js.pot noVNC.html.pot\n"
  },
  {
    "path": "services/gateway/noVNC/po/cs.po",
    "content": "# Czech translations for noVNC package.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Petr <petr@kle.cz>, 2018.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.0.0-testing.2\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2018-10-19 12:00+0200\\n\"\n\"PO-Revision-Date: 2018-10-19 12:00+0200\\n\"\n\"Last-Translator: Petr <petr@kle.cz>\\n\"\n\"Language-Team: Czech\\n\"\n\"Language: cs\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\\n\"\n\n#: ../app/ui.js:389\nmsgid \"Connecting...\"\nmsgstr \"Připojení...\"\n\n#: ../app/ui.js:396\nmsgid \"Disconnecting...\"\nmsgstr \"Odpojení...\"\n\n#: ../app/ui.js:402\nmsgid \"Reconnecting...\"\nmsgstr \"Obnova připojení...\"\n\n#: ../app/ui.js:407\nmsgid \"Internal error\"\nmsgstr \"Vnitřní chyba\"\n\n#: ../app/ui.js:997\nmsgid \"Must set host\"\nmsgstr \"Hostitel musí být nastavení\"\n\n#: ../app/ui.js:1079\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Připojení (šifrované) k \"\n\n#: ../app/ui.js:1081\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Připojení (nešifrované) k \"\n\n#: ../app/ui.js:1104\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Něco se pokazilo, odpojeno\"\n\n#: ../app/ui.js:1107\nmsgid \"Failed to connect to server\"\nmsgstr \"Chyba připojení k serveru\"\n\n#: ../app/ui.js:1117\nmsgid \"Disconnected\"\nmsgstr \"Odpojeno\"\n\n#: ../app/ui.js:1130\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"Nové připojení bylo odmítnuto s odůvodněním: \"\n\n#: ../app/ui.js:1133\nmsgid \"New connection has been rejected\"\nmsgstr \"Nové připojení bylo odmítnuto\"\n\n#: ../app/ui.js:1153\nmsgid \"Password is required\"\nmsgstr \"Je vyžadováno heslo\"\n\n#: ../vnc.html:84\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC narazilo na chybu:\"\n\n#: ../vnc.html:94\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Skrýt/zobrazit ovládací panel\"\n\n#: ../vnc.html:101\nmsgid \"Move/Drag viewport\"\nmsgstr \"Přesunout/přetáhnout výřez\"\n\n#: ../vnc.html:101\nmsgid \"viewport drag\"\nmsgstr \"přesun výřezu\"\n\n#: ../vnc.html:107 ../vnc.html:110 ../vnc.html:113 ../vnc.html:116\nmsgid \"Active Mouse Button\"\nmsgstr \"Aktivní tlačítka myši\"\n\n#: ../vnc.html:107\nmsgid \"No mousebutton\"\nmsgstr \"Žádné\"\n\n#: ../vnc.html:110\nmsgid \"Left mousebutton\"\nmsgstr \"Levé tlačítko myši\"\n\n#: ../vnc.html:113\nmsgid \"Middle mousebutton\"\nmsgstr \"Prostřední tlačítko myši\"\n\n#: ../vnc.html:116\nmsgid \"Right mousebutton\"\nmsgstr \"Pravé tlačítko myši\"\n\n#: ../vnc.html:119\nmsgid \"Keyboard\"\nmsgstr \"Klávesnice\"\n\n#: ../vnc.html:119\nmsgid \"Show keyboard\"\nmsgstr \"Zobrazit klávesnici\"\n\n#: ../vnc.html:126\nmsgid \"Extra keys\"\nmsgstr \"Extra klávesy\"\n\n#: ../vnc.html:126\nmsgid \"Show extra keys\"\nmsgstr \"Zobrazit extra klávesy\"\n\n#: ../vnc.html:131\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:131\nmsgid \"Toggle Ctrl\"\nmsgstr \"Přepnout Ctrl\"\n\n#: ../vnc.html:134\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:134\nmsgid \"Toggle Alt\"\nmsgstr \"Přepnout Alt\"\n\n#: ../vnc.html:137\nmsgid \"Send Tab\"\nmsgstr \"Odeslat tabulátor\"\n\n#: ../vnc.html:137\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:140\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:140\nmsgid \"Send Escape\"\nmsgstr \"Odeslat Esc\"\n\n#: ../vnc.html:143\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:143\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Poslat Ctrl-Alt-Del\"\n\n#: ../vnc.html:151\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Vypnutí/Restart\"\n\n#: ../vnc.html:151\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Vypnutí/Restart...\"\n\n#: ../vnc.html:157\nmsgid \"Power\"\nmsgstr \"Napájení\"\n\n#: ../vnc.html:159\nmsgid \"Shutdown\"\nmsgstr \"Vypnout\"\n\n#: ../vnc.html:160\nmsgid \"Reboot\"\nmsgstr \"Restart\"\n\n#: ../vnc.html:161\nmsgid \"Reset\"\nmsgstr \"Reset\"\n\n#: ../vnc.html:166 ../vnc.html:172\nmsgid \"Clipboard\"\nmsgstr \"Schránka\"\n\n#: ../vnc.html:176\nmsgid \"Clear\"\nmsgstr \"Vymazat\"\n\n#: ../vnc.html:182\nmsgid \"Fullscreen\"\nmsgstr \"Celá obrazovka\"\n\n#: ../vnc.html:187 ../vnc.html:194\nmsgid \"Settings\"\nmsgstr \"Nastavení\"\n\n#: ../vnc.html:197\nmsgid \"Shared mode\"\nmsgstr \"Sdílený režim\"\n\n#: ../vnc.html:200\nmsgid \"View only\"\nmsgstr \"Pouze prohlížení\"\n\n#: ../vnc.html:204\nmsgid \"Clip to window\"\nmsgstr \"Přizpůsobit oknu\"\n\n#: ../vnc.html:207\nmsgid \"Scaling mode:\"\nmsgstr \"Přizpůsobení velikosti\"\n\n#: ../vnc.html:209\nmsgid \"None\"\nmsgstr \"Žádné\"\n\n#: ../vnc.html:210\nmsgid \"Local scaling\"\nmsgstr \"Místní\"\n\n#: ../vnc.html:211\nmsgid \"Remote resizing\"\nmsgstr \"Vzdálené\"\n\n#: ../vnc.html:216\nmsgid \"Advanced\"\nmsgstr \"Pokročilé\"\n\n#: ../vnc.html:219\nmsgid \"Repeater ID:\"\nmsgstr \"ID opakovače\"\n\n#: ../vnc.html:223\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:226\nmsgid \"Encrypt\"\nmsgstr \"Šifrování:\"\n\n#: ../vnc.html:229\nmsgid \"Host:\"\nmsgstr \"Hostitel:\"\n\n#: ../vnc.html:233\nmsgid \"Port:\"\nmsgstr \"Port:\"\n\n#: ../vnc.html:237\nmsgid \"Path:\"\nmsgstr \"Cesta\"\n\n#: ../vnc.html:244\nmsgid \"Automatic reconnect\"\nmsgstr \"Automatická obnova připojení\"\n\n#: ../vnc.html:247\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"Zpoždění připojení (ms)\"\n\n#: ../vnc.html:252\nmsgid \"Show dot when no cursor\"\nmsgstr \"Tečka místo chybějícího kurzoru myši\"\n\n#: ../vnc.html:257\nmsgid \"Logging:\"\nmsgstr \"Logování:\"\n\n#: ../vnc.html:269\nmsgid \"Disconnect\"\nmsgstr \"Odpojit\"\n\n#: ../vnc.html:288\nmsgid \"Connect\"\nmsgstr \"Připojit\"\n\n#: ../vnc.html:298\nmsgid \"Password:\"\nmsgstr \"Heslo\"\n\n#: ../vnc.html:302\nmsgid \"Send Password\"\nmsgstr \"Odeslat heslo\"\n\n#: ../vnc.html:312\nmsgid \"Cancel\"\nmsgstr \"Zrušit\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/de.po",
    "content": "# German translations for noVNC package\n# German translation for noVNC.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Loek Janssen <loekjanssen@gmail.com>, 2016.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 0.6.1\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2017-11-24 07:16+0000\\n\"\n\"PO-Revision-Date: 2017-11-24 08:20+0100\\n\"\n\"Last-Translator: Dominik Csapak <d.csapak@proxmox.com>\\n\"\n\"Language-Team: none\\n\"\n\"Language: de\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"X-Generator: Poedit 1.8.11\\n\"\n\n#: ../app/ui.js:404\nmsgid \"Connecting...\"\nmsgstr \"Verbinden...\"\n\n#: ../app/ui.js:411\nmsgid \"Disconnecting...\"\nmsgstr \"Verbindung trennen...\"\n\n#: ../app/ui.js:417\nmsgid \"Reconnecting...\"\nmsgstr \"Verbindung wiederherstellen...\"\n\n#: ../app/ui.js:422\nmsgid \"Internal error\"\nmsgstr \"Interner Fehler\"\n\n#: ../app/ui.js:1019\nmsgid \"Must set host\"\nmsgstr \"Richten Sie den Server ein\"\n\n#: ../app/ui.js:1099\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Verbunden mit (verschlüsselt) \"\n\n#: ../app/ui.js:1101\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Verbunden mit (unverschlüsselt) \"\n\n#: ../app/ui.js:1119\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Etwas lief schief, Verbindung wurde getrennt\"\n\n#: ../app/ui.js:1129\nmsgid \"Disconnected\"\nmsgstr \"Verbindung zum Server getrennt\"\n\n#: ../app/ui.js:1142\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"Verbindung wurde aus folgendem Grund abgelehnt: \"\n\n#: ../app/ui.js:1145\nmsgid \"New connection has been rejected\"\nmsgstr \"Verbindung wurde abgelehnt\"\n\n#: ../app/ui.js:1166\nmsgid \"Password is required\"\nmsgstr \"Passwort ist erforderlich\"\n\n#: ../vnc.html:89\nmsgid \"noVNC encountered an error:\"\nmsgstr \"Ein Fehler ist aufgetreten:\"\n\n#: ../vnc.html:99\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Kontrollleiste verstecken/anzeigen\"\n\n#: ../vnc.html:106\nmsgid \"Move/Drag viewport\"\nmsgstr \"Ansichtsfenster verschieben/ziehen\"\n\n#: ../vnc.html:106\nmsgid \"viewport drag\"\nmsgstr \"Ansichtsfenster ziehen\"\n\n#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121\nmsgid \"Active Mouse Button\"\nmsgstr \"Aktive Maustaste\"\n\n#: ../vnc.html:112\nmsgid \"No mousebutton\"\nmsgstr \"Keine Maustaste\"\n\n#: ../vnc.html:115\nmsgid \"Left mousebutton\"\nmsgstr \"Linke Maustaste\"\n\n#: ../vnc.html:118\nmsgid \"Middle mousebutton\"\nmsgstr \"Mittlere Maustaste\"\n\n#: ../vnc.html:121\nmsgid \"Right mousebutton\"\nmsgstr \"Rechte Maustaste\"\n\n#: ../vnc.html:124\nmsgid \"Keyboard\"\nmsgstr \"Tastatur\"\n\n#: ../vnc.html:124\nmsgid \"Show keyboard\"\nmsgstr \"Tastatur anzeigen\"\n\n#: ../vnc.html:131\nmsgid \"Extra keys\"\nmsgstr \"Zusatztasten\"\n\n#: ../vnc.html:131\nmsgid \"Show extra keys\"\nmsgstr \"Zusatztasten anzeigen\"\n\n#: ../vnc.html:136\nmsgid \"Ctrl\"\nmsgstr \"Strg\"\n\n#: ../vnc.html:136\nmsgid \"Toggle Ctrl\"\nmsgstr \"Strg umschalten\"\n\n#: ../vnc.html:139\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:139\nmsgid \"Toggle Alt\"\nmsgstr \"Alt umschalten\"\n\n#: ../vnc.html:142\nmsgid \"Send Tab\"\nmsgstr \"Tab senden\"\n\n#: ../vnc.html:142\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:145\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:145\nmsgid \"Send Escape\"\nmsgstr \"Escape senden\"\n\n#: ../vnc.html:148\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Strg+Alt+Entf\"\n\n#: ../vnc.html:148\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Strg+Alt+Entf senden\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Herunterfahren/Neustarten\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Herunterfahren/Neustarten...\"\n\n#: ../vnc.html:162\nmsgid \"Power\"\nmsgstr \"Energie\"\n\n#: ../vnc.html:164\nmsgid \"Shutdown\"\nmsgstr \"Herunterfahren\"\n\n#: ../vnc.html:165\nmsgid \"Reboot\"\nmsgstr \"Neustarten\"\n\n#: ../vnc.html:166\nmsgid \"Reset\"\nmsgstr \"Zurücksetzen\"\n\n#: ../vnc.html:171 ../vnc.html:177\nmsgid \"Clipboard\"\nmsgstr \"Zwischenablage\"\n\n#: ../vnc.html:181\nmsgid \"Clear\"\nmsgstr \"Löschen\"\n\n#: ../vnc.html:187\nmsgid \"Fullscreen\"\nmsgstr \"Vollbild\"\n\n#: ../vnc.html:192 ../vnc.html:199\nmsgid \"Settings\"\nmsgstr \"Einstellungen\"\n\n#: ../vnc.html:202\nmsgid \"Shared mode\"\nmsgstr \"Geteilter Modus\"\n\n#: ../vnc.html:205\nmsgid \"View only\"\nmsgstr \"Nur betrachten\"\n\n#: ../vnc.html:209\nmsgid \"Clip to window\"\nmsgstr \"Auf Fenster begrenzen\"\n\n#: ../vnc.html:212\nmsgid \"Scaling mode:\"\nmsgstr \"Skalierungsmodus:\"\n\n#: ../vnc.html:214\nmsgid \"None\"\nmsgstr \"Keiner\"\n\n#: ../vnc.html:215\nmsgid \"Local scaling\"\nmsgstr \"Lokales skalieren\"\n\n#: ../vnc.html:216\nmsgid \"Remote resizing\"\nmsgstr \"Serverseitiges skalieren\"\n\n#: ../vnc.html:221\nmsgid \"Advanced\"\nmsgstr \"Erweitert\"\n\n#: ../vnc.html:224\nmsgid \"Repeater ID:\"\nmsgstr \"Repeater ID:\"\n\n#: ../vnc.html:228\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:231\nmsgid \"Encrypt\"\nmsgstr \"Verschlüsselt\"\n\n#: ../vnc.html:234\nmsgid \"Host:\"\nmsgstr \"Server:\"\n\n#: ../vnc.html:238\nmsgid \"Port:\"\nmsgstr \"Port:\"\n\n#: ../vnc.html:242\nmsgid \"Path:\"\nmsgstr \"Pfad:\"\n\n#: ../vnc.html:249\nmsgid \"Automatic reconnect\"\nmsgstr \"Automatisch wiederverbinden\"\n\n#: ../vnc.html:252\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"Wiederverbindungsverzögerung (ms):\"\n\n#: ../vnc.html:258\nmsgid \"Logging:\"\nmsgstr \"Protokollierung:\"\n\n#: ../vnc.html:270\nmsgid \"Disconnect\"\nmsgstr \"Verbindung trennen\"\n\n#: ../vnc.html:289\nmsgid \"Connect\"\nmsgstr \"Verbinden\"\n\n#: ../vnc.html:299\nmsgid \"Password:\"\nmsgstr \"Passwort:\"\n\n#: ../vnc.html:313\nmsgid \"Cancel\"\nmsgstr \"Abbrechen\"\n\n#: ../vnc.html:329\nmsgid \"Canvas not supported.\"\nmsgstr \"Canvas nicht unterstützt.\"\n\n#~ msgid \"Disconnect timeout\"\n#~ msgstr \"Zeitüberschreitung beim Trennen\"\n\n#~ msgid \"Local Downscaling\"\n#~ msgstr \"Lokales herunterskalieren\"\n\n#~ msgid \"Local Cursor\"\n#~ msgstr \"Lokaler Mauszeiger\"\n\n#~ msgid \"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen\"\n#~ msgstr \"'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt\"\n\n#~ msgid \"True Color\"\n#~ msgstr \"True Color\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/el.po",
    "content": "# Greek translations for noVNC package.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Giannis Kosmas <kosmasgiannis@gmail.com>, 2016.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 0.6.1\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2022-12-27 15:24+0100\\n\"\n\"PO-Revision-Date: 2017-10-11 16:16+0200\\n\"\n\"Last-Translator: Giannis Kosmas <kosmasgiannis@gmail.com>\\n\"\n\"Language-Team: none\\n\"\n\"Language: el\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\n#: ../app/ui.js:69\nmsgid \"HTTPS is required for full functionality\"\nmsgstr \"Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα\"\n\n#: ../app/ui.js:410\nmsgid \"Connecting...\"\nmsgstr \"Συνδέεται...\"\n\n#: ../app/ui.js:417\nmsgid \"Disconnecting...\"\nmsgstr \"Aποσυνδέεται...\"\n\n#: ../app/ui.js:423\nmsgid \"Reconnecting...\"\nmsgstr \"Επανασυνδέεται...\"\n\n#: ../app/ui.js:428\nmsgid \"Internal error\"\nmsgstr \"Εσωτερικό σφάλμα\"\n\n#: ../app/ui.js:1026\nmsgid \"Must set host\"\nmsgstr \"Πρέπει να οριστεί ο διακομιστής\"\n\n#: ../app/ui.js:1110\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Συνδέθηκε (κρυπτογραφημένα) με το \"\n\n#: ../app/ui.js:1112\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Συνδέθηκε (μη κρυπτογραφημένα) με το \"\n\n#: ../app/ui.js:1135\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Κάτι πήγε στραβά, η σύνδεση διακόπηκε\"\n\n#: ../app/ui.js:1138\nmsgid \"Failed to connect to server\"\nmsgstr \"Αποτυχία στη σύνδεση με το διακομιστή\"\n\n#: ../app/ui.js:1150\nmsgid \"Disconnected\"\nmsgstr \"Αποσυνδέθηκε\"\n\n#: ../app/ui.js:1165\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"Η νέα σύνδεση απορρίφθηκε διότι: \"\n\n#: ../app/ui.js:1168\nmsgid \"New connection has been rejected\"\nmsgstr \"Η νέα σύνδεση απορρίφθηκε \"\n\n#: ../app/ui.js:1234\nmsgid \"Credentials are required\"\nmsgstr \"Απαιτούνται διαπιστευτήρια\"\n\n#: ../vnc.html:57\nmsgid \"noVNC encountered an error:\"\nmsgstr \"το noVNC αντιμετώπισε ένα σφάλμα:\"\n\n#: ../vnc.html:67\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Απόκρυψη/Εμφάνιση γραμμής ελέγχου\"\n\n#: ../vnc.html:76\nmsgid \"Drag\"\nmsgstr \"Σύρσιμο\"\n\n#: ../vnc.html:76\nmsgid \"Move/Drag Viewport\"\nmsgstr \"Μετακίνηση/Σύρσιμο Θεατού πεδίου\"\n\n#: ../vnc.html:82\nmsgid \"Keyboard\"\nmsgstr \"Πληκτρολόγιο\"\n\n#: ../vnc.html:82\nmsgid \"Show Keyboard\"\nmsgstr \"Εμφάνιση Πληκτρολογίου\"\n\n#: ../vnc.html:87\nmsgid \"Extra keys\"\nmsgstr \"Επιπλέον πλήκτρα\"\n\n#: ../vnc.html:87\nmsgid \"Show Extra Keys\"\nmsgstr \"Εμφάνιση Επιπλέον Πλήκτρων\"\n\n#: ../vnc.html:92\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:92\nmsgid \"Toggle Ctrl\"\nmsgstr \"Εναλλαγή Ctrl\"\n\n#: ../vnc.html:95\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:95\nmsgid \"Toggle Alt\"\nmsgstr \"Εναλλαγή Alt\"\n\n#: ../vnc.html:98\nmsgid \"Toggle Windows\"\nmsgstr \"Εναλλαγή Παράθυρων\"\n\n#: ../vnc.html:98\nmsgid \"Windows\"\nmsgstr \"Παράθυρα\"\n\n#: ../vnc.html:101\nmsgid \"Send Tab\"\nmsgstr \"Αποστολή Tab\"\n\n#: ../vnc.html:101\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:104\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:104\nmsgid \"Send Escape\"\nmsgstr \"Αποστολή Escape\"\n\n#: ../vnc.html:107\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:107\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Αποστολή Ctrl-Alt-Del\"\n\n#: ../vnc.html:114\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Κλείσιμο/Επανεκκίνηση\"\n\n#: ../vnc.html:114\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Κλείσιμο/Επανεκκίνηση...\"\n\n#: ../vnc.html:120\nmsgid \"Power\"\nmsgstr \"Απενεργοποίηση\"\n\n#: ../vnc.html:122\nmsgid \"Shutdown\"\nmsgstr \"Κλείσιμο\"\n\n#: ../vnc.html:123\nmsgid \"Reboot\"\nmsgstr \"Επανεκκίνηση\"\n\n#: ../vnc.html:124\nmsgid \"Reset\"\nmsgstr \"Επαναφορά\"\n\n#: ../vnc.html:129 ../vnc.html:135\nmsgid \"Clipboard\"\nmsgstr \"Πρόχειρο\"\n\n#: ../vnc.html:137\nmsgid \"Edit clipboard content in the textarea below.\"\nmsgstr \"Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.\"\n\n#: ../vnc.html:145\n#, fuzzy\nmsgid \"Full Screen\"\nmsgstr \"Πλήρης Οθόνη\"\n\n#: ../vnc.html:150 ../vnc.html:156\nmsgid \"Settings\"\nmsgstr \"Ρυθμίσεις\"\n\n#: ../vnc.html:160\nmsgid \"Shared Mode\"\nmsgstr \"Κοινόχρηστη Λειτουργία\"\n\n#: ../vnc.html:163\nmsgid \"View Only\"\nmsgstr \"Μόνο Θέαση\"\n\n#: ../vnc.html:167\nmsgid \"Clip to Window\"\nmsgstr \"Αποκοπή στο όριο του Παράθυρου\"\n\n#: ../vnc.html:170\nmsgid \"Scaling Mode:\"\nmsgstr \"Λειτουργία Κλιμάκωσης:\"\n\n#: ../vnc.html:172\nmsgid \"None\"\nmsgstr \"Καμία\"\n\n#: ../vnc.html:173\nmsgid \"Local Scaling\"\nmsgstr \"Τοπική Κλιμάκωση\"\n\n#: ../vnc.html:174\nmsgid \"Remote Resizing\"\nmsgstr \"Απομακρυσμένη Αλλαγή μεγέθους\"\n\n#: ../vnc.html:179\nmsgid \"Advanced\"\nmsgstr \"Για προχωρημένους\"\n\n#: ../vnc.html:182\nmsgid \"Quality:\"\nmsgstr \"Ποιότητα:\"\n\n#: ../vnc.html:186\nmsgid \"Compression level:\"\nmsgstr \"Επίπεδο συμπίεσης:\"\n\n#: ../vnc.html:191\nmsgid \"Repeater ID:\"\nmsgstr \"Repeater ID:\"\n\n#: ../vnc.html:195\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:198\nmsgid \"Encrypt\"\nmsgstr \"Κρυπτογράφηση\"\n\n#: ../vnc.html:201\nmsgid \"Host:\"\nmsgstr \"Όνομα διακομιστή:\"\n\n#: ../vnc.html:205\nmsgid \"Port:\"\nmsgstr \"Πόρτα διακομιστή:\"\n\n#: ../vnc.html:209\nmsgid \"Path:\"\nmsgstr \"Διαδρομή:\"\n\n#: ../vnc.html:216\nmsgid \"Automatic Reconnect\"\nmsgstr \"Αυτόματη επανασύνδεση\"\n\n#: ../vnc.html:219\nmsgid \"Reconnect Delay (ms):\"\nmsgstr \"Καθυστέρηση επανασύνδεσης (ms):\"\n\n#: ../vnc.html:224\nmsgid \"Show Dot when No Cursor\"\nmsgstr \"Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας\"\n\n#: ../vnc.html:229\nmsgid \"Logging:\"\nmsgstr \"Καταγραφή:\"\n\n#: ../vnc.html:238\nmsgid \"Version:\"\nmsgstr \"Έκδοση:\"\n\n#: ../vnc.html:246\nmsgid \"Disconnect\"\nmsgstr \"Αποσύνδεση\"\n\n#: ../vnc.html:269\nmsgid \"Connect\"\nmsgstr \"Σύνδεση\"\n\n#: ../vnc.html:278\nmsgid \"Server identity\"\nmsgstr \"Ταυτότητα Διακομιστή\"\n\n#: ../vnc.html:281\nmsgid \"The server has provided the following identifying information:\"\nmsgstr \"Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:\"\n\n#: ../vnc.html:285\nmsgid \"Fingerprint:\"\nmsgstr \"Δακτυλικό αποτύπωμα:\"\n\n#: ../vnc.html:288\nmsgid \"\"\n\"Please verify that the information is correct and press \\\"Approve\\\". \"\n\"Otherwise press \\\"Reject\\\".\"\nmsgstr \"\"\n\"Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \\\"Αποδοχή\\\". \"\n\"Αλλιώς πιέστε \\\"Απόρριψη\\\".\"\n\n#: ../vnc.html:293\nmsgid \"Approve\"\nmsgstr \"Αποδοχή\"\n\n#: ../vnc.html:294\nmsgid \"Reject\"\nmsgstr \"Απόρριψη\"\n\n#: ../vnc.html:302\nmsgid \"Credentials\"\nmsgstr \"Διαπιστευτήρια\"\n\n#: ../vnc.html:306\nmsgid \"Username:\"\nmsgstr \"Κωδικός Χρήστη:\"\n\n#: ../vnc.html:310\nmsgid \"Password:\"\nmsgstr \"Κωδικός Πρόσβασης:\"\n\n#: ../vnc.html:314\nmsgid \"Send Credentials\"\nmsgstr \"Αποστολή Διαπιστευτηρίων\"\n\n#: ../vnc.html:323\nmsgid \"Cancel\"\nmsgstr \"Ακύρωση\"\n\n#~ msgid \"Password is required\"\n#~ msgstr \"Απαιτείται ο κωδικός πρόσβασης\"\n\n#~ msgid \"viewport drag\"\n#~ msgstr \"σύρσιμο θεατού πεδίου\"\n\n#~ msgid \"Active Mouse Button\"\n#~ msgstr \"Ενεργό Πλήκτρο Ποντικιού\"\n\n#~ msgid \"No mousebutton\"\n#~ msgstr \"Χωρίς Πλήκτρο Ποντικιού\"\n\n#~ msgid \"Left mousebutton\"\n#~ msgstr \"Αριστερό Πλήκτρο Ποντικιού\"\n\n#~ msgid \"Middle mousebutton\"\n#~ msgstr \"Μεσαίο Πλήκτρο Ποντικιού\"\n\n#~ msgid \"Right mousebutton\"\n#~ msgstr \"Δεξί Πλήκτρο Ποντικιού\"\n\n#~ msgid \"Clear\"\n#~ msgstr \"Καθάρισμα\"\n\n#~ msgid \"Canvas not supported.\"\n#~ msgstr \"Δεν υποστηρίζεται το στοιχείο Canvas\"\n\n#~ msgid \"Disconnect timeout\"\n#~ msgstr \"Παρέλευση χρονικού ορίου αποσύνδεσης\"\n\n#~ msgid \"Local Downscaling\"\n#~ msgstr \"Τοπική Συρρίκνωση\"\n\n#~ msgid \"Local Cursor\"\n#~ msgstr \"Τοπικός Δρομέας\"\n\n#~ msgid \"\"\n#~ \"Forcing clipping mode since scrollbars aren't supported by IE in \"\n#~ \"fullscreen\"\n#~ msgstr \"\"\n#~ \"Εφαρμογή λειτουργίας αποκοπής αφού δεν υποστηρίζονται οι λωρίδες κύλισης \"\n#~ \"σε πλήρη οθόνη στον IE\"\n\n#~ msgid \"True Color\"\n#~ msgstr \"Πραγματικά Χρώματα\"\n\n#~ msgid \"Style:\"\n#~ msgstr \"Στυλ:\"\n\n#~ msgid \"default\"\n#~ msgstr \"προεπιλεγμένο\"\n\n#~ msgid \"Apply\"\n#~ msgstr \"Εφαρμογή\"\n\n#~ msgid \"Connection\"\n#~ msgstr \"Σύνδεση\"\n\n#~ msgid \"Token:\"\n#~ msgstr \"Διακριτικό:\"\n\n#~ msgid \"Send Password\"\n#~ msgstr \"Αποστολή Κωδικού Πρόσβασης\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/es.po",
    "content": "# Spanish translations for noVNC package\n# Traducciones al español para el paquete noVNC.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Juanjo Diaz <juanjo.diazmo@gmail.com>, 2018.\n# Adrian Scillato <ascillato@gmail.com>, 2021.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.0.0-testing.2\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2017-10-06 10:07+0200\\n\"\n\"PO-Revision-Date: 2021-04-23 12:00-0300\\n\"\n\"Last-Translator: Adrian Scillato <ascillato@gmail.com>\\n\"\n\"Language-Team: Spanish\\n\"\n\"Language: es\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\n#: ../app/ui.js:430\nmsgid \"Connecting...\"\nmsgstr \"Conectando...\"\n\n#: ../app/ui.js:438\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Conectado (con encriptación) a\"\n\n#: ../app/ui.js:440\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Conectado (sin encriptación) a\"\n\n#: ../app/ui.js:446\nmsgid \"Disconnecting...\"\nmsgstr \"Desconectando...\"\n\n#: ../app/ui.js:450\nmsgid \"Disconnected\"\nmsgstr \"Desconectado\"\n\n#: ../app/ui.js:1052 ../core/rfb.js:248\nmsgid \"Must set host\"\nmsgstr \"Se debe configurar el host\"\n\n#: ../app/ui.js:1101\nmsgid \"Reconnecting...\"\nmsgstr \"Reconectando...\"\n\n#: ../app/ui.js:1140\nmsgid \"Password is required\"\nmsgstr \"La contraseña es obligatoria\"\n\n#: ../core/rfb.js:548\nmsgid \"Disconnect timeout\"\nmsgstr \"Tiempo de desconexión agotado\"\n\n#: ../vnc.html:89\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC ha encontrado un error:\"\n\n#: ../vnc.html:99\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Ocultar/Mostrar la barra de control\"\n\n#: ../vnc.html:106\nmsgid \"Move/Drag viewport\"\nmsgstr \"Mover/Arrastrar la ventana\"\n\n#: ../vnc.html:106\nmsgid \"viewport drag\"\nmsgstr \"Arrastrar la ventana\"\n\n#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121\nmsgid \"Active Mouse Button\"\nmsgstr \"Botón activo del ratón\"\n\n#: ../vnc.html:112\nmsgid \"No mousebutton\"\nmsgstr \"Ningún botón del ratón\"\n\n#: ../vnc.html:115\nmsgid \"Left mousebutton\"\nmsgstr \"Botón izquierdo del ratón\"\n\n#: ../vnc.html:118\nmsgid \"Middle mousebutton\"\nmsgstr \"Botón central del ratón\"\n\n#: ../vnc.html:121\nmsgid \"Right mousebutton\"\nmsgstr \"Botón derecho del ratón\"\n\n#: ../vnc.html:124\nmsgid \"Keyboard\"\nmsgstr \"Teclado\"\n\n#: ../vnc.html:124\nmsgid \"Show keyboard\"\nmsgstr \"Mostrar teclado\"\n\n#: ../vnc.html:131\nmsgid \"Extra keys\"\nmsgstr \"Teclas adicionales\"\n\n#: ../vnc.html:131\nmsgid \"Show Extra Keys\"\nmsgstr \"Mostrar Teclas Adicionales\"\n\n#: ../vnc.html:136\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:136\nmsgid \"Toggle Ctrl\"\nmsgstr \"Pulsar/Soltar Ctrl\"\n\n#: ../vnc.html:139\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:139\nmsgid \"Toggle Alt\"\nmsgstr \"Pulsar/Soltar Alt\"\n\n#: ../vnc.html:142\nmsgid \"Send Tab\"\nmsgstr \"Enviar Tabulación\"\n\n#: ../vnc.html:142\nmsgid \"Tab\"\nmsgstr \"Tabulación\"\n\n#: ../vnc.html:145\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:145\nmsgid \"Send Escape\"\nmsgstr \"Enviar Escape\"\n\n#: ../vnc.html:148\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:148\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Enviar Ctrl+Alt+Del\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Apagar/Reiniciar\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Apagar/Reiniciar...\"\n\n#: ../vnc.html:162\nmsgid \"Power\"\nmsgstr \"Encender\"\n\n#: ../vnc.html:164\nmsgid \"Shutdown\"\nmsgstr \"Apagar\"\n\n#: ../vnc.html:165\nmsgid \"Reboot\"\nmsgstr \"Reiniciar\"\n\n#: ../vnc.html:166\nmsgid \"Reset\"\nmsgstr \"Restablecer\"\n\n#: ../vnc.html:171 ../vnc.html:177\nmsgid \"Clipboard\"\nmsgstr \"Portapapeles\"\n\n#: ../vnc.html:181\nmsgid \"Clear\"\nmsgstr \"Vaciar\"\n\n#: ../vnc.html:187\nmsgid \"Fullscreen\"\nmsgstr \"Pantalla Completa\"\n\n#: ../vnc.html:192 ../vnc.html:199\nmsgid \"Settings\"\nmsgstr \"Configuraciones\"\n\n#: ../vnc.html:200\nmsgid \"Encrypt\"\nmsgstr \"Encriptar\"\n\n#: ../vnc.html:202\nmsgid \"Shared Mode\"\nmsgstr \"Modo Compartido\"\n\n#: ../vnc.html:205\nmsgid \"View only\"\nmsgstr \"Solo visualización\"\n\n#: ../vnc.html:209\nmsgid \"Clip to window\"\nmsgstr \"Recortar al tamaño de la ventana\"\n\n#: ../vnc.html:212\nmsgid \"Scaling mode:\"\nmsgstr \"Modo de escalado:\"\n\n#: ../vnc.html:214\nmsgid \"None\"\nmsgstr \"Ninguno\"\n\n#: ../vnc.html:215\nmsgid \"Local Scaling\"\nmsgstr \"Escalado Local\"\n\n#: ../vnc.html:216\nmsgid \"Local Downscaling\"\nmsgstr \"Reducción de escala local\"\n\n#: ../vnc.html:217\nmsgid \"Remote resizing\"\nmsgstr \"Cambio de tamaño remoto\"\n\n#: ../vnc.html:222\nmsgid \"Advanced\"\nmsgstr \"Avanzado\"\n\n#: ../vnc.html:225\nmsgid \"Local Cursor\"\nmsgstr \"Cursor Local\"\n\n#: ../vnc.html:229\nmsgid \"Repeater ID:\"\nmsgstr \"ID del Repetidor:\"\n\n#: ../vnc.html:233\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:239\nmsgid \"Host:\"\nmsgstr \"Host:\"\n\n#: ../vnc.html:243\nmsgid \"Port:\"\nmsgstr \"Puerto:\"\n\n#: ../vnc.html:247\nmsgid \"Path:\"\nmsgstr \"Ruta:\"\n\n#: ../vnc.html:254\nmsgid \"Automatic reconnect\"\nmsgstr \"Reconexión automática\"\n\n#: ../vnc.html:257\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"Retraso en la reconexión (ms):\"\n\n#: ../vnc.html:263\nmsgid \"Logging:\"\nmsgstr \"Registrando:\"\n\n#: ../vnc.html:275\nmsgid \"Disconnect\"\nmsgstr \"Desconectar\"\n\n#: ../vnc.html:294\nmsgid \"Connect\"\nmsgstr \"Conectar\"\n\n#: ../vnc.html:304\nmsgid \"Password:\"\nmsgstr \"Contraseña:\"\n\n#: ../vnc.html:318\nmsgid \"Cancel\"\nmsgstr \"Cancelar\"\n\n#: ../vnc.html:334\nmsgid \"Canvas not supported.\"\nmsgstr \"Canvas no soportado.\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/fr.po",
    "content": "# French translations for noVNC package\n# Traductions françaises du paquet noVNC.\n# Copyright (C) 2021 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Jose <jose.matsuda@canada.ca>, 2021.\n# Lowxorx <lowxorx@lahan.fr>, 2022.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.6.0\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2025-02-14 10:14+0100\\n\"\n\"PO-Revision-Date: 2025-02-17 10:04+0100\\n\"\n\"Last-Translator: Martine & Philippe <martineke.breizh@gmail.com>\\n\"\n\"Language-Team: French\\n\"\n\"Language: fr\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n\"X-Generator: Poedit 3.5\\n\"\n\n#: ../app/ui.js:84\nmsgid \"\"\n\"Running without HTTPS is not recommended, crashes or other issues are likely.\"\nmsgstr \"\"\n\"Lancer sans HTTPS n'est pas recommandé, crashs ou autres problèmes en vue.\"\n\n#: ../app/ui.js:413\nmsgid \"Connecting...\"\nmsgstr \"En cours de connexion...\"\n\n#: ../app/ui.js:420\nmsgid \"Disconnecting...\"\nmsgstr \"Déconnexion en cours...\"\n\n#: ../app/ui.js:426\nmsgid \"Reconnecting...\"\nmsgstr \"Reconnexion en cours...\"\n\n#: ../app/ui.js:431\nmsgid \"Internal error\"\nmsgstr \"Erreur interne\"\n\n#: ../app/ui.js:1079\nmsgid \"Failed to connect to server: \"\nmsgstr \"Échec de connexion au serveur \"\n\n#: ../app/ui.js:1145\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Connecté (chiffré) à \"\n\n#: ../app/ui.js:1147\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Connecté (non chiffré) à \"\n\n#: ../app/ui.js:1170\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Quelque chose s'est mal passé, la connexion a été fermée\"\n\n#: ../app/ui.js:1173\nmsgid \"Failed to connect to server\"\nmsgstr \"Échec de connexion au serveur\"\n\n#: ../app/ui.js:1185\nmsgid \"Disconnected\"\nmsgstr \"Déconnecté\"\n\n#: ../app/ui.js:1200\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"Une nouvelle connexion a été rejetée avec motif : \"\n\n#: ../app/ui.js:1203\nmsgid \"New connection has been rejected\"\nmsgstr \"Une nouvelle connexion a été rejetée\"\n\n#: ../app/ui.js:1269\nmsgid \"Credentials are required\"\nmsgstr \"Les identifiants sont requis\"\n\n#: ../vnc.html:106\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC a rencontré une erreur :\"\n\n#: ../vnc.html:116\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Masquer/Afficher la barre de contrôle\"\n\n#: ../vnc.html:125\nmsgid \"Drag\"\nmsgstr \"Faire glisser\"\n\n#: ../vnc.html:125\nmsgid \"Move/Drag viewport\"\nmsgstr \"Déplacer la fenêtre de visualisation\"\n\n#: ../vnc.html:131\nmsgid \"Keyboard\"\nmsgstr \"Clavier\"\n\n#: ../vnc.html:131\nmsgid \"Show keyboard\"\nmsgstr \"Afficher le clavier\"\n\n#: ../vnc.html:136\nmsgid \"Extra keys\"\nmsgstr \"Touches supplémentaires\"\n\n#: ../vnc.html:136\nmsgid \"Show extra keys\"\nmsgstr \"Afficher les touches supplémentaires\"\n\n#: ../vnc.html:141\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:141\nmsgid \"Toggle Ctrl\"\nmsgstr \"Basculer Ctrl\"\n\n#: ../vnc.html:144\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:144\nmsgid \"Toggle Alt\"\nmsgstr \"Basculer Alt\"\n\n#: ../vnc.html:147\nmsgid \"Toggle Windows\"\nmsgstr \"Basculer Windows\"\n\n#: ../vnc.html:147\nmsgid \"Windows\"\nmsgstr \"Fenêtre\"\n\n#: ../vnc.html:150\nmsgid \"Send Tab\"\nmsgstr \"Envoyer Tab\"\n\n#: ../vnc.html:150\nmsgid \"Tab\"\nmsgstr \"Tabulation\"\n\n#: ../vnc.html:153\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:153\nmsgid \"Send Escape\"\nmsgstr \"Envoyer Escape\"\n\n#: ../vnc.html:156\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:156\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Envoyer Ctrl-Alt-Del\"\n\n#: ../vnc.html:163\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Arrêter/Redémarrer\"\n\n#: ../vnc.html:163\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Arrêter/Redémarrer...\"\n\n#: ../vnc.html:169\nmsgid \"Power\"\nmsgstr \"Alimentation\"\n\n#: ../vnc.html:171\nmsgid \"Shutdown\"\nmsgstr \"Arrêter\"\n\n#: ../vnc.html:172\nmsgid \"Reboot\"\nmsgstr \"Redémarrer\"\n\n#: ../vnc.html:173\nmsgid \"Reset\"\nmsgstr \"Réinitialiser\"\n\n#: ../vnc.html:178 ../vnc.html:184\nmsgid \"Clipboard\"\nmsgstr \"Presse-papiers\"\n\n#: ../vnc.html:186\nmsgid \"Edit clipboard content in the textarea below.\"\nmsgstr \"Editer le contenu du presse-papier dans la zone ci-dessous.\"\n\n#: ../vnc.html:194\nmsgid \"Full screen\"\nmsgstr \"Plein écran\"\n\n#: ../vnc.html:199 ../vnc.html:205\nmsgid \"Settings\"\nmsgstr \"Paramètres\"\n\n#: ../vnc.html:211\nmsgid \"Shared mode\"\nmsgstr \"Mode partagé\"\n\n#: ../vnc.html:218\nmsgid \"View only\"\nmsgstr \"Afficher uniquement\"\n\n#: ../vnc.html:226\nmsgid \"Clip to window\"\nmsgstr \"Ajuster à la fenêtre\"\n\n#: ../vnc.html:231\nmsgid \"Scaling mode:\"\nmsgstr \"Mode mise à l'échelle :\"\n\n#: ../vnc.html:233\nmsgid \"None\"\nmsgstr \"Aucun\"\n\n#: ../vnc.html:234\nmsgid \"Local scaling\"\nmsgstr \"Mise à l'échelle locale\"\n\n#: ../vnc.html:235\nmsgid \"Remote resizing\"\nmsgstr \"Redimensionnement à distance\"\n\n#: ../vnc.html:240\nmsgid \"Advanced\"\nmsgstr \"Avancé\"\n\n#: ../vnc.html:243\nmsgid \"Quality:\"\nmsgstr \"Qualité :\"\n\n#: ../vnc.html:247\nmsgid \"Compression level:\"\nmsgstr \"Niveau de compression :\"\n\n#: ../vnc.html:252\nmsgid \"Repeater ID:\"\nmsgstr \"ID Répéteur :\"\n\n#: ../vnc.html:256\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:261\nmsgid \"Encrypt\"\nmsgstr \"Chiffrer\"\n\n#: ../vnc.html:266\nmsgid \"Host:\"\nmsgstr \"Hôte :\"\n\n#: ../vnc.html:270\nmsgid \"Port:\"\nmsgstr \"Port :\"\n\n#: ../vnc.html:274\nmsgid \"Path:\"\nmsgstr \"Chemin :\"\n\n#: ../vnc.html:283\nmsgid \"Automatic reconnect\"\nmsgstr \"Reconnecter automatiquement\"\n\n#: ../vnc.html:288\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"Délai de reconnexion (ms) :\"\n\n#: ../vnc.html:295\nmsgid \"Show dot when no cursor\"\nmsgstr \"Afficher le point lorsqu'il n'y a pas de curseur\"\n\n#: ../vnc.html:302\nmsgid \"Logging:\"\nmsgstr \"Se connecter :\"\n\n#: ../vnc.html:311\nmsgid \"Version:\"\nmsgstr \"Version :\"\n\n#: ../vnc.html:319\nmsgid \"Disconnect\"\nmsgstr \"Déconnecter\"\n\n#: ../vnc.html:342\nmsgid \"Connect\"\nmsgstr \"Connecter\"\n\n#: ../vnc.html:351\nmsgid \"Server identity\"\nmsgstr \"Identité du serveur\"\n\n#: ../vnc.html:354\nmsgid \"The server has provided the following identifying information:\"\nmsgstr \"Le serveur a fourni l'identification suivante :\"\n\n#: ../vnc.html:357\nmsgid \"Fingerprint:\"\nmsgstr \"Empreinte digitale :\"\n\n#: ../vnc.html:361\nmsgid \"\"\n\"Please verify that the information is correct and press \\\"Approve\\\". \"\n\"Otherwise press \\\"Reject\\\".\"\nmsgstr \"\"\n\"SVP, verifiez que l'information est correcte et pressez \\\"Accepter\\\". Sinon \"\n\"pressez \\\"Refuser\\\".\"\n\n#: ../vnc.html:366\nmsgid \"Approve\"\nmsgstr \"Accepter\"\n\n#: ../vnc.html:367\nmsgid \"Reject\"\nmsgstr \"Refuser\"\n\n#: ../vnc.html:375\nmsgid \"Credentials\"\nmsgstr \"Envoyer les identifiants\"\n\n#: ../vnc.html:379\nmsgid \"Username:\"\nmsgstr \"Nom d'utilisateur :\"\n\n#: ../vnc.html:383\nmsgid \"Password:\"\nmsgstr \"Mot de passe :\"\n\n#: ../vnc.html:387\nmsgid \"Send credentials\"\nmsgstr \"Envoyer les identifiants\"\n\n#: ../vnc.html:396\nmsgid \"Cancel\"\nmsgstr \"Annuler\"\n\n#~ msgid \"Must set host\"\n#~ msgstr \"Doit définir l'hôte\"\n\n#~ msgid \"Clear\"\n#~ msgstr \"Effacer\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/it.po",
    "content": "# Italian translations for noVNC\n# Traduzione italiana di noVNC\n# Copyright (C) 2022 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Fabio Fantoni <fabio.fantoni@m2r.biz>, 2022.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.3.0\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2021-08-27 16:03+0200\\n\"\n\"PO-Revision-Date: 2022-09-08 13:27+0200\\n\"\n\"Last-Translator: Fabio Fantoni <fabio.fantoni@m2r.biz>\\n\"\n\"Language-Team: Italian\\n\"\n\"Language: it\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"X-Generator: Poedit 3.1.1\\n\"\n\n#: ../app/ui.js:400\nmsgid \"Connecting...\"\nmsgstr \"Connessione in corso...\"\n\n#: ../app/ui.js:407\nmsgid \"Disconnecting...\"\nmsgstr \"Disconnessione...\"\n\n#: ../app/ui.js:413\nmsgid \"Reconnecting...\"\nmsgstr \"Riconnessione...\"\n\n#: ../app/ui.js:418\nmsgid \"Internal error\"\nmsgstr \"Errore interno\"\n\n#: ../app/ui.js:1009\nmsgid \"Must set host\"\nmsgstr \"Devi impostare l'host\"\n\n#: ../app/ui.js:1091\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Connesso (crittografato) a \"\n\n#: ../app/ui.js:1093\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Connesso (non crittografato) a\"\n\n#: ../app/ui.js:1116\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Qualcosa è andato storto, la connessione è stata chiusa\"\n\n#: ../app/ui.js:1119\nmsgid \"Failed to connect to server\"\nmsgstr \"Impossibile connettersi al server\"\n\n#: ../app/ui.js:1129\nmsgid \"Disconnected\"\nmsgstr \"Disconnesso\"\n\n#: ../app/ui.js:1144\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"La nuova connessione è stata rifiutata con motivo: \"\n\n#: ../app/ui.js:1147\nmsgid \"New connection has been rejected\"\nmsgstr \"La nuova connessione è stata rifiutata\"\n\n#: ../app/ui.js:1182\nmsgid \"Credentials are required\"\nmsgstr \"Le credenziali sono obbligatorie\"\n\n#: ../vnc.html:61\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC ha riscontrato un errore:\"\n\n#: ../vnc.html:71\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Nascondi/Mostra la barra di controllo\"\n\n#: ../vnc.html:78\nmsgid \"Drag\"\nmsgstr \"\"\n\n#: ../vnc.html:78\nmsgid \"Move/Drag viewport\"\nmsgstr \"\"\n\n#: ../vnc.html:84\nmsgid \"Keyboard\"\nmsgstr \"Tastiera\"\n\n#: ../vnc.html:84\nmsgid \"Show keyboard\"\nmsgstr \"Mostra tastiera\"\n\n#: ../vnc.html:89\nmsgid \"Extra keys\"\nmsgstr \"Tasti Aggiuntivi\"\n\n#: ../vnc.html:89\nmsgid \"Show Extra Keys\"\nmsgstr \"Mostra Tasti Aggiuntivi\"\n\n#: ../vnc.html:94\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:94\nmsgid \"Toggle Ctrl\"\nmsgstr \"Tieni premuto Ctrl\"\n\n#: ../vnc.html:97\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:97\nmsgid \"Toggle Alt\"\nmsgstr \"Tieni premuto Alt\"\n\n#: ../vnc.html:100\nmsgid \"Toggle Windows\"\nmsgstr \"Tieni premuto Windows\"\n\n#: ../vnc.html:100\nmsgid \"Windows\"\nmsgstr \"Windows\"\n\n#: ../vnc.html:103\nmsgid \"Send Tab\"\nmsgstr \"Invia Tab\"\n\n#: ../vnc.html:103\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:106\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:106\nmsgid \"Send Escape\"\nmsgstr \"Invia Esc\"\n\n#: ../vnc.html:109\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Canc\"\n\n#: ../vnc.html:109\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Invia Ctrl-Alt-Canc\"\n\n#: ../vnc.html:116\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Spegnimento/Riavvio\"\n\n#: ../vnc.html:116\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Spegnimento/Riavvio...\"\n\n#: ../vnc.html:122\nmsgid \"Power\"\nmsgstr \"Alimentazione\"\n\n#: ../vnc.html:124\nmsgid \"Shutdown\"\nmsgstr \"Spegnimento\"\n\n#: ../vnc.html:125\nmsgid \"Reboot\"\nmsgstr \"Riavvio\"\n\n#: ../vnc.html:126\nmsgid \"Reset\"\nmsgstr \"Reset\"\n\n#: ../vnc.html:131 ../vnc.html:137\nmsgid \"Clipboard\"\nmsgstr \"Clipboard\"\n\n#: ../vnc.html:141\nmsgid \"Clear\"\nmsgstr \"Pulisci\"\n\n#: ../vnc.html:147\nmsgid \"Fullscreen\"\nmsgstr \"Schermo intero\"\n\n#: ../vnc.html:152 ../vnc.html:159\nmsgid \"Settings\"\nmsgstr \"Impostazioni\"\n\n#: ../vnc.html:162\nmsgid \"Shared mode\"\nmsgstr \"Modalità condivisa\"\n\n#: ../vnc.html:165\nmsgid \"View Only\"\nmsgstr \"Sola Visualizzazione\"\n\n#: ../vnc.html:169\nmsgid \"Clip to window\"\nmsgstr \"\"\n\n#: ../vnc.html:172\nmsgid \"Scaling mode:\"\nmsgstr \"Modalità di ridimensionamento:\"\n\n#: ../vnc.html:174\nmsgid \"None\"\nmsgstr \"Nessuna\"\n\n#: ../vnc.html:175\nmsgid \"Local Scaling\"\nmsgstr \"Ridimensionamento Locale\"\n\n#: ../vnc.html:176\nmsgid \"Remote Resizing\"\nmsgstr \"Ridimensionamento Remoto\"\n\n#: ../vnc.html:181\nmsgid \"Advanced\"\nmsgstr \"Avanzate\"\n\n#: ../vnc.html:184\nmsgid \"Quality:\"\nmsgstr \"Qualità:\"\n\n#: ../vnc.html:188\nmsgid \"Compression level:\"\nmsgstr \"Livello Compressione:\"\n\n#: ../vnc.html:193\nmsgid \"Repeater ID:\"\nmsgstr \"ID Ripetitore:\"\n\n#: ../vnc.html:197\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:200\nmsgid \"Encrypt\"\nmsgstr \"Crittografa\"\n\n#: ../vnc.html:203\nmsgid \"Host:\"\nmsgstr \"Host:\"\n\n#: ../vnc.html:207\nmsgid \"Port:\"\nmsgstr \"Porta:\"\n\n#: ../vnc.html:211\nmsgid \"Path:\"\nmsgstr \"Percorso:\"\n\n#: ../vnc.html:218\nmsgid \"Automatic Reconnect\"\nmsgstr \"Riconnessione Automatica\"\n\n#: ../vnc.html:221\nmsgid \"Reconnect Delay (ms):\"\nmsgstr \"Ritardo Riconnessione (ms):\"\n\n#: ../vnc.html:226\nmsgid \"Show Dot when No Cursor\"\nmsgstr \"Mostra Punto quando Nessun Cursore\"\n\n#: ../vnc.html:231\nmsgid \"Logging:\"\nmsgstr \"\"\n\n#: ../vnc.html:240\nmsgid \"Version:\"\nmsgstr \"Versione:\"\n\n#: ../vnc.html:248\nmsgid \"Disconnect\"\nmsgstr \"Disconnetti\"\n\n#: ../vnc.html:267\nmsgid \"Connect\"\nmsgstr \"Connetti\"\n\n#: ../vnc.html:277\nmsgid \"Username:\"\nmsgstr \"Utente:\"\n\n#: ../vnc.html:281\nmsgid \"Password:\"\nmsgstr \"Password:\"\n\n#: ../vnc.html:285\nmsgid \"Send Credentials\"\nmsgstr \"Invia Credenziale\"\n\n#: ../vnc.html:295\nmsgid \"Cancel\"\nmsgstr \"Annulla\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/ja.po",
    "content": "# Japanese translations for noVNC package\n# noVNC パッケージに対する日訳\n# Copyright (C) 2019-2024 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# nnn1590 <nnn1590@nnn1590.org>, 2019-2024.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.5.0\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2024-06-03 14:10+0200\\n\"\n\"PO-Revision-Date: 2024-12-14 15:22+0900\\n\"\n\"Last-Translator: nnn1590 <nnn1590@nnn1590.org>\\n\"\n\"Language-Team: Japanese\\n\"\n\"Language: ja\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=1; plural=0;\\n\"\n\"X-Generator: Poedit 2.3\\n\"\n\n#: ../app/ui.js:69\nmsgid \"\"\n\"Running without HTTPS is not recommended, crashes or other issues are likely.\"\nmsgstr \"\"\n\"HTTPS接続なしで実行することは推奨されません。クラッシュしたりその他の問題が発\"\n\"生したりする可能性があります。\"\n\n#: ../app/ui.js:410\nmsgid \"Connecting...\"\nmsgstr \"接続しています...\"\n\n#: ../app/ui.js:417\nmsgid \"Disconnecting...\"\nmsgstr \"切断しています...\"\n\n#: ../app/ui.js:423\nmsgid \"Reconnecting...\"\nmsgstr \"再接続しています...\"\n\n#: ../app/ui.js:428\nmsgid \"Internal error\"\nmsgstr \"内部エラー\"\n\n#: ../app/ui.js:1026\nmsgid \"Must set host\"\nmsgstr \"ホストを設定する必要があります\"\n\n#: ../app/ui.js:1052\nmsgid \"Failed to connect to server: \"\nmsgstr \"サーバーへの接続に失敗しました: \"\n\n#: ../app/ui.js:1118\nmsgid \"Connected (encrypted) to \"\nmsgstr \"接続しました (暗号化済み): \"\n\n#: ../app/ui.js:1120\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"接続しました (暗号化されていません): \"\n\n#: ../app/ui.js:1143\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"問題が発生したため、接続が閉じられました\"\n\n#: ../app/ui.js:1146\nmsgid \"Failed to connect to server\"\nmsgstr \"サーバーへの接続に失敗しました\"\n\n#: ../app/ui.js:1158\nmsgid \"Disconnected\"\nmsgstr \"切断しました\"\n\n#: ../app/ui.js:1173\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"新規接続は次の理由で拒否されました: \"\n\n#: ../app/ui.js:1176\nmsgid \"New connection has been rejected\"\nmsgstr \"新規接続は拒否されました\"\n\n#: ../app/ui.js:1242\nmsgid \"Credentials are required\"\nmsgstr \"資格情報が必要です\"\n\n#: ../vnc.html:55\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC でエラーが発生しました:\"\n\n#: ../vnc.html:65\nmsgid \"Hide/Show the control bar\"\nmsgstr \"コントロールバーを隠す/表示する\"\n\n#: ../vnc.html:74\nmsgid \"Drag\"\nmsgstr \"ドラッグ\"\n\n#: ../vnc.html:74\nmsgid \"Move/Drag viewport\"\nmsgstr \"ビューポートを移動/ドラッグ\"\n\n#: ../vnc.html:80\nmsgid \"Keyboard\"\nmsgstr \"キーボード\"\n\n#: ../vnc.html:80\nmsgid \"Show keyboard\"\nmsgstr \"キーボードを表示\"\n\n#: ../vnc.html:85\nmsgid \"Extra keys\"\nmsgstr \"追加キー\"\n\n#: ../vnc.html:85\nmsgid \"Show extra keys\"\nmsgstr \"追加キーを表示\"\n\n#: ../vnc.html:90\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:90\nmsgid \"Toggle Ctrl\"\nmsgstr \"Ctrl キーをトグル\"\n\n#: ../vnc.html:93\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:93\nmsgid \"Toggle Alt\"\nmsgstr \"Alt キーをトグル\"\n\n#: ../vnc.html:96\nmsgid \"Toggle Windows\"\nmsgstr \"Windows キーをトグル\"\n\n#: ../vnc.html:96\nmsgid \"Windows\"\nmsgstr \"Windows\"\n\n#: ../vnc.html:99\nmsgid \"Send Tab\"\nmsgstr \"Tab キーを送信\"\n\n#: ../vnc.html:99\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:102\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:102\nmsgid \"Send Escape\"\nmsgstr \"Escape キーを送信\"\n\n#: ../vnc.html:105\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:105\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Ctrl-Alt-Del を送信\"\n\n#: ../vnc.html:112\nmsgid \"Shutdown/Reboot\"\nmsgstr \"シャットダウン/再起動\"\n\n#: ../vnc.html:112\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"シャットダウン/再起動...\"\n\n#: ../vnc.html:118\nmsgid \"Power\"\nmsgstr \"電源\"\n\n#: ../vnc.html:120\nmsgid \"Shutdown\"\nmsgstr \"シャットダウン\"\n\n#: ../vnc.html:121\nmsgid \"Reboot\"\nmsgstr \"再起動\"\n\n#: ../vnc.html:122\nmsgid \"Reset\"\nmsgstr \"リセット\"\n\n#: ../vnc.html:127 ../vnc.html:133\nmsgid \"Clipboard\"\nmsgstr \"クリップボード\"\n\n#: ../vnc.html:135\nmsgid \"Edit clipboard content in the textarea below.\"\nmsgstr \"以下の入力欄からクリップボードの内容を編集できます。\"\n\n#: ../vnc.html:143\nmsgid \"Full screen\"\nmsgstr \"全画面表示\"\n\n#: ../vnc.html:148 ../vnc.html:154\nmsgid \"Settings\"\nmsgstr \"設定\"\n\n#: ../vnc.html:158\nmsgid \"Shared mode\"\nmsgstr \"共有モード\"\n\n#: ../vnc.html:161\nmsgid \"View only\"\nmsgstr \"表示専用\"\n\n#: ../vnc.html:165\nmsgid \"Clip to window\"\nmsgstr \"ウィンドウにクリップ\"\n\n#: ../vnc.html:168\nmsgid \"Scaling mode:\"\nmsgstr \"スケーリングモード:\"\n\n#: ../vnc.html:170\nmsgid \"None\"\nmsgstr \"なし\"\n\n#: ../vnc.html:171\nmsgid \"Local scaling\"\nmsgstr \"ローカルでスケーリング\"\n\n#: ../vnc.html:172\nmsgid \"Remote resizing\"\nmsgstr \"リモートでリサイズ\"\n\n#: ../vnc.html:177\nmsgid \"Advanced\"\nmsgstr \"高度\"\n\n#: ../vnc.html:180\nmsgid \"Quality:\"\nmsgstr \"品質:\"\n\n#: ../vnc.html:184\nmsgid \"Compression level:\"\nmsgstr \"圧縮レベル:\"\n\n#: ../vnc.html:189\nmsgid \"Repeater ID:\"\nmsgstr \"リピーター ID:\"\n\n#: ../vnc.html:193\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:196\nmsgid \"Encrypt\"\nmsgstr \"暗号化\"\n\n#: ../vnc.html:199\nmsgid \"Host:\"\nmsgstr \"ホスト:\"\n\n#: ../vnc.html:203\nmsgid \"Port:\"\nmsgstr \"ポート:\"\n\n#: ../vnc.html:207\nmsgid \"Path:\"\nmsgstr \"パス:\"\n\n#: ../vnc.html:214\nmsgid \"Automatic reconnect\"\nmsgstr \"自動再接続\"\n\n#: ../vnc.html:217\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"再接続する遅延 (ミリ秒):\"\n\n#: ../vnc.html:222\nmsgid \"Show dot when no cursor\"\nmsgstr \"カーソルがないときにドットを表示する\"\n\n#: ../vnc.html:227\nmsgid \"Logging:\"\nmsgstr \"ロギング:\"\n\n#: ../vnc.html:236\nmsgid \"Version:\"\nmsgstr \"バージョン:\"\n\n#: ../vnc.html:244\nmsgid \"Disconnect\"\nmsgstr \"切断\"\n\n#: ../vnc.html:267\nmsgid \"Connect\"\nmsgstr \"接続\"\n\n#: ../vnc.html:276\nmsgid \"Server identity\"\nmsgstr \"サーバーの識別情報\"\n\n#: ../vnc.html:279\nmsgid \"The server has provided the following identifying information:\"\nmsgstr \"サーバーは以下の識別情報を提供しています:\"\n\n#: ../vnc.html:283\nmsgid \"Fingerprint:\"\nmsgstr \"フィンガープリント:\"\n\n#: ../vnc.html:286\nmsgid \"\"\n\"Please verify that the information is correct and press \\\"Approve\\\". \"\n\"Otherwise press \\\"Reject\\\".\"\nmsgstr \"\"\n\"この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。\"\n\n#: ../vnc.html:291\nmsgid \"Approve\"\nmsgstr \"承認\"\n\n#: ../vnc.html:292\nmsgid \"Reject\"\nmsgstr \"拒否\"\n\n#: ../vnc.html:300\nmsgid \"Credentials\"\nmsgstr \"資格情報\"\n\n#: ../vnc.html:304\nmsgid \"Username:\"\nmsgstr \"ユーザー名:\"\n\n#: ../vnc.html:308\nmsgid \"Password:\"\nmsgstr \"パスワード:\"\n\n#: ../vnc.html:312\nmsgid \"Send credentials\"\nmsgstr \"資格情報を送信\"\n\n#: ../vnc.html:321\nmsgid \"Cancel\"\nmsgstr \"キャンセル\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/ko.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Baw Appie <pp121324@gmail.com>, 2018.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.0.0-testing.2\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2018-01-31 16:29+0100\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: Baw Appie <pp121324@gmail.com>\\n\"\n\"Language-Team: Korean\\n\"\n\"Language: ko\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: ../app/ui.js:395\nmsgid \"Connecting...\"\nmsgstr \"연결중...\"\n\n#: ../app/ui.js:402\nmsgid \"Disconnecting...\"\nmsgstr \"연결 해제중...\"\n\n#: ../app/ui.js:408\nmsgid \"Reconnecting...\"\nmsgstr \"재연결중...\"\n\n#: ../app/ui.js:413\nmsgid \"Internal error\"\nmsgstr \"내부 오류\"\n\n#: ../app/ui.js:1002\nmsgid \"Must set host\"\nmsgstr \"호스트는 설정되어야 합니다.\"\n\n#: ../app/ui.js:1083\nmsgid \"Connected (encrypted) to \"\nmsgstr \"다음과 (암호화되어) 연결되었습니다:\"\n\n#: ../app/ui.js:1085\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"다음과 (암호화 없이) 연결되었습니다:\"\n\n#: ../app/ui.js:1108\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"무언가 잘못되었습니다, 연결이 닫혔습니다.\"\n\n#: ../app/ui.js:1111\nmsgid \"Failed to connect to server\"\nmsgstr \"서버에 연결하지 못했습니다.\"\n\n#: ../app/ui.js:1121\nmsgid \"Disconnected\"\nmsgstr \"연결이 해제되었습니다.\"\n\n#: ../app/ui.js:1134\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"새 연결이 다음 이유로 거부되었습니다:\"\n\n#: ../app/ui.js:1137\nmsgid \"New connection has been rejected\"\nmsgstr \"새 연결이 거부되었습니다.\"\n\n#: ../app/ui.js:1158\nmsgid \"Password is required\"\nmsgstr \"비밀번호가 필요합니다.\"\n\n#: ../vnc.html:91\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC에 오류가 발생했습니다:\"\n\n#: ../vnc.html:101\nmsgid \"Hide/Show the control bar\"\nmsgstr \"컨트롤 바 숨기기/보이기\"\n\n#: ../vnc.html:108\nmsgid \"Move/Drag viewport\"\nmsgstr \"움직이기/드래그 뷰포트\"\n\n#: ../vnc.html:108\nmsgid \"viewport drag\"\nmsgstr \"뷰포트 드래그\"\n\n#: ../vnc.html:114 ../vnc.html:117 ../vnc.html:120 ../vnc.html:123\nmsgid \"Active Mouse Button\"\nmsgstr \"마우스 버튼 활성화\"\n\n#: ../vnc.html:114\nmsgid \"No mousebutton\"\nmsgstr \"마우스 버튼 없음\"\n\n#: ../vnc.html:117\nmsgid \"Left mousebutton\"\nmsgstr \"왼쪽 마우스 버튼\"\n\n#: ../vnc.html:120\nmsgid \"Middle mousebutton\"\nmsgstr \"중간 마우스 버튼\"\n\n#: ../vnc.html:123\nmsgid \"Right mousebutton\"\nmsgstr \"오른쪽 마우스 버튼\"\n\n#: ../vnc.html:126\nmsgid \"Keyboard\"\nmsgstr \"키보드\"\n\n#: ../vnc.html:126\nmsgid \"Show keyboard\"\nmsgstr \"키보드 보이기\"\n\n#: ../vnc.html:133\nmsgid \"Extra keys\"\nmsgstr \"기타 키들\"\n\n#: ../vnc.html:133\nmsgid \"Show extra keys\"\nmsgstr \"기타 키들 보이기\"\n\n#: ../vnc.html:138\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:138\nmsgid \"Toggle Ctrl\"\nmsgstr \"Ctrl 켜기/끄기\"\n\n#: ../vnc.html:141\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:141\nmsgid \"Toggle Alt\"\nmsgstr \"Alt 켜기/끄기\"\n\n#: ../vnc.html:144\nmsgid \"Send Tab\"\nmsgstr \"Tab 보내기\"\n\n#: ../vnc.html:144\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:147\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:147\nmsgid \"Send Escape\"\nmsgstr \"Esc 보내기\"\n\n#: ../vnc.html:150\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:150\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Ctrl+Alt+Del 보내기\"\n\n#: ../vnc.html:158\nmsgid \"Shutdown/Reboot\"\nmsgstr \"셧다운/리붓\"\n\n#: ../vnc.html:158\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"셧다운/리붓...\"\n\n#: ../vnc.html:164\nmsgid \"Power\"\nmsgstr \"전원\"\n\n#: ../vnc.html:166\nmsgid \"Shutdown\"\nmsgstr \"셧다운\"\n\n#: ../vnc.html:167\nmsgid \"Reboot\"\nmsgstr \"리붓\"\n\n#: ../vnc.html:168\nmsgid \"Reset\"\nmsgstr \"리셋\"\n\n#: ../vnc.html:173 ../vnc.html:179\nmsgid \"Clipboard\"\nmsgstr \"클립보드\"\n\n#: ../vnc.html:183\nmsgid \"Clear\"\nmsgstr \"지우기\"\n\n#: ../vnc.html:189\nmsgid \"Fullscreen\"\nmsgstr \"전체화면\"\n\n#: ../vnc.html:194 ../vnc.html:201\nmsgid \"Settings\"\nmsgstr \"설정\"\n\n#: ../vnc.html:204\nmsgid \"Shared mode\"\nmsgstr \"공유 모드\"\n\n#: ../vnc.html:207\nmsgid \"View only\"\nmsgstr \"보기 전용\"\n\n#: ../vnc.html:211\nmsgid \"Clip to window\"\nmsgstr \"창에 클립\"\n\n#: ../vnc.html:214\nmsgid \"Scaling mode:\"\nmsgstr \"스케일링 모드:\"\n\n#: ../vnc.html:216\nmsgid \"None\"\nmsgstr \"없음\"\n\n#: ../vnc.html:217\nmsgid \"Local scaling\"\nmsgstr \"로컬 스케일링\"\n\n#: ../vnc.html:218\nmsgid \"Remote resizing\"\nmsgstr \"원격 크기 조절\"\n\n#: ../vnc.html:223\nmsgid \"Advanced\"\nmsgstr \"고급\"\n\n#: ../vnc.html:226\nmsgid \"Repeater ID:\"\nmsgstr \"중계 ID\"\n\n#: ../vnc.html:230\nmsgid \"WebSocket\"\nmsgstr \"웹소켓\"\n\n#: ../vnc.html:233\nmsgid \"Encrypt\"\nmsgstr \"암호화\"\n\n#: ../vnc.html:236\nmsgid \"Host:\"\nmsgstr \"호스트:\"\n\n#: ../vnc.html:240\nmsgid \"Port:\"\nmsgstr \"포트:\"\n\n#: ../vnc.html:244\nmsgid \"Path:\"\nmsgstr \"위치:\"\n\n#: ../vnc.html:251\nmsgid \"Automatic reconnect\"\nmsgstr \"자동 재연결\"\n\n#: ../vnc.html:254\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"재연결 지연 시간 (ms)\"\n\n#: ../vnc.html:260\nmsgid \"Logging:\"\nmsgstr \"로깅\"\n\n#: ../vnc.html:272\nmsgid \"Disconnect\"\nmsgstr \"연결 해제\"\n\n#: ../vnc.html:291\nmsgid \"Connect\"\nmsgstr \"연결\"\n\n#: ../vnc.html:301\nmsgid \"Password:\"\nmsgstr \"비밀번호:\"\n\n#: ../vnc.html:305\nmsgid \"Send Password\"\nmsgstr \"비밀번호 전송\"\n\n#: ../vnc.html:315\nmsgid \"Cancel\"\nmsgstr \"취소\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/nl.po",
    "content": "# Dutch translations for noVNC package\n# Nederlandse vertalingen voor het pakket noVNC.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Loek Janssen <loekjanssen@gmail.com>, 2016.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.6.0\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2025-02-14 10:14+0100\\n\"\n\"PO-Revision-Date: 2025-03-03 18:20+0100\\n\"\n\"Last-Translator: Harold Horsman <haroldhorsman@gmail.com>\\n\"\n\"Language-Team: none\\n\"\n\"Language: nl\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"X-Generator: Poedit 3.5\\n\"\n\n#: ../app/ui.js:84\nmsgid \"\"\n\"Running without HTTPS is not recommended, crashes or other issues are likely.\"\nmsgstr \"\"\n\"Het is niet aan te raden om zonder HTTPS te werken, crashes of andere \"\n\"problemen zijn dan waarschijnlijk.\"\n\n#: ../app/ui.js:413\nmsgid \"Connecting...\"\nmsgstr \"Aan het verbinden…\"\n\n#: ../app/ui.js:420\nmsgid \"Disconnecting...\"\nmsgstr \"Bezig om verbinding te verbreken...\"\n\n#: ../app/ui.js:426\nmsgid \"Reconnecting...\"\nmsgstr \"Opnieuw verbinding maken...\"\n\n#: ../app/ui.js:431\nmsgid \"Internal error\"\nmsgstr \"Interne fout\"\n\n#: ../app/ui.js:1079\n#, fuzzy\nmsgid \"Failed to connect to server: \"\nmsgstr \"Verbinding maken met server is mislukt\"\n\n#: ../app/ui.js:1145\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Verbonden (versleuteld) met \"\n\n#: ../app/ui.js:1147\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Verbonden (onversleuteld) met \"\n\n#: ../app/ui.js:1170\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Er iets fout gelopen, verbinding werd verbroken\"\n\n#: ../app/ui.js:1173\nmsgid \"Failed to connect to server\"\nmsgstr \"Verbinding maken met server is mislukt\"\n\n#: ../app/ui.js:1185\nmsgid \"Disconnected\"\nmsgstr \"Verbinding verbroken\"\n\n#: ../app/ui.js:1200\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"Nieuwe verbinding is geweigerd met de volgende reden: \"\n\n#: ../app/ui.js:1203\nmsgid \"New connection has been rejected\"\nmsgstr \"Nieuwe verbinding is geweigerd\"\n\n#: ../app/ui.js:1269\nmsgid \"Credentials are required\"\nmsgstr \"Inloggegevens zijn nodig\"\n\n#: ../vnc.html:106\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC heeft een fout bemerkt:\"\n\n#: ../vnc.html:116\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Verberg/Toon de bedieningsbalk\"\n\n#: ../vnc.html:125\nmsgid \"Drag\"\nmsgstr \"Sleep\"\n\n#: ../vnc.html:125\n#, fuzzy\nmsgid \"Move/Drag viewport\"\nmsgstr \"Verplaats/Versleep Kijkvenster\"\n\n#: ../vnc.html:131\nmsgid \"Keyboard\"\nmsgstr \"Toetsenbord\"\n\n#: ../vnc.html:131\n#, fuzzy\nmsgid \"Show keyboard\"\nmsgstr \"Toon Toetsenbord\"\n\n#: ../vnc.html:136\nmsgid \"Extra keys\"\nmsgstr \"Extra toetsen\"\n\n#: ../vnc.html:136\n#, fuzzy\nmsgid \"Show extra keys\"\nmsgstr \"Toon Extra Toetsen\"\n\n#: ../vnc.html:141\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:141\nmsgid \"Toggle Ctrl\"\nmsgstr \"Ctrl omschakelen\"\n\n#: ../vnc.html:144\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:144\nmsgid \"Toggle Alt\"\nmsgstr \"Alt omschakelen\"\n\n#: ../vnc.html:147\nmsgid \"Toggle Windows\"\nmsgstr \"Vensters omschakelen\"\n\n#: ../vnc.html:147\nmsgid \"Windows\"\nmsgstr \"Vensters\"\n\n#: ../vnc.html:150\nmsgid \"Send Tab\"\nmsgstr \"Tab Sturen\"\n\n#: ../vnc.html:150\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:153\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:153\nmsgid \"Send Escape\"\nmsgstr \"Escape Sturen\"\n\n#: ../vnc.html:156\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl-Alt-Del\"\n\n#: ../vnc.html:156\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Ctrl-Alt-Del Sturen\"\n\n#: ../vnc.html:163\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Uitschakelen/Herstarten\"\n\n#: ../vnc.html:163\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Uitschakelen/Herstarten...\"\n\n#: ../vnc.html:169\nmsgid \"Power\"\nmsgstr \"Systeem\"\n\n#: ../vnc.html:171\nmsgid \"Shutdown\"\nmsgstr \"Uitschakelen\"\n\n#: ../vnc.html:172\nmsgid \"Reboot\"\nmsgstr \"Herstarten\"\n\n#: ../vnc.html:173\nmsgid \"Reset\"\nmsgstr \"Resetten\"\n\n#: ../vnc.html:178 ../vnc.html:184\nmsgid \"Clipboard\"\nmsgstr \"Klembord\"\n\n#: ../vnc.html:186\nmsgid \"Edit clipboard content in the textarea below.\"\nmsgstr \"Edit de inhoud van het klembord in het tekstveld hieronder\"\n\n#: ../vnc.html:194\n#, fuzzy\nmsgid \"Full screen\"\nmsgstr \"Volledig Scherm\"\n\n#: ../vnc.html:199 ../vnc.html:205\nmsgid \"Settings\"\nmsgstr \"Instellingen\"\n\n#: ../vnc.html:211\n#, fuzzy\nmsgid \"Shared mode\"\nmsgstr \"Gedeelde Modus\"\n\n#: ../vnc.html:218\n#, fuzzy\nmsgid \"View only\"\nmsgstr \"Alleen Kijken\"\n\n#: ../vnc.html:226\nmsgid \"Clip to window\"\nmsgstr \"Randen buiten venster afsnijden\"\n\n#: ../vnc.html:231\nmsgid \"Scaling mode:\"\nmsgstr \"Schaalmodus:\"\n\n#: ../vnc.html:233\nmsgid \"None\"\nmsgstr \"Geen\"\n\n#: ../vnc.html:234\n#, fuzzy\nmsgid \"Local scaling\"\nmsgstr \"Lokaal Schalen\"\n\n#: ../vnc.html:235\n#, fuzzy\nmsgid \"Remote resizing\"\nmsgstr \"Op Afstand Formaat Wijzigen\"\n\n#: ../vnc.html:240\nmsgid \"Advanced\"\nmsgstr \"Geavanceerd\"\n\n#: ../vnc.html:243\nmsgid \"Quality:\"\nmsgstr \"Kwaliteit:\"\n\n#: ../vnc.html:247\nmsgid \"Compression level:\"\nmsgstr \"Compressieniveau:\"\n\n#: ../vnc.html:252\nmsgid \"Repeater ID:\"\nmsgstr \"Repeater ID:\"\n\n#: ../vnc.html:256\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:261\nmsgid \"Encrypt\"\nmsgstr \"Versleutelen\"\n\n#: ../vnc.html:266\nmsgid \"Host:\"\nmsgstr \"Host:\"\n\n#: ../vnc.html:270\nmsgid \"Port:\"\nmsgstr \"Poort:\"\n\n#: ../vnc.html:274\nmsgid \"Path:\"\nmsgstr \"Pad:\"\n\n#: ../vnc.html:283\n#, fuzzy\nmsgid \"Automatic reconnect\"\nmsgstr \"Automatisch Opnieuw Verbinden\"\n\n#: ../vnc.html:288\n#, fuzzy\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"Vertraging voor Opnieuw Verbinden (ms):\"\n\n#: ../vnc.html:295\nmsgid \"Show dot when no cursor\"\nmsgstr \"Geef stip weer indien geen cursor\"\n\n#: ../vnc.html:302\nmsgid \"Logging:\"\nmsgstr \"Logmeldingen:\"\n\n#: ../vnc.html:311\nmsgid \"Version:\"\nmsgstr \"Versie:\"\n\n#: ../vnc.html:319\nmsgid \"Disconnect\"\nmsgstr \"Verbinding verbreken\"\n\n#: ../vnc.html:342\nmsgid \"Connect\"\nmsgstr \"Verbinden\"\n\n#: ../vnc.html:351\nmsgid \"Server identity\"\nmsgstr \"Serveridentiteit\"\n\n#: ../vnc.html:354\nmsgid \"The server has provided the following identifying information:\"\nmsgstr \"De server geeft de volgende identificerende informatie:\"\n\n#: ../vnc.html:357\nmsgid \"Fingerprint:\"\nmsgstr \"Vingerafdruk:\"\n\n#: ../vnc.html:361\nmsgid \"\"\n\"Please verify that the information is correct and press \\\"Approve\\\". \"\n\"Otherwise press \\\"Reject\\\".\"\nmsgstr \"\"\n\"Verifieer dat de informatie is correct en druk “OK”. Druk anders op \"\n\"“Afwijzen”.\"\n\n#: ../vnc.html:366\nmsgid \"Approve\"\nmsgstr \"OK\"\n\n#: ../vnc.html:367\nmsgid \"Reject\"\nmsgstr \"Afwijzen\"\n\n#: ../vnc.html:375\nmsgid \"Credentials\"\nmsgstr \"Inloggegevens\"\n\n#: ../vnc.html:379\nmsgid \"Username:\"\nmsgstr \"Gebruikersnaam:\"\n\n#: ../vnc.html:383\nmsgid \"Password:\"\nmsgstr \"Wachtwoord:\"\n\n# Translated by Harold Horsman\n#: ../vnc.html:387\nmsgid \"Send credentials\"\nmsgstr \"Stuur inloggegevens\"\n\n#: ../vnc.html:396\nmsgid \"Cancel\"\nmsgstr \"Annuleren\"\n\n#~ msgid \"Must set host\"\n#~ msgstr \"Host moeten worden ingesteld\"\n\n#~ msgid \"Password is required\"\n#~ msgstr \"Wachtwoord is vereist\"\n\n#~ msgid \"viewport drag\"\n#~ msgstr \"kijkvenster slepen\"\n\n#~ msgid \"Active Mouse Button\"\n#~ msgstr \"Actieve Muisknop\"\n\n#~ msgid \"No mousebutton\"\n#~ msgstr \"Geen muisknop\"\n\n#~ msgid \"Left mousebutton\"\n#~ msgstr \"Linker muisknop\"\n\n#~ msgid \"Middle mousebutton\"\n#~ msgstr \"Middelste muisknop\"\n\n#~ msgid \"Right mousebutton\"\n#~ msgstr \"Rechter muisknop\"\n\n#~ msgid \"Clear\"\n#~ msgstr \"Wissen\"\n\n#~ msgid \"Send Password\"\n#~ msgstr \"Verzend Wachtwoord:\"\n\n#~ msgid \"Disconnect timeout\"\n#~ msgstr \"Timeout tijdens verbreken van verbinding\"\n\n#~ msgid \"Local Downscaling\"\n#~ msgstr \"Lokaal Neerschalen\"\n\n#~ msgid \"Local Cursor\"\n#~ msgstr \"Lokale Cursor\"\n\n#~ msgid \"Canvas not supported.\"\n#~ msgstr \"Canvas wordt niet ondersteund.\"\n\n#~ msgid \"\"\n#~ \"Forcing clipping mode since scrollbars aren't supported by IE in \"\n#~ \"fullscreen\"\n#~ msgstr \"\"\n#~ \"''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-\"\n#~ \"modus in IE niet worden ondersteund\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/noVNC.pot",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.6.0\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2025-02-14 10:14+0100\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=CHARSET\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: ../app/ui.js:84\nmsgid \"\"\n\"Running without HTTPS is not recommended, crashes or other issues are likely.\"\nmsgstr \"\"\n\n#: ../app/ui.js:413\nmsgid \"Connecting...\"\nmsgstr \"\"\n\n#: ../app/ui.js:420\nmsgid \"Disconnecting...\"\nmsgstr \"\"\n\n#: ../app/ui.js:426\nmsgid \"Reconnecting...\"\nmsgstr \"\"\n\n#: ../app/ui.js:431\nmsgid \"Internal error\"\nmsgstr \"\"\n\n#: ../app/ui.js:1079\nmsgid \"Failed to connect to server: \"\nmsgstr \"\"\n\n#: ../app/ui.js:1145\nmsgid \"Connected (encrypted) to \"\nmsgstr \"\"\n\n#: ../app/ui.js:1147\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"\"\n\n#: ../app/ui.js:1170\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"\"\n\n#: ../app/ui.js:1173\nmsgid \"Failed to connect to server\"\nmsgstr \"\"\n\n#: ../app/ui.js:1185\nmsgid \"Disconnected\"\nmsgstr \"\"\n\n#: ../app/ui.js:1200\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"\"\n\n#: ../app/ui.js:1203\nmsgid \"New connection has been rejected\"\nmsgstr \"\"\n\n#: ../app/ui.js:1269\nmsgid \"Credentials are required\"\nmsgstr \"\"\n\n#: ../vnc.html:106\nmsgid \"noVNC encountered an error:\"\nmsgstr \"\"\n\n#: ../vnc.html:116\nmsgid \"Hide/Show the control bar\"\nmsgstr \"\"\n\n#: ../vnc.html:125\nmsgid \"Drag\"\nmsgstr \"\"\n\n#: ../vnc.html:125\nmsgid \"Move/Drag viewport\"\nmsgstr \"\"\n\n#: ../vnc.html:131\nmsgid \"Keyboard\"\nmsgstr \"\"\n\n#: ../vnc.html:131\nmsgid \"Show keyboard\"\nmsgstr \"\"\n\n#: ../vnc.html:136\nmsgid \"Extra keys\"\nmsgstr \"\"\n\n#: ../vnc.html:136\nmsgid \"Show extra keys\"\nmsgstr \"\"\n\n#: ../vnc.html:141\nmsgid \"Ctrl\"\nmsgstr \"\"\n\n#: ../vnc.html:141\nmsgid \"Toggle Ctrl\"\nmsgstr \"\"\n\n#: ../vnc.html:144\nmsgid \"Alt\"\nmsgstr \"\"\n\n#: ../vnc.html:144\nmsgid \"Toggle Alt\"\nmsgstr \"\"\n\n#: ../vnc.html:147\nmsgid \"Toggle Windows\"\nmsgstr \"\"\n\n#: ../vnc.html:147\nmsgid \"Windows\"\nmsgstr \"\"\n\n#: ../vnc.html:150\nmsgid \"Send Tab\"\nmsgstr \"\"\n\n#: ../vnc.html:150\nmsgid \"Tab\"\nmsgstr \"\"\n\n#: ../vnc.html:153\nmsgid \"Esc\"\nmsgstr \"\"\n\n#: ../vnc.html:153\nmsgid \"Send Escape\"\nmsgstr \"\"\n\n#: ../vnc.html:156\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"\"\n\n#: ../vnc.html:156\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"\"\n\n#: ../vnc.html:163\nmsgid \"Shutdown/Reboot\"\nmsgstr \"\"\n\n#: ../vnc.html:163\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"\"\n\n#: ../vnc.html:169\nmsgid \"Power\"\nmsgstr \"\"\n\n#: ../vnc.html:171\nmsgid \"Shutdown\"\nmsgstr \"\"\n\n#: ../vnc.html:172\nmsgid \"Reboot\"\nmsgstr \"\"\n\n#: ../vnc.html:173\nmsgid \"Reset\"\nmsgstr \"\"\n\n#: ../vnc.html:178 ../vnc.html:184\nmsgid \"Clipboard\"\nmsgstr \"\"\n\n#: ../vnc.html:186\nmsgid \"Edit clipboard content in the textarea below.\"\nmsgstr \"\"\n\n#: ../vnc.html:194\nmsgid \"Full screen\"\nmsgstr \"\"\n\n#: ../vnc.html:199 ../vnc.html:205\nmsgid \"Settings\"\nmsgstr \"\"\n\n#: ../vnc.html:211\nmsgid \"Shared mode\"\nmsgstr \"\"\n\n#: ../vnc.html:218\nmsgid \"View only\"\nmsgstr \"\"\n\n#: ../vnc.html:226\nmsgid \"Clip to window\"\nmsgstr \"\"\n\n#: ../vnc.html:231\nmsgid \"Scaling mode:\"\nmsgstr \"\"\n\n#: ../vnc.html:233\nmsgid \"None\"\nmsgstr \"\"\n\n#: ../vnc.html:234\nmsgid \"Local scaling\"\nmsgstr \"\"\n\n#: ../vnc.html:235\nmsgid \"Remote resizing\"\nmsgstr \"\"\n\n#: ../vnc.html:240\nmsgid \"Advanced\"\nmsgstr \"\"\n\n#: ../vnc.html:243\nmsgid \"Quality:\"\nmsgstr \"\"\n\n#: ../vnc.html:247\nmsgid \"Compression level:\"\nmsgstr \"\"\n\n#: ../vnc.html:252\nmsgid \"Repeater ID:\"\nmsgstr \"\"\n\n#: ../vnc.html:256\nmsgid \"WebSocket\"\nmsgstr \"\"\n\n#: ../vnc.html:261\nmsgid \"Encrypt\"\nmsgstr \"\"\n\n#: ../vnc.html:266\nmsgid \"Host:\"\nmsgstr \"\"\n\n#: ../vnc.html:270\nmsgid \"Port:\"\nmsgstr \"\"\n\n#: ../vnc.html:274\nmsgid \"Path:\"\nmsgstr \"\"\n\n#: ../vnc.html:283\nmsgid \"Automatic reconnect\"\nmsgstr \"\"\n\n#: ../vnc.html:288\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"\"\n\n#: ../vnc.html:295\nmsgid \"Show dot when no cursor\"\nmsgstr \"\"\n\n#: ../vnc.html:302\nmsgid \"Logging:\"\nmsgstr \"\"\n\n#: ../vnc.html:311\nmsgid \"Version:\"\nmsgstr \"\"\n\n#: ../vnc.html:319\nmsgid \"Disconnect\"\nmsgstr \"\"\n\n#: ../vnc.html:342\nmsgid \"Connect\"\nmsgstr \"\"\n\n#: ../vnc.html:351\nmsgid \"Server identity\"\nmsgstr \"\"\n\n#: ../vnc.html:354\nmsgid \"The server has provided the following identifying information:\"\nmsgstr \"\"\n\n#: ../vnc.html:357\nmsgid \"Fingerprint:\"\nmsgstr \"\"\n\n#: ../vnc.html:361\nmsgid \"\"\n\"Please verify that the information is correct and press \\\"Approve\\\". \"\n\"Otherwise press \\\"Reject\\\".\"\nmsgstr \"\"\n\n#: ../vnc.html:366\nmsgid \"Approve\"\nmsgstr \"\"\n\n#: ../vnc.html:367\nmsgid \"Reject\"\nmsgstr \"\"\n\n#: ../vnc.html:375\nmsgid \"Credentials\"\nmsgstr \"\"\n\n#: ../vnc.html:379\nmsgid \"Username:\"\nmsgstr \"\"\n\n#: ../vnc.html:383\nmsgid \"Password:\"\nmsgstr \"\"\n\n#: ../vnc.html:387\nmsgid \"Send credentials\"\nmsgstr \"\"\n\n#: ../vnc.html:396\nmsgid \"Cancel\"\nmsgstr \"\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/pl.po",
    "content": "# Polish translations for noVNC package.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Mariusz Jamro <mariusz.jamro@gmail.com>, 2017.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 0.6.1\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2017-11-21 19:53+0100\\n\"\n\"PO-Revision-Date: 2017-11-21 19:54+0100\\n\"\n\"Last-Translator: Mariusz Jamro <mariusz.jamro@gmail.com>\\n\"\n\"Language-Team: Polish\\n\"\n\"Language: pl\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 \"\n\"|| n%100>=20) ? 1 : 2);\\n\"\n\"X-Generator: Poedit 2.0.1\\n\"\n\n#: ../app/ui.js:404\nmsgid \"Connecting...\"\nmsgstr \"Łączenie...\"\n\n#: ../app/ui.js:411\nmsgid \"Disconnecting...\"\nmsgstr \"Rozłączanie...\"\n\n#: ../app/ui.js:417\nmsgid \"Reconnecting...\"\nmsgstr \"Łączenie...\"\n\n#: ../app/ui.js:422\nmsgid \"Internal error\"\nmsgstr \"Błąd wewnętrzny\"\n\n#: ../app/ui.js:1019\nmsgid \"Must set host\"\nmsgstr \"Host i port są wymagane\"\n\n#: ../app/ui.js:1099\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Połączenie (szyfrowane) z \"\n\n#: ../app/ui.js:1101\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Połączenie (nieszyfrowane) z \"\n\n#: ../app/ui.js:1119\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Coś poszło źle, połączenie zostało zamknięte\"\n\n#: ../app/ui.js:1129\nmsgid \"Disconnected\"\nmsgstr \"Rozłączony\"\n\n#: ../app/ui.js:1142\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"Nowe połączenie zostało odrzucone z powodu: \"\n\n#: ../app/ui.js:1145\nmsgid \"New connection has been rejected\"\nmsgstr \"Nowe połączenie zostało odrzucone\"\n\n#: ../app/ui.js:1166\nmsgid \"Password is required\"\nmsgstr \"Hasło jest wymagane\"\n\n#: ../vnc.html:89\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC napotkało błąd:\"\n\n#: ../vnc.html:99\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Pokaż/Ukryj pasek ustawień\"\n\n#: ../vnc.html:106\nmsgid \"Move/Drag Viewport\"\nmsgstr \"Ruszaj/Przeciągaj Viewport\"\n\n#: ../vnc.html:106\nmsgid \"viewport drag\"\nmsgstr \"przeciągnij viewport\"\n\n#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121\nmsgid \"Active Mouse Button\"\nmsgstr \"Aktywny Przycisk Myszy\"\n\n#: ../vnc.html:112\nmsgid \"No mousebutton\"\nmsgstr \"Brak przycisku myszy\"\n\n#: ../vnc.html:115\nmsgid \"Left mousebutton\"\nmsgstr \"Lewy przycisk myszy\"\n\n#: ../vnc.html:118\nmsgid \"Middle mousebutton\"\nmsgstr \"Środkowy przycisk myszy\"\n\n#: ../vnc.html:121\nmsgid \"Right mousebutton\"\nmsgstr \"Prawy przycisk myszy\"\n\n#: ../vnc.html:124\nmsgid \"Keyboard\"\nmsgstr \"Klawiatura\"\n\n#: ../vnc.html:124\nmsgid \"Show keyboard\"\nmsgstr \"Pokaż klawiaturę\"\n\n#: ../vnc.html:131\nmsgid \"Extra keys\"\nmsgstr \"Przyciski dodatkowe\"\n\n#: ../vnc.html:131\nmsgid \"Show extra keys\"\nmsgstr \"Pokaż przyciski dodatkowe\"\n\n#: ../vnc.html:136\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:136\nmsgid \"Toggle Ctrl\"\nmsgstr \"Przełącz Ctrl\"\n\n#: ../vnc.html:139\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:139\nmsgid \"Toggle Alt\"\nmsgstr \"Przełącz Alt\"\n\n#: ../vnc.html:142\nmsgid \"Send Tab\"\nmsgstr \"Wyślij Tab\"\n\n#: ../vnc.html:142\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:145\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:145\nmsgid \"Send Escape\"\nmsgstr \"Wyślij Escape\"\n\n#: ../vnc.html:148\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:148\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Wyślij Ctrl-Alt-Del\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Wyłącz/Uruchom ponownie\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Wyłącz/Uruchom ponownie...\"\n\n#: ../vnc.html:162\nmsgid \"Power\"\nmsgstr \"Włączony\"\n\n#: ../vnc.html:164\nmsgid \"Shutdown\"\nmsgstr \"Wyłącz\"\n\n#: ../vnc.html:165\nmsgid \"Reboot\"\nmsgstr \"Uruchom ponownie\"\n\n#: ../vnc.html:166\nmsgid \"Reset\"\nmsgstr \"Resetuj\"\n\n#: ../vnc.html:171 ../vnc.html:177\nmsgid \"Clipboard\"\nmsgstr \"Schowek\"\n\n#: ../vnc.html:181\nmsgid \"Clear\"\nmsgstr \"Wyczyść\"\n\n#: ../vnc.html:187\nmsgid \"Fullscreen\"\nmsgstr \"Pełny ekran\"\n\n#: ../vnc.html:192 ../vnc.html:199\nmsgid \"Settings\"\nmsgstr \"Ustawienia\"\n\n#: ../vnc.html:202\nmsgid \"Shared Mode\"\nmsgstr \"Tryb Współdzielenia\"\n\n#: ../vnc.html:205\nmsgid \"View Only\"\nmsgstr \"Tylko Podgląd\"\n\n#: ../vnc.html:209\nmsgid \"Clip to Window\"\nmsgstr \"Przytnij do Okna\"\n\n#: ../vnc.html:212\nmsgid \"Scaling Mode:\"\nmsgstr \"Tryb Skalowania:\"\n\n#: ../vnc.html:214\nmsgid \"None\"\nmsgstr \"Brak\"\n\n#: ../vnc.html:215\nmsgid \"Local scaling\"\nmsgstr \"Skalowanie lokalne\"\n\n#: ../vnc.html:216\nmsgid \"Remote resizing\"\nmsgstr \"Skalowanie zdalne\"\n\n#: ../vnc.html:221\nmsgid \"Advanced\"\nmsgstr \"Zaawansowane\"\n\n#: ../vnc.html:224\nmsgid \"Repeater ID:\"\nmsgstr \"ID Repeatera:\"\n\n#: ../vnc.html:228\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:231\nmsgid \"Encrypt\"\nmsgstr \"Szyfrowanie\"\n\n#: ../vnc.html:234\nmsgid \"Host:\"\nmsgstr \"Host:\"\n\n#: ../vnc.html:238\nmsgid \"Port:\"\nmsgstr \"Port:\"\n\n#: ../vnc.html:242\nmsgid \"Path:\"\nmsgstr \"Ścieżka:\"\n\n#: ../vnc.html:249\nmsgid \"Automatic reconnect\"\nmsgstr \"Automatycznie wznawiaj połączenie\"\n\n#: ../vnc.html:252\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"Opóźnienie wznawiania (ms):\"\n\n#: ../vnc.html:258\nmsgid \"Logging:\"\nmsgstr \"Poziom logowania:\"\n\n#: ../vnc.html:270\nmsgid \"Disconnect\"\nmsgstr \"Rozłącz\"\n\n#: ../vnc.html:289\nmsgid \"Connect\"\nmsgstr \"Połącz\"\n\n#: ../vnc.html:299\nmsgid \"Password:\"\nmsgstr \"Hasło:\"\n\n#: ../vnc.html:313\nmsgid \"Cancel\"\nmsgstr \"Anuluj\"\n\n#: ../vnc.html:329\nmsgid \"Canvas not supported.\"\nmsgstr \"Element Canvas nie jest wspierany.\"\n\n#~ msgid \"Disconnect timeout\"\n#~ msgstr \"Timeout rozłączenia\"\n\n#~ msgid \"Local Downscaling\"\n#~ msgstr \"Downscaling lokalny\"\n\n#~ msgid \"Local Cursor\"\n#~ msgstr \"Lokalny kursor\"\n\n#~ msgid \"\"\n#~ \"Forcing clipping mode since scrollbars aren't supported by IE in \"\n#~ \"fullscreen\"\n#~ msgstr \"\"\n#~ \"Wymuszam clipping mode ponieważ paski przewijania nie są wspierane przez \"\n#~ \"IE w trybie pełnoekranowym\"\n\n#~ msgid \"True Color\"\n#~ msgstr \"True Color\"\n\n#~ msgid \"Style:\"\n#~ msgstr \"Styl:\"\n\n#~ msgid \"default\"\n#~ msgstr \"domyślny\"\n\n#~ msgid \"Apply\"\n#~ msgstr \"Zapisz\"\n\n#~ msgid \"Connection\"\n#~ msgstr \"Połączenie\"\n\n#~ msgid \"Token:\"\n#~ msgstr \"Token:\"\n\n#~ msgid \"Send Password\"\n#~ msgstr \"Wyślij Hasło\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/po2js",
    "content": "#!/usr/bin/env node\n/*\n * ps2js: gettext .po to noVNC .js converter\n * Copyright (C) 2018 The noVNC authors\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\nconst { program } = require('commander');\nconst fs = require('fs');\nconst pofile = require(\"pofile\");\n\nprogram\n    .argument('<input>')\n    .argument('<output>')\n    .parse(process.argv);\n\nlet data = fs.readFileSync(program.args[0], \"utf8\");\nlet po = pofile.parse(data);\n\nconst bodyPart = po.items\n    .filter(item => item.msgid !== \"\")\n    .filter(item => item.msgstr[0] !== \"\")\n    .filter(item => !item.flags.fuzzy)\n    .filter(item => !item.obsolete)\n    .map(item => \"    \" + JSON.stringify(item.msgid) + \": \" + JSON.stringify(item.msgstr[0]))\n    .join(\",\\n\");\n\nconst output = \"{\\n\" + bodyPart + \"\\n}\";\n\nfs.writeFileSync(program.args[1], output);\n"
  },
  {
    "path": "services/gateway/noVNC/po/pt_BR.po",
    "content": "# Portuguese translations for noVNC package.\n# Copyright (C) 2021 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n#  <liddack@outlook.com>, 2021.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.2.0\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2021-03-15 21:55-0300\\n\"\n\"PO-Revision-Date: 2021-03-15 22:09-0300\\n\"\n\"Last-Translator: <liddack@outlook.com>\\n\"\n\"Language-Team: Brazilian Portuguese\\n\"\n\"Language: pt_BR\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n\"X-Generator: Poedit 2.4.1\\n\"\n\n#: ../app/ui.js:400\nmsgid \"Connecting...\"\nmsgstr \"Conectando...\"\n\n#: ../app/ui.js:407\nmsgid \"Disconnecting...\"\nmsgstr \"Desconectando...\"\n\n#: ../app/ui.js:413\nmsgid \"Reconnecting...\"\nmsgstr \"Reconectando...\"\n\n#: ../app/ui.js:418\nmsgid \"Internal error\"\nmsgstr \"Erro interno\"\n\n#: ../app/ui.js:1009\nmsgid \"Must set host\"\nmsgstr \"É necessário definir o host\"\n\n#: ../app/ui.js:1091\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Conectado (com criptografia) a \"\n\n#: ../app/ui.js:1093\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Conectado (sem criptografia) a \"\n\n#: ../app/ui.js:1116\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Algo deu errado. A conexão foi encerrada.\"\n\n#: ../app/ui.js:1119\nmsgid \"Failed to connect to server\"\nmsgstr \"Falha ao conectar-se ao servidor\"\n\n#: ../app/ui.js:1129\nmsgid \"Disconnected\"\nmsgstr \"Desconectado\"\n\n#: ../app/ui.js:1144\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"A nova conexão foi rejeitada pelo motivo: \"\n\n#: ../app/ui.js:1147\nmsgid \"New connection has been rejected\"\nmsgstr \"A nova conexão foi rejeitada\"\n\n#: ../app/ui.js:1182\nmsgid \"Credentials are required\"\nmsgstr \"Credenciais são obrigatórias\"\n\n#: ../vnc.html:61\nmsgid \"noVNC encountered an error:\"\nmsgstr \"O noVNC encontrou um erro:\"\n\n#: ../vnc.html:71\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Esconder/mostrar a barra de controles\"\n\n#: ../vnc.html:78\nmsgid \"Drag\"\nmsgstr \"Arrastar\"\n\n#: ../vnc.html:78\nmsgid \"Move/Drag viewport\"\nmsgstr \"Mover/arrastar a janela\"\n\n#: ../vnc.html:84\nmsgid \"Keyboard\"\nmsgstr \"Teclado\"\n\n#: ../vnc.html:84\nmsgid \"Show keyboard\"\nmsgstr \"Mostrar teclado\"\n\n#: ../vnc.html:89\nmsgid \"Extra keys\"\nmsgstr \"Teclas adicionais\"\n\n#: ../vnc.html:89\nmsgid \"Show extra keys\"\nmsgstr \"Mostrar teclas adicionais\"\n\n#: ../vnc.html:94\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:94\nmsgid \"Toggle Ctrl\"\nmsgstr \"Pressionar/soltar Ctrl\"\n\n#: ../vnc.html:97\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:97\nmsgid \"Toggle Alt\"\nmsgstr \"Pressionar/soltar Alt\"\n\n#: ../vnc.html:100\nmsgid \"Toggle Windows\"\nmsgstr \"Pressionar/soltar Windows\"\n\n#: ../vnc.html:100\nmsgid \"Windows\"\nmsgstr \"Windows\"\n\n#: ../vnc.html:103\nmsgid \"Send Tab\"\nmsgstr \"Enviar Tab\"\n\n#: ../vnc.html:103\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:106\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:106\nmsgid \"Send Escape\"\nmsgstr \"Enviar Esc\"\n\n#: ../vnc.html:109\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:109\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Enviar Ctrl-Alt-Del\"\n\n#: ../vnc.html:116\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Desligar/reiniciar\"\n\n#: ../vnc.html:116\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Desligar/reiniciar...\"\n\n#: ../vnc.html:122\nmsgid \"Power\"\nmsgstr \"Ligar\"\n\n#: ../vnc.html:124\nmsgid \"Shutdown\"\nmsgstr \"Desligar\"\n\n#: ../vnc.html:125\nmsgid \"Reboot\"\nmsgstr \"Reiniciar\"\n\n#: ../vnc.html:126\nmsgid \"Reset\"\nmsgstr \"Reiniciar (forçado)\"\n\n#: ../vnc.html:131 ../vnc.html:137\nmsgid \"Clipboard\"\nmsgstr \"Área de transferência\"\n\n#: ../vnc.html:141\nmsgid \"Clear\"\nmsgstr \"Limpar\"\n\n#: ../vnc.html:147\nmsgid \"Fullscreen\"\nmsgstr \"Tela cheia\"\n\n#: ../vnc.html:152 ../vnc.html:159\nmsgid \"Settings\"\nmsgstr \"Configurações\"\n\n#: ../vnc.html:162\nmsgid \"Shared mode\"\nmsgstr \"Modo compartilhado\"\n\n#: ../vnc.html:165\nmsgid \"View only\"\nmsgstr \"Apenas visualizar\"\n\n#: ../vnc.html:169\nmsgid \"Clip to window\"\nmsgstr \"Recortar à janela\"\n\n#: ../vnc.html:172\nmsgid \"Scaling mode:\"\nmsgstr \"Modo de dimensionamento:\"\n\n#: ../vnc.html:174\nmsgid \"None\"\nmsgstr \"Nenhum\"\n\n#: ../vnc.html:175\nmsgid \"Local scaling\"\nmsgstr \"Local\"\n\n#: ../vnc.html:176\nmsgid \"Remote resizing\"\nmsgstr \"Remoto\"\n\n#: ../vnc.html:181\nmsgid \"Advanced\"\nmsgstr \"Avançado\"\n\n#: ../vnc.html:184\nmsgid \"Quality:\"\nmsgstr \"Qualidade:\"\n\n#: ../vnc.html:188\nmsgid \"Compression level:\"\nmsgstr \"Nível de compressão:\"\n\n#: ../vnc.html:193\nmsgid \"Repeater ID:\"\nmsgstr \"ID do repetidor:\"\n\n#: ../vnc.html:197\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:200\nmsgid \"Encrypt\"\nmsgstr \"Criptografar\"\n\n#: ../vnc.html:203\nmsgid \"Host:\"\nmsgstr \"Host:\"\n\n#: ../vnc.html:207\nmsgid \"Port:\"\nmsgstr \"Porta:\"\n\n#: ../vnc.html:211\nmsgid \"Path:\"\nmsgstr \"Caminho:\"\n\n#: ../vnc.html:218\nmsgid \"Automatic reconnect\"\nmsgstr \"Reconexão automática\"\n\n#: ../vnc.html:221\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"Atraso da reconexão (ms)\"\n\n#: ../vnc.html:226\nmsgid \"Show dot when no cursor\"\nmsgstr \"Mostrar ponto quando não há cursor\"\n\n#: ../vnc.html:231\nmsgid \"Logging:\"\nmsgstr \"Registros:\"\n\n#: ../vnc.html:240\nmsgid \"Version:\"\nmsgstr \"Versão:\"\n\n#: ../vnc.html:248\nmsgid \"Disconnect\"\nmsgstr \"Desconectar\"\n\n#: ../vnc.html:267\nmsgid \"Connect\"\nmsgstr \"Conectar\"\n\n#: ../vnc.html:277\nmsgid \"Username:\"\nmsgstr \"Nome de usuário:\"\n\n#: ../vnc.html:281\nmsgid \"Password:\"\nmsgstr \"Senha:\"\n\n#: ../vnc.html:285\nmsgid \"Send credentials\"\nmsgstr \"Enviar credenciais\"\n\n#: ../vnc.html:295\nmsgid \"Cancel\"\nmsgstr \"Cancelar\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/ru.po",
    "content": "# Russian translations for noVNC package\n# Русский перевод для пакета noVNC.\n# Copyright (C) 2019 Dmitriy Shweew\n# This file is distributed under the same license as the noVNC package.\n# Dmitriy Shweew <shweew@it-advisor.ru>, 2019.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.5.0\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2021-08-27 16:03+0200\\n\"\n\"PO-Revision-Date: 2024-02-11 03:58+0300\\n\"\n\"Last-Translator: Dim5x <dim5x@yahoo.com>\\n\"\n\"Language-Team: Russian\\n\"\n\"Language: ru\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n\"\n\"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\\n\"\n\"X-Generator: Poedit 2.2.1\\n\"\n\"X-Poedit-Flags-xgettext: --add-comments\\n\"\n\n#: ../app/ui.js:400\nmsgid \"Connecting...\"\nmsgstr \"Подключение...\"\n\n#: ../app/ui.js:407\nmsgid \"Disconnecting...\"\nmsgstr \"Отключение...\"\n\n#: ../app/ui.js:413\nmsgid \"Reconnecting...\"\nmsgstr \"Переподключение...\"\n\n#: ../app/ui.js:418\nmsgid \"Internal error\"\nmsgstr \"Внутренняя ошибка\"\n\n#: ../app/ui.js:1009\nmsgid \"Must set host\"\nmsgstr \"Задайте имя сервера или IP\"\n\n#: ../app/ui.js:1091\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Подключено (с шифрованием) к \"\n\n#: ../app/ui.js:1093\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Подключено (без шифрования) к \"\n\n#: ../app/ui.js:1116\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Что-то пошло не так, подключение разорвано\"\n\n#: ../app/ui.js:1119\nmsgid \"Failed to connect to server\"\nmsgstr \"Ошибка подключения к серверу\"\n\n#: ../app/ui.js:1129\nmsgid \"Disconnected\"\nmsgstr \"Отключено\"\n\n#: ../app/ui.js:1144\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"Новое соединение отклонено по причине: \"\n\n#: ../app/ui.js:1147\nmsgid \"New connection has been rejected\"\nmsgstr \"Новое соединение отклонено\"\n\n#: ../app/ui.js:1182\nmsgid \"Credentials are required\"\nmsgstr \"Требуются учетные данные\"\n\n#: ../vnc.html:61\nmsgid \"noVNC encountered an error:\"\nmsgstr \"Ошибка noVNC: \"\n\n#: ../vnc.html:71\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Скрыть/Показать контрольную панель\"\n\n#: ../vnc.html:78\nmsgid \"Drag\"\nmsgstr \"Переместить\"\n\n#: ../vnc.html:78\nmsgid \"Move/Drag viewport\"\nmsgstr \"Переместить окно\"\n\n#: ../vnc.html:84\nmsgid \"Keyboard\"\nmsgstr \"Клавиатура\"\n\n#: ../vnc.html:84\nmsgid \"Show keyboard\"\nmsgstr \"Показать клавиатуру\"\n\n#: ../vnc.html:89\nmsgid \"Extra keys\"\nmsgstr \"Дополнительные Кнопки\"\n\n#: ../vnc.html:89\nmsgid \"Show Extra Keys\"\nmsgstr \"Показать Дополнительные Кнопки\"\n\n#: ../vnc.html:94\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:94\nmsgid \"Toggle Ctrl\"\nmsgstr \"Зажать Ctrl\"\n\n#: ../vnc.html:97\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:97\nmsgid \"Toggle Alt\"\nmsgstr \"Зажать Alt\"\n\n#: ../vnc.html:100\nmsgid \"Toggle Windows\"\nmsgstr \"Зажать Windows\"\n\n#: ../vnc.html:100\nmsgid \"Windows\"\nmsgstr \"Вкладка\"\n\n#: ../vnc.html:103\nmsgid \"Send Tab\"\nmsgstr \"Передать нажатие Tab\"\n\n#: ../vnc.html:103\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:106\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:106\nmsgid \"Send Escape\"\nmsgstr \"Передать нажатие Escape\"\n\n#: ../vnc.html:109\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:109\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Передать нажатие Ctrl-Alt-Del\"\n\n#: ../vnc.html:116\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Выключить/Перезагрузить\"\n\n#: ../vnc.html:116\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Выключить/Перезагрузить...\"\n\n#: ../vnc.html:122\nmsgid \"Power\"\nmsgstr \"Питание\"\n\n#: ../vnc.html:124\nmsgid \"Shutdown\"\nmsgstr \"Выключить\"\n\n#: ../vnc.html:125\nmsgid \"Reboot\"\nmsgstr \"Перезагрузить\"\n\n#: ../vnc.html:126\nmsgid \"Reset\"\nmsgstr \"Сброс\"\n\n#: ../vnc.html:131 ../vnc.html:137\nmsgid \"Clipboard\"\nmsgstr \"Буфер обмена\"\n\n#: ../vnc.html:141\nmsgid \"Clear\"\nmsgstr \"Очистить\"\n\n#: ../vnc.html:147\nmsgid \"Fullscreen\"\nmsgstr \"Во весь экран\"\n\n#: ../vnc.html:152 ../vnc.html:159\nmsgid \"Settings\"\nmsgstr \"Настройки\"\n\n#: ../vnc.html:162\nmsgid \"Shared mode\"\nmsgstr \"Общий режим\"\n\n#: ../vnc.html:165\nmsgid \"View Only\"\nmsgstr \"Только Просмотр\"\n\n#: ../vnc.html:169\nmsgid \"Clip to window\"\nmsgstr \"В окно\"\n\n#: ../vnc.html:172\nmsgid \"Scaling mode:\"\nmsgstr \"Масштаб:\"\n\n#: ../vnc.html:174\nmsgid \"None\"\nmsgstr \"Нет\"\n\n#: ../vnc.html:175\nmsgid \"Local scaling\"\nmsgstr \"Локальный масштаб\"\n\n#: ../vnc.html:176\nmsgid \"Remote resizing\"\nmsgstr \"Удаленная перенастройка размера\"\n\n#: ../vnc.html:181\nmsgid \"Advanced\"\nmsgstr \"Дополнительно\"\n\n#: ../vnc.html:184\nmsgid \"Quality:\"\nmsgstr \"Качество\"\n\n#: ../vnc.html:188\nmsgid \"Compression level:\"\nmsgstr \"Уровень Сжатия\"\n\n#: ../vnc.html:193\nmsgid \"Repeater ID:\"\nmsgstr \"Идентификатор ID:\"\n\n#: ../vnc.html:197\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:200\nmsgid \"Encrypt\"\nmsgstr \"Шифрование\"\n\n#: ../vnc.html:203\nmsgid \"Host:\"\nmsgstr \"Сервер:\"\n\n#: ../vnc.html:207\nmsgid \"Port:\"\nmsgstr \"Порт:\"\n\n#: ../vnc.html:211\nmsgid \"Path:\"\nmsgstr \"Путь:\"\n\n#: ../vnc.html:218\nmsgid \"Automatic reconnect\"\nmsgstr \"Автоматическое переподключение\"\n\n#: ../vnc.html:221\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"Задержка переподключения (мс):\"\n\n#: ../vnc.html:226\nmsgid \"Show dot when no cursor\"\nmsgstr \"Показать точку вместо курсора\"\n\n#: ../vnc.html:231\nmsgid \"Logging:\"\nmsgstr \"Лог:\"\n\n#: ../vnc.html:240\nmsgid \"Version:\"\nmsgstr \"Версия\"\n\n#: ../vnc.html:248\nmsgid \"Disconnect\"\nmsgstr \"Отключение\"\n\n#: ../vnc.html:267\nmsgid \"Connect\"\nmsgstr \"Подключение\"\n\n#: ../vnc.html:277\nmsgid \"Username:\"\nmsgstr \"Имя Пользователя\"\n\n#: ../vnc.html:281\nmsgid \"Password:\"\nmsgstr \"Пароль:\"\n\n#: ../vnc.html:285\nmsgid \"Send Credentials\"\nmsgstr \"Передача Учетных Данных\"\n\n#: ../vnc.html:295\nmsgid \"Cancel\"\nmsgstr \"Выход\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/sv.po",
    "content": "# Swedish translations for noVNC package\n# Svenska översättningar för paketet noVNC.\n# Copyright (C) 2025 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Samuel Mannehed <samuel@cendio.se>, 2020.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.6.0\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2025-02-14 10:14+0100\\n\"\n\"PO-Revision-Date: 2025-02-14 10:29+0100\\n\"\n\"Last-Translator: Alexander Zeijlon <aleze@cendio.com>\\n\"\n\"Language-Team: none\\n\"\n\"Language: sv\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n != 1);\\n\"\n\"X-Generator: Poedit 3.5\\n\"\n\n#: ../app/ui.js:84\nmsgid \"\"\n\"Running without HTTPS is not recommended, crashes or other issues are likely.\"\nmsgstr \"\"\n\"Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är \"\n\"troliga.\"\n\n#: ../app/ui.js:413\nmsgid \"Connecting...\"\nmsgstr \"Ansluter...\"\n\n#: ../app/ui.js:420\nmsgid \"Disconnecting...\"\nmsgstr \"Kopplar ner...\"\n\n#: ../app/ui.js:426\nmsgid \"Reconnecting...\"\nmsgstr \"Återansluter...\"\n\n#: ../app/ui.js:431\nmsgid \"Internal error\"\nmsgstr \"Internt fel\"\n\n#: ../app/ui.js:1079\nmsgid \"Failed to connect to server: \"\nmsgstr \"Misslyckades att ansluta till servern: \"\n\n#: ../app/ui.js:1145\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Ansluten (krypterat) till \"\n\n#: ../app/ui.js:1147\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Ansluten (okrypterat) till \"\n\n#: ../app/ui.js:1170\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Något gick fel, anslutningen avslutades\"\n\n#: ../app/ui.js:1173\nmsgid \"Failed to connect to server\"\nmsgstr \"Misslyckades att ansluta till servern\"\n\n#: ../app/ui.js:1185\nmsgid \"Disconnected\"\nmsgstr \"Frånkopplad\"\n\n#: ../app/ui.js:1200\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"Ny anslutning har blivit nekad med följande skäl: \"\n\n#: ../app/ui.js:1203\nmsgid \"New connection has been rejected\"\nmsgstr \"Ny anslutning har blivit nekad\"\n\n#: ../app/ui.js:1269\nmsgid \"Credentials are required\"\nmsgstr \"Användaruppgifter krävs\"\n\n#: ../vnc.html:106\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC stötte på ett problem:\"\n\n#: ../vnc.html:116\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Göm/Visa kontrollbaren\"\n\n#: ../vnc.html:125\nmsgid \"Drag\"\nmsgstr \"Dra\"\n\n#: ../vnc.html:125\nmsgid \"Move/Drag viewport\"\nmsgstr \"Flytta/Dra vyn\"\n\n#: ../vnc.html:131\nmsgid \"Keyboard\"\nmsgstr \"Tangentbord\"\n\n#: ../vnc.html:131\nmsgid \"Show keyboard\"\nmsgstr \"Visa tangentbord\"\n\n#: ../vnc.html:136\nmsgid \"Extra keys\"\nmsgstr \"Extraknappar\"\n\n#: ../vnc.html:136\nmsgid \"Show extra keys\"\nmsgstr \"Visa extraknappar\"\n\n#: ../vnc.html:141\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:141\nmsgid \"Toggle Ctrl\"\nmsgstr \"Växla Ctrl\"\n\n#: ../vnc.html:144\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:144\nmsgid \"Toggle Alt\"\nmsgstr \"Växla Alt\"\n\n#: ../vnc.html:147\nmsgid \"Toggle Windows\"\nmsgstr \"Växla Windows\"\n\n#: ../vnc.html:147\nmsgid \"Windows\"\nmsgstr \"Windows\"\n\n#: ../vnc.html:150\nmsgid \"Send Tab\"\nmsgstr \"Skicka Tab\"\n\n#: ../vnc.html:150\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:153\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:153\nmsgid \"Send Escape\"\nmsgstr \"Skicka Escape\"\n\n#: ../vnc.html:156\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:156\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Skicka Ctrl-Alt-Del\"\n\n#: ../vnc.html:163\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Stäng av/Boota om\"\n\n#: ../vnc.html:163\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Stäng av/Boota om...\"\n\n#: ../vnc.html:169\nmsgid \"Power\"\nmsgstr \"Ström\"\n\n#: ../vnc.html:171\nmsgid \"Shutdown\"\nmsgstr \"Stäng av\"\n\n#: ../vnc.html:172\nmsgid \"Reboot\"\nmsgstr \"Boota om\"\n\n#: ../vnc.html:173\nmsgid \"Reset\"\nmsgstr \"Återställ\"\n\n#: ../vnc.html:178 ../vnc.html:184\nmsgid \"Clipboard\"\nmsgstr \"Urklipp\"\n\n#: ../vnc.html:186\nmsgid \"Edit clipboard content in the textarea below.\"\nmsgstr \"Redigera urklippets innehåll i fältet nedan.\"\n\n#: ../vnc.html:194\nmsgid \"Full screen\"\nmsgstr \"Fullskärm\"\n\n#: ../vnc.html:199 ../vnc.html:205\nmsgid \"Settings\"\nmsgstr \"Inställningar\"\n\n#: ../vnc.html:211\nmsgid \"Shared mode\"\nmsgstr \"Delat läge\"\n\n#: ../vnc.html:218\nmsgid \"View only\"\nmsgstr \"Endast visning\"\n\n#: ../vnc.html:226\nmsgid \"Clip to window\"\nmsgstr \"Begränsa till fönster\"\n\n#: ../vnc.html:231\nmsgid \"Scaling mode:\"\nmsgstr \"Skalningsläge:\"\n\n#: ../vnc.html:233\nmsgid \"None\"\nmsgstr \"Ingen\"\n\n#: ../vnc.html:234\nmsgid \"Local scaling\"\nmsgstr \"Lokal skalning\"\n\n#: ../vnc.html:235\nmsgid \"Remote resizing\"\nmsgstr \"Ändra storlek\"\n\n#: ../vnc.html:240\nmsgid \"Advanced\"\nmsgstr \"Avancerat\"\n\n#: ../vnc.html:243\nmsgid \"Quality:\"\nmsgstr \"Kvalitet:\"\n\n#: ../vnc.html:247\nmsgid \"Compression level:\"\nmsgstr \"Kompressionsnivå:\"\n\n#: ../vnc.html:252\nmsgid \"Repeater ID:\"\nmsgstr \"Repeater-ID:\"\n\n#: ../vnc.html:256\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:261\nmsgid \"Encrypt\"\nmsgstr \"Kryptera\"\n\n#: ../vnc.html:266\nmsgid \"Host:\"\nmsgstr \"Värd:\"\n\n#: ../vnc.html:270\nmsgid \"Port:\"\nmsgstr \"Port:\"\n\n#: ../vnc.html:274\nmsgid \"Path:\"\nmsgstr \"Sökväg:\"\n\n#: ../vnc.html:283\nmsgid \"Automatic reconnect\"\nmsgstr \"Automatisk återanslutning\"\n\n#: ../vnc.html:288\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"Fördröjning (ms):\"\n\n#: ../vnc.html:295\nmsgid \"Show dot when no cursor\"\nmsgstr \"Visa prick när ingen muspekare finns\"\n\n#: ../vnc.html:302\nmsgid \"Logging:\"\nmsgstr \"Loggning:\"\n\n#: ../vnc.html:311\nmsgid \"Version:\"\nmsgstr \"Version:\"\n\n#: ../vnc.html:319\nmsgid \"Disconnect\"\nmsgstr \"Koppla från\"\n\n#: ../vnc.html:342\nmsgid \"Connect\"\nmsgstr \"Anslut\"\n\n#: ../vnc.html:351\nmsgid \"Server identity\"\nmsgstr \"Server-identitet\"\n\n#: ../vnc.html:354\nmsgid \"The server has provided the following identifying information:\"\nmsgstr \"Servern har gett följande identifierande information:\"\n\n#: ../vnc.html:357\nmsgid \"Fingerprint:\"\nmsgstr \"Fingeravtryck:\"\n\n#: ../vnc.html:361\nmsgid \"\"\n\"Please verify that the information is correct and press \\\"Approve\\\". \"\n\"Otherwise press \\\"Reject\\\".\"\nmsgstr \"\"\n\"Kontrollera att informationen är korrekt och tryck sedan \\\"Godkänn\\\". Tryck \"\n\"annars \\\"Neka\\\".\"\n\n#: ../vnc.html:366\nmsgid \"Approve\"\nmsgstr \"Godkänn\"\n\n#: ../vnc.html:367\nmsgid \"Reject\"\nmsgstr \"Neka\"\n\n#: ../vnc.html:375\nmsgid \"Credentials\"\nmsgstr \"Användaruppgifter\"\n\n#: ../vnc.html:379\nmsgid \"Username:\"\nmsgstr \"Användarnamn:\"\n\n#: ../vnc.html:383\nmsgid \"Password:\"\nmsgstr \"Lösenord:\"\n\n#: ../vnc.html:387\nmsgid \"Send credentials\"\nmsgstr \"Skicka användaruppgifter\"\n\n#: ../vnc.html:396\nmsgid \"Cancel\"\nmsgstr \"Avbryt\"\n\n#~ msgid \"Must set host\"\n#~ msgstr \"Du måste specifiera en värd\"\n\n#~ msgid \"HTTPS is required for full functionality\"\n#~ msgstr \"HTTPS krävs för full funktionalitet\"\n\n#~ msgid \"Clear\"\n#~ msgstr \"Rensa\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/tr.po",
    "content": "# Turkish translations for noVNC package\n# Turkish translation for noVNC.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Ömer ÇAKMAK <farukomercakmak@gmail.com>, 2018.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 0.6.1\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2017-11-24 07:16+0000\\n\"\n\"PO-Revision-Date: 2018-01-05 19:07+0300\\n\"\n\"Last-Translator: Ömer ÇAKMAK <farukomercakmak@gmail.com>\\n\"\n\"Language-Team: Türkçe <gnome-turk@gnome.org>\\n\"\n\"Language: tr\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=1; plural=0;\\n\"\n\"X-Generator: Gtranslator 2.91.7\\n\"\n\n#: ../app/ui.js:404\nmsgid \"Connecting...\"\nmsgstr \"Bağlanıyor...\"\n\n#: ../app/ui.js:411\nmsgid \"Disconnecting...\"\nmsgstr \"Bağlantı kesiliyor...\"\n\n#: ../app/ui.js:417\nmsgid \"Reconnecting...\"\nmsgstr \"Yeniden bağlantı kuruluyor...\"\n\n#: ../app/ui.js:422\nmsgid \"Internal error\"\nmsgstr \"İç hata\"\n\n#: ../app/ui.js:1019\nmsgid \"Must set host\"\nmsgstr \"Sunucuyu kur\"\n\n#: ../app/ui.js:1099\nmsgid \"Connected (encrypted) to \"\nmsgstr \"Bağlı (şifrelenmiş)\"\n\n#: ../app/ui.js:1101\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"Bağlandı (şifrelenmemiş)\"\n\n#: ../app/ui.js:1119\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"Bir şeyler ters gitti, bağlantı kesildi\"\n\n#: ../app/ui.js:1129\nmsgid \"Disconnected\"\nmsgstr \"Bağlantı kesildi\"\n\n#: ../app/ui.js:1142\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"Bağlantı aşağıdaki nedenlerden dolayı reddedildi: \"\n\n#: ../app/ui.js:1145\nmsgid \"New connection has been rejected\"\nmsgstr \"Bağlantı reddedildi\"\n\n#: ../app/ui.js:1166\nmsgid \"Password is required\"\nmsgstr \"Şifre gerekli\"\n\n#: ../vnc.html:89\nmsgid \"noVNC encountered an error:\"\nmsgstr \"Bir hata oluştu:\"\n\n#: ../vnc.html:99\nmsgid \"Hide/Show the control bar\"\nmsgstr \"Denetim masasını Gizle/Göster\"\n\n#: ../vnc.html:106\nmsgid \"Move/Drag Viewport\"\nmsgstr \"Görünümü Taşı/Sürükle\"\n\n#: ../vnc.html:106\nmsgid \"viewport drag\"\nmsgstr \"Görüntü penceresini sürükle\"\n\n#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121\nmsgid \"Active Mouse Button\"\nmsgstr \"Aktif Fare Düğmesi\"\n\n#: ../vnc.html:112\nmsgid \"No mousebutton\"\nmsgstr \"Fare düğmesi yok\"\n\n#: ../vnc.html:115\nmsgid \"Left mousebutton\"\nmsgstr \"Farenin sol düğmesi\"\n\n#: ../vnc.html:118\nmsgid \"Middle mousebutton\"\nmsgstr \"Farenin orta düğmesi\"\n\n#: ../vnc.html:121\nmsgid \"Right mousebutton\"\nmsgstr \"Farenin sağ düğmesi\"\n\n#: ../vnc.html:124\nmsgid \"Keyboard\"\nmsgstr \"Klavye\"\n\n#: ../vnc.html:124\nmsgid \"Show Keyboard\"\nmsgstr \"Klavye Düzenini Göster\"\n\n#: ../vnc.html:131\nmsgid \"Extra keys\"\nmsgstr \"Ekstra tuşlar\"\n\n#: ../vnc.html:131\nmsgid \"Show extra keys\"\nmsgstr \"Ekstra tuşları göster\"\n\n#: ../vnc.html:136\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:136\nmsgid \"Toggle Ctrl\"\nmsgstr \"Ctrl Değiştir \"\n\n#: ../vnc.html:139\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:139\nmsgid \"Toggle Alt\"\nmsgstr \"Alt Değiştir\"\n\n#: ../vnc.html:142\nmsgid \"Send Tab\"\nmsgstr \"Sekme Gönder\"\n\n#: ../vnc.html:142\nmsgid \"Tab\"\nmsgstr \"Sekme\"\n\n#: ../vnc.html:145\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:145\nmsgid \"Send Escape\"\nmsgstr \"Boşluk Gönder\"\n\n#: ../vnc.html:148\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl + Alt + Del\"\n\n#: ../vnc.html:148\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"Ctrl-Alt-Del Gönder\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot\"\nmsgstr \"Kapat/Yeniden Başlat\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"Kapat/Yeniden Başlat...\"\n\n#: ../vnc.html:162\nmsgid \"Power\"\nmsgstr \"Güç\"\n\n#: ../vnc.html:164\nmsgid \"Shutdown\"\nmsgstr \"Kapat\"\n\n#: ../vnc.html:165\nmsgid \"Reboot\"\nmsgstr \"Yeniden Başlat\"\n\n#: ../vnc.html:166\nmsgid \"Reset\"\nmsgstr \"Sıfırla\"\n\n#: ../vnc.html:171 ../vnc.html:177\nmsgid \"Clipboard\"\nmsgstr \"Pano\"\n\n#: ../vnc.html:181\nmsgid \"Clear\"\nmsgstr \"Temizle\"\n\n#: ../vnc.html:187\nmsgid \"Fullscreen\"\nmsgstr \"Tam Ekran\"\n\n#: ../vnc.html:192 ../vnc.html:199\nmsgid \"Settings\"\nmsgstr \"Ayarlar\"\n\n#: ../vnc.html:202\nmsgid \"Shared Mode\"\nmsgstr \"Paylaşım Modu\"\n\n#: ../vnc.html:205\nmsgid \"View Only\"\nmsgstr \"Sadece Görüntüle\"\n\n#: ../vnc.html:209\nmsgid \"Clip to Window\"\nmsgstr \"Pencereye Tıkla\"\n\n#: ../vnc.html:212\nmsgid \"Scaling Mode:\"\nmsgstr \"Ölçekleme Modu:\"\n\n#: ../vnc.html:214\nmsgid \"None\"\nmsgstr \"Bilinmeyen\"\n\n#: ../vnc.html:215\nmsgid \"Local Scaling\"\nmsgstr \"Yerel Ölçeklendirme\"\n\n#: ../vnc.html:216\nmsgid \"Remote Resizing\"\nmsgstr \"Uzaktan Yeniden Boyutlandırma\"\n\n#: ../vnc.html:221\nmsgid \"Advanced\"\nmsgstr \"Gelişmiş\"\n\n#: ../vnc.html:224\nmsgid \"Repeater ID:\"\nmsgstr \"Tekralayıcı ID:\"\n\n#: ../vnc.html:228\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:231\nmsgid \"Encrypt\"\nmsgstr \"Şifrele\"\n\n#: ../vnc.html:234\nmsgid \"Host:\"\nmsgstr \"Ana makine:\"\n\n#: ../vnc.html:238\nmsgid \"Port:\"\nmsgstr \"Port:\"\n\n#: ../vnc.html:242\nmsgid \"Path:\"\nmsgstr \"Yol:\"\n\n#: ../vnc.html:249\nmsgid \"Automatic Reconnect\"\nmsgstr \"Otomatik Yeniden Bağlan\"\n\n#: ../vnc.html:252\nmsgid \"Reconnect Delay (ms):\"\nmsgstr \"Yeniden Bağlanma Süreci (ms):\"\n\n#: ../vnc.html:258\nmsgid \"Logging:\"\nmsgstr \"Giriş yapılıyor:\"\n\n#: ../vnc.html:270\nmsgid \"Disconnect\"\nmsgstr \"Bağlantıyı Kes\"\n\n#: ../vnc.html:289\nmsgid \"Connect\"\nmsgstr \"Bağlan\"\n\n#: ../vnc.html:299\nmsgid \"Password:\"\nmsgstr \"Parola:\"\n\n#: ../vnc.html:313\nmsgid \"Cancel\"\nmsgstr \"Vazgeç\"\n\n#: ../vnc.html:329\nmsgid \"Canvas not supported.\"\nmsgstr \"Tuval desteklenmiyor.\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/xgettext-html",
    "content": "#!/usr/bin/env node\n/*\n * xgettext-html: HTML gettext parser\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n */\n\nconst { program } = require('commander');\nconst jsdom = require(\"jsdom\");\nconst fs = require(\"fs\");\n\nprogram\n    .argument('<INPUT...>')\n    .requiredOption('-o, --output <FILE>', 'write output to specified file')\n    .parse(process.argv);\n\nconst strings = {};\n\nfunction addString(str, location) {\n    // We assume surrounding whitespace, and whitespace around line\n    // breaks, is just for source formatting\n    str = str.split(\"\\n\").map(s => s.trim()).join(\" \").trim();\n\n    if (str.length == 0) {\n        return;\n    }\n\n    if (strings[str] === undefined) {\n        strings[str] = {};\n    }\n    strings[str][location] = null;\n}\n\n// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate\nfunction process(elem, locator, enabled) {\n    function isAnyOf(searchElement, items) {\n        return items.indexOf(searchElement) !== -1;\n    }\n\n    if (elem.hasAttribute(\"translate\")) {\n        if (isAnyOf(elem.getAttribute(\"translate\"), [\"\", \"yes\"])) {\n            enabled = true;\n        } else if (isAnyOf(elem.getAttribute(\"translate\"), [\"no\"])) {\n            enabled = false;\n        }\n    }\n\n    if (enabled) {\n        if (elem.hasAttribute(\"abbr\") &&\n            elem.tagName === \"TH\") {\n            addString(elem.getAttribute(\"abbr\"), locator(elem));\n        }\n        if (elem.hasAttribute(\"alt\") &&\n            isAnyOf(elem.tagName, [\"AREA\", \"IMG\", \"INPUT\"])) {\n            addString(elem.getAttribute(\"alt\"), locator(elem));\n        }\n        if (elem.hasAttribute(\"download\") &&\n            isAnyOf(elem.tagName, [\"A\", \"AREA\"])) {\n            addString(elem.getAttribute(\"download\"), locator(elem));\n        }\n        if (elem.hasAttribute(\"label\") &&\n            isAnyOf(elem.tagName, [\"MENUITEM\", \"MENU\", \"OPTGROUP\",\n                                   \"OPTION\", \"TRACK\"])) {\n            addString(elem.getAttribute(\"label\"), locator(elem));\n        }\n        if (elem.hasAttribute(\"placeholder\") &&\n            isAnyOf(elem.tagName in [\"INPUT\", \"TEXTAREA\"])) {\n            addString(elem.getAttribute(\"placeholder\"), locator(elem));\n        }\n        if (elem.hasAttribute(\"title\")) {\n            addString(elem.getAttribute(\"title\"), locator(elem));\n        }\n        if (elem.hasAttribute(\"value\") &&\n            elem.tagName === \"INPUT\" &&\n            isAnyOf(elem.getAttribute(\"type\"), [\"reset\", \"button\", \"submit\"])) {\n            addString(elem.getAttribute(\"value\"), locator(elem));\n        }\n    }\n\n    for (let i = 0; i < elem.childNodes.length; i++) {\n        let node = elem.childNodes[i];\n        if (node.nodeType === node.ELEMENT_NODE) {\n            process(node, locator, enabled);\n        } else if (node.nodeType === node.TEXT_NODE && enabled) {\n            addString(node.data, locator(node));\n        }\n    }\n}\n\nfor (let i = 0; i < program.args.length; i++) {\n    const fn = program.args[i];\n    const file = fs.readFileSync(fn, \"utf8\");\n    const dom = new jsdom.JSDOM(file, { includeNodeLocations: true });\n    const body = dom.window.document.body;\n\n    let locator = (elem) => {\n        const offset = dom.nodeLocation(elem).startOffset;\n        const line = file.slice(0, offset).split(\"\\n\").length;\n        return fn + \":\" + line;\n    };\n\n    process(body, locator, true);\n}\n\nlet output = \"\";\n\nfor (let str in strings) {\n    output += \"#:\";\n    for (location in strings[str]) {\n        output += \" \" + location;\n    }\n    output += \"\\n\";\n\n    output += \"msgid \" + JSON.stringify(str) + \"\\n\";\n    output += \"msgstr \\\"\\\"\\n\";\n    output += \"\\n\";\n}\n\nfs.writeFileSync(program.opts().output, output);\n"
  },
  {
    "path": "services/gateway/noVNC/po/zh_CN.po",
    "content": "# Simplified Chinese translations for noVNC package.\n# Copyright (C) 2020 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Peter Dave Hello <hsu@peterdavehello.org>, 2018.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.1.0\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2024-06-03 14:10+0200\\n\"\n\"PO-Revision-Date: 2024-11-23 15:29+0800\\n\"\n\"Last-Translator: wxtewx <wxtewx@qq.com>\\n\"\n\"Language-Team: \\n\"\n\"Language: zh_CN\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"X-Generator: Poedit 3.5\\n\"\n\n#: ../app/ui.js:69\nmsgid \"\"\n\"Running without HTTPS is not recommended, crashes or other issues are likely.\"\nmsgstr \"不建议在没有 HTTPS 的情况下运行，可能会出现崩溃或出现其他问题。\"\n\n#: ../app/ui.js:410\nmsgid \"Connecting...\"\nmsgstr \"连接中...\"\n\n#: ../app/ui.js:417\nmsgid \"Disconnecting...\"\nmsgstr \"正在断开连接...\"\n\n#: ../app/ui.js:423\nmsgid \"Reconnecting...\"\nmsgstr \"重新连接中...\"\n\n#: ../app/ui.js:428\nmsgid \"Internal error\"\nmsgstr \"内部错误\"\n\n#: ../app/ui.js:1026\nmsgid \"Must set host\"\nmsgstr \"必须设置主机\"\n\n#: ../app/ui.js:1052\nmsgid \"Failed to connect to server: \"\nmsgstr \"无法连接到服务器：\"\n\n#: ../app/ui.js:1118\nmsgid \"Connected (encrypted) to \"\nmsgstr \"已连接（已加密）到\"\n\n#: ../app/ui.js:1120\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"已连接（未加密）到\"\n\n#: ../app/ui.js:1143\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"出了点问题，连接已关闭\"\n\n#: ../app/ui.js:1146\nmsgid \"Failed to connect to server\"\nmsgstr \"无法连接到服务器\"\n\n#: ../app/ui.js:1158\nmsgid \"Disconnected\"\nmsgstr \"已断开连接\"\n\n#: ../app/ui.js:1173\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"新连接被拒绝，原因如下：\"\n\n#: ../app/ui.js:1176\nmsgid \"New connection has been rejected\"\nmsgstr \"新连接已被拒绝\"\n\n#: ../app/ui.js:1242\nmsgid \"Credentials are required\"\nmsgstr \"需要凭证\"\n\n#: ../vnc.html:55\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC 遇到一个错误：\"\n\n#: ../vnc.html:65\nmsgid \"Hide/Show the control bar\"\nmsgstr \"显示/隐藏控制栏\"\n\n#: ../vnc.html:74\nmsgid \"Drag\"\nmsgstr \"拖动\"\n\n#: ../vnc.html:74\nmsgid \"Move/Drag viewport\"\nmsgstr \"移动/拖动窗口\"\n\n#: ../vnc.html:80\nmsgid \"Keyboard\"\nmsgstr \"键盘\"\n\n#: ../vnc.html:80\nmsgid \"Show keyboard\"\nmsgstr \"显示键盘\"\n\n#: ../vnc.html:85\nmsgid \"Extra keys\"\nmsgstr \"额外按键\"\n\n#: ../vnc.html:85\nmsgid \"Show extra keys\"\nmsgstr \"显示额外按键\"\n\n#: ../vnc.html:90\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:90\nmsgid \"Toggle Ctrl\"\nmsgstr \"切换 Ctrl\"\n\n#: ../vnc.html:93\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:93\nmsgid \"Toggle Alt\"\nmsgstr \"切换 Alt\"\n\n#: ../vnc.html:96\nmsgid \"Toggle Windows\"\nmsgstr \"切换窗口\"\n\n#: ../vnc.html:96\nmsgid \"Windows\"\nmsgstr \"窗口\"\n\n#: ../vnc.html:99\nmsgid \"Send Tab\"\nmsgstr \"发送 Tab 键\"\n\n#: ../vnc.html:99\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:102\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:102\nmsgid \"Send Escape\"\nmsgstr \"发送 Escape 键\"\n\n#: ../vnc.html:105\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl+Alt+Del\"\n\n#: ../vnc.html:105\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"发送 Ctrl+Alt+Del 键\"\n\n#: ../vnc.html:112\nmsgid \"Shutdown/Reboot\"\nmsgstr \"关机/重启\"\n\n#: ../vnc.html:112\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"关机/重启...\"\n\n#: ../vnc.html:118\nmsgid \"Power\"\nmsgstr \"电源\"\n\n#: ../vnc.html:120\nmsgid \"Shutdown\"\nmsgstr \"关机\"\n\n#: ../vnc.html:121\nmsgid \"Reboot\"\nmsgstr \"重启\"\n\n#: ../vnc.html:122\nmsgid \"Reset\"\nmsgstr \"重置\"\n\n#: ../vnc.html:127 ../vnc.html:133\nmsgid \"Clipboard\"\nmsgstr \"剪贴板\"\n\n#: ../vnc.html:135\nmsgid \"Edit clipboard content in the textarea below.\"\nmsgstr \"在下面的文本区域中编辑剪贴板内容。\"\n\n#: ../vnc.html:143\nmsgid \"Full screen\"\nmsgstr \"全屏\"\n\n#: ../vnc.html:148 ../vnc.html:154\nmsgid \"Settings\"\nmsgstr \"设置\"\n\n#: ../vnc.html:158\nmsgid \"Shared mode\"\nmsgstr \"分享模式\"\n\n#: ../vnc.html:161\nmsgid \"View only\"\nmsgstr \"仅查看\"\n\n#: ../vnc.html:165\nmsgid \"Clip to window\"\nmsgstr \"限制/裁切窗口大小\"\n\n#: ../vnc.html:168\nmsgid \"Scaling mode:\"\nmsgstr \"缩放模式：\"\n\n#: ../vnc.html:170\nmsgid \"None\"\nmsgstr \"无\"\n\n#: ../vnc.html:171\nmsgid \"Local scaling\"\nmsgstr \"本地缩放\"\n\n#: ../vnc.html:172\nmsgid \"Remote resizing\"\nmsgstr \"远程调整大小\"\n\n#: ../vnc.html:177\nmsgid \"Advanced\"\nmsgstr \"高级\"\n\n#: ../vnc.html:180\nmsgid \"Quality:\"\nmsgstr \"品质：\"\n\n#: ../vnc.html:184\nmsgid \"Compression level:\"\nmsgstr \"压缩级别：\"\n\n#: ../vnc.html:189\nmsgid \"Repeater ID:\"\nmsgstr \"中继站 ID\"\n\n#: ../vnc.html:193\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:196\nmsgid \"Encrypt\"\nmsgstr \"加密\"\n\n#: ../vnc.html:199\nmsgid \"Host:\"\nmsgstr \"主机：\"\n\n#: ../vnc.html:203\nmsgid \"Port:\"\nmsgstr \"端口：\"\n\n#: ../vnc.html:207\nmsgid \"Path:\"\nmsgstr \"路径：\"\n\n#: ../vnc.html:214\nmsgid \"Automatic reconnect\"\nmsgstr \"自动重新连接\"\n\n#: ../vnc.html:217\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"重新连接间隔 (ms)：\"\n\n#: ../vnc.html:222\nmsgid \"Show dot when no cursor\"\nmsgstr \"无光标时显示点\"\n\n#: ../vnc.html:227\nmsgid \"Logging:\"\nmsgstr \"日志级别：\"\n\n#: ../vnc.html:236\nmsgid \"Version:\"\nmsgstr \"版本：\"\n\n#: ../vnc.html:244\nmsgid \"Disconnect\"\nmsgstr \"断开连接\"\n\n#: ../vnc.html:267\nmsgid \"Connect\"\nmsgstr \"连接\"\n\n#: ../vnc.html:276\nmsgid \"Server identity\"\nmsgstr \"服务器身份\"\n\n#: ../vnc.html:279\nmsgid \"The server has provided the following identifying information:\"\nmsgstr \"服务器提供了以下识别信息：\"\n\n#: ../vnc.html:283\nmsgid \"Fingerprint:\"\nmsgstr \"指纹：\"\n\n#: ../vnc.html:286\nmsgid \"\"\n\"Please verify that the information is correct and press \\\"Approve\\\". \"\n\"Otherwise press \\\"Reject\\\".\"\nmsgstr \"请核实信息是否正确，并按 “同意”，否则按 “拒绝”。\"\n\n#: ../vnc.html:291\nmsgid \"Approve\"\nmsgstr \"同意\"\n\n#: ../vnc.html:292\nmsgid \"Reject\"\nmsgstr \"拒绝\"\n\n#: ../vnc.html:300\nmsgid \"Credentials\"\nmsgstr \"凭证\"\n\n#: ../vnc.html:304\nmsgid \"Username:\"\nmsgstr \"用户名:\"\n\n#: ../vnc.html:308\nmsgid \"Password:\"\nmsgstr \"密码：\"\n\n#: ../vnc.html:312\nmsgid \"Send credentials\"\nmsgstr \"发送凭证\"\n\n#: ../vnc.html:321\nmsgid \"Cancel\"\nmsgstr \"取消\"\n\n#~ msgid \"Password is required\"\n#~ msgstr \"请提供密码\"\n\n#~ msgid \"Disconnect timeout\"\n#~ msgstr \"超时断开\"\n\n#~ msgid \"viewport drag\"\n#~ msgstr \"窗口拖动\"\n\n#~ msgid \"Active Mouse Button\"\n#~ msgstr \"启动鼠标按键\"\n\n#~ msgid \"No mousebutton\"\n#~ msgstr \"禁用鼠标按键\"\n\n#~ msgid \"Left mousebutton\"\n#~ msgstr \"鼠标左键\"\n\n#~ msgid \"Middle mousebutton\"\n#~ msgstr \"鼠标中键\"\n\n#~ msgid \"Right mousebutton\"\n#~ msgstr \"鼠标右键\"\n\n#~ msgid \"Clear\"\n#~ msgstr \"清除\"\n\n#~ msgid \"Local Downscaling\"\n#~ msgstr \"降低本地尺寸\"\n\n#~ msgid \"Local Cursor\"\n#~ msgstr \"本地光标\"\n\n#~ msgid \"Canvas not supported.\"\n#~ msgstr \"不支持 Canvas。\"\n"
  },
  {
    "path": "services/gateway/noVNC/po/zh_TW.po",
    "content": "# Traditional Chinese translations for noVNC package.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as the noVNC package.\n# Peter Dave Hello <hsu@peterdavehello.org>, 2018.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: noVNC 1.0.0-testing.2\\n\"\n\"Report-Msgid-Bugs-To: novnc@googlegroups.com\\n\"\n\"POT-Creation-Date: 2018-01-10 00:53+0800\\n\"\n\"PO-Revision-Date: 2018-01-10 01:33+0800\\n\"\n\"Last-Translator: Peter Dave Hello <hsu@peterdavehello.org>\\n\"\n\"Language-Team: Peter Dave Hello <hsu@peterdavehello.org>\\n\"\n\"Language: zh\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: ../app/ui.js:395\nmsgid \"Connecting...\"\nmsgstr \"連線中...\"\n\n#: ../app/ui.js:402\nmsgid \"Disconnecting...\"\nmsgstr \"正在中斷連線...\"\n\n#: ../app/ui.js:408\nmsgid \"Reconnecting...\"\nmsgstr \"重新連線中...\"\n\n#: ../app/ui.js:413\nmsgid \"Internal error\"\nmsgstr \"內部錯誤\"\n\n#: ../app/ui.js:1015\nmsgid \"Must set host\"\nmsgstr \"請提供主機資訊\"\n\n#: ../app/ui.js:1097\nmsgid \"Connected (encrypted) to \"\nmsgstr \"已加密連線到\"\n\n#: ../app/ui.js:1099\nmsgid \"Connected (unencrypted) to \"\nmsgstr \"未加密連線到\"\n\n#: ../app/ui.js:1120\nmsgid \"Something went wrong, connection is closed\"\nmsgstr \"發生錯誤，連線已關閉\"\n\n#: ../app/ui.js:1123\nmsgid \"Failed to connect to server\"\nmsgstr \"無法連線到伺服器\"\n\n#: ../app/ui.js:1133\nmsgid \"Disconnected\"\nmsgstr \"連線已中斷\"\n\n#: ../app/ui.js:1146\nmsgid \"New connection has been rejected with reason: \"\nmsgstr \"連線被拒絕，原因：\"\n\n#: ../app/ui.js:1149\nmsgid \"New connection has been rejected\"\nmsgstr \"連線被拒絕\"\n\n#: ../app/ui.js:1170\nmsgid \"Password is required\"\nmsgstr \"請提供密碼\"\n\n#: ../vnc.html:89\nmsgid \"noVNC encountered an error:\"\nmsgstr \"noVNC 遇到一個錯誤：\"\n\n#: ../vnc.html:99\nmsgid \"Hide/Show the control bar\"\nmsgstr \"顯示/隱藏控制列\"\n\n#: ../vnc.html:106\nmsgid \"Move/Drag viewport\"\nmsgstr \"拖放顯示範圍\"\n\n#: ../vnc.html:106\nmsgid \"viewport drag\"\nmsgstr \"顯示範圍拖放\"\n\n#: ../vnc.html:112 ../vnc.html:115 ../vnc.html:118 ../vnc.html:121\nmsgid \"Active Mouse Button\"\nmsgstr \"啟用滑鼠按鍵\"\n\n#: ../vnc.html:112\nmsgid \"No mousebutton\"\nmsgstr \"無滑鼠按鍵\"\n\n#: ../vnc.html:115\nmsgid \"Left mousebutton\"\nmsgstr \"滑鼠左鍵\"\n\n#: ../vnc.html:118\nmsgid \"Middle mousebutton\"\nmsgstr \"滑鼠中鍵\"\n\n#: ../vnc.html:121\nmsgid \"Right mousebutton\"\nmsgstr \"滑鼠右鍵\"\n\n#: ../vnc.html:124\nmsgid \"Keyboard\"\nmsgstr \"鍵盤\"\n\n#: ../vnc.html:124\nmsgid \"Show keyboard\"\nmsgstr \"顯示鍵盤\"\n\n#: ../vnc.html:131\nmsgid \"Extra keys\"\nmsgstr \"額外按鍵\"\n\n#: ../vnc.html:131\nmsgid \"Show extra keys\"\nmsgstr \"顯示額外按鍵\"\n\n#: ../vnc.html:136\nmsgid \"Ctrl\"\nmsgstr \"Ctrl\"\n\n#: ../vnc.html:136\nmsgid \"Toggle Ctrl\"\nmsgstr \"切換 Ctrl\"\n\n#: ../vnc.html:139\nmsgid \"Alt\"\nmsgstr \"Alt\"\n\n#: ../vnc.html:139\nmsgid \"Toggle Alt\"\nmsgstr \"切換 Alt\"\n\n#: ../vnc.html:142\nmsgid \"Send Tab\"\nmsgstr \"送出 Tab 鍵\"\n\n#: ../vnc.html:142\nmsgid \"Tab\"\nmsgstr \"Tab\"\n\n#: ../vnc.html:145\nmsgid \"Esc\"\nmsgstr \"Esc\"\n\n#: ../vnc.html:145\nmsgid \"Send Escape\"\nmsgstr \"送出 Escape 鍵\"\n\n#: ../vnc.html:148\nmsgid \"Ctrl+Alt+Del\"\nmsgstr \"Ctrl-Alt-Del\"\n\n#: ../vnc.html:148\nmsgid \"Send Ctrl-Alt-Del\"\nmsgstr \"送出 Ctrl-Alt-Del 快捷鍵\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot\"\nmsgstr \"關機/重新啟動\"\n\n#: ../vnc.html:156\nmsgid \"Shutdown/Reboot...\"\nmsgstr \"關機/重新啟動...\"\n\n#: ../vnc.html:162\nmsgid \"Power\"\nmsgstr \"電源\"\n\n#: ../vnc.html:164\nmsgid \"Shutdown\"\nmsgstr \"關機\"\n\n#: ../vnc.html:165\nmsgid \"Reboot\"\nmsgstr \"重新啟動\"\n\n#: ../vnc.html:166\nmsgid \"Reset\"\nmsgstr \"重設\"\n\n#: ../vnc.html:171 ../vnc.html:177\nmsgid \"Clipboard\"\nmsgstr \"剪貼簿\"\n\n#: ../vnc.html:181\nmsgid \"Clear\"\nmsgstr \"清除\"\n\n#: ../vnc.html:187\nmsgid \"Fullscreen\"\nmsgstr \"全螢幕\"\n\n#: ../vnc.html:192 ../vnc.html:199\nmsgid \"Settings\"\nmsgstr \"設定\"\n\n#: ../vnc.html:202\nmsgid \"Shared mode\"\nmsgstr \"分享模式\"\n\n#: ../vnc.html:205\nmsgid \"View only\"\nmsgstr \"僅檢視\"\n\n#: ../vnc.html:209\nmsgid \"Clip to window\"\nmsgstr \"限制/裁切視窗大小\"\n\n#: ../vnc.html:212\nmsgid \"Scaling mode:\"\nmsgstr \"縮放模式：\"\n\n#: ../vnc.html:214\nmsgid \"None\"\nmsgstr \"無\"\n\n#: ../vnc.html:215\nmsgid \"Local scaling\"\nmsgstr \"本機縮放\"\n\n#: ../vnc.html:216\nmsgid \"Remote resizing\"\nmsgstr \"遠端調整大小\"\n\n#: ../vnc.html:221\nmsgid \"Advanced\"\nmsgstr \"進階\"\n\n#: ../vnc.html:224\nmsgid \"Repeater ID:\"\nmsgstr \"中繼站 ID\"\n\n#: ../vnc.html:228\nmsgid \"WebSocket\"\nmsgstr \"WebSocket\"\n\n#: ../vnc.html:231\nmsgid \"Encrypt\"\nmsgstr \"加密\"\n\n#: ../vnc.html:234\nmsgid \"Host:\"\nmsgstr \"主機：\"\n\n#: ../vnc.html:238\nmsgid \"Port:\"\nmsgstr \"連接埠：\"\n\n#: ../vnc.html:242\nmsgid \"Path:\"\nmsgstr \"路徑：\"\n\n#: ../vnc.html:249\nmsgid \"Automatic reconnect\"\nmsgstr \"自動重新連線\"\n\n#: ../vnc.html:252\nmsgid \"Reconnect delay (ms):\"\nmsgstr \"重新連線間隔 (ms)：\"\n\n#: ../vnc.html:258\nmsgid \"Logging:\"\nmsgstr \"日誌級別：\"\n\n#: ../vnc.html:270\nmsgid \"Disconnect\"\nmsgstr \"中斷連線\"\n\n#: ../vnc.html:289\nmsgid \"Connect\"\nmsgstr \"連線\"\n\n#: ../vnc.html:299\nmsgid \"Password:\"\nmsgstr \"密碼：\"\n\n#: ../vnc.html:313\nmsgid \"Cancel\"\nmsgstr \"取消\"\n"
  },
  {
    "path": "services/gateway/noVNC/snap/hooks/configure",
    "content": "#!/bin/sh -e\n\nsnapctl restart novnc.novncsvc\n"
  },
  {
    "path": "services/gateway/noVNC/snap/local/svc_wrapper.sh",
    "content": "#!/bin/bash\n\n# `snapctl get services` returns a JSON array, example:\n#{\n#\"n6801\": {\n#   \"listen\": 6801,\n#   \"vnc\": \"localhost:5901\"\n#},\n#\"n6802\": {\n#    \"listen\": 6802,\n#   \"vnc\": \"localhost:5902\"\n#}\n#}\nsnapctl get services | jq -c '.[]' | while read service; do # for each service the user specified..\n    # get the important data for the service (listen port, VNC host:port)\n    listen_port=\"$(echo $service | jq --raw-output '.listen')\"\n    vnc_host_port=\"$(echo $service | jq --raw-output '.vnc')\" # --raw-output removes any quotation marks from the output\n    \n    # check whether those values are valid\n    expr \"$listen_port\" : '^[0-9]\\+$' > /dev/null\n    listen_port_valid=$?\n    if [ ! $listen_port_valid ] || [ -z \"$vnc_host_port\" ]; then\n        # invalid values mean the service is disabled, do nothing except for printing a message (logged in /var/log/system or systemd journal)\n        echo \"novnc: not starting service ${service} with listen_port ${listen_port} and vnc_host_port ${vnc_host_port}\"\n    else\n        # start (and fork with '&') the service using the specified listen port and VNC host:port\n        $SNAP/novnc_proxy --listen $listen_port --vnc $vnc_host_port &\n    fi\ndone\n"
  },
  {
    "path": "services/gateway/noVNC/snap/snapcraft.yaml",
    "content": "name: novnc\nbase: core22 # the base snap is the execution environment for this snap\nversion: git\nsummary: Open Source VNC client using HTML5 (WebSockets, Canvas)\ndescription: |\n  Open Source VNC client using HTML5 (WebSockets, Canvas).\n  noVNC is both a VNC client JavaScript library as well as an\n  application built on top of that library. noVNC runs well in any\n  modern browser including mobile browsers (iOS and Android).\n\ngrade: stable\nconfinement: strict\n\nparts:\n    novnc:\n        source: .\n        plugin: dump\n        organize:\n            utils/novnc_proxy: /\n        stage:\n            - vnc.html\n            - app\n            - core/**/*.js\n            - vendor/**/*.js\n            - novnc_proxy\n\n    novnc-deps:\n        plugin: nil\n        stage-packages:\n            - bash\n\n    svc-script:\n        source: snap/local\n        plugin: dump\n        stage:\n            - svc_wrapper.sh\n\n    svc-script-deps:\n        plugin: nil\n        stage-packages:\n            - bash\n            - jq\n\n    websockify:\n        source: https://github.com/novnc/websockify/archive/v0.13.0.tar.gz\n        plugin: python\n        stage-packages:\n            - python3-numpy\n\nhooks:\n    configure:\n        plugs: [network, network-bind]\n\napps:\n    novnc:\n        command: ./novnc_proxy\n        plugs: [network, network-bind]\n    novncsvc:\n        command: ./svc_wrapper.sh\n        daemon: forking\n        plugs: [network, network-bind]\n"
  },
  {
    "path": "services/gateway/noVNC/tests/assertions.js",
    "content": "import * as chai from '../node_modules/chai/chai.js';\nimport sinon from '../node_modules/sinon/pkg/sinon-esm.js';\nimport sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js';\n\nwindow.expect = chai.expect;\n\nwindow.sinon = sinon;\nchai.use(sinonChai);\n\n// noVNC specific assertions\nchai.use(function (_chai, utils) {\n    function _equal(a, b) {\n        return a === b;\n    }\n    _chai.Assertion.addMethod('displayed', function (targetData, cmp=_equal) {\n        const obj = this._obj;\n        const ctx = obj._target.getContext('2d');\n        const data = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;\n        const len = data.length;\n        new chai.Assertion(len).to.be.equal(targetData.length, \"unexpected display size\");\n        let same = true;\n        for (let i = 0; i < len; i++) {\n            if (!cmp(data[i], targetData[i])) {\n                same = false;\n                break;\n            }\n        }\n        if (!same) {\n            // eslint-disable-next-line no-console\n            console.log(\"expected data: %o, actual data: %o\", targetData, data);\n        }\n        this.assert(same,\n                    \"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}\",\n                    \"expected #{this} not to have displayed the image #{act}\",\n                    targetData,\n                    data);\n    });\n\n    _chai.Assertion.addMethod('sent', function (targetData) {\n        const obj = this._obj;\n        const data = obj._websocket._getSentData();\n        let same = true;\n        if (data.length != targetData.length) {\n            same = false;\n        } else {\n            for (let i = 0; i < data.length; i++) {\n                if (data[i] != targetData[i]) {\n                    same = false;\n                    break;\n                }\n            }\n        }\n        if (!same) {\n            // eslint-disable-next-line no-console\n            console.log(\"expected data: %o, actual data: %o\", targetData, data);\n        }\n        this.assert(same,\n                    \"expected #{this} to have sent the data #{exp}, but it actually sent #{act}\",\n                    \"expected #{this} not to have sent the data #{act}\",\n                    Array.prototype.slice.call(targetData),\n                    Array.prototype.slice.call(data));\n    });\n\n    _chai.Assertion.addProperty('array', function () {\n        utils.flag(this, 'array', true);\n    });\n\n    _chai.Assertion.overwriteMethod('equal', function (_super) {\n        return function assertArrayEqual(target) {\n            if (utils.flag(this, 'array')) {\n                const obj = this._obj;\n\n                let same = true;\n\n                if (utils.flag(this, 'deep')) {\n                    for (let i = 0; i < obj.length; i++) {\n                        if (!utils.eql(obj[i], target[i])) {\n                            same = false;\n                            break;\n                        }\n                    }\n\n                    this.assert(same,\n                                \"expected #{this} to have elements deeply equal to #{exp}\",\n                                \"expected #{this} not to have elements deeply equal to #{exp}\",\n                                Array.prototype.slice.call(target));\n                } else {\n                    for (let i = 0; i < obj.length; i++) {\n                        if (obj[i] != target[i]) {\n                            same = false;\n                            break;\n                        }\n                    }\n\n                    this.assert(same,\n                                \"expected #{this} to have elements equal to #{exp}\",\n                                \"expected #{this} not to have elements equal to #{exp}\",\n                                Array.prototype.slice.call(target));\n                }\n            } else {\n                _super.apply(this, arguments);\n            }\n        };\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/fake.websocket.js",
    "content": "import Base64 from '../core/base64.js';\n\nexport default class FakeWebSocket {\n    constructor(uri, protocols) {\n        this.url = uri;\n        this.binaryType = \"arraybuffer\";\n        this.extensions = \"\";\n\n        this.onerror = null;\n        this.onmessage = null;\n        this.onopen = null;\n\n        if (!protocols || typeof protocols === 'string') {\n            this.protocol = protocols;\n        } else {\n            this.protocol = protocols[0];\n        }\n\n        this._sendQueue = new Uint8Array(20000);\n\n        this.readyState = FakeWebSocket.CONNECTING;\n        this.bufferedAmount = 0;\n\n        this._isFake = true;\n    }\n\n    close(code, reason) {\n        this.readyState = FakeWebSocket.CLOSED;\n        if (this.onclose) {\n            this.onclose(new CloseEvent(\"close\", { 'code': code, 'reason': reason, 'wasClean': true }));\n        }\n    }\n\n    send(data) {\n        if (this.protocol == 'base64') {\n            data = Base64.decode(data);\n        } else {\n            data = new Uint8Array(data);\n        }\n        if (this.bufferedAmount + data.length > this._sendQueue.length) {\n            let newlen = this._sendQueue.length;\n            while (this.bufferedAmount + data.length > newlen) {\n                newlen *= 2;\n            }\n            let newbuf = new Uint8Array(newlen);\n            newbuf.set(this._sendQueue);\n            this._sendQueue = newbuf;\n        }\n        this._sendQueue.set(data, this.bufferedAmount);\n        this.bufferedAmount += data.length;\n    }\n\n    _getSentData() {\n        const res = this._sendQueue.slice(0, this.bufferedAmount);\n        this.bufferedAmount = 0;\n        return res;\n    }\n\n    _open() {\n        this.readyState = FakeWebSocket.OPEN;\n        if (this.onopen) {\n            this.onopen(new Event('open'));\n        }\n    }\n\n    _receiveData(data) {\n        if (data.length < 4096) {\n            // Break apart the data to expose bugs where we assume data is\n            // neatly packaged\n            for (let i = 0;i < data.length;i++) {\n                let buf = data.slice(i, i+1);\n                this.onmessage(new MessageEvent(\"message\", { 'data': buf.buffer }));\n            }\n        } else {\n            this.onmessage(new MessageEvent(\"message\", { 'data': data.buffer }));\n        }\n    }\n}\n\nFakeWebSocket.OPEN = WebSocket.OPEN;\nFakeWebSocket.CONNECTING = WebSocket.CONNECTING;\nFakeWebSocket.CLOSING = WebSocket.CLOSING;\nFakeWebSocket.CLOSED = WebSocket.CLOSED;\n\nFakeWebSocket._isFake = true;\n\nFakeWebSocket.replace = () => {\n    if (!WebSocket._isFake) {\n        const realVersion = WebSocket;\n        // eslint-disable-next-line no-global-assign\n        WebSocket = FakeWebSocket;\n        FakeWebSocket._realVersion = realVersion;\n    }\n};\n\nFakeWebSocket.restore = () => {\n    if (WebSocket._isFake) {\n        // eslint-disable-next-line no-global-assign\n        WebSocket = WebSocket._realVersion;\n    }\n};\n"
  },
  {
    "path": "services/gateway/noVNC/tests/playback-ui.js",
    "content": "/* global VNC_frame_data, VNC_frame_encoding */\n\nimport * as WebUtil from '../app/webutil.js';\nimport RecordingPlayer from './playback.js';\nimport Base64 from '../core/base64.js';\n\nlet frames = null;\n\nfunction message(str) {\n    const cell = document.getElementById('messages');\n    cell.textContent += str + \"\\n\";\n    cell.scrollTop = cell.scrollHeight;\n}\n\nfunction loadFile() {\n    const fname = WebUtil.getQueryVar('data', null);\n\n    if (!fname) {\n        return Promise.reject(\"Must specify data=FOO in query string.\");\n    }\n\n    message(\"Loading \" + fname + \"...\");\n\n    return new Promise((resolve, reject) => {\n        const script = document.createElement(\"script\");\n        script.onload = resolve;\n        script.onerror = () => { reject(\"Failed to load \" + fname); };\n        document.body.appendChild(script);\n        script.src = \"../recordings/\" + fname;\n    });\n}\n\nfunction enableUI() {\n    const iterations = WebUtil.getQueryVar('iterations', 3);\n    document.getElementById('iterations').value = iterations;\n\n    const mode = WebUtil.getQueryVar('mode', 3);\n    if (mode === 'realtime') {\n        document.getElementById('mode2').checked = true;\n    } else {\n        document.getElementById('mode1').checked = true;\n    }\n\n    /* eslint-disable-next-line camelcase */\n    message(\"Loaded \" + VNC_frame_data.length + \" frames\");\n\n    const startButton = document.getElementById('startButton');\n    startButton.disabled = false;\n    startButton.addEventListener('click', start);\n\n    message(\"Converting...\");\n\n    /* eslint-disable-next-line camelcase */\n    frames = VNC_frame_data;\n\n    let encoding;\n\n    /* eslint-disable camelcase */\n    if (window.VNC_frame_encoding) {\n        // Only present in older recordings\n        encoding = VNC_frame_encoding;\n    /* eslint-enable camelcase */\n    } else {\n        let frame = frames[0];\n        let start = frame.indexOf('{', 1) + 1;\n        if (frame.slice(start, start+4) === 'UkZC') {\n            encoding = 'base64';\n        } else {\n            encoding = 'binary';\n        }\n    }\n\n    for (let i = 0;i < frames.length;i++) {\n        let frame = frames[i];\n\n        if (frame === \"EOF\") {\n            frames.splice(i);\n            break;\n        }\n\n        let dataIdx = frame.indexOf('{', 1) + 1;\n\n        let time = parseInt(frame.slice(1, dataIdx - 1));\n\n        let u8;\n        if (encoding === 'base64') {\n            u8 = Base64.decode(frame.slice(dataIdx));\n        } else {\n            u8 = new Uint8Array(frame.length - dataIdx);\n            for (let j = 0; j < frame.length - dataIdx; j++) {\n                u8[j] = frame.charCodeAt(dataIdx + j);\n            }\n        }\n\n        frames[i] = { fromClient: frame[0] === '}',\n                      timestamp: time,\n                      data: u8 };\n    }\n\n    message(\"Ready\");\n}\n\nclass IterationPlayer {\n    constructor(iterations, frames) {\n        this._iterations = iterations;\n\n        this._iteration = undefined;\n        this._player = undefined;\n\n        this._startTime = undefined;\n\n        this._frames = frames;\n\n        this._state = 'running';\n\n        this.onfinish = () => {};\n        this.oniterationfinish = () => {};\n        this.rfbdisconnected = () => {};\n    }\n\n    start(realtime) {\n        this._iteration = 0;\n        this._startTime = (new Date()).getTime();\n\n        this._realtime = realtime;\n\n        this._nextIteration();\n    }\n\n    _nextIteration() {\n        const player = new RecordingPlayer(this._frames, this._disconnected.bind(this));\n        player.onfinish = this._iterationFinish.bind(this);\n\n        if (this._state !== 'running') { return; }\n\n        this._iteration++;\n        if (this._iteration > this._iterations) {\n            this._finish();\n            return;\n        }\n\n        player.run(this._realtime, false);\n    }\n\n    _finish() {\n        const endTime = (new Date()).getTime();\n        const totalDuration = endTime - this._startTime;\n\n        const evt = new CustomEvent('finish',\n                                    { detail:\n                                      { duration: totalDuration,\n                                        iterations: this._iterations } } );\n        this.onfinish(evt);\n    }\n\n    _iterationFinish(duration) {\n        const evt = new CustomEvent('iterationfinish',\n                                    { detail:\n                                      { duration: duration,\n                                        number: this._iteration } } );\n        this.oniterationfinish(evt);\n\n        this._nextIteration();\n    }\n\n    _disconnected(clean, frame) {\n        if (!clean) {\n            this._state = 'failed';\n        }\n\n        const evt = new CustomEvent('rfbdisconnected',\n                                    { detail:\n                                      { clean: clean,\n                                        frame: frame,\n                                        iteration: this._iteration } } );\n        this.onrfbdisconnected(evt);\n    }\n}\n\nfunction start() {\n    document.getElementById('startButton').value = \"Running\";\n    document.getElementById('startButton').disabled = true;\n\n    const iterations = document.getElementById('iterations').value;\n\n    let realtime;\n\n    if (document.getElementById('mode1').checked) {\n        message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`);\n        realtime = false;\n    } else {\n        message(`Starting realtime playback [${iterations} iteration(s)]`);\n        realtime = true;\n    }\n\n    const player = new IterationPlayer(iterations, frames);\n    player.oniterationfinish = (evt) => {\n        message(`Iteration ${evt.detail.number} took ${evt.detail.duration}ms`);\n    };\n    player.onrfbdisconnected = (evt) => {\n        if (!evt.detail.clean) {\n            message(`noVNC sent disconnected during iteration ${evt.detail.iteration} frame ${evt.detail.frame}`);\n\n            document.getElementById('startButton').disabled = false;\n            document.getElementById('startButton').value = \"Start\";\n        }\n    };\n    player.onfinish = (evt) => {\n        const iterTime = parseInt(evt.detail.duration / evt.detail.iterations, 10);\n        message(`${evt.detail.iterations} iterations took ${evt.detail.duration}ms (average ${iterTime}ms / iteration)`);\n\n        document.getElementById('startButton').disabled = false;\n        document.getElementById('startButton').value = \"Start\";\n    };\n    player.start(realtime);\n}\n\nloadFile().then(enableUI).catch(e => message(\"Error loading recording: \" + e));\n"
  },
  {
    "path": "services/gateway/noVNC/tests/playback.js",
    "content": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n */\n\nimport RFB from '../core/rfb.js';\nimport * as Log from '../core/util/logging.js';\n\n// Immediate polyfill\nif (window.setImmediate === undefined) {\n    let _immediateIdCounter = 1;\n    const _immediateFuncs = {};\n\n    window.setImmediate = (func) => {\n        const index = _immediateIdCounter++;\n        _immediateFuncs[index] = func;\n        window.postMessage(\"noVNC immediate trigger:\" + index, \"*\");\n        return index;\n    };\n\n    window.clearImmediate = (id) => {\n        _immediateFuncs[id];\n    };\n\n    window.addEventListener(\"message\", (event) => {\n        if ((typeof event.data !== \"string\") ||\n            (event.data.indexOf(\"noVNC immediate trigger:\") !== 0)) {\n            return;\n        }\n\n        const index = event.data.slice(\"noVNC immediate trigger:\".length);\n\n        const callback = _immediateFuncs[index];\n        if (callback === undefined) {\n            return;\n        }\n\n        delete _immediateFuncs[index];\n\n        callback();\n    });\n}\n\nclass FakeWebSocket {\n    constructor() {\n        this.binaryType = \"arraybuffer\";\n        this.protocol = \"\";\n        this.readyState = \"open\";\n\n        this.onerror = () => {};\n        this.onmessage = () => {};\n        this.onopen = () => {};\n    }\n\n    send() {\n    }\n\n    close() {\n    }\n}\n\nexport default class RecordingPlayer {\n    constructor(frames, disconnected) {\n        this._frames = frames;\n\n        this._disconnected = disconnected;\n\n        this._rfb = undefined;\n        this._frameLength = this._frames.length;\n\n        this._frameIndex = 0;\n        this._startTime = undefined;\n        this._realtime = true;\n        this._trafficManagement = true;\n\n        this._running = false;\n\n        this.onfinish = () => {};\n    }\n\n    run(realtime, trafficManagement) {\n        // initialize a new RFB\n        this._ws = new FakeWebSocket();\n        this._rfb = new RFB(document.getElementById('VNC_screen'), this._ws);\n        this._rfb.viewOnly = true;\n        this._rfb.addEventListener(\"disconnect\",\n                                   this._handleDisconnect.bind(this));\n        this._rfb.addEventListener(\"credentialsrequired\",\n                                   this._handleCredentials.bind(this));\n\n        // reset the frame index and timer\n        this._frameIndex = 0;\n        this._startTime = (new Date()).getTime();\n\n        this._realtime = realtime;\n        this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;\n\n        this._running = true;\n        this._queueNextPacket();\n    }\n\n    _queueNextPacket() {\n        if (!this._running) { return; }\n\n        let frame = this._frames[this._frameIndex];\n\n        // skip send frames\n        while (this._frameIndex < this._frameLength && frame.fromClient) {\n            this._frameIndex++;\n            frame = this._frames[this._frameIndex];\n        }\n\n        if (this._frameIndex >= this._frameLength) {\n            Log.Debug('Finished, no more frames');\n            this._finish();\n            return;\n        }\n\n        if (this._realtime) {\n            const toffset = (new Date()).getTime() - this._startTime;\n            let delay = frame.timestamp - toffset;\n            if (delay < 1) delay = 1;\n\n            setTimeout(this._doPacket.bind(this), delay);\n        } else {\n            setImmediate(this._doPacket.bind(this));\n        }\n    }\n\n    _doPacket() {\n        // Avoid having excessive queue buildup in non-realtime mode\n        if (this._trafficManagement && this._rfb._flushing) {\n            this._rfb.flush()\n                .then(() => {\n                    this._doPacket();\n                });\n            return;\n        }\n\n        const frame = this._frames[this._frameIndex];\n\n        this._ws.onmessage({'data': frame.data});\n        this._frameIndex++;\n\n        this._queueNextPacket();\n    }\n\n    _finish() {\n        if (this._rfb._display.pending()) {\n            this._rfb._display.flush()\n                .then(() => { this._finish(); });\n        } else {\n            this._running = false;\n            this._ws.onclose({code: 1000, reason: \"\"});\n            delete this._rfb;\n            this.onfinish((new Date()).getTime() - this._startTime);\n        }\n    }\n\n    _handleDisconnect(evt) {\n        this._running = false;\n        this._disconnected(evt.detail.clean, this._frameIndex);\n    }\n\n    _handleCredentials(evt) {\n        this._rfb.sendCredentials({\"username\": \"Foo\",\n                                   \"password\": \"Bar\",\n                                   \"target\": \"Baz\"});\n    }\n}\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.base64.js",
    "content": "import Base64 from '../core/base64.js';\n\ndescribe('Base64 tools', function () {\n    \"use strict\";\n\n    const BIN_ARR = new Array(256);\n    for (let i = 0; i < 256; i++) {\n        BIN_ARR[i] = i;\n    }\n\n    const B64_STR = \"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==\";\n\n\n    describe('encode', function () {\n        it('should encode a binary string into Base64', function () {\n            const encoded = Base64.encode(BIN_ARR);\n            expect(encoded).to.equal(B64_STR);\n        });\n    });\n\n    describe('decode', function () {\n        it('should decode a Base64 string into a normal string', function () {\n            const decoded = Base64.decode(B64_STR);\n            expect(decoded).to.deep.equal(BIN_ARR);\n        });\n\n        it('should throw an error if we have extra characters at the end of the string', function () {\n            expect(() => Base64.decode(B64_STR+'abcdef')).to.throw(Error);\n        });\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.browser.js",
    "content": "import { isMac, isWindows, isIOS, isAndroid, isChromeOS,\n         isSafari, isFirefox, isChrome, isChromium, isOpera, isEdge,\n         isGecko, isWebKit, isBlink } from '../core/util/browser.js';\n\ndescribe('OS detection', function () {\n    let origNavigator;\n    beforeEach(function () {\n        // window.navigator is a protected read-only property in many\n        // environments, so we need to redefine it whilst running these\n        // tests.\n        origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n        Object.defineProperty(window, \"navigator\", {value: {}});\n    });\n\n    afterEach(function () {\n        Object.defineProperty(window, \"navigator\", origNavigator);\n    });\n\n    it('should handle macOS', function () {\n        const platforms = [\n            \"MacIntel\",\n            \"MacPPC\",\n        ];\n\n        navigator.userAgent = \"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15\";\n        platforms.forEach((platform) => {\n            navigator.platform = platform;\n            expect(isMac()).to.be.true;\n            expect(isWindows()).to.be.false;\n            expect(isIOS()).to.be.false;\n            expect(isAndroid()).to.be.false;\n            expect(isChromeOS()).to.be.false;\n        });\n    });\n\n    it('should handle Windows', function () {\n        const platforms = [\n            \"Win32\",\n            \"Win64\",\n        ];\n\n        navigator.userAgent = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36\";\n        platforms.forEach((platform) => {\n            navigator.platform = platform;\n            expect(isMac()).to.be.false;\n            expect(isWindows()).to.be.true;\n            expect(isIOS()).to.be.false;\n            expect(isAndroid()).to.be.false;\n            expect(isChromeOS()).to.be.false;\n        });\n    });\n\n    it('should handle iOS', function () {\n        const platforms = [\n            \"iPhone\",\n            \"iPod\",\n            \"iPad\",\n        ];\n\n        navigator.userAgent = \"Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Mobile/15E148 Safari/604.1\";\n        platforms.forEach((platform) => {\n            navigator.platform = platform;\n            expect(isMac()).to.be.false;\n            expect(isWindows()).to.be.false;\n            expect(isIOS()).to.be.true;\n            expect(isAndroid()).to.be.false;\n            expect(isChromeOS()).to.be.false;\n        });\n    });\n\n    it('should handle Android', function () {\n        let userAgents = [\n            \"Mozilla/5.0 (Linux; Android 13; SM-G960U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.128 Mobile Safari/537.36\",\n            \"Mozilla/5.0 (Android 13; Mobile; LG-M255; rv:108.0) Gecko/108.0 Firefox/108.0\",\n        ];\n\n        navigator.platform = \"Linux x86_64\";\n        userAgents.forEach((ua) => {\n            navigator.userAgent = ua;\n            expect(isMac()).to.be.false;\n            expect(isWindows()).to.be.false;\n            expect(isIOS()).to.be.false;\n            expect(isAndroid()).to.be.true;\n            expect(isChromeOS()).to.be.false;\n        });\n    });\n\n    it('should handle ChromeOS', function () {\n        let userAgents = [\n            \"Mozilla/5.0 (X11; CrOS x86_64 15183.59.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.75 Safari/537.36\",\n            \"Mozilla/5.0 (X11; CrOS aarch64 15183.59.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.75 Safari/537.36\",\n        ];\n\n        navigator.platform = \"Linux x86_64\";\n        userAgents.forEach((ua) => {\n            navigator.userAgent = ua;\n            expect(isMac()).to.be.false;\n            expect(isWindows()).to.be.false;\n            expect(isIOS()).to.be.false;\n            expect(isAndroid()).to.be.false;\n            expect(isChromeOS()).to.be.true;\n        });\n    });\n});\n\ndescribe('Browser detection', function () {\n    let origNavigator;\n    beforeEach(function () {\n        // window.navigator is a protected read-only property in many\n        // environments, so we need to redefine it whilst running these\n        // tests.\n        origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n        Object.defineProperty(window, \"navigator\", {value: {}});\n    });\n\n    afterEach(function () {\n        Object.defineProperty(window, \"navigator\", origNavigator);\n    });\n\n    it('should handle Chrome', function () {\n        navigator.userAgent = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36\";\n\n        expect(isSafari()).to.be.false;\n        expect(isFirefox()).to.be.false;\n        expect(isChrome()).to.be.true;\n        expect(isChromium()).to.be.false;\n        expect(isOpera()).to.be.false;\n        expect(isEdge()).to.be.false;\n\n        expect(isGecko()).to.be.false;\n        expect(isWebKit()).to.be.false;\n        expect(isBlink()).to.be.true;\n    });\n\n    it('should handle Chromium', function () {\n        navigator.userAgent = \"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Raspbian Chromium/74.0.3729.157 Chrome/74.0.3729.157 Safari/537.36\";\n\n        expect(isSafari()).to.be.false;\n        expect(isFirefox()).to.be.false;\n        expect(isChrome()).to.be.false;\n        expect(isChromium()).to.be.true;\n        expect(isOpera()).to.be.false;\n        expect(isEdge()).to.be.false;\n\n        expect(isGecko()).to.be.false;\n        expect(isWebKit()).to.be.false;\n        expect(isBlink()).to.be.true;\n    });\n\n    it('should handle Firefox', function () {\n        navigator.userAgent = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0\";\n\n        expect(isSafari()).to.be.false;\n        expect(isFirefox()).to.be.true;\n        expect(isChrome()).to.be.false;\n        expect(isChromium()).to.be.false;\n        expect(isOpera()).to.be.false;\n        expect(isEdge()).to.be.false;\n\n        expect(isGecko()).to.be.true;\n        expect(isWebKit()).to.be.false;\n        expect(isBlink()).to.be.false;\n    });\n\n    it('should handle Seamonkey', function () {\n        navigator.userAgent = \"Mozilla/5.0 (Windows NT 6.1; rv:36.0) Gecko/20100101 Firefox/36.0 Seamonkey/2.33.1\";\n\n        expect(isSafari()).to.be.false;\n        expect(isFirefox()).to.be.false;\n        expect(isChrome()).to.be.false;\n        expect(isChromium()).to.be.false;\n        expect(isOpera()).to.be.false;\n        expect(isEdge()).to.be.false;\n\n        expect(isGecko()).to.be.true;\n        expect(isWebKit()).to.be.false;\n        expect(isBlink()).to.be.false;\n    });\n\n    it('should handle Safari', function () {\n        navigator.userAgent = \"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15\";\n\n        expect(isSafari()).to.be.true;\n        expect(isFirefox()).to.be.false;\n        expect(isChrome()).to.be.false;\n        expect(isChromium()).to.be.false;\n        expect(isOpera()).to.be.false;\n        expect(isEdge()).to.be.false;\n\n        expect(isGecko()).to.be.false;\n        expect(isWebKit()).to.be.true;\n        expect(isBlink()).to.be.false;\n    });\n\n    it('should handle Edge', function () {\n        navigator.userAgent = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.34\";\n\n        expect(isSafari()).to.be.false;\n        expect(isFirefox()).to.be.false;\n        expect(isChrome()).to.be.false;\n        expect(isChromium()).to.be.false;\n        expect(isOpera()).to.be.false;\n        expect(isEdge()).to.be.true;\n\n        expect(isGecko()).to.be.false;\n        expect(isWebKit()).to.be.false;\n        expect(isBlink()).to.be.true;\n    });\n\n    it('should handle Opera', function () {\n        navigator.userAgent = \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 OPR/91.0.4516.20\";\n\n        expect(isSafari()).to.be.false;\n        expect(isFirefox()).to.be.false;\n        expect(isChrome()).to.be.false;\n        expect(isChromium()).to.be.false;\n        expect(isOpera()).to.be.true;\n        expect(isEdge()).to.be.false;\n\n        expect(isGecko()).to.be.false;\n        expect(isWebKit()).to.be.false;\n        expect(isBlink()).to.be.true;\n    });\n\n    it('should handle Epiphany', function () {\n        navigator.userAgent = \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Safari/605.1.15 Epiphany/605.1.15\";\n\n        expect(isSafari()).to.be.false;\n        expect(isFirefox()).to.be.false;\n        expect(isChrome()).to.be.false;\n        expect(isChromium()).to.be.false;\n        expect(isOpera()).to.be.false;\n        expect(isEdge()).to.be.false;\n\n        expect(isGecko()).to.be.false;\n        expect(isWebKit()).to.be.true;\n        expect(isBlink()).to.be.false;\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.copyrect.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport CopyRectDecoder from '../core/decoders/copyrect.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\ndescribe('CopyRect decoder', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        decoder = new CopyRectDecoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(4, 4);\n    });\n\n    it('should handle the CopyRect encoding', function () {\n        // seed some initial data to copy\n        display.fillRect(0, 0, 4, 4, [ 0x11, 0x22, 0x33 ]);\n        display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done;\n        done = testDecodeRect(decoder, 0, 2, 2, 2,\n                              [0x00, 0x02, 0x00, 0x00],\n                              display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 2, 2, 2,\n                              [0x00, 0x00, 0x00, 0x00],\n                              display, 24);\n        expect(done).to.be.true;\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle empty rects', function () {\n        display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n        display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0,\n                                  [0x00, 0x00, 0x00, 0x00],\n                                  display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.deflator.js",
    "content": "import { inflateInit, inflate } from \"../vendor/pako/lib/zlib/inflate.js\";\nimport ZStream from \"../vendor/pako/lib/zlib/zstream.js\";\nimport Deflator from \"../core/deflator.js\";\n\nfunction _inflator(compText, expected) {\n    let strm = new ZStream();\n    let chunkSize = 1024 * 10 * 10;\n    strm.output = new Uint8Array(chunkSize);\n\n    inflateInit(strm, 5);\n\n    if (expected > chunkSize) {\n        chunkSize = expected;\n        strm.output = new Uint8Array(chunkSize);\n    }\n\n    /* eslint-disable camelcase */\n    strm.input = compText;\n    strm.avail_in = strm.input.length;\n    strm.next_in = 0;\n\n    strm.next_out = 0;\n    strm.avail_out = expected.length;\n    /* eslint-enable camelcase */\n\n    let ret = inflate(strm, 0);\n\n    // Check that return code is not an error\n    expect(ret).to.be.greaterThan(-1);\n\n    return new Uint8Array(strm.output.buffer, 0, strm.next_out);\n}\n\ndescribe('Deflate data', function () {\n\n    it('should be able to deflate messages', function () {\n        let deflator = new Deflator();\n\n        let text = \"123asdf\";\n        let preText = new Uint8Array(text.length);\n        for (let i = 0; i < preText.length; i++) {\n            preText[i] = text.charCodeAt(i);\n        }\n\n        let compText = deflator.deflate(preText);\n\n        let inflatedText = _inflator(compText, text.length);\n        expect(inflatedText).to.array.equal(preText);\n\n    });\n\n    it('should be able to deflate large messages', function () {\n        let deflator = new Deflator();\n\n        /* Generate a big string with random characters. Used because\n           repetition of letters might be deflated more effectively than\n           random ones. */\n        let text = \"\";\n        let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n        for (let i = 0; i < 300000; i++) {\n            text += characters.charAt(Math.floor(Math.random() * characters.length));\n        }\n\n        let preText = new Uint8Array(text.length);\n        for (let i = 0; i < preText.length; i++) {\n            preText[i] = text.charCodeAt(i);\n        }\n\n        let compText = deflator.deflate(preText);\n\n        //Check that the compressed size is expected size\n        expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2);\n\n        let inflatedText = _inflator(compText, text.length);\n\n        expect(inflatedText).to.array.equal(preText);\n\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.display.js",
    "content": "import Base64 from '../core/base64.js';\nimport Display from '../core/display.js';\n\ndescribe('Display/Canvas helper', function () {\n    const checkedData = new Uint8ClampedArray([\n        0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n        0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n        0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n        0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n    ]);\n\n    const basicData = new Uint8ClampedArray([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);\n\n    function makeImageCanvas(inputData, width, height) {\n        const canvas = document.createElement('canvas');\n        canvas.width = width;\n        canvas.height = height;\n        const ctx = canvas.getContext('2d');\n        const data = new ImageData(inputData, width, height);\n        ctx.putImageData(data, 0, 0);\n        return canvas;\n    }\n\n    function makeImagePng(inputData, width, height) {\n        const canvas = makeImageCanvas(inputData, width, height);\n        const url = canvas.toDataURL();\n        const data = url.split(\",\")[1];\n        return Base64.decode(data);\n    }\n\n    describe('viewport handling', function () {\n        let display;\n        beforeEach(function () {\n            display = new Display(document.createElement('canvas'));\n            display.clipViewport = true;\n            display.resize(5, 5);\n            display.viewportChangeSize(3, 3);\n            display.viewportChangePos(1, 1);\n        });\n\n        it('should take viewport location into consideration when drawing images', function () {\n            display.resize(4, 4);\n            display.viewportChangeSize(2, 2);\n            display.drawImage(makeImageCanvas(basicData, 4, 1), 1, 1);\n            display.flip();\n\n            const expected = new Uint8Array(16);\n            for (let i = 0; i < 8; i++) { expected[i] = basicData[i]; }\n            for (let i = 8; i < 16; i++) { expected[i] = 0; }\n            expect(display).to.have.displayed(expected);\n        });\n\n        it('should resize the target canvas when resizing the viewport', function () {\n            display.viewportChangeSize(2, 2);\n            expect(display._target.width).to.equal(2);\n            expect(display._target.height).to.equal(2);\n        });\n\n        it('should move the viewport if necessary', function () {\n            display.viewportChangeSize(5, 5);\n            expect(display.absX(0)).to.equal(0);\n            expect(display.absY(0)).to.equal(0);\n            expect(display._target.width).to.equal(5);\n            expect(display._target.height).to.equal(5);\n        });\n\n        it('should limit the viewport to the framebuffer size', function () {\n            display.viewportChangeSize(6, 6);\n            expect(display._target.width).to.equal(5);\n            expect(display._target.height).to.equal(5);\n        });\n\n        it('should redraw when moving the viewport', function () {\n            display.flip = sinon.spy();\n            display.viewportChangePos(-1, 1);\n            expect(display.flip).to.have.been.calledOnce;\n        });\n\n        it('should redraw when resizing the viewport', function () {\n            display.flip = sinon.spy();\n            display.viewportChangeSize(2, 2);\n            expect(display.flip).to.have.been.calledOnce;\n        });\n\n        it('should show the entire framebuffer when disabling the viewport', function () {\n            display.clipViewport = false;\n            expect(display.absX(0)).to.equal(0);\n            expect(display.absY(0)).to.equal(0);\n            expect(display._target.width).to.equal(5);\n            expect(display._target.height).to.equal(5);\n        });\n\n        it('should ignore viewport changes when the viewport is disabled', function () {\n            display.clipViewport = false;\n            display.viewportChangeSize(2, 2);\n            display.viewportChangePos(1, 1);\n            expect(display.absX(0)).to.equal(0);\n            expect(display.absY(0)).to.equal(0);\n            expect(display._target.width).to.equal(5);\n            expect(display._target.height).to.equal(5);\n        });\n\n        it('should show the entire framebuffer just after enabling the viewport', function () {\n            display.clipViewport = false;\n            display.clipViewport = true;\n            expect(display.absX(0)).to.equal(0);\n            expect(display.absY(0)).to.equal(0);\n            expect(display._target.width).to.equal(5);\n            expect(display._target.height).to.equal(5);\n        });\n    });\n\n    describe('resizing', function () {\n        let display;\n        beforeEach(function () {\n            display = new Display(document.createElement('canvas'));\n            display.clipViewport = false;\n            display.resize(4, 4);\n        });\n\n        it('should change the size of the logical canvas', function () {\n            display.resize(5, 7);\n            expect(display._fbWidth).to.equal(5);\n            expect(display._fbHeight).to.equal(7);\n        });\n\n        it('should keep the framebuffer data', function () {\n            display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);\n            display.resize(2, 2);\n            display.flip();\n            const expected = [];\n            for (let i = 0; i < 4 * 2*2; i += 4) {\n                expected[i] = 0xff;\n                expected[i+1] = expected[i+2] = 0;\n                expected[i+3] = 0xff;\n            }\n            expect(display).to.have.displayed(new Uint8Array(expected));\n        });\n\n        describe('viewport', function () {\n            beforeEach(function () {\n                display.clipViewport = true;\n                display.viewportChangeSize(3, 3);\n                display.viewportChangePos(1, 1);\n            });\n\n            it('should keep the viewport position and size if possible', function () {\n                display.resize(6, 6);\n                expect(display.absX(0)).to.equal(1);\n                expect(display.absY(0)).to.equal(1);\n                expect(display._target.width).to.equal(3);\n                expect(display._target.height).to.equal(3);\n            });\n\n            it('should move the viewport if necessary', function () {\n                display.resize(3, 3);\n                expect(display.absX(0)).to.equal(0);\n                expect(display.absY(0)).to.equal(0);\n                expect(display._target.width).to.equal(3);\n                expect(display._target.height).to.equal(3);\n            });\n\n            it('should shrink the viewport if necessary', function () {\n                display.resize(2, 2);\n                expect(display.absX(0)).to.equal(0);\n                expect(display.absY(0)).to.equal(0);\n                expect(display._target.width).to.equal(2);\n                expect(display._target.height).to.equal(2);\n            });\n        });\n    });\n\n    describe('rescaling', function () {\n        let display;\n        let canvas;\n\n        beforeEach(function () {\n            canvas = document.createElement('canvas');\n            display = new Display(canvas);\n            display.clipViewport = true;\n            display.resize(4, 4);\n            display.viewportChangeSize(3, 3);\n            display.viewportChangePos(1, 1);\n            document.body.appendChild(canvas);\n        });\n\n        afterEach(function () {\n            document.body.removeChild(canvas);\n        });\n\n        it('should not change the bitmap size of the canvas', function () {\n            display.scale = 2.0;\n            expect(canvas.width).to.equal(3);\n            expect(canvas.height).to.equal(3);\n        });\n\n        it('should change the effective rendered size of the canvas', function () {\n            display.scale = 2.0;\n            expect(canvas.clientWidth).to.equal(6);\n            expect(canvas.clientHeight).to.equal(6);\n        });\n\n        it('should not change when resizing', function () {\n            display.scale = 2.0;\n            display.resize(5, 5);\n            expect(display.scale).to.equal(2.0);\n            expect(canvas.width).to.equal(3);\n            expect(canvas.height).to.equal(3);\n            expect(canvas.clientWidth).to.equal(6);\n            expect(canvas.clientHeight).to.equal(6);\n        });\n    });\n\n    describe('autoscaling', function () {\n        let display;\n        let canvas;\n\n        beforeEach(function () {\n            canvas = document.createElement('canvas');\n            display = new Display(canvas);\n            display.clipViewport = true;\n            display.resize(4, 3);\n            display.viewportChangeSize(4, 3);\n            document.body.appendChild(canvas);\n        });\n\n        afterEach(function () {\n            document.body.removeChild(canvas);\n        });\n\n        it('should preserve aspect ratio while autoscaling', function () {\n            display.autoscale(16, 9);\n            expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);\n        });\n\n        it('should use width to determine scale when the current aspect ratio is wider than the target', function () {\n            display.autoscale(9, 16);\n            expect(display.absX(9)).to.equal(4);\n            expect(display.absY(18)).to.equal(8);\n            expect(canvas.clientWidth).to.equal(9);\n            expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)\n        });\n\n        it('should use height to determine scale when the current aspect ratio is taller than the target', function () {\n            display.autoscale(16, 9);\n            expect(display.absX(9)).to.equal(3);\n            expect(display.absY(18)).to.equal(6);\n            expect(canvas.clientWidth).to.equal(12);  // 16 * (4 / 3)\n            expect(canvas.clientHeight).to.equal(9);\n\n        });\n\n        it('should not change the bitmap size of the canvas', function () {\n            display.autoscale(16, 9);\n            expect(canvas.width).to.equal(4);\n            expect(canvas.height).to.equal(3);\n        });\n    });\n\n    describe('drawing', function () {\n\n        // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the\n        //                     basic cases\n        let display;\n        beforeEach(function () {\n            display = new Display(document.createElement('canvas'));\n            display.resize(4, 4);\n        });\n\n        it('should not draw directly on the target canvas', function () {\n            display.fillRect(0, 0, 4, 4, [0xff, 0, 0]);\n            display.flip();\n            display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);\n            const expected = [];\n            for (let i = 0; i < 4 * display._fbWidth * display._fbHeight; i += 4) {\n                expected[i] = 0xff;\n                expected[i+1] = expected[i+2] = 0;\n                expected[i+3] = 0xff;\n            }\n            expect(display).to.have.displayed(new Uint8Array(expected));\n        });\n\n        it('should support filling a rectangle with particular color via #fillRect', function () {\n            display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);\n            display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);\n            display.fillRect(2, 2, 2, 2, [0, 0, 0xff]);\n            display.flip();\n            expect(display).to.have.displayed(checkedData);\n        });\n\n        it('should support copying an portion of the canvas via #copyImage', function () {\n            display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);\n            display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);\n            display.copyImage(0, 0, 2, 2, 2, 2);\n            display.flip();\n            expect(display).to.have.displayed(checkedData);\n        });\n\n        it('should support drawing images via #imageRect', async function () {\n            display.imageRect(0, 0, 4, 4, \"image/png\", makeImagePng(checkedData, 4, 4));\n            display.flip();\n            await display.flush();\n            expect(display).to.have.displayed(checkedData);\n        });\n\n        it('should support blit images with true color via #blitImage', function () {\n            display.blitImage(0, 0, 4, 4, checkedData, 0);\n            display.flip();\n            expect(display).to.have.displayed(checkedData);\n        });\n\n        it('should support drawing an image object via #drawImage', function () {\n            const img = makeImageCanvas(checkedData, 4, 4);\n            display.drawImage(img, 0, 0);\n            display.flip();\n            expect(display).to.have.displayed(checkedData);\n        });\n    });\n\n    describe('the render queue processor', function () {\n        let display;\n        beforeEach(function () {\n            display = new Display(document.createElement('canvas'));\n            display.resize(4, 4);\n            sinon.spy(display, '_scanRenderQ');\n        });\n\n        it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {\n            display._renderQPush({ type: 'noop' });  // does nothing\n            expect(display._scanRenderQ).to.have.been.calledOnce;\n        });\n\n        it('should not try to process an item when it is pushed on if we are waiting for other items', function () {\n            display._renderQ.length = 2;\n            display._renderQPush({ type: 'noop' });\n            expect(display._scanRenderQ).to.not.have.been.called;\n        });\n\n        it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {\n            const img = { complete: false, width: 4, height: 4, addEventListener: sinon.spy() };\n            display._renderQ = [{ type: 'img', x: 3, y: 4, width: 4, height: 4, img: img },\n                                { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];\n            display.drawImage = sinon.spy();\n            display.fillRect = sinon.spy();\n\n            display._scanRenderQ();\n            expect(display.drawImage).to.not.have.been.called;\n            expect(display.fillRect).to.not.have.been.called;\n            expect(img.addEventListener).to.have.been.calledOnce;\n\n            display._renderQ[0].img.complete = true;\n            display._scanRenderQ();\n            expect(display.drawImage).to.have.been.calledOnce;\n            expect(display.fillRect).to.have.been.calledOnce;\n            expect(img.addEventListener).to.have.been.calledOnce;\n        });\n\n        it('should resolve promise when queue is flushed', async function () {\n            display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);\n            let promise = display.flush();\n            expect(promise).to.be.an.instanceOf(Promise);\n            await promise;\n        });\n\n        it('should draw a blit image on type \"blit\"', function () {\n            display.blitImage = sinon.spy();\n            display._renderQPush({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });\n            expect(display.blitImage).to.have.been.calledOnce;\n            expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);\n        });\n\n        it('should copy a region on type \"copy\"', function () {\n            display.copyImage = sinon.spy();\n            display._renderQPush({ type: 'copy', x: 3, y: 4, width: 5, height: 6, oldX: 7, oldY: 8 });\n            expect(display.copyImage).to.have.been.calledOnce;\n            expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);\n        });\n\n        it('should fill a rect with a given color on type \"fill\"', function () {\n            display.fillRect = sinon.spy();\n            display._renderQPush({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});\n            expect(display.fillRect).to.have.been.calledOnce;\n            expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);\n        });\n\n        it('should draw an image from an image object on type \"img\" (if complete)', function () {\n            display.drawImage = sinon.spy();\n            display._renderQPush({ type: 'img', x: 3, y: 4, img: { complete: true } });\n            expect(display.drawImage).to.have.been.calledOnce;\n            expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);\n        });\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.gesturehandler.js",
    "content": "import EventTargetMixin from '../core/util/eventtarget.js';\n\nimport GestureHandler from '../core/input/gesturehandler.js';\n\nclass DummyTarget extends EventTargetMixin {\n}\n\ndescribe('Gesture handler', function () {\n    let target, handler;\n    let gestures;\n    let clock;\n    let touches;\n\n    before(function () {\n        clock = sinon.useFakeTimers();\n    });\n\n    after(function () {\n        clock.restore();\n    });\n\n    beforeEach(function () {\n        target = new DummyTarget();\n        gestures = sinon.spy();\n        target.addEventListener('gesturestart', gestures);\n        target.addEventListener('gesturemove', gestures);\n        target.addEventListener('gestureend', gestures);\n        touches = [];\n        handler = new GestureHandler();\n        handler.attach(target);\n    });\n\n    afterEach(function () {\n        if (handler) {\n            handler.detach();\n        }\n        target = null;\n        gestures = null;\n    });\n\n    function touchStart(id, x, y) {\n        let touch = { identifier: id,\n                      clientX: x, clientY: y };\n        touches.push(touch);\n        let ev = { type: 'touchstart',\n                   touches: touches,\n                   targetTouches: touches,\n                   changedTouches: [ touch ],\n                   stopPropagation: sinon.spy(),\n                   preventDefault: sinon.spy() };\n        target.dispatchEvent(ev);\n    }\n\n    function touchMove(id, x, y) {\n        let touch = touches.find(t => t.identifier === id);\n        touch.clientX = x;\n        touch.clientY = y;\n        let ev = { type: 'touchmove',\n                   touches: touches,\n                   targetTouches: touches,\n                   changedTouches: [ touch ],\n                   stopPropagation: sinon.spy(),\n                   preventDefault: sinon.spy() };\n        target.dispatchEvent(ev);\n    }\n\n    function touchEnd(id) {\n        let idx = touches.findIndex(t => t.identifier === id);\n        let touch = touches.splice(idx, 1)[0];\n        let ev = { type: 'touchend',\n                   touches: touches,\n                   targetTouches: touches,\n                   changedTouches: [ touch ],\n                   stopPropagation: sinon.spy(),\n                   preventDefault: sinon.spy() };\n        target.dispatchEvent(ev);\n    }\n\n    describe('Single finger tap', function () {\n        it('should handle single finger tap', function () {\n            touchStart(1, 20.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'onetap',\n                                        clientX: 20.0,\n                                        clientY: 30.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'onetap',\n                                        clientX: 20.0,\n                                        clientY: 30.0 } }));\n        });\n    });\n\n    describe('Two finger tap', function () {\n        it('should handle two finger tap', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 50.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchEnd(1);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchEnd(2);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'twotap',\n                                        clientX: 25.0,\n                                        clientY: 40.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'twotap',\n                                        clientX: 25.0,\n                                        clientY: 40.0 } }));\n        });\n\n        it('should ignore slow starting two finger tap', function () {\n            touchStart(1, 20.0, 30.0);\n\n            clock.tick(500);\n\n            touchStart(2, 30.0, 50.0);\n            touchEnd(1);\n            touchEnd(2);\n\n            expect(gestures).to.not.have.been.called;\n        });\n\n        it('should ignore slow ending two finger tap', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 50.0);\n            touchEnd(1);\n\n            clock.tick(500);\n\n            touchEnd(2);\n\n            expect(gestures).to.not.have.been.called;\n        });\n\n        it('should ignore slow two finger tap', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 50.0);\n\n            clock.tick(1500);\n\n            touchEnd(1);\n            touchEnd(2);\n\n            expect(gestures).to.not.have.been.called;\n        });\n    });\n\n    describe('Three finger tap', function () {\n        it('should handle three finger tap', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 50.0);\n            touchStart(3, 40.0, 40.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchEnd(1);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchEnd(2);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchEnd(3);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'threetap',\n                                        clientX: 30.0,\n                                        clientY: 40.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'threetap',\n                                        clientX: 30.0,\n                                        clientY: 40.0 } }));\n        });\n\n        it('should ignore slow starting three finger tap', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 50.0);\n\n            clock.tick(500);\n\n            touchStart(3, 40.0, 40.0);\n            touchEnd(1);\n            touchEnd(2);\n            touchEnd(3);\n\n            expect(gestures).to.not.have.been.called;\n        });\n\n        it('should ignore slow ending three finger tap', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 50.0);\n            touchStart(3, 40.0, 40.0);\n            touchEnd(1);\n            touchEnd(2);\n\n            clock.tick(500);\n\n            touchEnd(3);\n\n            expect(gestures).to.not.have.been.called;\n        });\n\n        it('should ignore three finger drag', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 50.0);\n            touchStart(3, 40.0, 40.0);\n\n            touchMove(1, 120.0, 130.0);\n            touchMove(2, 130.0, 150.0);\n            touchMove(3, 140.0, 140.0);\n\n            touchEnd(1);\n            touchEnd(2);\n            touchEnd(3);\n\n            expect(gestures).to.not.have.been.called;\n        });\n\n        it('should ignore slow three finger tap', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 50.0);\n            touchStart(3, 40.0, 40.0);\n\n            clock.tick(1500);\n\n            touchEnd(1);\n            touchEnd(2);\n            touchEnd(3);\n\n            expect(gestures).to.not.have.been.called;\n        });\n    });\n\n    describe('Single finger drag', function () {\n        it('should handle horizontal single finger drag', function () {\n            touchStart(1, 20.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 40.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 80.0, 30.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'drag',\n                                        clientX: 20.0,\n                                        clientY: 30.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'drag',\n                                        clientX: 80.0,\n                                        clientY: 30.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'drag',\n                                        clientX: 80.0,\n                                        clientY: 30.0 } }));\n        });\n\n        it('should handle vertical single finger drag', function () {\n            touchStart(1, 20.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 20.0, 50.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 20.0, 90.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'drag',\n                                        clientX: 20.0,\n                                        clientY: 30.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'drag',\n                                        clientX: 20.0,\n                                        clientY: 90.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'drag',\n                                        clientX: 20.0,\n                                        clientY: 90.0 } }));\n        });\n\n        it('should handle diagonal single finger drag', function () {\n            touchStart(1, 120.0, 130.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 90.0, 100.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 60.0, 70.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'drag',\n                                        clientX: 120.0,\n                                        clientY: 130.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'drag',\n                                        clientX: 60.0,\n                                        clientY: 70.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'drag',\n                                        clientX: 60.0,\n                                        clientY: 70.0 } }));\n        });\n    });\n\n    describe('Long press', function () {\n        it('should handle long press', function () {\n            touchStart(1, 20.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            clock.tick(1500);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'longpress',\n                                        clientX: 20.0,\n                                        clientY: 30.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'longpress',\n                                        clientX: 20.0,\n                                        clientY: 30.0 } }));\n        });\n\n        it('should handle long press drag', function () {\n            touchStart(1, 20.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            clock.tick(1500);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'longpress',\n                                        clientX: 20.0,\n                                        clientY: 30.0 } }));\n\n            gestures.resetHistory();\n\n            touchMove(1, 120.0, 50.0);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'longpress',\n                                        clientX: 120.0,\n                                        clientY: 50.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'longpress',\n                                        clientX: 120.0,\n                                        clientY: 50.0 } }));\n        });\n    });\n\n    describe('Two finger drag', function () {\n        it('should handle fast and distinct horizontal two finger drag', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 40.0, 30.0);\n            touchMove(2, 50.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(2, 90.0, 30.0);\n            touchMove(1, 80.0, 30.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'twodrag',\n                                        clientX: 25.0,\n                                        clientY: 30.0,\n                                        magnitudeX: 0.0,\n                                        magnitudeY: 0.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'twodrag',\n                                        clientX: 25.0,\n                                        clientY: 30.0,\n                                        magnitudeX: 60.0,\n                                        magnitudeY: 0.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'twodrag',\n                                        clientX: 25.0,\n                                        clientY: 30.0,\n                                        magnitudeX: 60.0,\n                                        magnitudeY: 0.0 } }));\n        });\n\n        it('should handle fast and distinct vertical two finger drag', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 20.0, 100.0);\n            touchMove(2, 30.0, 40.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(2, 30.0, 90.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'twodrag',\n                                        clientX: 25.0,\n                                        clientY: 30.0,\n                                        magnitudeX: 0.0,\n                                        magnitudeY: 0.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'twodrag',\n                                        clientX: 25.0,\n                                        clientY: 30.0,\n                                        magnitudeX: 0.0,\n                                        magnitudeY: 65.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'twodrag',\n                                        clientX: 25.0,\n                                        clientY: 30.0,\n                                        magnitudeX: 0.0,\n                                        magnitudeY: 65.0 } }));\n        });\n\n        it('should handle fast and distinct diagonal two finger drag', function () {\n            touchStart(1, 120.0, 130.0);\n            touchStart(2, 130.0, 130.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 80.0, 90.0);\n            touchMove(2, 100.0, 130.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(2, 60.0, 70.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'twodrag',\n                                        clientX: 125.0,\n                                        clientY: 130.0,\n                                        magnitudeX: 0.0,\n                                        magnitudeY: 0.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'twodrag',\n                                        clientX: 125.0,\n                                        clientY: 130.0,\n                                        magnitudeX: -55.0,\n                                        magnitudeY: -50.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'twodrag',\n                                        clientX: 125.0,\n                                        clientY: 130.0,\n                                        magnitudeX: -55.0,\n                                        magnitudeY: -50.0 } }));\n        });\n\n        it('should ignore fast almost two finger dragging', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 30.0);\n            touchMove(1, 80.0, 30.0);\n            touchMove(2, 70.0, 30.0);\n            touchEnd(1);\n            touchEnd(2);\n\n            expect(gestures).to.not.have.been.called;\n\n            clock.tick(1500);\n\n            expect(gestures).to.not.have.been.called;\n        });\n\n        it('should handle slow horizontal two finger drag', function () {\n            touchStart(1, 50.0, 40.0);\n            touchStart(2, 60.0, 40.0);\n            touchMove(1, 80.0, 40.0);\n            touchMove(2, 110.0, 40.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            clock.tick(60);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'twodrag',\n                                        clientX: 55.0,\n                                        clientY: 40.0,\n                                        magnitudeX: 0.0,\n                                        magnitudeY: 0.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'twodrag',\n                                        clientX: 55.0,\n                                        clientY: 40.0,\n                                        magnitudeX: 40.0,\n                                        magnitudeY: 0.0 } }));\n        });\n\n        it('should handle slow vertical two finger drag', function () {\n            touchStart(1, 40.0, 40.0);\n            touchStart(2, 40.0, 60.0);\n            touchMove(2, 40.0, 80.0);\n            touchMove(1, 40.0, 100.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            clock.tick(60);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'twodrag',\n                                        clientX: 40.0,\n                                        clientY: 50.0,\n                                        magnitudeX: 0.0,\n                                        magnitudeY: 0.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'twodrag',\n                                        clientX: 40.0,\n                                        clientY: 50.0,\n                                        magnitudeX: 0.0,\n                                        magnitudeY: 40.0 } }));\n        });\n\n        it('should handle slow diagonal two finger drag', function () {\n            touchStart(1, 50.0, 40.0);\n            touchStart(2, 40.0, 60.0);\n            touchMove(1, 70.0, 60.0);\n            touchMove(2, 90.0, 110.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            clock.tick(60);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'twodrag',\n                                        clientX: 45.0,\n                                        clientY: 50.0,\n                                        magnitudeX: 0.0,\n                                        magnitudeY: 0.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'twodrag',\n                                        clientX: 45.0,\n                                        clientY: 50.0,\n                                        magnitudeX: 35.0,\n                                        magnitudeY: 35.0 } }));\n        });\n\n        it('should ignore too slow two finger drag', function () {\n            touchStart(1, 20.0, 30.0);\n\n            clock.tick(500);\n\n            touchStart(2, 30.0, 30.0);\n            touchMove(1, 40.0, 30.0);\n            touchMove(2, 50.0, 30.0);\n            touchMove(1, 80.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n        });\n    });\n\n    describe('Pinch', function () {\n        it('should handle pinching distinctly and fast inwards', function () {\n            touchStart(1, 0.0, 0.0);\n            touchStart(2, 130.0, 130.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 50.0, 40.0);\n            touchMove(2, 100.0, 130.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(2, 60.0, 70.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'pinch',\n                                        clientX: 65.0,\n                                        clientY: 65.0,\n                                        magnitudeX: 130.0,\n                                        magnitudeY: 130.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'pinch',\n                                        clientX: 65.0,\n                                        clientY: 65.0,\n                                        magnitudeX: 10.0,\n                                        magnitudeY: 30.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'pinch',\n                                        clientX: 65.0,\n                                        clientY: 65.0,\n                                        magnitudeX: 10.0,\n                                        magnitudeY: 30.0 } }));\n        });\n\n        it('should handle pinching fast and distinctly outwards', function () {\n            touchStart(1, 100.0, 100.0);\n            touchStart(2, 110.0, 100.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 130.0, 70.0);\n            touchMove(2, 0.0, 200.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 180.0, 20.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'pinch',\n                                        clientX: 105.0,\n                                        clientY: 100.0,\n                                        magnitudeX: 10.0,\n                                        magnitudeY: 0.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'pinch',\n                                        clientX: 105.0,\n                                        clientY: 100.0,\n                                        magnitudeX: 180.0,\n                                        magnitudeY: 180.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'pinch',\n                                        clientX: 105.0,\n                                        clientY: 100.0,\n                                        magnitudeX: 180.0,\n                                        magnitudeY: 180.0 } }));\n        });\n\n        it('should ignore fast almost pinching', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 130.0, 130.0);\n            touchMove(1, 80.0, 70.0);\n            touchEnd(1);\n            touchEnd(2);\n\n            expect(gestures).to.not.have.been.called;\n\n            clock.tick(1500);\n\n            expect(gestures).to.not.have.been.called;\n        });\n\n        it('should handle pinching inwards slowly', function () {\n            touchStart(1, 0.0, 0.0);\n            touchStart(2, 130.0, 130.0);\n            touchMove(1, 50.0, 40.0);\n            touchMove(2, 100.0, 130.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            clock.tick(60);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'pinch',\n                                        clientX: 65.0,\n                                        clientY: 65.0,\n                                        magnitudeX: 130.0,\n                                        magnitudeY: 130.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'pinch',\n                                        clientX: 65.0,\n                                        clientY: 65.0,\n                                        magnitudeX: 50.0,\n                                        magnitudeY: 90.0 } }));\n        });\n\n        it('should handle pinching outwards slowly', function () {\n            touchStart(1, 100.0, 130.0);\n            touchStart(2, 110.0, 130.0);\n            touchMove(2, 200.0, 130.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            clock.tick(60);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'pinch',\n                                        clientX: 105.0,\n                                        clientY: 130.0,\n                                        magnitudeX: 10.0,\n                                        magnitudeY: 0.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'pinch',\n                                        clientX: 105.0,\n                                        clientY: 130.0,\n                                        magnitudeX: 100.0,\n                                        magnitudeY: 0.0 } }));\n        });\n\n        it('should ignore pinching too slowly', function () {\n            touchStart(1, 0.0, 0.0);\n\n            clock.tick(500);\n\n            touchStart(2, 130.0, 130.0);\n            touchMove(2, 100.0, 130.0);\n            touchMove(1, 50.0, 40.0);\n\n            expect(gestures).to.not.have.been.called;\n        });\n    });\n\n    describe('Ignoring', function () {\n        it('should ignore extra touches during gesture', function () {\n            touchStart(1, 20.0, 30.0);\n            touchMove(1, 40.0, 30.0);\n            touchMove(1, 80.0, 30.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'drag' } }));\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'drag' } }));\n\n            gestures.resetHistory();\n\n            touchStart(2, 10.0, 10.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 100.0, 50.0);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'drag',\n                                        clientX: 100.0,\n                                        clientY: 50.0 } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'drag',\n                                        clientX: 100.0,\n                                        clientY: 50.0 } }));\n        });\n\n        it('should ignore extra touches when waiting for gesture to end', function () {\n            touchStart(1, 20.0, 30.0);\n            touchStart(2, 30.0, 30.0);\n            touchMove(1, 40.0, 30.0);\n            touchMove(2, 90.0, 30.0);\n            touchMove(1, 80.0, 30.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'twodrag' } }));\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'twodrag' } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'twodrag' } }));\n\n            gestures.resetHistory();\n\n            touchStart(3, 10.0, 10.0);\n            touchEnd(3);\n\n            expect(gestures).to.not.have.been.called;\n        });\n\n        it('should ignore extra touches after gesture', function () {\n            touchStart(1, 20.0, 30.0);\n            touchMove(1, 40.0, 30.0);\n            touchMove(1, 80.0, 30.0);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'drag' } }));\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'drag' } }));\n\n            gestures.resetHistory();\n\n            touchStart(2, 10.0, 10.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchMove(1, 100.0, 50.0);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gesturemove',\n                              detail: { type: 'drag' } }));\n\n            gestures.resetHistory();\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledOnceWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'drag' } }));\n\n            gestures.resetHistory();\n\n            touchEnd(2);\n\n            expect(gestures).to.not.have.been.called;\n\n            // Check that everything is reseted after trailing ignores are released\n\n            touchStart(3, 20.0, 30.0);\n            touchEnd(3);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'onetap' } }));\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'onetap' } }));\n        });\n\n        it('should properly reset after a gesture', function () {\n            touchStart(1, 20.0, 30.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchEnd(1);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'onetap',\n                                        clientX: 20.0,\n                                        clientY: 30.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'onetap',\n                                        clientX: 20.0,\n                                        clientY: 30.0 } }));\n\n            gestures.resetHistory();\n\n            touchStart(2, 70.0, 80.0);\n\n            expect(gestures).to.not.have.been.called;\n\n            touchEnd(2);\n\n            expect(gestures).to.have.been.calledTwice;\n\n            expect(gestures.firstCall).to.have.been.calledWith(\n                sinon.match({ type: 'gesturestart',\n                              detail: { type: 'onetap',\n                                        clientX: 70.0,\n                                        clientY: 80.0 } }));\n\n            expect(gestures.secondCall).to.have.been.calledWith(\n                sinon.match({ type: 'gestureend',\n                              detail: { type: 'onetap',\n                                        clientX: 70.0,\n                                        clientY: 80.0 } }));\n        });\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.h264.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport { H264Parser } from '../core/decoders/h264.js';\nimport H264Decoder from '../core/decoders/h264.js';\nimport Base64 from '../core/base64.js';\nimport { supportsWebCodecsH264Decode } from '../core/util/browser.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\n/* This is a 3 frame 16x16 video where the first frame is solid red, the second\n * is solid green and the third is solid blue.\n *\n * The colour space is BT.709. It is encoded into the stream.\n */\nconst redGreenBlue16x16Video = new Uint8Array(Base64.decode(\n    'AAAAAWdCwBTZnpuAgICgAAADACAAAAZB4oVNAAAAAWjJYyyAAAABBgX//4HcRem95tlIt5Ys' +\n    '2CDZI+7veDI2NCAtIGNvcmUgMTY0IHIzMTA4IDMxZTE5ZjkgLSBILjI2NC9NUEVHLTQgQVZD' +\n    'IGNvZGVjIC0gQ29weWxlZnQgMjAwMy0yMDIzIC0gaHR0cDovL3d3dy52aWRlb2xhbi5vcmcv' +\n    'eDI2NC5odG1sIC0gb3B0aW9uczogY2FiYWM9MCByZWY9NSBkZWJsb2NrPTE6MDowIGFuYWx5' +\n    'c2U9MHgxOjB4MTExIG1lPWhleCBzdWJtZT04IHBzeT0xIHBzeV9yZD0xLjAwOjAuMDAgbWl4' +\n    'ZWRfcmVmPTEgbWVfcmFuZ2U9MTYgY2hyb21hX21lPTEgdHJlbGxpcz0yIDh4OGRjdD0wIGNx' +\n    'bT0wIGRlYWR6b25lPTIxLDExIGZhc3RfcHNraXA9MSBjaHJvbWFfcXBfb2Zmc2V0PS0yIHRo' +\n    'cmVhZHM9MSBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNp' +\n    'bWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9' +\n    'MCBiZnJhbWVzPTAgd2VpZ2h0cD0wIGtleWludD1pbmZpbml0ZSBrZXlpbnRfbWluPTI1IHNj' +\n    'ZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NTAgcmM9YWJyIG1idHJl' +\n    'ZT0xIGJpdHJhdGU9NDAwIHJhdGV0b2w9MS4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02' +\n    'OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAABZYiEBrxmKAAPVccAAS04' +\n    '4AA5DRJMnkycJk4TPwAAAAFBiIga8RigADVVHAAGaGOAANtuAAAAAUGIkBr///wRRQABVf8c' +\n    'AAcho4AAiD4='));\n\nfunction createSolidColorFrameBuffer(color, width, height) {\n    const r = (color >> 24) & 0xff;\n    const g = (color >> 16) & 0xff;\n    const b = (color >> 8) & 0xff;\n    const a = (color >> 0) & 0xff;\n\n    const size = width * height * 4;\n    let array = new Uint8ClampedArray(size);\n\n    for (let i = 0; i < size / 4; ++i) {\n        array[i * 4 + 0] = r;\n        array[i * 4 + 1] = g;\n        array[i * 4 + 2] = b;\n        array[i * 4 + 3] = a;\n    }\n\n    return array;\n}\n\nfunction makeMessageHeader(length, resetContext, resetAllContexts) {\n    let flags = 0;\n    if (resetContext) {\n        flags |= 1;\n    }\n    if (resetAllContexts) {\n        flags |= 2;\n    }\n\n    let header = new Uint8Array(8);\n    let i = 0;\n\n    let appendU32 = (v) => {\n        header[i++] = (v >> 24) & 0xff;\n        header[i++] = (v >> 16) & 0xff;\n        header[i++] = (v >> 8) & 0xff;\n        header[i++] = v & 0xff;\n    };\n\n    appendU32(length);\n    appendU32(flags);\n\n    return header;\n}\n\nfunction wrapRectData(data, resetContext, resetAllContexts) {\n    let header = makeMessageHeader(data.length, resetContext, resetAllContexts);\n    return Array.from(header).concat(Array.from(data));\n}\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\nfunction almost(a, b) {\n    let diff = Math.abs(a - b);\n    return diff < 5;\n}\n\ndescribe('H.264 parser', function () {\n    it('should parse constrained baseline video', function () {\n        let parser = new H264Parser(redGreenBlue16x16Video);\n\n        let frame = parser.parse();\n        expect(frame).to.have.property('key', true);\n\n        expect(parser).to.have.property('profileIdc', 66);\n        expect(parser).to.have.property('constraintSet', 192);\n        expect(parser).to.have.property('levelIdc', 20);\n\n        frame = parser.parse();\n        expect(frame).to.have.property('key', false);\n\n        frame = parser.parse();\n        expect(frame).to.have.property('key', false);\n\n        frame = parser.parse();\n        expect(frame).to.be.null;\n    });\n});\n\ndescribe('H.264 decoder unit test', function () {\n    let decoder;\n\n    beforeEach(function () {\n        if (!supportsWebCodecsH264Decode) {\n            this.skip();\n            return;\n        }\n        decoder = new H264Decoder();\n    });\n\n    it('creates and resets context', function () {\n        let context = decoder._getContext(1, 2, 3, 4);\n        expect(context._width).to.equal(3);\n        expect(context._height).to.equal(4);\n        expect(decoder._contexts).to.not.be.empty;\n        decoder._resetContext(1, 2, 3, 4);\n        expect(decoder._contexts).to.be.empty;\n    });\n\n    it('resets all contexts', function () {\n        decoder._getContext(0, 0, 1, 1);\n        decoder._getContext(2, 2, 1, 1);\n        expect(decoder._contexts).to.not.be.empty;\n        decoder._resetAllContexts();\n        expect(decoder._contexts).to.be.empty;\n    });\n\n    it('caches contexts', function () {\n        let c1 = decoder._getContext(1, 2, 3, 4);\n        c1.lastUsed = 1;\n        let c2 = decoder._getContext(1, 2, 3, 4);\n        c2.lastUsed = 2;\n        expect(Object.keys(decoder._contexts).length).to.equal(1);\n        expect(c1.lastUsed).to.equal(c2.lastUsed);\n    });\n\n    it('deletes oldest context', function () {\n        for (let i = 0; i < 65; ++i) {\n            let context = decoder._getContext(i, 0, 1, 1);\n            context.lastUsed = i;\n        }\n\n        expect(decoder._findOldestContextId()).to.equal('1,0,1,1');\n        expect(decoder._contexts[decoder._contextId(0, 0, 1, 1)]).to.be.undefined;\n        expect(decoder._contexts[decoder._contextId(1, 0, 1, 1)]).to.not.be.null;\n        expect(decoder._contexts[decoder._contextId(63, 0, 1, 1)]).to.not.be.null;\n        expect(decoder._contexts[decoder._contextId(64, 0, 1, 1)]).to.not.be.null;\n    });\n});\n\ndescribe('H.264 decoder functional test', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        if (!supportsWebCodecsH264Decode) {\n            this.skip();\n            return;\n        }\n        decoder = new H264Decoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(16, 16);\n    });\n\n    it('should handle H.264 rect', async function () {\n        let data = wrapRectData(redGreenBlue16x16Video, false, false);\n        let done = testDecodeRect(decoder, 0, 0, 16, 16, data, display, 24);\n        expect(done).to.be.true;\n        await display.flush();\n        let targetData = createSolidColorFrameBuffer(0x0000ffff, 16, 16);\n        expect(display).to.have.displayed(targetData, almost);\n    });\n\n    it('should handle specific context reset', async function () {\n        let data = wrapRectData(redGreenBlue16x16Video, false, false);\n        let done = testDecodeRect(decoder, 0, 0, 16, 16, data, display, 24);\n        expect(done).to.be.true;\n        await display.flush();\n        let targetData = createSolidColorFrameBuffer(0x0000ffff, 16, 16);\n        expect(display).to.have.displayed(targetData, almost);\n\n        data = wrapRectData([], true, false);\n        done = testDecodeRect(decoder, 0, 0, 16, 16, data, display, 24);\n        expect(done).to.be.true;\n        await display.flush();\n\n        expect(decoder._contexts[decoder._contextId(0, 0, 16, 16)]._decoder).to.be.null;\n    });\n\n    it('should handle global context reset', async function () {\n        let data = wrapRectData(redGreenBlue16x16Video, false, false);\n        let done = testDecodeRect(decoder, 0, 0, 16, 16, data, display, 24);\n        expect(done).to.be.true;\n        await display.flush();\n        let targetData = createSolidColorFrameBuffer(0x0000ffff, 16, 16);\n        expect(display).to.have.displayed(targetData, almost);\n\n        data = wrapRectData([], false, true);\n        done = testDecodeRect(decoder, 0, 0, 16, 16, data, display, 24);\n        expect(done).to.be.true;\n        await display.flush();\n\n        expect(decoder._contexts[decoder._contextId(0, 0, 16, 16)]._decoder).to.be.null;\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.helper.js",
    "content": "import keysyms from '../core/input/keysymdef.js';\nimport * as KeyboardUtil from \"../core/input/util.js\";\n\ndescribe('Helpers', function () {\n    \"use strict\";\n\n    describe('keysyms.lookup', function () {\n        it('should map ASCII characters to keysyms', function () {\n            expect(keysyms.lookup('a'.charCodeAt())).to.be.equal(0x61);\n            expect(keysyms.lookup('A'.charCodeAt())).to.be.equal(0x41);\n        });\n        it('should map Latin-1 characters to keysyms', function () {\n            expect(keysyms.lookup('ø'.charCodeAt())).to.be.equal(0xf8);\n\n            expect(keysyms.lookup('é'.charCodeAt())).to.be.equal(0xe9);\n        });\n        it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function () {\n            expect(keysyms.lookup('Š'.charCodeAt())).to.be.equal(0x01a9);\n        });\n        it('should map characters which aren\\'t in Latin1 *or* Windows-1252 to keysyms', function () {\n            expect(keysyms.lookup('ũ'.charCodeAt())).to.be.equal(0x03fd);\n        });\n        it('should map unknown codepoints to the Unicode range', function () {\n            expect(keysyms.lookup('\\n'.charCodeAt())).to.be.equal(0x100000a);\n            expect(keysyms.lookup('\\u262D'.charCodeAt())).to.be.equal(0x100262d);\n        });\n        // This requires very recent versions of most browsers... skipping for now\n        it.skip('should map UCS-4 codepoints to the Unicode range', function () {\n            //expect(keysyms.lookup('\\u{1F686}'.codePointAt())).to.be.equal(0x101f686);\n        });\n    });\n\n    describe('getKeycode', function () {\n        it('should pass through proper code', function () {\n            expect(KeyboardUtil.getKeycode({code: 'Semicolon'})).to.be.equal('Semicolon');\n        });\n        it('should map legacy values', function () {\n            expect(KeyboardUtil.getKeycode({code: ''})).to.be.equal('Unidentified');\n            expect(KeyboardUtil.getKeycode({code: 'OSLeft'})).to.be.equal('MetaLeft');\n        });\n        it('should map keyCode to code when possible', function () {\n            expect(KeyboardUtil.getKeycode({keyCode: 0x14})).to.be.equal('CapsLock');\n            expect(KeyboardUtil.getKeycode({keyCode: 0x5b})).to.be.equal('MetaLeft');\n            expect(KeyboardUtil.getKeycode({keyCode: 0x35})).to.be.equal('Digit5');\n            expect(KeyboardUtil.getKeycode({keyCode: 0x65})).to.be.equal('Numpad5');\n        });\n        it('should map keyCode left/right side', function () {\n            expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 1})).to.be.equal('ShiftLeft');\n            expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 2})).to.be.equal('ShiftRight');\n            expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 1})).to.be.equal('ControlLeft');\n            expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 2})).to.be.equal('ControlRight');\n        });\n        it('should map keyCode on numpad', function () {\n            expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 0})).to.be.equal('Enter');\n            expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 3})).to.be.equal('NumpadEnter');\n            expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 0})).to.be.equal('End');\n            expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 3})).to.be.equal('Numpad1');\n        });\n        it('should return Unidentified when it cannot map the keyCode', function () {\n            expect(KeyboardUtil.getKeycode({keycode: 0x42})).to.be.equal('Unidentified');\n        });\n\n        describe('Fix Meta on macOS', function () {\n            let origNavigator;\n            beforeEach(function () {\n                // window.navigator is a protected read-only property in many\n                // environments, so we need to redefine it whilst running these\n                // tests.\n                origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n                Object.defineProperty(window, \"navigator\", {value: {}});\n                window.navigator.platform = \"Mac x86_64\";\n            });\n            afterEach(function () {\n                Object.defineProperty(window, \"navigator\", origNavigator);\n            });\n\n            it('should respect ContextMenu on modern browser', function () {\n                expect(KeyboardUtil.getKeycode({code: 'ContextMenu', keyCode: 0x5d})).to.be.equal('ContextMenu');\n            });\n            it('should translate legacy ContextMenu to MetaRight', function () {\n                expect(KeyboardUtil.getKeycode({keyCode: 0x5d})).to.be.equal('MetaRight');\n            });\n        });\n    });\n\n    describe('getKey', function () {\n        it('should prefer key', function () {\n            expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a');\n        });\n        it('should map legacy values', function () {\n            expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta');\n            expect(KeyboardUtil.getKey({key: 'UIKeyInputLeftArrow'})).to.be.equal('ArrowLeft');\n        });\n        it('should handle broken Delete', function () {\n            expect(KeyboardUtil.getKey({key: '\\x00', code: 'NumpadDecimal'})).to.be.equal('Delete');\n        });\n        it('should use code if no key', function () {\n            expect(KeyboardUtil.getKey({code: 'NumpadBackspace'})).to.be.equal('Backspace');\n        });\n        it('should not use code fallback for character keys', function () {\n            expect(KeyboardUtil.getKey({code: 'KeyA'})).to.be.equal('Unidentified');\n            expect(KeyboardUtil.getKey({code: 'Digit1'})).to.be.equal('Unidentified');\n            expect(KeyboardUtil.getKey({code: 'Period'})).to.be.equal('Unidentified');\n            expect(KeyboardUtil.getKey({code: 'Numpad1'})).to.be.equal('Unidentified');\n        });\n        it('should use charCode if no key', function () {\n            expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Š');\n            // Broken Oculus browser\n            expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43, key: 'Unidentified'})).to.be.equal('Š');\n        });\n        it('should return Unidentified when it cannot map the key', function () {\n            expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified');\n        });\n    });\n\n    describe('getKeysym', function () {\n        describe('Non-character keys', function () {\n            it('should recognize the right keys', function () {\n                expect(KeyboardUtil.getKeysym({key: 'Enter'})).to.be.equal(0xFF0D);\n                expect(KeyboardUtil.getKeysym({key: 'Backspace'})).to.be.equal(0xFF08);\n                expect(KeyboardUtil.getKeysym({key: 'Tab'})).to.be.equal(0xFF09);\n                expect(KeyboardUtil.getKeysym({key: 'Shift'})).to.be.equal(0xFFE1);\n                expect(KeyboardUtil.getKeysym({key: 'Control'})).to.be.equal(0xFFE3);\n                expect(KeyboardUtil.getKeysym({key: 'Alt'})).to.be.equal(0xFFE9);\n                expect(KeyboardUtil.getKeysym({key: 'Meta'})).to.be.equal(0xFFEB);\n                expect(KeyboardUtil.getKeysym({key: 'Escape'})).to.be.equal(0xFF1B);\n                expect(KeyboardUtil.getKeysym({key: 'ArrowUp'})).to.be.equal(0xFF52);\n            });\n            it('should map left/right side', function () {\n                expect(KeyboardUtil.getKeysym({key: 'Shift', location: 1})).to.be.equal(0xFFE1);\n                expect(KeyboardUtil.getKeysym({key: 'Shift', location: 2})).to.be.equal(0xFFE2);\n                expect(KeyboardUtil.getKeysym({key: 'Control', location: 1})).to.be.equal(0xFFE3);\n                expect(KeyboardUtil.getKeysym({key: 'Control', location: 2})).to.be.equal(0xFFE4);\n            });\n            it('should handle AltGraph', function () {\n                expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Alt', location: 2})).to.be.equal(0xFFEA);\n                expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph', location: 2})).to.be.equal(0xFE03);\n            });\n            it('should handle Windows key with incorrect location', function () {\n                expect(KeyboardUtil.getKeysym({key: 'Meta', location: 0})).to.be.equal(0xFFEC);\n            });\n            it('should handle Clear/NumLock key with incorrect location', function () {\n                this.skip(); // Broken because of Clear/NumLock override\n                expect(KeyboardUtil.getKeysym({key: 'Clear', code: 'NumLock', location: 3})).to.be.equal(0xFF0B);\n            });\n            it('should handle Meta/Windows distinction', function () {\n                expect(KeyboardUtil.getKeysym({code: 'AltLeft', key: 'Meta', location: 1})).to.be.equal(0xFFE7);\n                expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Meta', location: 2})).to.be.equal(0xFFE8);\n                expect(KeyboardUtil.getKeysym({code: 'MetaLeft', key: 'Meta', location: 1})).to.be.equal(0xFFEB);\n                expect(KeyboardUtil.getKeysym({code: 'MetaRight', key: 'Meta', location: 2})).to.be.equal(0xFFEC);\n            });\n            it('should send NumLock even if key is Clear', function () {\n                expect(KeyboardUtil.getKeysym({key: 'Clear', code: 'NumLock'})).to.be.equal(0xFF7F);\n            });\n            it('should return null for unknown keys', function () {\n                expect(KeyboardUtil.getKeysym({key: 'Semicolon'})).to.be.null;\n                expect(KeyboardUtil.getKeysym({key: 'BracketRight'})).to.be.null;\n            });\n            it('should handle remappings', function () {\n                expect(KeyboardUtil.getKeysym({code: 'ControlLeft', key: 'Tab'})).to.be.equal(0xFF09);\n            });\n        });\n\n        describe('Numpad', function () {\n            it('should handle Numpad numbers', function () {\n                expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035);\n                expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5);\n            });\n            it('should handle Numpad non-character keys', function () {\n                expect(KeyboardUtil.getKeysym({code: 'Home', key: 'Home', location: 0})).to.be.equal(0xFF50);\n                expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: 'Home', location: 3})).to.be.equal(0xFF95);\n                expect(KeyboardUtil.getKeysym({code: 'Delete', key: 'Delete', location: 0})).to.be.equal(0xFFFF);\n                expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F);\n            });\n            it('should handle Numpad Decimal key', function () {\n                expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE);\n                expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);\n            });\n        });\n\n        describe('Japanese IM keys on Windows', function () {\n            let origNavigator;\n            beforeEach(function () {\n                // window.navigator is a protected read-only property in many\n                // environments, so we need to redefine it whilst running these\n                // tests.\n                origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n                Object.defineProperty(window, \"navigator\", {value: {}});\n                window.navigator.platform = \"Windows\";\n            });\n\n            afterEach(function () {\n                Object.defineProperty(window, \"navigator\", origNavigator);\n            });\n\n            const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,\n                           'Romaji': 0xff24, 'KanaMode': 0xff24 };\n            for (let [key, keysym] of Object.entries(keys)) {\n                it(`should fake combined key for ${key} on Windows`, function () {\n                    expect(KeyboardUtil.getKeysym({code: 'FakeIM', key: key})).to.be.equal(keysym);\n                });\n            }\n        });\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.hextile.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport HextileDecoder from '../core/decoders/hextile.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\nfunction push32(arr, num) {\n    arr.push((num >> 24) & 0xFF,\n             (num >> 16) & 0xFF,\n             (num >>  8) & 0xFF,\n             num & 0xFF);\n}\n\ndescribe('Hextile decoder', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        decoder = new HextileDecoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(4, 4);\n    });\n\n    it('should handle a tile with fg, bg specified, normal subrects', function () {\n        let data = [];\n        data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects\n        push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color\n        data.push(0x00); // becomes 0000ff00 --> #0000FF fg color\n        data.push(0x00);\n        data.push(0xff);\n        data.push(0x00);\n        data.push(2); // 2 subrects\n        data.push(0); // x: 0, y: 0\n        data.push(1 | (1 << 4)); // width: 2, height: 2\n        data.push(2 | (2 << 4)); // x: 2, y: 2\n        data.push(1 | (1 << 4)); // width: 2, height: 2\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle a raw tile', function () {\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        let data = [];\n        data.push(0x01); // raw\n        for (let i = 0; i < targetData.length; i += 4) {\n            data.push(targetData[i]);\n            data.push(targetData[i + 1]);\n            data.push(targetData[i + 2]);\n            // Last byte zero to test correct alpha handling\n            data.push(0);\n        }\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle a tile with only bg specified (solid bg)', function () {\n        let data = [];\n        data.push(0x02);\n        push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n\n        let expected = [];\n        for (let i = 0; i < 16; i++) {\n            push32(expected, 0x00ff00ff);\n        }\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(new Uint8Array(expected));\n    });\n\n    it('should handle a tile with only bg specified and an empty frame afterwards', function () {\n        // set the width so we can have two tiles\n        display.resize(8, 4);\n\n        let data = [];\n\n        // send a bg frame\n        data.push(0x02);\n        push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color\n\n        // send an empty frame\n        data.push(0x00);\n\n        let done = testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24);\n\n        let expected = [];\n        for (let i = 0; i < 16; i++) {\n            push32(expected, 0x00ff00ff);     // rect 1: solid\n        }\n        for (let i = 0; i < 16; i++) {\n            push32(expected, 0x00ff00ff);    // rect 2: same bkground color\n        }\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(new Uint8Array(expected));\n    });\n\n    it('should handle a tile with bg and coloured subrects', function () {\n        let data = [];\n        data.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects\n        push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color\n        data.push(2); // 2 subrects\n        data.push(0x00); // becomes 0000ff00 --> #0000FF fg color\n        data.push(0x00);\n        data.push(0xff);\n        data.push(0x00);\n        data.push(0); // x: 0, y: 0\n        data.push(1 | (1 << 4)); // width: 2, height: 2\n        data.push(0x00); // becomes 0000ff00 --> #0000FF fg color\n        data.push(0x00);\n        data.push(0xff);\n        data.push(0x00);\n        data.push(2 | (2 << 4)); // x: 2, y: 2\n        data.push(1 | (1 << 4)); // width: 2, height: 2\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should carry over fg and bg colors from the previous tile if not specified', function () {\n        display.resize(4, 17);\n\n        let data = [];\n        data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects\n        push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color\n        data.push(0x00); // becomes 0000ffff --> #0000FF fg color\n        data.push(0x00);\n        data.push(0xff);\n        data.push(0xff);\n        data.push(8); // 8 subrects\n        for (let i = 0; i < 4; i++) {\n            data.push((0 << 4) | (i * 4)); // x: 0, y: i*4\n            data.push(1 | (1 << 4)); // width: 2, height: 2\n            data.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2\n            data.push(1 | (1 << 4)); // width: 2, height: 2\n        }\n        data.push(0x08); // anysubrects\n        data.push(1); // 1 subrect\n        data.push(0); // x: 0, y: 0\n        data.push(1 | (1 << 4)); // width: 2, height: 2\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24);\n\n        let targetData = [\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ];\n\n        let expected = [];\n        for (let i = 0; i < 4; i++) {\n            expected = expected.concat(targetData);\n        }\n        expected = expected.concat(targetData.slice(0, 16));\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(new Uint8Array(expected));\n    });\n\n    it('should fail on an invalid subencoding', function () {\n        let data = [45];  // an invalid subencoding\n        expect(() => testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24)).to.throw();\n    });\n\n    it('should handle empty rects', function () {\n        display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n        display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.inflator.js",
    "content": "import { deflateInit, deflate, Z_FULL_FLUSH } from \"../vendor/pako/lib/zlib/deflate.js\";\nimport ZStream from \"../vendor/pako/lib/zlib/zstream.js\";\nimport Inflator from \"../core/inflator.js\";\n\nfunction _deflator(data) {\n    let strm = new ZStream();\n\n    deflateInit(strm, 5);\n\n    /* eslint-disable camelcase */\n    strm.input = data;\n    strm.avail_in = strm.input.length;\n    strm.next_in = 0;\n    /* eslint-enable camelcase */\n\n    let chunks = [];\n    let totalLen = 0;\n    while (strm.avail_in > 0) {\n        /* eslint-disable camelcase */\n        strm.output = new Uint8Array(1024 * 10 * 10);\n        strm.avail_out = strm.output.length;\n        strm.next_out = 0;\n        /* eslint-enable camelcase */\n\n        let ret = deflate(strm, Z_FULL_FLUSH);\n\n        // Check that return code is not an error\n        expect(ret).to.be.greaterThan(-1);\n\n        let chunk = new Uint8Array(strm.output.buffer, 0, strm.next_out);\n        totalLen += chunk.length;\n        chunks.push(chunk);\n    }\n\n    // Combine chunks into a single data\n\n    let outData = new Uint8Array(totalLen);\n    let offset = 0;\n\n    for (let i = 0; i < chunks.length; i++) {\n        outData.set(chunks[i], offset);\n        offset += chunks[i].length;\n    }\n\n    return outData;\n}\n\ndescribe('Inflate data', function () {\n\n    it('should be able to inflate messages', function () {\n        let inflator = new Inflator();\n\n        let text = \"123asdf\";\n        let preText = new Uint8Array(text.length);\n        for (let i = 0; i < preText.length; i++) {\n            preText[i] = text.charCodeAt(i);\n        }\n\n        let compText = _deflator(preText);\n\n        inflator.setInput(compText);\n        let inflatedText = inflator.inflate(preText.length);\n\n        expect(inflatedText).to.array.equal(preText);\n\n    });\n\n    it('should be able to inflate large messages', function () {\n        let inflator = new Inflator();\n\n        /* Generate a big string with random characters. Used because\n           repetition of letters might be deflated more effectively than\n           random ones. */\n        let text = \"\";\n        let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n        for (let i = 0; i < 300000; i++) {\n            text += characters.charAt(Math.floor(Math.random() * characters.length));\n        }\n\n        let preText = new Uint8Array(text.length);\n        for (let i = 0; i < preText.length; i++) {\n            preText[i] = text.charCodeAt(i);\n        }\n\n        let compText = _deflator(preText);\n\n        //Check that the compressed size is expected size\n        expect(compText.length).to.be.greaterThan((1024 * 10 * 10) * 2);\n\n        inflator.setInput(compText);\n        let inflatedText = inflator.inflate(preText.length);\n\n        expect(inflatedText).to.array.equal(preText);\n    });\n\n    it('should throw an error on insufficient data', function () {\n        let inflator = new Inflator();\n\n        let text = \"123asdf\";\n        let preText = new Uint8Array(text.length);\n        for (let i = 0; i < preText.length; i++) {\n            preText[i] = text.charCodeAt(i);\n        }\n\n        let compText = _deflator(preText);\n\n        inflator.setInput(compText);\n        expect(() => inflator.inflate(preText.length * 2)).to.throw();\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.int.js",
    "content": "import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js';\n\ndescribe('Integer casting', function () {\n    it('should cast unsigned to signed', function () {\n        let expected = 4294967286;\n        expect(toUnsigned32bit(-10)).to.equal(expected);\n    });\n\n    it('should cast signed to unsigned', function () {\n        let expected = -10;\n        expect(toSigned32bit(4294967286)).to.equal(expected);\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.jpeg.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport JPEGDecoder from '../core/decoders/jpeg.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\ndescribe('JPEG decoder', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        decoder = new JPEGDecoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(4, 4);\n    });\n\n    it('should handle JPEG rects', async function () {\n        let data = [\n            // JPEG data\n            0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,\n            0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,\n            0x01, 0x2c, 0x00, 0x42, 0xff, 0xdb, 0x00, 0x43,\n            0x00, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x03,\n            0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,\n            0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07,\n            0x07, 0x06, 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b,\n            0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d,\n            0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10,\n            0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,\n            0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15,\n            0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04,\n            0x04, 0x05, 0x04, 0x05, 0x09, 0x05, 0x05, 0x09,\n            0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0,\n            0x00, 0x11, 0x08, 0x00, 0x04, 0x00, 0x04, 0x03,\n            0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11,\n            0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01,\n            0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\n            0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,\n            0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00,\n            0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,\n            0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,\n            0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,\n            0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,\n            0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23,\n            0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24,\n            0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,\n            0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29,\n            0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,\n            0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,\n            0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,\n            0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,\n            0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,\n            0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,\n            0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,\n            0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,\n            0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,\n            0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,\n            0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,\n            0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3,\n            0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,\n            0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,\n            0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\n            0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,\n            0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00,\n            0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07,\n            0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00,\n            0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,\n            0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13,\n            0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1,\n            0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,\n            0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,\n            0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27,\n            0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,\n            0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,\n            0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,\n            0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,\n            0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,\n            0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,\n            0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,\n            0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,\n            0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,\n            0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,\n            0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,\n            0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,\n            0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,\n            0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,\n            0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,\n            0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9,\n            0xf7, 0xfb, 0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8,\n            0x3f, 0xf0, 0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d,\n            0x7e, 0x6f, 0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a,\n            0x8f, 0xfe, 0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd,\n            0xa7, 0xff, 0x00, 0x10, 0x77, 0x0d, 0xff, 0x00,\n            0x43, 0xec, 0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f,\n            0xff, 0xd9,\n        ];\n\n        let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n        expect(decodeDone).to.be.true;\n\n        let targetData = new Uint8Array([\n            0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255\n        ]);\n\n        // Browsers have rounding errors, so we need an approximate\n        // comparing function\n        function almost(a, b) {\n            let diff = Math.abs(a - b);\n            return diff < 5;\n        }\n\n        await display.flush();\n        expect(display).to.have.displayed(targetData, almost);\n    });\n\n    it('should handle JPEG rects without Huffman and quantification tables', async function () {\n        let data1 = [\n            // JPEG data\n            0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,\n            0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,\n            0x01, 0x2c, 0x00, 0x42, 0xff, 0xdb, 0x00, 0x43,\n            0x00, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x03,\n            0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05,\n            0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07,\n            0x07, 0x06, 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b,\n            0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d,\n            0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10,\n            0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f,\n            0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15,\n            0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04,\n            0x04, 0x05, 0x04, 0x05, 0x09, 0x05, 0x05, 0x09,\n            0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,\n            0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0,\n            0x00, 0x11, 0x08, 0x00, 0x04, 0x00, 0x04, 0x03,\n            0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11,\n            0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01,\n            0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\n            0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,\n            0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00,\n            0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,\n            0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,\n            0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,\n            0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,\n            0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23,\n            0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24,\n            0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,\n            0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29,\n            0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,\n            0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,\n            0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,\n            0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,\n            0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,\n            0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,\n            0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,\n            0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,\n            0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,\n            0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,\n            0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,\n            0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3,\n            0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,\n            0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,\n            0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\n            0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,\n            0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00,\n            0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07,\n            0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00,\n            0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,\n            0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13,\n            0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1,\n            0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,\n            0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1,\n            0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27,\n            0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,\n            0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,\n            0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,\n            0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,\n            0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,\n            0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,\n            0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,\n            0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,\n            0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,\n            0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,\n            0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,\n            0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,\n            0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,\n            0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,\n            0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00,\n            0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9,\n            0xf7, 0xfb, 0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8,\n            0x3f, 0xf0, 0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d,\n            0x7e, 0x6f, 0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a,\n            0x8f, 0xfe, 0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd,\n            0xa7, 0xff, 0x00, 0x10, 0x77, 0x0d, 0xff, 0x00,\n            0x43, 0xec, 0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f,\n            0xff, 0xd9,\n        ];\n\n        let decodeDone;\n\n        decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data1, display, 24);\n        expect(decodeDone).to.be.true;\n\n        display.fillRect(0, 0, 4, 4, [128, 128, 128, 255]);\n\n        let data2 = [\n            // JPEG data\n            0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,\n            0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c,\n            0x01, 0x2c, 0x00, 0x73, 0xff, 0xc0, 0x00, 0x11,\n            0x08, 0x00, 0x04, 0x00, 0x04, 0x03, 0x01, 0x11,\n            0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff,\n            0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11,\n            0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9, 0xf7, 0xfb,\n            0x67, 0x56, 0xff, 0x00, 0x9f, 0xf8, 0x3f, 0xf0,\n            0x51, 0xa7, 0xff, 0x00, 0xf2, 0x3d, 0x7e, 0x6f,\n            0xfd, 0xab, 0x94, 0x7f, 0xd0, 0x9a, 0x8f, 0xfe,\n            0x0d, 0xc7, 0x7f, 0xf3, 0x61, 0xfd, 0xa7, 0xff,\n            0x00, 0x10, 0x77, 0x0d, 0xff, 0x00, 0x43, 0xec,\n            0xcf, 0xff, 0x00, 0x0b, 0xab, 0x1f, 0xff, 0xd9,\n        ];\n\n        decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data2, display, 24);\n        expect(decodeDone).to.be.true;\n\n        let targetData = new Uint8Array([\n            0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255\n        ]);\n\n        // Browsers have rounding errors, so we need an approximate\n        // comparing function\n        function almost(a, b) {\n            let diff = Math.abs(a - b);\n            return diff < 5;\n        }\n\n        await display.flush();\n        expect(display).to.have.displayed(targetData, almost);\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.keyboard.js",
    "content": "import Keyboard from '../core/input/keyboard.js';\n\ndescribe('Key event handling', function () {\n    \"use strict\";\n\n    // The real KeyboardEvent constructor might not work everywhere we\n    // want to run these tests\n    function keyevent(typeArg, KeyboardEventInit) {\n        const e = { type: typeArg };\n        for (let key in KeyboardEventInit) {\n            e[key] = KeyboardEventInit[key];\n        }\n        e.stopPropagation = sinon.spy();\n        e.preventDefault = sinon.spy();\n        e.getModifierState = function (key) {\n            return e[key];\n        };\n\n        return e;\n    }\n\n    describe('Decode keyboard events', function () {\n        it('should decode keydown events', function (done) {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = (keysym, code, down) => {\n                expect(keysym).to.be.equal(0x61);\n                expect(code).to.be.equal('KeyA');\n                expect(down).to.be.equal(true);\n                done();\n            };\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));\n        });\n        it('should decode keyup events', function (done) {\n            let calls = 0;\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = (keysym, code, down) => {\n                expect(keysym).to.be.equal(0x61);\n                expect(code).to.be.equal('KeyA');\n                if (calls++ === 1) {\n                    expect(down).to.be.equal(false);\n                    done();\n                }\n            };\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));\n            kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));\n        });\n    });\n\n    describe('Fake keyup', function () {\n        it('should fake keyup events for virtual keyboards', function (done) {\n            let count = 0;\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = (keysym, code, down) => {\n                switch (count++) {\n                    case 0:\n                        expect(keysym).to.be.equal(0x61);\n                        expect(code).to.be.equal('Unidentified');\n                        expect(down).to.be.equal(true);\n                        break;\n                    case 1:\n                        expect(keysym).to.be.equal(0x61);\n                        expect(code).to.be.equal('Unidentified');\n                        expect(down).to.be.equal(false);\n                        done();\n                }\n            };\n            kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'}));\n        });\n    });\n\n    describe('Track key state', function () {\n        it('should send release using the same keysym as the press', function (done) {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = (keysym, code, down) => {\n                expect(keysym).to.be.equal(0x61);\n                expect(code).to.be.equal('KeyA');\n                if (!down) {\n                    done();\n                }\n            };\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));\n            kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));\n        });\n        it('should send the same keysym for multiple presses', function () {\n            let count = 0;\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = (keysym, code, down) => {\n                expect(keysym).to.be.equal(0x61);\n                expect(code).to.be.equal('KeyA');\n                expect(down).to.be.equal(true);\n                count++;\n            };\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'}));\n            expect(count).to.be.equal(2);\n        });\n        it('should do nothing on keyup events if no keys are down', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));\n            expect(kbd.onkeyevent).to.not.have.been.called;\n        });\n\n        describe('Legacy events', function () {\n            it('should track keys using keyCode if no code', function (done) {\n                const kbd = new Keyboard(document);\n                kbd.onkeyevent = (keysym, code, down) => {\n                    expect(keysym).to.be.equal(0x61);\n                    expect(code).to.be.equal('Platform65');\n                    if (!down) {\n                        done();\n                    }\n                };\n                kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'}));\n                kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'}));\n            });\n            it('should ignore compositing code', function () {\n                const kbd = new Keyboard(document);\n                kbd.onkeyevent = (keysym, code, down) => {\n                    expect(keysym).to.be.equal(0x61);\n                    expect(code).to.be.equal('Unidentified');\n                };\n                kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'}));\n            });\n            it('should track keys using keyIdentifier if no code', function (done) {\n                const kbd = new Keyboard(document);\n                kbd.onkeyevent = (keysym, code, down) => {\n                    expect(keysym).to.be.equal(0x61);\n                    expect(code).to.be.equal('Platform65');\n                    if (!down) {\n                        done();\n                    }\n                };\n                kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'}));\n                kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'}));\n            });\n        });\n    });\n\n    describe('Shuffle modifiers on macOS', function () {\n        let origNavigator;\n        beforeEach(function () {\n            // window.navigator is a protected read-only property in many\n            // environments, so we need to redefine it whilst running these\n            // tests.\n            origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n            Object.defineProperty(window, \"navigator\", {value: {}});\n            window.navigator.platform = \"Mac x86_64\";\n        });\n        afterEach(function () {\n            Object.defineProperty(window, \"navigator\", origNavigator);\n        });\n\n        it('should change Alt to AltGraph', function () {\n            let count = 0;\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = (keysym, code, down) => {\n                switch (count++) {\n                    case 0:\n                        expect(keysym).to.be.equal(0xFF7E);\n                        expect(code).to.be.equal('AltLeft');\n                        break;\n                    case 1:\n                        expect(keysym).to.be.equal(0xFE03);\n                        expect(code).to.be.equal('AltRight');\n                        break;\n                }\n            };\n            kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));\n            kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));\n            expect(count).to.be.equal(2);\n        });\n        it('should change left Super to Alt', function (done) {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = (keysym, code, down) => {\n                expect(keysym).to.be.equal(0xFFE9);\n                expect(code).to.be.equal('MetaLeft');\n                done();\n            };\n            kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1}));\n        });\n        it('should change right Super to left Super', function (done) {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = (keysym, code, down) => {\n                expect(keysym).to.be.equal(0xFFEB);\n                expect(code).to.be.equal('MetaRight');\n                done();\n            };\n            kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2}));\n        });\n    });\n\n    describe('Meta key combination on iOS and macOS', function () {\n        let origNavigator;\n        beforeEach(function () {\n            // window.navigator is a protected read-only property in many\n            // environments, so we need to redefine it whilst running these\n            // tests.\n            origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n            Object.defineProperty(window, \"navigator\", {value: {}});\n            if (window.navigator.platform !== undefined) {\n                // Object.defineProperty() doesn't work properly in old\n                // versions of Chrome\n                this.skip();\n            }\n        });\n\n        afterEach(function () {\n            if (origNavigator !== undefined) {\n                Object.defineProperty(window, \"navigator\", origNavigator);\n            }\n        });\n\n        it('should send keyup when meta key is pressed on iOS', function () {\n            window.navigator.platform = \"iPad\";\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n\n            kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            kbd.onkeyevent.resetHistory();\n\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a', metaKey: true}));\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0x61, \"KeyA\", true);\n            expect(kbd.onkeyevent).to.have.been.calledWith(0x61, \"KeyA\", false);\n            kbd.onkeyevent.resetHistory();\n\n            kbd._handleKeyUp(keyevent('keyup', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n        });\n\n        it('should send keyup when meta key is pressed on macOS', function () {\n            window.navigator.platform = \"Mac\";\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n\n            kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            kbd.onkeyevent.resetHistory();\n\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a', metaKey: true}));\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0x61, \"KeyA\", true);\n            expect(kbd.onkeyevent).to.have.been.calledWith(0x61, \"KeyA\", false);\n            kbd.onkeyevent.resetHistory();\n\n            kbd._handleKeyUp(keyevent('keyup', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n        });\n    });\n\n    describe('Caps Lock on iOS and macOS', function () {\n        let origNavigator;\n        beforeEach(function () {\n            // window.navigator is a protected read-only property in many\n            // environments, so we need to redefine it whilst running these\n            // tests.\n            origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n            Object.defineProperty(window, \"navigator\", {value: {}});\n        });\n\n        afterEach(function () {\n            Object.defineProperty(window, \"navigator\", origNavigator);\n        });\n\n        it('should toggle caps lock on key press on iOS', function () {\n            window.navigator.platform = \"iPad\";\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));\n\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", true);\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", false);\n        });\n\n        it('should toggle caps lock on key press on mac', function () {\n            window.navigator.platform = \"Mac\";\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));\n\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", true);\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", false);\n        });\n\n        it('should toggle caps lock on key release on iOS', function () {\n            window.navigator.platform = \"iPad\";\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));\n\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", true);\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", false);\n        });\n\n        it('should toggle caps lock on key release on mac', function () {\n            window.navigator.platform = \"Mac\";\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));\n\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", true);\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", false);\n        });\n    });\n\n    describe('Modifier status info', function () {\n        let origNavigator;\n        beforeEach(function () {\n            // window.navigator is a protected read-only property in many\n            // environments, so we need to redefine it whilst running these\n            // tests.\n            origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n            Object.defineProperty(window, \"navigator\", {value: {}});\n        });\n\n        afterEach(function () {\n            Object.defineProperty(window, \"navigator\", origNavigator);\n        });\n\n        it('should provide caps lock state', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));\n\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, \"KeyA\", true, false, true);\n        });\n\n        it('should provide num lock state', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: true, CapsLock: false}));\n\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, \"KeyA\", true, true, false);\n        });\n\n        it('should have no num lock state on mac', function () {\n            window.navigator.platform = \"Mac\";\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));\n\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, \"KeyA\", true, null, true);\n        });\n    });\n\n    describe('Japanese IM keys on Windows', function () {\n        let origNavigator;\n        beforeEach(function () {\n            // window.navigator is a protected read-only property in many\n            // environments, so we need to redefine it whilst running these\n            // tests.\n            origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n            Object.defineProperty(window, \"navigator\", {value: {}});\n            window.navigator.platform = \"Windows\";\n        });\n\n        afterEach(function () {\n            Object.defineProperty(window, \"navigator\", origNavigator);\n        });\n\n        const keys = { 'Zenkaku': 0xff2a, 'Hankaku': 0xff2a,\n                       'Alphanumeric': 0xff30, 'Katakana': 0xff26,\n                       'Hiragana': 0xff25, 'Romaji': 0xff24,\n                       'KanaMode': 0xff24 };\n        for (let [key, keysym] of Object.entries(keys)) {\n            it(`should fake key release for ${key} on Windows`, function () {\n                let kbd = new Keyboard(document);\n                kbd.onkeyevent = sinon.spy();\n                kbd._handleKeyDown(keyevent('keydown', {code: 'FakeIM', key: key}));\n\n                expect(kbd.onkeyevent).to.have.been.calledTwice;\n                expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(keysym, \"FakeIM\", true);\n                expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(keysym, \"FakeIM\", false);\n            });\n        }\n    });\n\n    describe('Escape AltGraph on Windows', function () {\n        let origNavigator;\n        beforeEach(function () {\n            // window.navigator is a protected read-only property in many\n            // environments, so we need to redefine it whilst running these\n            // tests.\n            origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n            Object.defineProperty(window, \"navigator\", {value: {}});\n            window.navigator.platform = \"Windows x86_64\";\n\n            this.clock = sinon.useFakeTimers();\n        });\n        afterEach(function () {\n            Object.defineProperty(window, \"navigator\", origNavigator);\n            if (this.clock !== undefined) {\n                this.clock.restore();\n            }\n        });\n\n        it('should supress ControlLeft until it knows if it is AltGr', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));\n            expect(kbd.onkeyevent).to.not.have.been.called;\n        });\n\n        it('should not trigger on repeating ControlLeft', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, \"ControlLeft\", true);\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, \"ControlLeft\", true);\n        });\n\n        it('should not supress ControlRight', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlRight', key: 'Control', location: 2}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe4, \"ControlRight\", true);\n        });\n\n        it('should release ControlLeft after 100 ms', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));\n            expect(kbd.onkeyevent).to.not.have.been.called;\n            this.clock.tick(100);\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe3, \"ControlLeft\", true);\n        });\n\n        it('should release ControlLeft on other key press', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));\n            expect(kbd.onkeyevent).to.not.have.been.called;\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, \"ControlLeft\", true);\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0x61, \"KeyA\", true);\n\n            // Check that the timer is properly dead\n            kbd.onkeyevent.resetHistory();\n            this.clock.tick(100);\n            expect(kbd.onkeyevent).to.not.have.been.called;\n        });\n\n        it('should release ControlLeft on other key release', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x61, \"KeyA\", true);\n            kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));\n            expect(kbd.onkeyevent).to.have.been.calledThrice;\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, \"ControlLeft\", true);\n            expect(kbd.onkeyevent.thirdCall).to.have.been.calledWith(0x61, \"KeyA\", false);\n\n            // Check that the timer is properly dead\n            kbd.onkeyevent.resetHistory();\n            this.clock.tick(100);\n            expect(kbd.onkeyevent).to.not.have.been.called;\n        });\n\n        it('should release ControlLeft on blur', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));\n            expect(kbd.onkeyevent).to.not.have.been.called;\n            kbd._allKeysUp();\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, \"ControlLeft\", true);\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, \"ControlLeft\", false);\n\n            // Check that the timer is properly dead\n            kbd.onkeyevent.resetHistory();\n            this.clock.tick(100);\n            expect(kbd.onkeyevent).to.not.have.been.called;\n        });\n\n        it('should generate AltGraph for quick Ctrl+Alt sequence', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));\n            this.clock.tick(20);\n            kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);\n\n            // Check that the timer is properly dead\n            kbd.onkeyevent.resetHistory();\n            this.clock.tick(100);\n            expect(kbd.onkeyevent).to.not.have.been.called;\n        });\n\n        it('should generate Ctrl, Alt for slow Ctrl+Alt sequence', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));\n            this.clock.tick(60);\n            kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, \"ControlLeft\", true);\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffea, \"AltRight\", true);\n\n            // Check that the timer is properly dead\n            kbd.onkeyevent.resetHistory();\n            this.clock.tick(100);\n            expect(kbd.onkeyevent).to.not.have.been.called;\n        });\n\n        it('should generate AltGraph for quick Ctrl+AltGraph sequence', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));\n            this.clock.tick(20);\n            kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2, timeStamp: Date.now()}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);\n\n            // Check that the timer is properly dead\n            kbd.onkeyevent.resetHistory();\n            this.clock.tick(100);\n            expect(kbd.onkeyevent).to.not.have.been.called;\n        });\n\n        it('should generate Ctrl, AltGraph for slow Ctrl+AltGraph sequence', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));\n            this.clock.tick(60);\n            kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2, timeStamp: Date.now()}));\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, \"ControlLeft\", true);\n            expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xfe03, \"AltRight\", true);\n\n            // Check that the timer is properly dead\n            kbd.onkeyevent.resetHistory();\n            this.clock.tick(100);\n            expect(kbd.onkeyevent).to.not.have.been.called;\n        });\n\n        it('should pass through single Alt', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffea, 'AltRight', true);\n        });\n\n        it('should pass through single AltGr', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n            kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);\n        });\n    });\n\n    describe('Missing Shift keyup on Windows', function () {\n        let origNavigator;\n        beforeEach(function () {\n            // window.navigator is a protected read-only property in many\n            // environments, so we need to redefine it whilst running these\n            // tests.\n            origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n            Object.defineProperty(window, \"navigator\", {value: {}});\n            window.navigator.platform = \"Windows x86_64\";\n\n            this.clock = sinon.useFakeTimers();\n        });\n        afterEach(function () {\n            Object.defineProperty(window, \"navigator\", origNavigator);\n            if (this.clock !== undefined) {\n                this.clock.restore();\n            }\n        });\n\n        it('should fake a left Shift keyup', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);\n            kbd.onkeyevent.resetHistory();\n\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);\n            kbd.onkeyevent.resetHistory();\n\n            kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftLeft', key: 'Shift', location: 1}));\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);\n        });\n\n        it('should fake a right Shift keyup', function () {\n            const kbd = new Keyboard(document);\n            kbd.onkeyevent = sinon.spy();\n\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);\n            kbd.onkeyevent.resetHistory();\n\n            kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));\n            expect(kbd.onkeyevent).to.have.been.calledOnce;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);\n            kbd.onkeyevent.resetHistory();\n\n            kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftRight', key: 'Shift', location: 2}));\n            expect(kbd.onkeyevent).to.have.been.calledTwice;\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);\n            expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);\n        });\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.localization.js",
    "content": "import _, { Localizer, l10n } from '../app/localization.js';\n\ndescribe('Localization', function () {\n    \"use strict\";\n\n    let origNavigator;\n    let fetch;\n\n    beforeEach(function () {\n        // window.navigator is a protected read-only property in many\n        // environments, so we need to redefine it whilst running these\n        // tests.\n        origNavigator = Object.getOwnPropertyDescriptor(window, \"navigator\");\n\n        Object.defineProperty(window, \"navigator\", {value: {}});\n        window.navigator.languages = [];\n\n        fetch = sinon.stub(window, \"fetch\");\n        fetch.resolves(new Response(\"{}\"));\n    });\n    afterEach(function () {\n        fetch.restore();\n\n        Object.defineProperty(window, \"navigator\", origNavigator);\n    });\n\n    describe('Singleton', function () {\n        it('should export a singleton object', function () {\n            expect(l10n).to.be.instanceOf(Localizer);\n        });\n        it('should export a singleton translation function', async function () {\n            // FIXME: Can we use some spy instead?\n            window.navigator.languages = [\"de\"];\n            fetch.resolves(new Response(JSON.stringify({ \"Foobar\": \"gazonk\" })));\n            await l10n.setup([\"de\"]);\n            expect(_(\"Foobar\")).to.equal(\"gazonk\");\n        });\n    });\n\n    describe('language selection', function () {\n        it('should use English by default', function () {\n            let lclz = new Localizer();\n            expect(lclz.language).to.equal('en');\n        });\n        it('should use English if no user language matches', async function () {\n            window.navigator.languages = [\"nl\", \"de\"];\n            let lclz = new Localizer();\n            await lclz.setup([\"es\", \"fr\"]);\n            expect(lclz.language).to.equal('en');\n        });\n        it('should fall back to generic English for other English', async function () {\n            window.navigator.languages = [\"en-AU\", \"de\"];\n            let lclz = new Localizer();\n            await lclz.setup([\"de\", \"fr\", \"en-GB\"]);\n            expect(lclz.language).to.equal('en');\n        });\n        it('should prefer specific English over generic', async function () {\n            window.navigator.languages = [\"en-GB\", \"de\"];\n            let lclz = new Localizer();\n            await lclz.setup([\"de\", \"en-AU\", \"en-GB\"]);\n            expect(lclz.language).to.equal('en-GB');\n        });\n        it('should use the most preferred user language', async function () {\n            window.navigator.languages = [\"nl\", \"de\", \"fr\"];\n            let lclz = new Localizer();\n            await lclz.setup([\"es\", \"fr\", \"de\"]);\n            expect(lclz.language).to.equal('de');\n        });\n        it('should prefer sub-languages languages', async function () {\n            window.navigator.languages = [\"pt-BR\"];\n            let lclz = new Localizer();\n            await lclz.setup([\"pt\", \"pt-BR\"]);\n            expect(lclz.language).to.equal('pt-BR');\n        });\n        it('should fall back to language \"parents\"', async function () {\n            window.navigator.languages = [\"pt-BR\"];\n            let lclz = new Localizer();\n            await lclz.setup([\"fr\", \"pt\", \"de\"]);\n            expect(lclz.language).to.equal('pt');\n        });\n        it('should not use specific language when user asks for a generic language', async function () {\n            window.navigator.languages = [\"pt\", \"de\"];\n            let lclz = new Localizer();\n            await lclz.setup([\"fr\", \"pt-BR\", \"de\"]);\n            expect(lclz.language).to.equal('de');\n        });\n        it('should handle underscore as a separator', async function () {\n            window.navigator.languages = [\"pt-BR\"];\n            let lclz = new Localizer();\n            await lclz.setup([\"pt_BR\"]);\n            expect(lclz.language).to.equal('pt_BR');\n        });\n        it('should handle difference in case', async function () {\n            window.navigator.languages = [\"pt-br\"];\n            let lclz = new Localizer();\n            await lclz.setup([\"pt-BR\"]);\n            expect(lclz.language).to.equal('pt-BR');\n        });\n    });\n\n    describe('Translation loading', function () {\n        it('should not fetch a translation for English', async function () {\n            window.navigator.languages = [];\n            let lclz = new Localizer();\n            await lclz.setup([]);\n            expect(fetch).to.not.have.been.called;\n        });\n        it('should fetch dictionary relative base URL', async function () {\n            window.navigator.languages = [\"de\", \"fr\"];\n            fetch.resolves(new Response('{ \"Foobar\": \"gazonk\" }'));\n            let lclz = new Localizer();\n            await lclz.setup([\"ru\", \"fr\"], \"/some/path/\");\n            expect(fetch).to.have.been.calledOnceWith(\"/some/path/fr.json\");\n            expect(lclz.get(\"Foobar\")).to.equal(\"gazonk\");\n        });\n        it('should handle base URL without trailing slash', async function () {\n            window.navigator.languages = [\"de\", \"fr\"];\n            fetch.resolves(new Response('{ \"Foobar\": \"gazonk\" }'));\n            let lclz = new Localizer();\n            await lclz.setup([\"ru\", \"fr\"], \"/some/path\");\n            expect(fetch).to.have.been.calledOnceWith(\"/some/path/fr.json\");\n            expect(lclz.get(\"Foobar\")).to.equal(\"gazonk\");\n        });\n        it('should handle current base URL', async function () {\n            window.navigator.languages = [\"de\", \"fr\"];\n            fetch.resolves(new Response('{ \"Foobar\": \"gazonk\" }'));\n            let lclz = new Localizer();\n            await lclz.setup([\"ru\", \"fr\"]);\n            expect(fetch).to.have.been.calledOnceWith(\"fr.json\");\n            expect(lclz.get(\"Foobar\")).to.equal(\"gazonk\");\n        });\n        it('should fail if dictionary cannot be found', async function () {\n            window.navigator.languages = [\"de\", \"fr\"];\n            fetch.resolves(new Response('{}', { status: 404 }));\n            let lclz = new Localizer();\n            let ok = false;\n            try {\n                await lclz.setup([\"ru\", \"fr\"], \"/some/path/\");\n            } catch (e) {\n                ok = true;\n            }\n            expect(ok).to.be.true;\n        });\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.raw.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport RawDecoder from '../core/decoders/raw.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\ndescribe('Raw decoder', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        decoder = new RawDecoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(4, 4);\n    });\n\n    it('should handle the Raw encoding', function () {\n        let done;\n\n        done = testDecodeRect(decoder, 0, 0, 2, 2,\n                              [0xff, 0x00, 0x00, 0,\n                               0x00, 0xff, 0x00, 0,\n                               0x00, 0xff, 0x00, 0,\n                               0xff, 0x00, 0x00, 0],\n                              display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 0, 2, 2,\n                              [0x00, 0x00, 0xff, 0,\n                               0x00, 0x00, 0xff, 0,\n                               0x00, 0x00, 0xff, 0,\n                               0x00, 0x00, 0xff, 0],\n                              display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 2, 4, 1,\n                              [0xee, 0x00, 0xff, 0,\n                               0x00, 0xee, 0xff, 0,\n                               0xaa, 0xee, 0xff, 0,\n                               0xab, 0xee, 0xff, 0],\n                              display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 3, 4, 1,\n                              [0xee, 0x00, 0xff, 0,\n                               0x00, 0xee, 0xff, 0,\n                               0xaa, 0xee, 0xff, 0,\n                               0xab, 0xee, 0xff, 0],\n                              display, 24);\n        expect(done).to.be.true;\n\n        let targetData = new Uint8Array([\n            0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,\n            0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255\n        ]);\n\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle the Raw encoding in low colour mode', function () {\n        let done;\n\n        done = testDecodeRect(decoder, 0, 0, 2, 2,\n                              [0x30, 0x30, 0x30, 0x30],\n                              display, 8);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 0, 2, 2,\n                              [0x0c, 0x0c, 0x0c, 0x0c],\n                              display, 8);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 2, 4, 1,\n                              [0x0c, 0x0c, 0x30, 0x30],\n                              display, 8);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 3, 4, 1,\n                              [0x0c, 0x0c, 0x30, 0x30],\n                              display, 8);\n        expect(done).to.be.true;\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle empty rects', function () {\n        display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n        display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle empty rects in low colour mode', function () {\n        display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n        display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 8);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.rfb.js",
    "content": "import RFB from '../core/rfb.js';\nimport Websock from '../core/websock.js';\nimport ZStream from \"../vendor/pako/lib/zlib/zstream.js\";\nimport { deflateInit, deflate, Z_DEFAULT_COMPRESSION } from \"../vendor/pako/lib/zlib/deflate.js\";\nimport { encodings } from '../core/encodings.js';\nimport { toUnsigned32bit } from '../core/util/int.js';\nimport { encodeUTF8 } from '../core/util/strings.js';\nimport KeyTable from '../core/input/keysym.js';\nimport legacyCrypto from '../core/crypto/crypto.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction push8(arr, num) {\n    \"use strict\";\n    arr.push(num & 0xFF);\n}\n\nfunction push16(arr, num) {\n    \"use strict\";\n    arr.push((num >> 8) & 0xFF,\n             num & 0xFF);\n}\n\nfunction push32(arr, num) {\n    \"use strict\";\n    arr.push((num >> 24) & 0xFF,\n             (num >> 16) & 0xFF,\n             (num >>  8) & 0xFF,\n             num & 0xFF);\n}\n\nfunction pushString(arr, string) {\n    let utf8 = unescape(encodeURIComponent(string));\n    for (let i = 0; i < utf8.length; i++) {\n        arr.push(utf8.charCodeAt(i));\n    }\n}\n\nfunction deflateWithSize(data) {\n    // Adds the size of the string in front before deflating\n\n    let unCompData = [];\n    unCompData.push((data.length >> 24) & 0xFF,\n                    (data.length >> 16) & 0xFF,\n                    (data.length >>  8) & 0xFF,\n                    (data.length & 0xFF));\n\n    for (let i = 0; i < data.length; i++) {\n        unCompData.push(data.charCodeAt(i));\n    }\n\n    let strm = new ZStream();\n    let chunkSize = 1024 * 10 * 10;\n    strm.output = new Uint8Array(chunkSize);\n    deflateInit(strm, Z_DEFAULT_COMPRESSION);\n\n    /* eslint-disable camelcase */\n    strm.input = unCompData;\n    strm.avail_in = strm.input.length;\n    strm.next_in = 0;\n    strm.next_out = 0;\n    strm.avail_out = chunkSize;\n    /* eslint-enable camelcase */\n\n    deflate(strm, 3);\n\n    return new Uint8Array(strm.output.buffer, 0, strm.next_out);\n}\n\ndescribe('Remote Frame Buffer protocol client', function () {\n    let clock;\n    let raf;\n    let fakeResizeObserver = null;\n    const realObserver = window.ResizeObserver;\n\n    // Since we are using fake timers we don't actually want\n    // to wait for the browser to observe the size change,\n    // that's why we use a fake ResizeObserver\n    class FakeResizeObserver {\n        constructor(handler) {\n            this.fire = handler;\n            fakeResizeObserver = this;\n        }\n        disconnect() {}\n        observe(target, options) {}\n        unobserve(target) {}\n    }\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    before(function () {\n        this.clock = clock = sinon.useFakeTimers(Date.now());\n        // sinon doesn't support this yet\n        raf = window.requestAnimationFrame;\n        window.requestAnimationFrame = setTimeout;\n        // We must do this in a 'before' since it needs to be set before\n        // the RFB constructor, which runs in beforeEach further down\n        window.ResizeObserver = FakeResizeObserver;\n        // Use a single set of buffers instead of reallocating to\n        // speed up tests\n        const sock = new Websock();\n        const _sQ = new Uint8Array(sock._sQbufferSize);\n        const rQ = new Uint8Array(sock._rQbufferSize);\n\n        Websock.prototype._oldAllocateBuffers = Websock.prototype._allocateBuffers;\n        Websock.prototype._allocateBuffers = function () {\n            this._sQ = _sQ;\n            this._rQ = rQ;\n        };\n\n        // Avoiding printing the entire Websock buffer on errors\n        Websock.prototype.inspect = function () { return \"[object Websock]\"; };\n    });\n\n    after(function () {\n        Websock.prototype._allocateBuffers = Websock.prototype._oldAllocateBuffers;\n        delete Websock.prototype.inspect;\n        this.clock.restore();\n        window.requestAnimationFrame = raf;\n        window.ResizeObserver = realObserver;\n    });\n\n    let container;\n    let rfbs;\n\n    beforeEach(function () {\n        // Create a container element for all RFB objects to attach to\n        container = document.createElement('div');\n        container.style.width = \"100%\";\n        container.style.height = \"100%\";\n        document.body.appendChild(container);\n\n        // And track all created RFB objects\n        rfbs = [];\n    });\n    afterEach(function () {\n        // Make sure every created RFB object is properly cleaned up\n        // or they might affect subsequent tests\n        rfbs.forEach(function (rfb) {\n            rfb.disconnect();\n            expect(rfb._disconnect).to.have.been.called;\n        });\n        rfbs = [];\n\n        document.body.removeChild(container);\n        container = null;\n    });\n\n    function makeRFB(url, options) {\n        url = url || 'wss://host:8675';\n        const rfb = new RFB(container, url, options);\n        clock.tick();\n        rfb._sock._websocket._open();\n        rfb._rfbConnectionState = 'connected';\n        sinon.spy(rfb, \"_disconnect\");\n        rfbs.push(rfb);\n        return rfb;\n    }\n\n    function elementToClient(x, y, client) {\n        let res = { x: 0, y: 0 };\n\n        let bounds = client._canvas.getBoundingClientRect();\n\n        /*\n         * If the canvas is on a fractional position we will calculate\n         * a fractional mouse position. But that gets truncated when we\n         * send the event, AND the same thing happens in RFB when it\n         * generates the PointerEvent message. To compensate for that\n         * fact we round the value upwards here.\n         */\n        res.x = Math.ceil(bounds.left + x);\n        res.y = Math.ceil(bounds.top + y);\n\n        return res;\n    }\n\n    function sendMouseMoveEvent(x, y, buttons, client) {\n        let pos = elementToClient(x, y, client);\n        let ev;\n\n        ev = new MouseEvent('mousemove',\n                            { 'screenX': pos.x + window.screenX,\n                              'screenY': pos.y + window.screenY,\n                              'clientX': pos.x,\n                              'clientY': pos.y,\n                              'buttons': buttons });\n        client._canvas.dispatchEvent(ev);\n    }\n\n    function sendMouseButtonEvent(x, y, down, buttons, client) {\n        let pos = elementToClient(x, y, client);\n        let ev;\n\n        ev = new MouseEvent(down ? 'mousedown' : 'mouseup',\n                            { 'screenX': pos.x + window.screenX,\n                              'screenY': pos.y + window.screenY,\n                              'clientX': pos.x,\n                              'clientY': pos.y,\n                              'buttons': buttons});\n        client._canvas.dispatchEvent(ev);\n    }\n\n    function gestureStart(gestureType, x, y, client,\n                          magnitudeX = 0, magnitudeY = 0) {\n        let pos = elementToClient(x, y, client);\n        let detail = { type: gestureType, clientX: pos.x, clientY: pos.y };\n\n        detail.magnitudeX = magnitudeX;\n        detail.magnitudeY = magnitudeY;\n\n        let ev = new CustomEvent('gesturestart', { detail: detail });\n        client._canvas.dispatchEvent(ev);\n    }\n\n    function gestureMove(gestureType, x, y, client,\n                         magnitudeX = 0, magnitudeY = 0) {\n        let pos = elementToClient(x, y, client);\n        let detail = { type: gestureType, clientX: pos.x, clientY: pos.y };\n\n        detail.magnitudeX = magnitudeX;\n        detail.magnitudeY = magnitudeY;\n\n        let ev = new CustomEvent('gesturemove', { detail: detail }, client);\n        client._canvas.dispatchEvent(ev);\n    }\n\n    function gestureEnd(gestureType, x, y, client) {\n        let pos = elementToClient(x, y, client);\n        let detail = { type: gestureType, clientX: pos.x, clientY: pos.y };\n        let ev = new CustomEvent('gestureend', { detail: detail });\n        client._canvas.dispatchEvent(ev);\n    }\n\n    function sendFbuMsg(rectInfo, rectData, client, rectCnt) {\n        let data = [];\n\n        if (!rectCnt || rectCnt > -1) {\n            // header\n            data.push(0);  // msg type\n            data.push(0);  // padding\n            push16(data, rectCnt || rectData.length);\n        }\n\n        for (let i = 0; i < rectData.length; i++) {\n            if (rectInfo[i]) {\n                push16(data, rectInfo[i].x);\n                push16(data, rectInfo[i].y);\n                push16(data, rectInfo[i].width);\n                push16(data, rectInfo[i].height);\n                push32(data, rectInfo[i].encoding);\n            }\n            data = data.concat(rectData[i]);\n        }\n\n        client._sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    function sendExtendedDesktopSize(client, reason, result, width, height, screenId, screenFlags) {\n        let rectInfo = { x: reason, y: result, width: width, height: height, encoding: -308 };\n        let rectData = [\n            0x01,        // number of screens = 1\n            0x00, 0x00,\n            0x00,        // padding\n            (screenId >> 24) & 0xff,\n            (screenId >> 16) & 0xff,\n            (screenId >> 8) & 0xff,\n            screenId & 0xff,\n            0x00, 0x00,  // screen x\n            0x00, 0x00,  // screen y\n            (width >> 8) & 0xff,\n            width & 0xff,\n            (height >> 8) & 0xff,\n            height & 0xff,\n            (screenFlags >> 24) & 0xff,\n            (screenFlags >> 16) & 0xff,\n            (screenFlags >> 8) & 0xff,\n            screenFlags & 0xff];\n        sendFbuMsg([rectInfo], [rectData], client);\n    }\n\n    describe('Connecting/Disconnecting', function () {\n        describe('#RFB (constructor)', function () {\n            let open, attach;\n            beforeEach(function () {\n                open = sinon.spy(Websock.prototype, 'open');\n                attach = sinon.spy(Websock.prototype, 'attach');\n            });\n            afterEach(function () {\n                open.restore();\n                attach.restore();\n            });\n\n            it('should actually connect to the websocket', function () {\n                new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');\n                expect(open).to.have.been.calledOnceWithExactly('ws://HOST:8675/PATH', []);\n            });\n\n            it('should pass on connection problems', function () {\n                open.restore();\n                open = sinon.stub(Websock.prototype, 'open');\n                open.throws(new Error('Failure'));\n                expect(() => new RFB(document.createElement('div'), 'ws://HOST:8675/PATH')).to.throw('Failure');\n            });\n\n            it('should handle WebSocket/RTCDataChannel objects', function () {\n                let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);\n                new RFB(document.createElement('div'), sock);\n                expect(open).to.not.have.been.called;\n                expect(attach).to.have.been.calledOnceWithExactly(sock);\n            });\n\n            it('should handle already open WebSocket/RTCDataChannel objects', function () {\n                let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);\n                sock._open();\n                const client = new RFB(document.createElement('div'), sock);\n                let callback = sinon.spy();\n                client.addEventListener('disconnect', callback);\n                expect(open).to.not.have.been.called;\n                expect(attach).to.have.been.calledOnceWithExactly(sock);\n                // Check if it is ready for some data\n                sock._receiveData(new Uint8Array(['R', 'F', 'B', '0', '0', '3', '0', '0', '8']));\n                expect(callback).to.not.have.been.called;\n            });\n\n            it('should refuse closed WebSocket/RTCDataChannel objects', function () {\n                let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);\n                sock.readyState = WebSocket.CLOSED;\n                expect(() => new RFB(document.createElement('div'), sock)).to.throw();\n            });\n\n            it('should pass on attach problems', function () {\n                attach.restore();\n                attach = sinon.stub(Websock.prototype, 'attach');\n                attach.throws(new Error('Failure'));\n                let sock = new FakeWebSocket('ws://HOST:8675/PATH', []);\n                expect(() => new RFB(document.createElement('div'), sock)).to.throw('Failure');\n            });\n        });\n\n        describe('#disconnect', function () {\n            let client;\n            let close;\n\n            beforeEach(function () {\n                client = makeRFB();\n                close = sinon.stub(Websock.prototype, \"close\");\n            });\n            afterEach(function () {\n                close.restore();\n            });\n\n            it('should start closing WebSocket', function () {\n                let callback = sinon.spy();\n                client.addEventListener('disconnect', callback);\n                client.disconnect();\n                expect(close).to.have.been.calledOnceWithExactly();\n                expect(callback).to.not.have.been.called;\n            });\n\n            it('should send disconnect event', function () {\n                let callback = sinon.spy();\n                client.addEventListener('disconnect', callback);\n                client.disconnect();\n                close.thisValues[0]._eventHandlers.close(new CloseEvent(\"close\", { 'code': 1000, 'reason': \"\", 'wasClean': true }));\n                expect(callback).to.have.been.calledOnce;\n                expect(callback.args[0][0].detail.clean).to.be.true;\n            });\n\n            it('should force disconnect if disconnecting takes too long', function () {\n                let callback = sinon.spy();\n                client.addEventListener('disconnect', callback);\n                client.disconnect();\n                this.clock.tick(3 * 1000);\n                expect(callback).to.have.been.calledOnce;\n                expect(callback.args[0][0].detail.clean).to.be.true;\n            });\n\n            it('should not fail if disconnect completes before timeout', function () {\n                let callback = sinon.spy();\n                client.addEventListener('disconnect', callback);\n                client.disconnect();\n                client._updateConnectionState('disconnecting');\n                this.clock.tick(3 * 1000 / 2);\n                close.thisValues[0]._eventHandlers.close(new CloseEvent(\"close\", { 'code': 1000, 'reason': \"\", 'wasClean': true }));\n                this.clock.tick(3 * 1000 / 2 + 1);\n                expect(callback).to.have.been.calledOnce;\n                expect(callback.args[0][0].detail.clean).to.be.true;\n            });\n\n            it('should unregister error event handler', function () {\n                sinon.spy(client._sock, 'off');\n                client.disconnect();\n                expect(client._sock.off).to.have.been.calledWith('error');\n            });\n\n            it('should unregister message event handler', function () {\n                sinon.spy(client._sock, 'off');\n                client.disconnect();\n                expect(client._sock.off).to.have.been.calledWith('message');\n            });\n\n            it('should unregister open event handler', function () {\n                sinon.spy(client._sock, 'off');\n                client.disconnect();\n                expect(client._sock.off).to.have.been.calledWith('open');\n            });\n        });\n    });\n\n    describe('Public API basic behavior', function () {\n        let client;\n        beforeEach(function () {\n            client = makeRFB();\n        });\n\n        describe('#sendCtrlAlDel', function () {\n            it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {\n                let esock = new Websock();\n                let ews = new FakeWebSocket();\n                ews._open();\n                esock.attach(ews);\n                RFB.messages.keyEvent(esock, 0xFFE3, 1);\n                RFB.messages.keyEvent(esock, 0xFFE9, 1);\n                RFB.messages.keyEvent(esock, 0xFFFF, 1);\n                RFB.messages.keyEvent(esock, 0xFFFF, 0);\n                RFB.messages.keyEvent(esock, 0xFFE9, 0);\n                RFB.messages.keyEvent(esock, 0xFFE3, 0);\n                let expected = ews._getSentData();\n\n                client.sendCtrlAltDel();\n\n                expect(client._sock).to.have.sent(expected);\n            });\n\n            it('should not send the keys if we are not in a normal state', function () {\n                client._rfbConnectionState = \"connecting\";\n                client.sendCtrlAltDel();\n                expect(client._sock).to.have.sent(new Uint8Array([]));\n            });\n\n            it('should not send the keys if we are set as view_only', function () {\n                client._viewOnly = true;\n                client.sendCtrlAltDel();\n                expect(client._sock).to.have.sent(new Uint8Array([]));\n            });\n        });\n\n        describe('#sendKey', function () {\n            it('should send a single key with the given code and state (down = true)', function () {\n                let esock = new Websock();\n                let ews = new FakeWebSocket();\n                ews._open();\n                esock.attach(ews);\n                RFB.messages.keyEvent(esock, 123, 1);\n                let expected = ews._getSentData();\n\n                client.sendKey(123, 'Key123', true);\n\n                expect(client._sock).to.have.sent(expected);\n            });\n\n            it('should send both a down and up event if the state is not specified', function () {\n                let esock = new Websock();\n                let ews = new FakeWebSocket();\n                ews._open();\n                esock.attach(ews);\n                RFB.messages.keyEvent(esock, 123, 1);\n                RFB.messages.keyEvent(esock, 123, 0);\n                let expected = ews._getSentData();\n\n                client.sendKey(123, 'Key123');\n\n                expect(client._sock).to.have.sent(expected);\n            });\n\n            it('should not send the key if we are not in a normal state', function () {\n                client._rfbConnectionState = \"connecting\";\n                client.sendKey(123, 'Key123');\n                expect(client._sock).to.have.sent(new Uint8Array([]));\n            });\n\n            it('should not send the key if we are set as view_only', function () {\n                client._viewOnly = true;\n                client.sendKey(123, 'Key123');\n                expect(client._sock).to.have.sent(new Uint8Array([]));\n            });\n\n            it('should send QEMU extended events if supported', function () {\n                client._qemuExtKeyEventSupported = true;\n                let esock = new Websock();\n                let ews = new FakeWebSocket();\n                ews._open();\n                esock.attach(ews);\n                RFB.messages.QEMUExtendedKeyEvent(esock, 0x20, true, 0x0039);\n                let expected = ews._getSentData();\n\n                client.sendKey(0x20, 'Space', true);\n\n                expect(client._sock).to.have.sent(expected);\n            });\n\n            it('should not send QEMU extended events if unknown key code', function () {\n                client._qemuExtKeyEventSupported = true;\n                let esock = new Websock();\n                let ews = new FakeWebSocket();\n                ews._open();\n                esock.attach(ews);\n                RFB.messages.keyEvent(esock, 123, 1);\n                let expected = ews._getSentData();\n\n                client.sendKey(123, 'FooBar', true);\n\n                expect(client._sock).to.have.sent(expected);\n            });\n        });\n\n        describe('#focus', function () {\n            it('should move focus to canvas object', function () {\n                sinon.spy(client._canvas, \"focus\");\n                client.focus();\n                expect(client._canvas.focus).to.have.been.calledOnce;\n            });\n\n            it('should include focus options', function () {\n                sinon.spy(client._canvas, \"focus\");\n                client.focus({ foobar: 12, gazonk: true });\n                expect(client._canvas.focus).to.have.been.calledOnce;\n                expect(client._canvas.focus).to.have.been.calledWith({ foobar: 12, gazonk: true});\n            });\n        });\n\n        describe('#blur', function () {\n            it('should remove focus from canvas object', function () {\n                sinon.spy(client._canvas, \"blur\");\n                client.blur();\n                expect(client._canvas.blur).to.have.been.calledOnce;\n            });\n        });\n\n        describe('#clipboardPasteFrom', function () {\n            describe('Clipboard update handling', function () {\n                beforeEach(function () {\n                    sinon.spy(RFB.messages, 'clientCutText');\n                    sinon.spy(RFB.messages, 'extendedClipboardNotify');\n                });\n\n                afterEach(function () {\n                    RFB.messages.clientCutText.restore();\n                    RFB.messages.extendedClipboardNotify.restore();\n                });\n\n                it('should send the given text in an clipboard update', function () {\n                    client.clipboardPasteFrom('abc');\n\n                    expect(RFB.messages.clientCutText).to.have.been.calledOnce;\n                    expect(RFB.messages.clientCutText).to.have.been.calledWith(client._sock,\n                                                                               new Uint8Array([97, 98, 99]));\n                });\n\n                it('should mask unsupported characters', function () {\n                    client.clipboardPasteFrom('abc€');\n\n                    expect(RFB.messages.clientCutText).to.have.been.calledOnce;\n                    expect(RFB.messages.clientCutText).to.have.been.calledWith(client._sock,\n                                                                               new Uint8Array([97, 98, 99, 63]));\n                });\n\n                it('should mask characters, not UTF-16 code points', function () {\n                    client.clipboardPasteFrom('😂');\n\n                    expect(RFB.messages.clientCutText).to.have.been.calledOnce;\n                    expect(RFB.messages.clientCutText).to.have.been.calledWith(client._sock,\n                                                                               new Uint8Array([63]));\n                });\n\n                it('should send an notify if extended clipboard is supported by server', function () {\n                    // Send our capabilities\n                    let data = [3, 0, 0, 0];\n                    const flags = [0x1F, 0x00, 0x00, 0x01];\n                    let fileSizes = [0x00, 0x00, 0x00, 0x1E];\n\n                    push32(data, toUnsigned32bit(-8));\n                    data = data.concat(flags);\n                    data = data.concat(fileSizes);\n                    client._sock._websocket._receiveData(new Uint8Array(data));\n\n                    client.clipboardPasteFrom('extended test');\n                    expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;\n                });\n            });\n\n            it('should flush multiple times for large clipboards', function () {\n                sinon.spy(client._sock, 'flush');\n                let longText = \"\";\n                for (let i = 0; i < client._sock._sQbufferSize + 100; i++) {\n                    longText += 'a';\n                }\n                client.clipboardPasteFrom(longText);\n                expect(client._sock.flush).to.have.been.calledTwice;\n            });\n\n            it('should not send the text if we are not in a normal state', function () {\n                sinon.spy(client._sock, 'flush');\n                client._rfbConnectionState = \"connecting\";\n                client.clipboardPasteFrom('abc');\n                expect(client._sock.flush).to.not.have.been.called;\n            });\n        });\n\n        describe(\"XVP operations\", function () {\n            beforeEach(function () {\n                client._rfbXvpVer = 1;\n            });\n\n            it('should send the shutdown signal on #machineShutdown', function () {\n                client.machineShutdown();\n                expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));\n            });\n\n            it('should send the reboot signal on #machineReboot', function () {\n                client.machineReboot();\n                expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));\n            });\n\n            it('should send the reset signal on #machineReset', function () {\n                client.machineReset();\n                expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));\n            });\n\n            it('should not send XVP operations with higher versions than we support', function () {\n                sinon.spy(client._sock, 'flush');\n                client._xvpOp(2, 7);\n                expect(client._sock.flush).to.not.have.been.called;\n            });\n        });\n    });\n\n    describe('Clipping', function () {\n        let client;\n\n        beforeEach(function () {\n            client = makeRFB();\n            container.style.width = '70px';\n            container.style.height = '80px';\n            client.clipViewport = true;\n        });\n\n        it('should update display clip state when changing the property', function () {\n            const spy = sinon.spy(client._display, \"clipViewport\", [\"set\"]);\n\n            client.clipViewport = false;\n            expect(spy.set).to.have.been.calledOnce;\n            expect(spy.set).to.have.been.calledWith(false);\n            spy.set.resetHistory();\n\n            client.clipViewport = true;\n            expect(spy.set).to.have.been.calledOnce;\n            expect(spy.set).to.have.been.calledWith(true);\n        });\n\n        it('should update the viewport when the container size changes', function () {\n            sinon.spy(client._display, \"viewportChangeSize\");\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n            clock.tick(1000);\n\n            expect(client._display.viewportChangeSize).to.have.been.calledOnce;\n            expect(client._display.viewportChangeSize).to.have.been.calledWith(40, 50);\n        });\n\n        it('should update the viewport when the remote session resizes', function () {\n            sinon.spy(client._display, \"viewportChangeSize\");\n\n            // Simple ExtendedDesktopSize FBU message\n            sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678);\n            // The resize will cause scrollbars on the container, this causes a\n            // resize observation in the browsers\n            fakeResizeObserver.fire();\n\n            // FIXME: Display implicitly calls viewportChangeSize() when\n            //        resizing the framebuffer, hence calledTwice.\n            expect(client._display.viewportChangeSize).to.have.been.calledTwice;\n            expect(client._display.viewportChangeSize).to.have.been.calledWith(70, 80);\n        });\n\n        it('should not update the viewport if not clipping', function () {\n            client.clipViewport = false;\n            sinon.spy(client._display, \"viewportChangeSize\");\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n            clock.tick(1000);\n\n            expect(client._display.viewportChangeSize).to.not.have.been.called;\n        });\n\n        it('should not update the viewport if scaling', function () {\n            client.scaleViewport = true;\n            sinon.spy(client._display, \"viewportChangeSize\");\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n            clock.tick(1000);\n\n            expect(client._display.viewportChangeSize).to.not.have.been.called;\n        });\n\n        describe('Clipping and remote resize', function () {\n            beforeEach(function () {\n                // Given a remote (100, 100) larger than the container (70x80),\n                client._resize(100, 100);\n                client._supportsSetDesktopSize = true;\n                client.resizeSession = true;\n                sinon.spy(RFB.messages, \"setDesktopSize\");\n            });\n            afterEach(function () {\n                RFB.messages.setDesktopSize.restore();\n            });\n            it('should not change remote size when changing clipping', function () {\n                // When changing clipping the scrollbars of the container\n                // will appear and disappear and thus trigger resize observations\n                client.clipViewport = false;\n                fakeResizeObserver.fire();\n                clock.tick(1000);\n                client.clipViewport = true;\n                fakeResizeObserver.fire();\n                clock.tick(1000);\n\n                // Then no resize requests should be sent\n                expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n            });\n        });\n\n        describe('Dragging', function () {\n            beforeEach(function () {\n                client = makeRFB();\n                client.dragViewport = true;\n                client._display.resize(100, 100);\n\n                sinon.spy(RFB.messages, \"pointerEvent\");\n            });\n\n            afterEach(function () {\n                RFB.messages.pointerEvent.restore();\n            });\n\n            it('should not send button messages when initiating viewport dragging', function () {\n                sendMouseButtonEvent(13, 9, true, 0x1, client);\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n            });\n\n            it('should send button messages when release without movement', function () {\n                // Just up and down\n                sendMouseButtonEvent(13, 9, true, 0x1, client);\n                sendMouseButtonEvent(13, 9, false, 0x0, client);\n\n                expect(RFB.messages.pointerEvent).to.have.been.calledTwice;\n                expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                                    13, 9, 0x1);\n                expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                                     13, 9, 0x0);\n            });\n\n            it('should send button messages when tapping', function () {\n                // Just up and down\n                gestureStart('onetap', 13, 9, client);\n                gestureEnd('onetap', 13, 9, client);\n\n                expect(RFB.messages.pointerEvent).to.have.been.calledThrice;\n                expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                                    13, 9, 0x0);\n                expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                                     13, 9, 0x1);\n                expect(RFB.messages.pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                                    13, 9, 0x0);\n            });\n\n            it('should send button messages when release with small movement', function () {\n                // Small movement\n                sendMouseButtonEvent(13, 9, true, 0x1, client);\n                sendMouseMoveEvent(15, 14, 0x1, client);\n                sendMouseButtonEvent(15, 14, false, 0x0, client);\n\n                expect(RFB.messages.pointerEvent).to.have.been.calledTwice;\n                expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                                    15, 14, 0x1);\n                expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                                     15, 14, 0x0);\n            });\n\n            it('should not send button messages when in view only', function () {\n                client._viewOnly = true;\n\n                sendMouseButtonEvent(13, 9, true, 0x1, client);\n                sendMouseButtonEvent(13, 9, false, 0x0, client);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n            });\n\n            it('should send button message directly when drag is disabled', function () {\n                client.dragViewport = false;\n                sendMouseButtonEvent(13, 9, true, 0x1, client);\n                expect(RFB.messages.pointerEvent).to.have.been.calledOnce;\n                expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                                    13, 9, 0x1);\n            });\n\n            it('should be initiate viewport dragging on sufficient movement', function () {\n                sinon.spy(client._display, \"viewportChangePos\");\n\n                // Too small movement\n                sendMouseButtonEvent(13, 9, true, 0x1, client);\n                sendMouseMoveEvent(18, 9, 0x1, client);\n\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n                expect(client._display.viewportChangePos).to.not.have.been.called;\n\n                // Sufficient movement\n\n                sendMouseMoveEvent(43, 9, 0x1, client);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n                expect(client._display.viewportChangePos).to.have.been.calledOnce;\n                expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0);\n\n                client._display.viewportChangePos.resetHistory();\n\n                // Now a small movement should move right away\n\n                sendMouseMoveEvent(43, 14, 0x1, client);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n                expect(client._display.viewportChangePos).to.have.been.calledOnce;\n                expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5);\n            });\n\n            it('should initiate viewport dragging on sufficient drag gesture movement', function () {\n                sinon.spy(client._display, \"viewportChangePos\");\n\n                // Sufficient movement\n                gestureStart('drag', 13, 9, client);\n                gestureMove('drag', 43, 9, client);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n                expect(client._display.viewportChangePos).to.have.been.calledOnce;\n                expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0);\n\n                client._display.viewportChangePos.resetHistory();\n                RFB.messages.pointerEvent.resetHistory();\n\n                // Now a small movement should move right away\n\n                gestureMove('drag', 43, 14, client);\n                gestureEnd('drag', 43, 14, client);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n\n                expect(client._display.viewportChangePos).to.have.been.calledOnce;\n                expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5);\n            });\n\n            it('should initiate viewport dragging on sufficient longpress gesture movement', function () {\n                sinon.spy(client._display, \"viewportChangePos\");\n\n                // A small movement below the threshold should not move.\n                gestureStart('longpress', 13, 9, client);\n                gestureMove('longpress', 14, 9, client);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n                expect(client._display.viewportChangePos).to.not.have.been.called;\n\n                client._display.viewportChangePos.resetHistory();\n                RFB.messages.pointerEvent.resetHistory();\n\n                gestureMove('longpress', 43, 9, client);\n                gestureEnd('longpress', 43, 9, client);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n                expect(client._display.viewportChangePos).to.have.been.calledOnce;\n                expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0);\n            });\n\n            it('should send button messages on small longpress gesture movement', function () {\n                sinon.spy(client._display, \"viewportChangePos\");\n\n                // A small movement below the threshold should not move.\n                gestureStart('longpress', 13, 9, client);\n                gestureMove('longpress', 14, 10, client);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n                expect(client._display.viewportChangePos).to.not.have.been.called;\n\n                client._display.viewportChangePos.resetHistory();\n                RFB.messages.pointerEvent.resetHistory();\n\n                gestureEnd('longpress', 14, 9, client);\n\n                expect(RFB.messages.pointerEvent).to.have.been.calledThrice;\n                expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                                    14, 9, 0x0);\n                expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                                     14, 9, 0x4);\n                expect(RFB.messages.pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                                    14, 9, 0x0);\n\n                expect(client._display.viewportChangePos).to.not.have.been.called;\n            });\n\n            it('should not send button messages when dragging ends', function () {\n                // First the movement\n\n                sendMouseButtonEvent(13, 9, true, 0x1, client);\n                sendMouseMoveEvent(43, 9, 0x1, client);\n                sendMouseButtonEvent(43, 9, false, 0x0, client);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;\n            });\n\n            it('should terminate viewport dragging on a button up event', function () {\n                // First the dragging movement\n\n                sendMouseButtonEvent(13, 9, true, 0x1, client);\n                sendMouseMoveEvent(43, 9, 0x1, client);\n                sendMouseButtonEvent(43, 9, false, 0x0, client);\n\n                // Another movement now should not move the viewport\n\n                sinon.spy(client._display, \"viewportChangePos\");\n\n                sendMouseMoveEvent(43, 59, 0x0, client);\n\n                expect(client._display.viewportChangePos).to.not.have.been.called;\n            });\n\n            it('should flush move events when initiating viewport drag', function () {\n                sendMouseMoveEvent(13, 9, 0x0, client);\n                sendMouseMoveEvent(14, 9, 0x0, client);\n                sendMouseButtonEvent(14, 9, true, 0x1, client);\n\n                expect(RFB.messages.pointerEvent).to.have.been.calledTwice;\n                expect(RFB.messages.pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                                    13, 9, 0x0);\n                expect(RFB.messages.pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                                     14, 9, 0x0);\n\n                RFB.messages.pointerEvent.resetHistory();\n\n                clock.tick(100);\n\n                expect(RFB.messages.pointerEvent).to.not.have.been.called;;\n            });\n        });\n    });\n\n    describe('Scaling', function () {\n        let client;\n        beforeEach(function () {\n            client = makeRFB();\n            container.style.width = '70px';\n            container.style.height = '80px';\n            client.scaleViewport = true;\n\n            sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678);\n        });\n\n        it('should update display scale factor when changing the property', function () {\n            const spy = sinon.spy(client._display, \"scale\", [\"set\"]);\n            sinon.spy(client._display, \"autoscale\");\n\n            client.scaleViewport = false;\n            expect(spy.set).to.have.been.calledOnce;\n            expect(spy.set).to.have.been.calledWith(1.0);\n            expect(client._display.autoscale).to.not.have.been.called;\n\n            client.scaleViewport = true;\n            expect(client._display.autoscale).to.have.been.calledOnce;\n            expect(client._display.autoscale).to.have.been.calledWith(70, 80);\n        });\n\n        it('should update the clipping setting when changing the property', function () {\n            client.clipViewport = true;\n\n            const spy = sinon.spy(client._display, \"clipViewport\", [\"set\"]);\n\n            client.scaleViewport = false;\n            expect(spy.set).to.have.been.calledOnce;\n            expect(spy.set).to.have.been.calledWith(true);\n\n            spy.set.resetHistory();\n\n            client.scaleViewport = true;\n            expect(spy.set).to.have.been.calledOnce;\n            expect(spy.set).to.have.been.calledWith(false);\n        });\n\n        it('should update the scaling when the container size changes', function () {\n            sinon.spy(client._display, \"autoscale\");\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n            clock.tick(1000);\n\n            expect(client._display.autoscale).to.have.been.calledOnce;\n            expect(client._display.autoscale).to.have.been.calledWith(40, 50);\n        });\n\n        it('should update the scaling resized back to initial size', function () {\n            sinon.spy(client._display, \"autoscale\");\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n            clock.tick(1000);\n\n            expect(client._display.autoscale).to.have.been.calledOnce;\n            expect(client._display.autoscale).to.have.been.calledWith(40, 50);\n            client._display.autoscale.resetHistory();\n\n            container.style.width = '70px';\n            container.style.height = '80px';\n            fakeResizeObserver.fire();\n            clock.tick(1000);\n\n            expect(client._display.autoscale).to.have.been.calledOnce;\n            expect(client._display.autoscale).to.have.been.calledWith(70, 80);\n            client._display.autoscale.resetHistory();\n        });\n\n        it('should update the scaling when the remote session resizes', function () {\n            sinon.spy(client._display, \"autoscale\");\n\n            sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678);\n            // The resize will cause scrollbars on the container, this causes a\n            // resize observation in the browsers\n            fakeResizeObserver.fire();\n\n            expect(client._display.autoscale).to.have.been.calledOnce;\n            expect(client._display.autoscale).to.have.been.calledWith(70, 80);\n        });\n\n        it('should not update the display scale factor if not scaling', function () {\n            client.scaleViewport = false;\n\n            sinon.spy(client._display, \"autoscale\");\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n            clock.tick(1000);\n\n            expect(client._display.autoscale).to.not.have.been.called;\n        });\n    });\n\n    describe('Remote resize', function () {\n        let client;\n        beforeEach(function () {\n            client = makeRFB();\n            client.resizeSession = true;\n            container.style.width = '70px';\n            container.style.height = '80px';\n\n            sinon.spy(RFB.messages, \"setDesktopSize\");\n\n            sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678);\n\n            if (RFB.messages.setDesktopSize.calledOnce) {\n                let width = RFB.messages.setDesktopSize.args[0][1];\n                let height = RFB.messages.setDesktopSize.args[0][2];\n                sendExtendedDesktopSize(client, 1, 0, width, height, 0x7890abcd, 0x12345678);\n                RFB.messages.setDesktopSize.resetHistory();\n                clock.tick(10000);\n            }\n        });\n\n        afterEach(function () {\n            RFB.messages.setDesktopSize.restore();\n        });\n\n        it('should only request a resize when turned on', function () {\n            client.resizeSession = false;\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n\n            client.resizeSession = true;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n        });\n\n        it('should request a resize when initially connecting', function () {\n            // Create a new object that hasn't yet seen a\n            // ExtendedDesktopSize rect\n            client = makeRFB();\n            client.resizeSession = true;\n            container.style.width = '70px';\n            container.style.height = '80px';\n\n            // First message should trigger a resize\n\n            sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678);\n\n            // It should match the current size of the container,\n            // not the reported size from the server\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(\n                sinon.match.object, 70, 80, 0x7890abcd, 0x12345678);\n\n            sendExtendedDesktopSize(client, 1, 0, 70, 80, 0x7890abcd, 0x12345678);\n            RFB.messages.setDesktopSize.resetHistory();\n\n            // Second message should not trigger a resize\n\n            sendExtendedDesktopSize(client, 0, 0, 4, 4, 0x7890abcd, 0x12345678);\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n        });\n\n        it('should request a resize when the container resizes', function () {\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(\n                sinon.match.object, 40, 50, 0x7890abcd, 0x12345678);\n        });\n\n        it('should not request the same size twice', function () {\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(\n                sinon.match.object, 40, 50, 0x7890abcd, 0x12345678);\n\n            // Server responds with the requested size 40x50\n            sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678);\n\n            RFB.messages.setDesktopSize.resetHistory();\n\n            // size is still 40x50\n            clock.tick(1000);\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n        });\n\n        it('should request a resize when resized back to initial size', function () {\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(\n                sinon.match.object, 40, 50, 0x7890abcd, 0x12345678);\n\n            sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678);\n            RFB.messages.setDesktopSize.resetHistory();\n\n            clock.tick(1000);\n            container.style.width = '70px';\n            container.style.height = '80px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(\n                sinon.match.object, 70, 80, 0x7890abcd, 0x12345678);\n        });\n\n        it('should rate limit resizes', function () {\n            container.style.width = '20px';\n            container.style.height = '30px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(\n                sinon.match.object, 20, 30, 0x7890abcd, 0x12345678);\n\n            sendExtendedDesktopSize(client, 1, 0, 20, 30, 0x7890abcd, 0x12345678);\n            RFB.messages.setDesktopSize.resetHistory();\n\n            clock.tick(20);\n\n            container.style.width = '30px';\n            container.style.height = '40px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n\n            clock.tick(20);\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n\n            clock.tick(80);\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(\n                sinon.match.object, 40, 50, 0x7890abcd, 0x12345678);\n        });\n\n        it('should not have overlapping resize requests', function () {\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n\n            RFB.messages.setDesktopSize.resetHistory();\n\n            clock.tick(1000);\n            container.style.width = '20px';\n            container.style.height = '30px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n        });\n\n        it('should finalize any pending resizes', function () {\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n\n            RFB.messages.setDesktopSize.resetHistory();\n\n            clock.tick(1000);\n            container.style.width = '20px';\n            container.style.height = '30px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n\n            // Server responds with the requested size 40x50\n            sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678);\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(\n                sinon.match.object, 20, 30, 0x7890abcd, 0x12345678);\n        });\n\n        it('should not finalize any pending resize if not needed', function () {\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n\n            RFB.messages.setDesktopSize.resetHistory();\n\n            // Server responds with the requested size 40x50\n            sendExtendedDesktopSize(client, 1, 0, 40, 50, 0x7890abcd, 0x12345678);\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n        });\n\n        it('should not finalize any pending resizes on errors', function () {\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n\n            RFB.messages.setDesktopSize.resetHistory();\n\n            clock.tick(1000);\n            container.style.width = '20px';\n            container.style.height = '30px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n\n            // Server failed the requested size 40x50\n            sendExtendedDesktopSize(client, 1, 1, 40, 50, 0x7890abcd, 0x12345678);\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n        });\n\n        it('should not resize when resize is disabled', function () {\n            client._resizeSession = false;\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n        });\n\n        it('should not resize when resize is not supported', function () {\n            client._supportsSetDesktopSize = false;\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n        });\n\n        it('should not resize when in view only mode', function () {\n            client._viewOnly = true;\n\n            container.style.width = '40px';\n            container.style.height = '50px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n        });\n\n        it('should not try to override a server resize', function () {\n            // Note that this will cause the browser to display scrollbars\n            // since the framebuffer is 100x100 and the container is 70x80.\n            // The usable space (clientWidth/clientHeight) will be even smaller\n            // due to the scrollbars taking up space.\n            sendExtendedDesktopSize(client, 0, 0, 100, 100, 0xabababab, 0x11223344);\n            // The scrollbars cause the ResizeObserver to fire\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.not.have.been.called;\n\n            // An actual size change must not be ignored afterwards\n            container.style.width = '120px';\n            container.style.height = '130px';\n            fakeResizeObserver.fire();\n\n            expect(RFB.messages.setDesktopSize).to.have.been.calledOnce;\n            expect(RFB.messages.setDesktopSize).to.have.been.calledWith(\n                sinon.match.object, 120, 130, 0xabababab, 0x11223344);\n        });\n    });\n\n    describe('Misc internals', function () {\n        describe('#_fail', function () {\n            let client;\n            beforeEach(function () {\n                client = makeRFB();\n            });\n\n            it('should close the WebSocket connection', function () {\n                sinon.spy(client._sock, 'close');\n                client._fail();\n                expect(client._sock.close).to.have.been.calledOnce;\n            });\n\n            it('should transition to disconnected', function () {\n                sinon.spy(client, '_updateConnectionState');\n                client._fail();\n                this.clock.tick(2000);\n                expect(client._updateConnectionState).to.have.been.called;\n                expect(client._rfbConnectionState).to.equal('disconnected');\n            });\n\n            it('should set clean_disconnect variable', function () {\n                client._rfbCleanDisconnect = true;\n                client._rfbConnectionState = 'connected';\n                client._fail();\n                expect(client._rfbCleanDisconnect).to.be.false;\n            });\n\n            it('should result in disconnect event with clean set to false', function () {\n                client._rfbConnectionState = 'connected';\n                const spy = sinon.spy();\n                client.addEventListener(\"disconnect\", spy);\n                client._fail();\n                this.clock.tick(2000);\n                expect(spy).to.have.been.calledOnce;\n                expect(spy.args[0][0].detail.clean).to.be.false;\n            });\n\n        });\n    });\n\n    describe('Protocol initialization states', function () {\n        let client;\n        beforeEach(function () {\n            client = makeRFB();\n            client._rfbConnectionState = 'connecting';\n        });\n\n        function sendVer(ver, client) {\n            const arr = new Uint8Array(12);\n            for (let i = 0; i < ver.length; i++) {\n                arr[i+4] = ver.charCodeAt(i);\n            }\n            arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' ';\n            arr[11] = '\\n';\n            client._sock._websocket._receiveData(arr);\n        }\n\n        function sendSecurity(type, cl) {\n            cl._sock._websocket._receiveData(new Uint8Array([1, type]));\n        }\n\n        describe('ProtocolVersion', function () {\n            describe('version parsing', function () {\n                it('should interpret version 003.003 as version 3.3', function () {\n                    sendVer('003.003', client);\n                    expect(client._rfbVersion).to.equal(3.3);\n                });\n\n                it('should interpret version 003.006 as version 3.3', function () {\n                    sendVer('003.006', client);\n                    expect(client._rfbVersion).to.equal(3.3);\n                });\n\n                it('should interpret version 003.889 as version 3.8', function () {\n                    sendVer('003.889', client);\n                    expect(client._rfbVersion).to.equal(3.8);\n                });\n\n                it('should interpret version 003.007 as version 3.7', function () {\n                    sendVer('003.007', client);\n                    expect(client._rfbVersion).to.equal(3.7);\n                });\n\n                it('should interpret version 003.008 as version 3.8', function () {\n                    sendVer('003.008', client);\n                    expect(client._rfbVersion).to.equal(3.8);\n                });\n\n                it('should interpret version 004.000 as version 3.8', function () {\n                    sendVer('004.000', client);\n                    expect(client._rfbVersion).to.equal(3.8);\n                });\n\n                it('should interpret version 004.001 as version 3.8', function () {\n                    sendVer('004.001', client);\n                    expect(client._rfbVersion).to.equal(3.8);\n                });\n\n                it('should interpret version 005.000 as version 3.8', function () {\n                    sendVer('005.000', client);\n                    expect(client._rfbVersion).to.equal(3.8);\n                });\n\n                it('should fail on an invalid version', function () {\n                    let callback = sinon.spy();\n                    client.addEventListener(\"disconnect\", callback);\n\n                    sendVer('002.000', client);\n\n                    expect(callback).to.have.been.calledOnce;\n                    expect(callback.args[0][0].detail.clean).to.be.false;\n                });\n            });\n\n            it('should send back the interpreted version', function () {\n                sendVer('004.000', client);\n\n                const expectedStr = 'RFB 003.008\\n';\n                const expected = [];\n                for (let i = 0; i < expectedStr.length; i++) {\n                    expected[i] = expectedStr.charCodeAt(i);\n                }\n\n                expect(client._sock).to.have.sent(new Uint8Array(expected));\n            });\n\n            it('should transition to the Security state on successful negotiation', function () {\n                sendVer('003.008', client);\n                expect(client._rfbInitState).to.equal('Security');\n            });\n\n            describe('Repeater', function () {\n                beforeEach(function () {\n                    client = makeRFB('wss://host:8675', { repeaterID: \"12345\" });\n                    client._rfbConnectionState = 'connecting';\n                });\n\n                it('should interpret version 000.000 as a repeater', function () {\n                    sendVer('000.000', client);\n                    expect(client._rfbVersion).to.equal(0);\n\n                    const sentData = client._sock._websocket._getSentData();\n                    expect(new Uint8Array(sentData.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0]));\n                    expect(sentData).to.have.length(250);\n                });\n\n                it('should handle two step repeater negotiation', function () {\n                    sendVer('000.000', client);\n                    sendVer('003.008', client);\n                    expect(client._rfbVersion).to.equal(3.8);\n                });\n            });\n        });\n\n        describe('Security', function () {\n            beforeEach(function () {\n                sendVer('003.008\\n', client);\n                client._sock._websocket._getSentData();\n            });\n\n            it('should respect server preference order', function () {\n                const authSchemes = [ 6, 79, 30, 188, 16, 6, 1 ];\n                client._sock._websocket._receiveData(new Uint8Array(authSchemes));\n                expect(client._sock).to.have.sent(new Uint8Array([30]));\n            });\n\n            it('should fail if there are no supported schemes', function () {\n                let callback = sinon.spy();\n                client.addEventListener(\"disconnect\", callback);\n\n                const authSchemes = [1, 32];\n                client._sock._websocket._receiveData(new Uint8Array(authSchemes));\n\n                expect(callback).to.have.been.calledOnce;\n                expect(callback.args[0][0].detail.clean).to.be.false;\n            });\n\n            it('should fail with the appropriate message if no types are sent', function () {\n                const failureData = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];\n                let callback = sinon.spy();\n                client.addEventListener(\"securityfailure\", callback);\n\n                client._sock._websocket._receiveData(new Uint8Array(failureData));\n\n                expect(callback).to.have.been.calledOnce;\n                expect(callback.args[0][0].detail.status).to.equal(1);\n                expect(callback.args[0][0].detail.reason).to.equal(\"whoops\");\n            });\n\n            it('should transition to the Authentication state and continue on successful negotiation', function () {\n                const authSchemes = [1, 2];\n                sinon.spy(client, \"_negotiateAuthentication\");\n                client._sock._websocket._receiveData(new Uint8Array(authSchemes));\n                expect(client._rfbInitState).to.equal('Authentication');\n                expect(client._negotiateAuthentication).to.have.been.calledOnce;\n            });\n        });\n\n        describe('Legacy authentication', function () {\n            it('should fail on auth scheme 0 (pre 3.7) with the given message', function () {\n                const errMsg = \"Whoopsies\";\n                const data = [0, 0, 0, 0];\n                const errLen = errMsg.length;\n                push32(data, errLen);\n                for (let i = 0; i < errLen; i++) {\n                    data.push(errMsg.charCodeAt(i));\n                }\n\n                sendVer('003.006\\n', client);\n                client._sock._websocket._getSentData();\n                let callback = sinon.spy();\n                client.addEventListener(\"securityfailure\", callback);\n\n                client._sock._websocket._receiveData(new Uint8Array(data));\n\n                expect(callback).to.have.been.calledOnce;\n                expect(callback.args[0][0].detail.status).to.equal(1);\n                expect(callback.args[0][0].detail.reason).to.equal(\"Whoopsies\");\n            });\n\n            it('should transition straight to ServerInitialisation on \"no auth\" for versions < 3.7', function () {\n                sendVer('003.006\\n', client);\n                client._sock._websocket._getSentData();\n\n                client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 1]));\n                expect(client._rfbInitState).to.equal('ServerInitialisation');\n            });\n\n            it('should transition straight to ServerInitialisation on \"no auth\" for versions < 3.8', function () {\n                sendVer('003.007\\n', client);\n                client._sock._websocket._getSentData();\n\n                sendSecurity(1, client);\n                expect(client._rfbInitState).to.equal('ServerInitialisation');\n            });\n        });\n\n        describe('Authentication', function () {\n            beforeEach(function () {\n                sendVer('003.008\\n', client);\n                client._sock._websocket._getSentData();\n            });\n\n            it('should transition straight to SecurityResult on \"no auth\" (1)', function () {\n                sendSecurity(1, client);\n                expect(client._rfbInitState).to.equal('SecurityResult');\n            });\n\n            it('should fail on an unknown auth scheme', function () {\n                let callback = sinon.spy();\n                client.addEventListener(\"disconnect\", callback);\n\n                sendSecurity(57, client);\n\n                expect(callback).to.have.been.calledOnce;\n                expect(callback.args[0][0].detail.clean).to.be.false;\n            });\n\n            describe('VNC authentication (type 2) handler', function () {\n                it('should fire the credentialsrequired event if missing a password', function () {\n                    const spy = sinon.spy();\n                    client.addEventListener(\"credentialsrequired\", spy);\n                    sendSecurity(2, client);\n\n                    const challenge = [];\n                    for (let i = 0; i < 16; i++) { challenge[i] = i; }\n                    client._sock._websocket._receiveData(new Uint8Array(challenge));\n\n                    expect(spy).to.have.been.calledOnce;\n                    expect(spy.args[0][0].detail.types).to.have.members([\"password\"]);\n                });\n\n                it('should encrypt the password with DES and then send it back', function () {\n                    client.addEventListener(\"credentialsrequired\", () => {\n                        client.sendCredentials({ password: 'passwd' });\n                    });\n                    sendSecurity(2, client);\n                    client._sock._websocket._getSentData(); // skip the choice of auth reply\n\n                    const challenge = [];\n                    for (let i = 0; i < 16; i++) { challenge[i] = i; }\n                    client._sock._websocket._receiveData(new Uint8Array(challenge));\n                    clock.tick();\n\n                    const desPass = RFB.genDES('passwd', challenge);\n                    expect(client._sock).to.have.sent(new Uint8Array(desPass));\n                });\n\n                it('should transition to SecurityResult immediately after sending the password', function () {\n                    client.addEventListener(\"credentialsrequired\", () => {\n                        client.sendCredentials({ password: 'passwd' });\n                    });\n                    sendSecurity(2, client);\n\n                    const challenge = [];\n                    for (let i = 0; i < 16; i++) { challenge[i] = i; }\n                    client._sock._websocket._receiveData(new Uint8Array(challenge));\n                    clock.tick();\n\n                    expect(client._rfbInitState).to.equal('SecurityResult');\n                });\n            });\n\n            describe('RSA-AES authentication (type 6) handler', function () {\n                function fakeGetRandomValues(arr) {\n                    if (arr.length === 16) {\n                        arr.set(new Uint8Array([\n                            0x1c, 0x08, 0xfe, 0x21, 0x78, 0xef, 0x4e, 0xf9,\n                            0x3f, 0x05, 0xec, 0xea, 0xd4, 0x6b, 0xa5, 0xd5,\n                        ]));\n                    } else {\n                        arr.set(new Uint8Array([\n                            0xee, 0xe2, 0xf1, 0x5a, 0x3c, 0xa7, 0xbe, 0x95,\n                            0x6f, 0x2a, 0x75, 0xfd, 0x62, 0x01, 0xcb, 0xbf,\n                            0x43, 0x74, 0xca, 0x47, 0x4d, 0xfb, 0x0f, 0xcf,\n                            0x3a, 0x6d, 0x55, 0x6b, 0x59, 0x3a, 0xf6, 0x87,\n                            0xcb, 0x03, 0xb7, 0x28, 0x35, 0x7b, 0x15, 0x8e,\n                            0xb6, 0xc8, 0x8f, 0x2d, 0x5e, 0x7b, 0x1c, 0x9a,\n                            0x32, 0x55, 0xe7, 0x64, 0x36, 0x25, 0x7b, 0xa3,\n                            0xe9, 0x4f, 0x6f, 0x97, 0xdc, 0xa4, 0xd4, 0x62,\n                            0x6d, 0x7f, 0xab, 0x02, 0x6b, 0x13, 0x56, 0x69,\n                            0xfb, 0xd0, 0xd4, 0x13, 0x76, 0xcd, 0x0d, 0xd0,\n                            0x1f, 0xd1, 0x0c, 0x63, 0x3a, 0x34, 0x20, 0x6c,\n                            0xbb, 0x60, 0x45, 0x82, 0x23, 0xfd, 0x7c, 0x77,\n                            0x6d, 0xcc, 0x5e, 0xaa, 0xc3, 0x0c, 0x43, 0xb7,\n                            0x8d, 0xc0, 0x27, 0x6e, 0xeb, 0x1d, 0x6c, 0x5f,\n                            0xd8, 0x1c, 0x3c, 0x1c, 0x60, 0x2e, 0x82, 0x15,\n                            0xfd, 0x2e, 0x5f, 0x3a, 0x15, 0x53, 0x14, 0x70,\n                            0x4f, 0xe1, 0x65, 0x68, 0x35, 0x6d, 0xc7, 0x64,\n                            0xdb, 0xdd, 0x09, 0x31, 0x4f, 0x7b, 0x6d, 0x6c,\n                            0x77, 0x59, 0x5e, 0x1e, 0xfa, 0x4b, 0x06, 0x14,\n                            0xbe, 0xdc, 0x9c, 0x3d, 0x7b, 0xed, 0xf3, 0x2b,\n                            0x19, 0x26, 0x11, 0x8e, 0x3f, 0xab, 0x73, 0x9a,\n                            0x0a, 0x3a, 0xaa, 0x85, 0x06, 0xd5, 0xca, 0x3f,\n                            0xc3, 0xe2, 0x33, 0x7f, 0x97, 0x74, 0x98, 0x8f,\n                            0x2f, 0xa5, 0xfc, 0x7e, 0xb1, 0x77, 0x71, 0x58,\n                            0xf0, 0xbc, 0x04, 0x59, 0xbb, 0xb4, 0xc6, 0xcc,\n                            0x0f, 0x06, 0xcd, 0xa2, 0xd5, 0x01, 0x2f, 0xb2,\n                            0x22, 0x0b, 0xfc, 0x1e, 0x59, 0x9f, 0xd3, 0x4f,\n                            0x30, 0x95, 0xc6, 0x80, 0x0f, 0x69, 0xf3, 0x4a,\n                            0xd4, 0x36, 0xb6, 0x5a, 0x0b, 0x16, 0x0d, 0x81,\n                            0x31, 0xb0, 0x69, 0xd4, 0x4e,\n                        ]));\n                    }\n                }\n\n                async function fakeGeneratekey() {\n                    let key = { \"alg\": \"RSA-OAEP-256\",\n                                \"d\": \"B7QR2yI8sXjo8vQhJpX9odqqR6wIuPr\" +\n                                     \"TM1B1JJEKVeSrr7OYcc1FRJ52Vap9LI\" +\n                                     \"AU-ezigs9QDvWMxknB8motLnG69Wck3\" +\n                                     \"7nt9_z4s8lFQp0nROA-oaR92HW34KNL\" +\n                                     \"1b2fEVWGI0N86h730MvTJC5O2cmKeMe\" +\n                                     \"zIG-oNqbbfFyP8AW-WLdDlgZm11-Fjz\" +\n                                     \"hbVpb0Bc7nRSgBPSV-EY6Sl-LuglxDx\" +\n                                     \"4LaTdQW7QE_WXoRUt-GYGfTseuFQQK5\" +\n                                     \"WeoyX3yBtQydpauW6rrgyWdtP4hDFIo\" +\n                                     \"ZsX6w1i-UMWMMwlIB5FdnUSi26igVGA\" +\n                                     \"DGpV_vGMP36bv-EHp0bY-Qp0gpIfLfgQ\",\n                                \"dp\": \"Z1v5UceFfV2bhmbG19eGYb30jFxqoR\" +\n                                      \"Bq36PKNY7IunMs1keYy0FpLbyGhtgM\" +\n                                      \"Z1Ymmc8wEzGYsCPEP-ykcun_rlyu7Y\" +\n                                      \"xmcnyC9YQqTqLyqvO-7rUqDvk9TMfd\" +\n                                      \"qWFP6heADRhKZmEbmcau6_m2MwwK9k\" +\n                                      \"OkMKWvpqp8_TpJMnAH7zE\",\n                                \"dq\": \"OBacRE15aY3NtCR4cvP5os3sT70JbD\" +\n                                      \"dDLHT3IHZM6rE35CYNpLDia2chm_wn\" +\n                                      \"McYvKFW9zC2ajRZ15i9c_VXQzS7ZlT\" +\n                                      \"aQYBFyMt7kVhxMEMFsPv1crD6t3uEI\" +\n                                      \"j0LNuNYyy0jkon_LPZKQFK654CiL-L\" +\n                                      \"2YaNXOH4HbHP02dWeVQIE\",\n                                \"e\": \"AQAB\",\n                                \"ext\": true,\n                                \"key_ops\": [\"decrypt\"],\n                                \"kty\": \"RSA\",\n                                \"n\": \"m1c92ZFk9ZI6l_O4YFiNxbv0Ng94SB3\" +\n                                     \"yThy1P_mcqrGDQkRiGVdcTxAk38T9Pg\" +\n                                     \"LztmspF-6U5TAHO-gSmmW88AC9m6f1M\" +\n                                     \"spps6r7zl-M_OG-TwvGzf3BDz8zEg1F\" +\n                                     \"PbZV7whO1M4TCAZ0PqwG7qCc6nK1WiA\" +\n                                     \"haKrSpzuPdL1igfNBsX7qu5wgw4ZTTG\" +\n                                     \"SLbVC_LfULQ5FADgFTRXUSaxm1F8C_L\" +\n                                     \"wy6a2e4nTcXilmtN2IHUjHegzm-Tq2H\" +\n                                     \"izmR3ARdWJpESYIW5-AXoiqj29tDrqC\" +\n                                     \"mu2WPkB2psVp83IzZfaQNQzjNfvA8Gp\" +\n                                     \"imkcDCkP5VMRrtKCcG4ZAFnO-A3NBX_Q\",\n                                \"p\": \"2Q_lNL7vCOBzAppYzCZo3WSh0hX-MOZ\" +\n                                     \"yPUznks5U2TjmfdNZoL6_FJRiGyyLvw\" +\n                                     \"SiZFdEAAvpAyESFfFigngAqMLSf448n\" +\n                                     \"Pg15VUGj533CotsEM0WpoEr1JCgqdUb\" +\n                                     \"gDAfJQIBcwOmegBqd7lWm7uzEnRCvou\" +\n                                     \"B70ybkJfpdprhkVE\",\n                                \"q\": \"tzTt-F3g2u_3Ctj26Ho9iN_wC_W0lXG\" +\n                                     \"zslLt5nLmss8JqdLoDDrijjU-gjeRh7\" +\n                                     \"lgiuHdUc3dorfFKbaMNOjoW3QKqt9oZ\" +\n                                     \"1JM0HKeRw0X2PnWW_0WK6DK5ASWDTXb\" +\n                                     \"Mq2sUZqJvYEyL74H2Zrt0RPAux7XQLE\" +\n                                     \"VgND6ROdXnMJ70O0\",\n                                \"qi\": \"qfl4cXQkz4BNqa2De0-PfdU-8d1w3o\" +\n                                      \"nnaGqx1Ds2fHzD_SJ4cNghn2TksoT9\" +\n                                      \"Qo64b3pUjH9igi2pyEjomk6D12N6FG\" +\n                                      \"0e10u7vFKv3W5YqUOgTpYdbcWHdZ2q\" +\n                                      \"ZWJU0XQZIrF8jLGTOO4GYP6_9sJ5R7\" +\n                                      \"Wk_0MdqQy8qvixWD4zLcY\",\n                    };\n                    key = await window.crypto.subtle.importKey(\"jwk\", key, {\n                        name: \"RSA-OAEP\",\n                        hash: {name: \"SHA-256\"}\n                    }, true, [\"decrypt\"]);\n                    return {privateKey: key};\n                }\n\n                before(() => {\n                    sinon.stub(window.crypto, \"getRandomValues\").callsFake(fakeGetRandomValues);\n                    sinon.stub(window.crypto.subtle, \"generateKey\").callsFake(fakeGeneratekey);\n                });\n                after(() => {\n                    window.crypto.getRandomValues.restore();\n                    window.crypto.subtle.generateKey.restore();\n                });\n\n                beforeEach(function () {\n                    sendSecurity(6, client);\n                    expect(client._sock).to.have.sent(new Uint8Array([6]));\n                });\n\n                const serverPublicKey = [\n                    0x00, 0x00, 0x08, 0x00, 0xac, 0x1a, 0xbc, 0x42,\n                    0x8a, 0x2a, 0x69, 0x65, 0x54, 0xf8, 0x9a, 0xe6,\n                    0x43, 0xaa, 0xf7, 0x27, 0xf6, 0x2a, 0xf8, 0x8f,\n                    0x36, 0xd4, 0xae, 0x54, 0x0f, 0x16, 0x28, 0x08,\n                    0xc2, 0x5b, 0xca, 0x23, 0xdc, 0x27, 0x88, 0x1a,\n                    0x12, 0x82, 0xa8, 0x54, 0xea, 0x00, 0x99, 0x8d,\n                    0x02, 0x1d, 0x77, 0x4a, 0xeb, 0xd0, 0x93, 0x40,\n                    0x79, 0x86, 0xcb, 0x37, 0xd4, 0xb2, 0xc7, 0xcd,\n                    0x93, 0xe1, 0x00, 0x4d, 0x86, 0xff, 0x97, 0x33,\n                    0x0c, 0xad, 0x51, 0x47, 0x45, 0x85, 0x56, 0x07,\n                    0x65, 0x21, 0x7c, 0x57, 0x6d, 0x68, 0x7d, 0xd7,\n                    0x00, 0x43, 0x0c, 0x9d, 0x3b, 0xa1, 0x5a, 0x11,\n                    0xed, 0x51, 0x77, 0xf9, 0xd1, 0x5b, 0x33, 0xd7,\n                    0x1a, 0xeb, 0x65, 0x57, 0xc0, 0x01, 0x51, 0xff,\n                    0x9b, 0x82, 0xb3, 0xeb, 0x82, 0xc2, 0x1f, 0xca,\n                    0x47, 0xc0, 0x6a, 0x09, 0xe0, 0xf7, 0xda, 0x39,\n                    0x85, 0x12, 0xe7, 0x45, 0x8d, 0xb4, 0x1a, 0xda,\n                    0xcb, 0x86, 0x58, 0x52, 0x37, 0x66, 0x9d, 0x8a,\n                    0xce, 0xf2, 0x18, 0x78, 0x7d, 0x7f, 0xf0, 0x07,\n                    0x94, 0x8e, 0x6b, 0x17, 0xd9, 0x00, 0x2a, 0x3a,\n                    0xb9, 0xd4, 0x77, 0xde, 0x70, 0x85, 0xc4, 0x3a,\n                    0x62, 0x10, 0x02, 0xee, 0xba, 0xd8, 0xc0, 0x62,\n                    0xd0, 0x8e, 0xc1, 0x98, 0x19, 0x8e, 0x39, 0x0f,\n                    0x3e, 0x1d, 0x61, 0xb1, 0x93, 0x13, 0x59, 0x39,\n                    0xcb, 0x96, 0xf2, 0x17, 0xc9, 0xe1, 0x41, 0xd3,\n                    0x20, 0xdd, 0x62, 0x5e, 0x7d, 0x53, 0xd6, 0xb7,\n                    0x1d, 0xfe, 0x02, 0x18, 0x1f, 0xe0, 0xef, 0x3d,\n                    0x94, 0xe3, 0x0a, 0x9c, 0x59, 0x54, 0xd8, 0x98,\n                    0x16, 0x9c, 0x31, 0xda, 0x41, 0x0f, 0x2e, 0x71,\n                    0x68, 0xe0, 0xa2, 0x62, 0x3e, 0xe5, 0x25, 0x31,\n                    0xcf, 0xfc, 0x67, 0x63, 0xc3, 0xb0, 0xda, 0x3f,\n                    0x7b, 0x59, 0xbe, 0x7e, 0x9e, 0xa8, 0xd0, 0x01,\n                    0x4f, 0x43, 0x7f, 0x8d, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x01, 0x00, 0x01,\n                ];\n\n                const serverRandom = [\n                    0x01, 0x00, 0x5b, 0x58, 0x2a, 0x96, 0x2d, 0xbb,\n                    0x88, 0xec, 0xc3, 0x54, 0x00, 0xf3, 0xbb, 0xbe,\n                    0x17, 0xa3, 0x84, 0xd3, 0xef, 0xd8, 0x4a, 0x31,\n                    0x09, 0x20, 0xdd, 0xbc, 0x16, 0x9d, 0xc9, 0x5b,\n                    0x99, 0x62, 0x86, 0xfe, 0x0b, 0x28, 0x4b, 0xfe,\n                    0x5b, 0x56, 0x2d, 0xcb, 0x6e, 0x6f, 0xec, 0xf0,\n                    0x53, 0x0c, 0x33, 0x84, 0x93, 0xc9, 0xbf, 0x79,\n                    0xde, 0xb3, 0xb9, 0x29, 0x60, 0x78, 0xde, 0xe6,\n                    0x1d, 0xa7, 0x89, 0x48, 0x3f, 0xd1, 0x58, 0x66,\n                    0x27, 0x9c, 0xd4, 0x6e, 0x72, 0x9c, 0x6e, 0x4a,\n                    0xc0, 0x69, 0x79, 0x6f, 0x79, 0x0f, 0x13, 0xc4,\n                    0x20, 0xcf, 0xa6, 0xbb, 0xce, 0x18, 0x6d, 0xd5,\n                    0x9e, 0xd9, 0x67, 0xbe, 0x61, 0x43, 0x67, 0x11,\n                    0x76, 0x2f, 0xfd, 0x78, 0x75, 0x2b, 0x89, 0x35,\n                    0xdd, 0x0f, 0x13, 0x7f, 0xee, 0x78, 0xad, 0x32,\n                    0x56, 0x21, 0x81, 0x08, 0x1f, 0xcf, 0x4c, 0x29,\n                    0xa3, 0xeb, 0x89, 0x2d, 0xbe, 0xba, 0x8d, 0xe4,\n                    0x69, 0x28, 0xba, 0x53, 0x82, 0xce, 0x5c, 0xf6,\n                    0x5e, 0x5e, 0xa5, 0xb3, 0x88, 0xd8, 0x3d, 0xab,\n                    0xf4, 0x24, 0x9e, 0x3f, 0x04, 0xaf, 0xdc, 0x48,\n                    0x90, 0x53, 0x37, 0xe6, 0x82, 0x1d, 0xe0, 0x15,\n                    0x91, 0xa1, 0xc6, 0xa9, 0x54, 0xe5, 0x2a, 0xb5,\n                    0x64, 0x2d, 0x93, 0xc0, 0xc0, 0xe1, 0x0f, 0x6a,\n                    0x4b, 0xdb, 0x77, 0xf8, 0x4a, 0x0f, 0x83, 0x36,\n                    0xdd, 0x5e, 0x1e, 0xdd, 0x39, 0x65, 0xa2, 0x11,\n                    0xc2, 0xcf, 0x56, 0x1e, 0xa1, 0x29, 0xae, 0x11,\n                    0x9f, 0x3a, 0x82, 0xc7, 0xbd, 0x89, 0x6e, 0x59,\n                    0xb8, 0x59, 0x17, 0xcb, 0x65, 0xa0, 0x4b, 0x4d,\n                    0xbe, 0x33, 0x32, 0x85, 0x9c, 0xca, 0x5e, 0x95,\n                    0xc2, 0x5a, 0xd0, 0xc9, 0x8b, 0xf1, 0xf5, 0x14,\n                    0xcf, 0x76, 0x80, 0xc2, 0x24, 0x0a, 0x39, 0x7e,\n                    0x60, 0x64, 0xce, 0xd9, 0xb8, 0xad, 0x24, 0xa8,\n                    0xdf, 0xcb,\n                ];\n\n                const serverHash = [\n                    0x00, 0x14, 0x39, 0x30, 0x66, 0xb5, 0x66, 0x8a,\n                    0xcd, 0xb9, 0xda, 0xe0, 0xde, 0xcb, 0xf6, 0x47,\n                    0x5f, 0x54, 0x66, 0xe0, 0xbc, 0x49, 0x37, 0x01,\n                    0xf2, 0x9e, 0xef, 0xcc, 0xcd, 0x4d, 0x6c, 0x0e,\n                    0xc6, 0xab, 0x28, 0xd4, 0x7b, 0x13,\n                ];\n\n                const subType = [\n                    0x00, 0x01, 0x30, 0x2a, 0xc3, 0x0b, 0xc2, 0x1c,\n                    0xeb, 0x02, 0x44, 0x92, 0x5d, 0xfd, 0xf9, 0xa7,\n                    0x94, 0xd0, 0x19,\n                ];\n\n                const clientPublicKey = [\n                    0x00, 0x00, 0x08, 0x00, 0x9b, 0x57, 0x3d, 0xd9,\n                    0x91, 0x64, 0xf5, 0x92, 0x3a, 0x97, 0xf3, 0xb8,\n                    0x60, 0x58, 0x8d, 0xc5, 0xbb, 0xf4, 0x36, 0x0f,\n                    0x78, 0x48, 0x1d, 0xf2, 0x4e, 0x1c, 0xb5, 0x3f,\n                    0xf9, 0x9c, 0xaa, 0xb1, 0x83, 0x42, 0x44, 0x62,\n                    0x19, 0x57, 0x5c, 0x4f, 0x10, 0x24, 0xdf, 0xc4,\n                    0xfd, 0x3e, 0x02, 0xf3, 0xb6, 0x6b, 0x29, 0x17,\n                    0xee, 0x94, 0xe5, 0x30, 0x07, 0x3b, 0xe8, 0x12,\n                    0x9a, 0x65, 0xbc, 0xf0, 0x00, 0xbd, 0x9b, 0xa7,\n                    0xf5, 0x32, 0xca, 0x69, 0xb3, 0xaa, 0xfb, 0xce,\n                    0x5f, 0x8c, 0xfc, 0xe1, 0xbe, 0x4f, 0x0b, 0xc6,\n                    0xcd, 0xfd, 0xc1, 0x0f, 0x3f, 0x33, 0x12, 0x0d,\n                    0x45, 0x3d, 0xb6, 0x55, 0xef, 0x08, 0x4e, 0xd4,\n                    0xce, 0x13, 0x08, 0x06, 0x74, 0x3e, 0xac, 0x06,\n                    0xee, 0xa0, 0x9c, 0xea, 0x72, 0xb5, 0x5a, 0x20,\n                    0x21, 0x68, 0xaa, 0xd2, 0xa7, 0x3b, 0x8f, 0x74,\n                    0xbd, 0x62, 0x81, 0xf3, 0x41, 0xb1, 0x7e, 0xea,\n                    0xbb, 0x9c, 0x20, 0xc3, 0x86, 0x53, 0x4c, 0x64,\n                    0x8b, 0x6d, 0x50, 0xbf, 0x2d, 0xf5, 0x0b, 0x43,\n                    0x91, 0x40, 0x0e, 0x01, 0x53, 0x45, 0x75, 0x12,\n                    0x6b, 0x19, 0xb5, 0x17, 0xc0, 0xbf, 0x2f, 0x0c,\n                    0xba, 0x6b, 0x67, 0xb8, 0x9d, 0x37, 0x17, 0x8a,\n                    0x59, 0xad, 0x37, 0x62, 0x07, 0x52, 0x31, 0xde,\n                    0x83, 0x39, 0xbe, 0x4e, 0xad, 0x87, 0x8b, 0x39,\n                    0x91, 0xdc, 0x04, 0x5d, 0x58, 0x9a, 0x44, 0x49,\n                    0x82, 0x16, 0xe7, 0xe0, 0x17, 0xa2, 0x2a, 0xa3,\n                    0xdb, 0xdb, 0x43, 0xae, 0xa0, 0xa6, 0xbb, 0x65,\n                    0x8f, 0x90, 0x1d, 0xa9, 0xb1, 0x5a, 0x7c, 0xdc,\n                    0x8c, 0xd9, 0x7d, 0xa4, 0x0d, 0x43, 0x38, 0xcd,\n                    0x7e, 0xf0, 0x3c, 0x1a, 0x98, 0xa6, 0x91, 0xc0,\n                    0xc2, 0x90, 0xfe, 0x55, 0x31, 0x1a, 0xed, 0x28,\n                    0x27, 0x06, 0xe1, 0x90, 0x05, 0x9c, 0xef, 0x80,\n                    0xdc, 0xd0, 0x57, 0xfd, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                    0x00, 0x01, 0x00, 0x01,\n                ];\n\n                const clientRandom = [\n                    0x01, 0x00, 0x84, 0x7f, 0x26, 0x54, 0x74, 0xf6,\n                    0x47, 0xaf, 0x33, 0x64, 0x0d, 0xa6, 0xe5, 0x30,\n                    0xba, 0xe6, 0xe4, 0x8e, 0x50, 0x40, 0x71, 0x1c,\n                    0x0e, 0x06, 0x63, 0xf5, 0x07, 0x2a, 0x26, 0x68,\n                    0xd6, 0xcf, 0xa6, 0x80, 0x84, 0x5e, 0x64, 0xd4,\n                    0x5e, 0x62, 0x31, 0xfe, 0x44, 0x51, 0x0b, 0x7c,\n                    0x4d, 0x55, 0xc5, 0x4a, 0x7e, 0x0d, 0x4d, 0x9b,\n                    0x84, 0xb4, 0x32, 0x2b, 0x4d, 0x8a, 0x34, 0x8d,\n                    0xc8, 0xcf, 0x19, 0x3b, 0x64, 0x82, 0x27, 0x9e,\n                    0xa7, 0x70, 0x2a, 0xc1, 0xb8, 0xf3, 0x6a, 0x3a,\n                    0xf2, 0x75, 0x6e, 0x1d, 0xeb, 0xb6, 0x70, 0x7a,\n                    0x15, 0x18, 0x38, 0x00, 0xb4, 0x4f, 0x55, 0xb5,\n                    0xd8, 0x03, 0x4e, 0xb8, 0x53, 0xff, 0x80, 0x62,\n                    0xf1, 0x9d, 0x27, 0xe8, 0x2a, 0x3d, 0x98, 0x19,\n                    0x32, 0x09, 0x7e, 0x9a, 0xb0, 0xc7, 0x46, 0x23,\n                    0x10, 0x85, 0x35, 0x00, 0x96, 0xce, 0xb3, 0x2c,\n                    0x84, 0x8d, 0xf4, 0x9e, 0xa8, 0x42, 0x67, 0xed,\n                    0x09, 0xa6, 0x09, 0x97, 0xb3, 0x64, 0x26, 0xfb,\n                    0x71, 0x11, 0x9b, 0x3f, 0xbb, 0x57, 0xb8, 0x5b,\n                    0x2e, 0xc5, 0x2d, 0x8c, 0x5c, 0xf7, 0xef, 0x27,\n                    0x25, 0x88, 0x42, 0x45, 0x43, 0xa4, 0xe7, 0xde,\n                    0xea, 0xf9, 0x15, 0x7b, 0x5d, 0x66, 0x24, 0xce,\n                    0xf7, 0xc8, 0x2f, 0xc5, 0xc0, 0x3d, 0xcd, 0xf2,\n                    0x62, 0xfc, 0x1a, 0x5e, 0xec, 0xff, 0xf1, 0x1b,\n                    0xc8, 0xdb, 0xc1, 0x0f, 0x54, 0x66, 0x9e, 0xfd,\n                    0x99, 0x9b, 0x23, 0x70, 0x62, 0x37, 0x80, 0xad,\n                    0x91, 0x6b, 0x84, 0x85, 0x6a, 0x4c, 0x80, 0x9e,\n                    0x60, 0x8a, 0x93, 0xa3, 0xc8, 0x8e, 0xc4, 0x4b,\n                    0x4d, 0xb4, 0x8e, 0x3e, 0xaf, 0xce, 0xcd, 0x83,\n                    0xe5, 0x21, 0x90, 0x95, 0x20, 0x3c, 0x82, 0xb4,\n                    0x7c, 0xab, 0x63, 0x9c, 0xae, 0xc3, 0xc9, 0x71,\n                    0x1a, 0xec, 0x34, 0x18, 0x47, 0xec, 0x5c, 0x4d,\n                    0xed, 0x84,\n                ];\n\n                const clientHash = [\n                    0x00, 0x14, 0x9c, 0x91, 0x9e, 0x76, 0xcf, 0x1e,\n                    0x66, 0x87, 0x5e, 0x29, 0xf1, 0x13, 0x80, 0xea,\n                    0x7d, 0xec, 0xae, 0xf9, 0x60, 0x01, 0xd3, 0x6f,\n                    0xb7, 0x9e, 0xb2, 0xcd, 0x2d, 0xc8, 0xf8, 0x84,\n                    0xb2, 0x9f, 0xc3, 0x7e, 0xb4, 0xbe,\n                ];\n\n                const credentialsData = [\n                    0x00, 0x08, 0x9d, 0xc8, 0x3a, 0xb8, 0x80, 0x4f,\n                    0xe3, 0x52, 0xdb, 0x62, 0x9e, 0x97, 0x64, 0x82,\n                    0xa8, 0xa1, 0x6b, 0x7e, 0x4d, 0x68, 0x8c, 0x29,\n                    0x91, 0x38,\n                ];\n\n                it('should fire the serververification event', async function () {\n                    let verification = new Promise((resolve, reject) => {\n                        client.addEventListener(\"serververification\", (e) => {\n                            resolve(e.detail.publickey);\n                        });\n                    });\n\n                    client._sock._websocket._receiveData(new Uint8Array(serverPublicKey));\n                    client._sock._websocket._receiveData(new Uint8Array(serverRandom));\n\n                    expect(await verification).to.deep.equal(new Uint8Array(serverPublicKey));\n                });\n\n                it('should handle approveServer and fire the credentialsrequired event', async function () {\n                    let verification = new Promise((resolve, reject) => {\n                        client.addEventListener(\"serververification\", (e) => {\n                            resolve(e.detail.publickey);\n                        });\n                    });\n                    let credentials = new Promise((resolve, reject) => {\n                        client.addEventListener(\"credentialsrequired\", (e) => {\n                            resolve(e.detail.types);\n                        });\n                    });\n\n                    client._sock._websocket._receiveData(new Uint8Array(serverPublicKey));\n                    client._sock._websocket._receiveData(new Uint8Array(serverRandom));\n\n                    await verification;\n                    client.approveServer();\n\n                    client._sock._websocket._receiveData(new Uint8Array(serverHash));\n                    client._sock._websocket._receiveData(new Uint8Array(subType));\n\n                    expect(await credentials).to.have.members([\"password\"]);\n                });\n\n                it('should send credentials to server', async function () {\n                    let verification = new Promise((resolve, reject) => {\n                        client.addEventListener(\"serververification\", (e) => {\n                            resolve(e.detail.publickey);\n                        });\n                    });\n                    let credentials = new Promise((resolve, reject) => {\n                        client.addEventListener(\"credentialsrequired\", (e) => {\n                            resolve(e.detail.types);\n                        });\n                    });\n\n                    client._sock._websocket._receiveData(new Uint8Array(serverPublicKey));\n                    client._sock._websocket._receiveData(new Uint8Array(serverRandom));\n\n                    await verification;\n                    client.approveServer();\n\n                    client._sock._websocket._receiveData(new Uint8Array(serverHash));\n                    client._sock._websocket._receiveData(new Uint8Array(subType));\n\n                    await credentials;\n\n                    let expected = [];\n                    expected = expected.concat(clientPublicKey);\n                    expected = expected.concat(clientRandom);\n                    expected = expected.concat(clientHash);\n                    expect(client._sock).to.have.sent(new Uint8Array(expected));\n\n                    client.sendCredentials({ \"password\": \"123456\" });\n                    clock.tick();\n\n                    // FIXME: We don't have a good way to know when\n                    //        the async stuff is done, so we hook in\n                    //        to this internal function that is\n                    //        called at the end\n                    await new Promise((resolve, reject) => {\n                        sinon.stub(client._sock._websocket, \"send\")\n                            .callsFake((data) => {\n                                FakeWebSocket.prototype.send.call(client._sock._websocket, data);\n                                resolve();\n                            });\n                    });\n\n                    expect(client._sock).to.have.sent(new Uint8Array(credentialsData));\n                });\n            });\n\n            describe('ARD authentication (type 30) handler', function () {\n                let byteArray = new Uint8Array(Array.from(new Uint8Array(128).keys()));\n                function fakeGetRandomValues(arr) {\n                    if (arr.length == 128) {\n                        arr.set(byteArray);\n                    }\n                    return arr;\n                }\n                before(() => {\n                    sinon.stub(window.crypto, \"getRandomValues\").callsFake(fakeGetRandomValues);\n                });\n                after(() => {\n                    window.crypto.getRandomValues.restore();\n                });\n                it('should fire the credentialsrequired event if all credentials are missing', function () {\n                    const spy = sinon.spy();\n                    client.addEventListener(\"credentialsrequired\", spy);\n                    sendSecurity(30, client);\n\n                    expect(spy).to.have.been.calledOnce;\n                    expect(spy.args[0][0].detail.types).to.have.members([\"username\", \"password\"]);\n                });\n\n                it('should fire the credentialsrequired event if some credentials are missing', function () {\n                    const spy = sinon.spy();\n                    client.addEventListener(\"credentialsrequired\", spy);\n                    client.sendCredentials({ password: 'password'});\n                    sendSecurity(30, client);\n\n                    expect(spy).to.have.been.calledOnce;\n                    expect(spy.args[0][0].detail.types).to.have.members([\"username\", \"password\"]);\n                });\n\n                it('should return properly encrypted credentials and public key', async function () {\n                    client.addEventListener(\"credentialsrequired\", () => {\n                        client.sendCredentials({ username: 'user',\n                                                 password: 'password' });\n                    });\n                    sendSecurity(30, client);\n\n                    expect(client._sock).to.have.sent([30]);\n\n                    const generator = new Uint8Array([127, 255]);\n                    const prime = new Uint8Array(byteArray);\n                    const serverKey = legacyCrypto.generateKey(\n                        { name: \"DH\", g: generator, p: prime }, false, [\"deriveBits\"]);\n                    const clientKey = legacyCrypto.generateKey(\n                        { name: \"DH\", g: generator, p: prime }, false, [\"deriveBits\"]);\n                    const serverPublicKey = legacyCrypto.exportKey(\"raw\", serverKey.publicKey);\n                    const clientPublicKey = legacyCrypto.exportKey(\"raw\", clientKey.publicKey);\n\n                    let data = [];\n\n                    data = data.concat(Array.from(generator));\n                    push16(data, prime.length);\n                    data = data.concat(Array.from(prime));\n                    data = data.concat(Array.from(serverPublicKey));\n\n                    client._sock._websocket._receiveData(new Uint8Array(data));\n\n                    // FIXME: We don't have a good way to know when the\n                    //        async stuff is done, so we hook in to this\n                    //        internal function that is called at the\n                    //        end\n                    await new Promise((resolve, reject) => {\n                        sinon.stub(client, \"_resumeAuthentication\")\n                            .callsFake(() => {\n                                RFB.prototype._resumeAuthentication.call(client);\n                                resolve();\n                            });\n                    });\n                    clock.tick();\n\n                    expect(client._rfbInitState).to.equal('SecurityResult');\n\n                    let expectEncrypted = new Uint8Array([\n                        199, 39, 204, 95, 190, 70, 127, 66, 5, 106, 153, 228, 123, 236, 150, 206,\n                        62, 107, 11, 4, 21, 242, 92, 184, 9, 81, 35, 125, 56, 167, 1, 215,\n                        182, 145, 183, 75, 245, 197, 47, 19, 122, 94, 64, 76, 77, 163, 222, 143,\n                        186, 174, 84, 39, 244, 179, 227, 114, 83, 231, 42, 106, 205, 43, 159, 110,\n                        209, 240, 157, 246, 237, 206, 134, 153, 195, 112, 92, 60, 28, 234, 91, 66,\n                        131, 38, 187, 195, 110, 167, 212, 241, 32, 250, 212, 213, 202, 89, 180, 21,\n                        71, 217, 209, 81, 42, 61, 118, 248, 65, 123, 98, 78, 139, 111, 202, 137,\n                        50, 185, 37, 173, 58, 99, 187, 53, 42, 125, 13, 165, 232, 163, 151, 42, 0]);\n\n                    let output = new Uint8Array(256);\n                    output.set(expectEncrypted, 0);\n                    output.set(clientPublicKey, 128);\n\n                    expect(client._sock).to.have.sent(output);\n                });\n            });\n\n            describe('MSLogonII authentication (type 113) handler', function () {\n                function fakeGetRandomValues(arr) {\n                    if (arr.length == 8) {\n                        arr.set(new Uint8Array([0, 0, 0, 0, 5, 6, 7, 8]));\n                    } else if (arr.length == 256) {\n                        arr.set(new Uint8Array(256));\n                    } else if (arr.length == 64) {\n                        arr.set(new Uint8Array(64));\n                    }\n                    return arr;\n                }\n                const expected = new Uint8Array([\n                    0x00, 0x00, 0x00, 0x00, 0x0a, 0xbc, 0x7c, 0xfd,\n                    0x58, 0x34, 0xd2, 0x24, 0x44, 0x60, 0xf0, 0xd1,\n                    0xa3, 0x73, 0x32, 0x02, 0x07, 0xce, 0xc1, 0x3f,\n                    0x10, 0x53, 0xf1, 0xdd, 0x99, 0xad, 0x44, 0x18,\n                    0xa1, 0xc4, 0xac, 0xc1, 0x1c, 0x13, 0x11, 0x85,\n                    0x3a, 0x6f, 0xcb, 0xc6, 0xb1, 0x6c, 0x68, 0x47,\n                    0x85, 0x01, 0xbb, 0xfa, 0x23, 0x8c, 0x59, 0x47,\n                    0x67, 0x47, 0x56, 0x6e, 0x6f, 0x9f, 0x07, 0x76,\n                    0x2e, 0x90, 0x1e, 0xdc, 0x80, 0xc4, 0x4b, 0x72,\n                    0xd2, 0xd5, 0xcd, 0x4b, 0x14, 0xff, 0x05, 0x8b,\n                    0x8d, 0xf1, 0x9b, 0xe0, 0xff, 0xa5, 0x3b, 0x56,\n                    0xb9, 0x6f, 0x84, 0x3e, 0x15, 0x84, 0x31, 0x4e,\n                    0x10, 0x0b, 0x56, 0xf4, 0x10, 0x05, 0x02, 0xc7,\n                    0x05, 0x0b, 0xc9, 0x66, 0x75, 0x32, 0xd3, 0x74,\n                    0xfc, 0x8c, 0xcf, 0xbd, 0x2d, 0x53, 0xd7, 0xa7,\n                    0xca, 0x82, 0x12, 0xce, 0xbb, 0x33, 0x09, 0x3f,\n                    0xff, 0x76, 0x7c, 0xdf, 0x2c, 0x2f, 0x4d, 0x95,\n                    0x86, 0xe4, 0x10, 0x07, 0x75, 0x1a, 0x6d, 0xdb,\n                    0x05, 0x91, 0x70, 0x34, 0x5c, 0x12, 0xbc, 0x4e,\n                    0x5e, 0xd0, 0x21, 0x39, 0x25, 0x2b, 0x62, 0x19,\n                    0x29, 0xa5, 0xe6, 0x93, 0x7b, 0xf8, 0x3f, 0xcf,\n                    0xd7, 0x3f, 0x0c, 0xd2, 0x68, 0x2d, 0x1e, 0x01,\n                    0x1a, 0x31, 0xc1, 0x59, 0x04, 0x06, 0xf6, 0x3b,\n                    0xec, 0x38, 0xef, 0x1b, 0x5b, 0x39, 0x88, 0xd3,\n                    0xe0, 0x5b, 0xb9, 0xef, 0xc3, 0x82, 0xfa, 0xdf,\n                    0x04, 0xf7, 0x65, 0x56, 0x82, 0x77, 0xfd, 0x63,\n                    0x10, 0xd7, 0xab, 0x0b, 0x5e, 0xd9, 0x07, 0x81,\n                    0x9d, 0xce, 0x26, 0xfb, 0x5d, 0xa8, 0x59, 0x2a,\n                    0xd9, 0xb8, 0xac, 0xcd, 0x6e, 0x61, 0x07, 0x39,\n                    0x9f, 0x8d, 0xdf, 0x53, 0x44, 0xab, 0x28, 0x01,\n                    0x86, 0x4d, 0x07, 0x8a, 0x5b, 0xdd, 0xc1, 0x18,\n                    0x29, 0xaa, 0xa2, 0xbe, 0xe2, 0x9c, 0x9e, 0xb0,\n                    0xb3, 0x2b, 0x2c, 0x93, 0x3e, 0x82, 0x07, 0xa6,\n                    0xef, 0x21, 0x2c, 0xa7, 0xf0, 0x65, 0xba, 0xda,\n                    0x13, 0xe4, 0x41, 0x87, 0x36, 0x1c, 0xa5, 0x81,\n                    0xae, 0xf3, 0x3e, 0xda, 0x03, 0x09, 0x63, 0x4b,\n                    0xb5, 0x29, 0x49, 0xfa, 0xbb, 0xa6, 0x31, 0x3c,\n                    0xc8, 0x15, 0xfb, 0xfc, 0xd6, 0xff, 0x04, 0x92,\n                    0x56, 0xbc, 0x66, 0xf1, 0x78, 0xfb, 0x14, 0x79,\n                    0x48, 0xd2, 0xcf, 0x87, 0x60, 0x23, 0xcf, 0xdb,\n                    0x1b, 0xad, 0x42, 0x32, 0x4e, 0x6d, 0x1f, 0x49,\n                ]);\n                before(() => {\n                    sinon.stub(window.crypto, \"getRandomValues\").callsFake(fakeGetRandomValues);\n                });\n                after(() => {\n                    window.crypto.getRandomValues.restore();\n                });\n                it('should send public value and encrypted credentials', function () {\n                    client.addEventListener(\"credentialsrequired\", () => {\n                        client.sendCredentials({ username: 'username',\n                                                 password: 'password123456' });\n                    });\n                    sendSecurity(113, client);\n\n                    expect(client._sock).to.have.sent([113]);\n\n                    const g = new Uint8Array([0, 0, 0, 0, 0, 1, 0, 1]);\n                    const p = new Uint8Array([0, 0, 0, 0, 0x25, 0x18, 0x26, 0x17]);\n                    const A = new Uint8Array([0, 0, 0, 0, 0x0e, 0x12, 0xd0, 0xf5]);\n\n                    client._sock._websocket._receiveData(g);\n                    client._sock._websocket._receiveData(p);\n                    client._sock._websocket._receiveData(A);\n                    clock.tick();\n\n                    expect(client._sock).to.have.sent(expected);\n                    expect(client._rfbInitState).to.equal('SecurityResult');\n                });\n            });\n\n            describe('XVP authentication (type 22) handler', function () {\n                it('should fall through to standard VNC authentication upon completion', function () {\n                    client.addEventListener(\"credentialsrequired\", () => {\n                        client.sendCredentials({ username: 'user',\n                                                 target: 'target',\n                                                 password: 'password' });\n                    });\n                    sinon.spy(client, \"_negotiateStdVNCAuth\");\n                    sendSecurity(22, client);\n                    clock.tick();\n                    expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;\n                });\n\n                it('should fire the credentialsrequired event if all credentials are missing', function () {\n                    const spy = sinon.spy();\n                    client.addEventListener(\"credentialsrequired\", spy);\n                    sendSecurity(22, client);\n\n                    expect(spy).to.have.been.calledOnce;\n                    expect(spy.args[0][0].detail.types).to.have.members([\"username\", \"password\", \"target\"]);\n                });\n\n                it('should fire the credentialsrequired event if some credentials are missing', function () {\n                    const spy = sinon.spy();\n                    client.addEventListener(\"credentialsrequired\", spy);\n                    client.sendCredentials({ username: 'user',\n                                             target: 'target' });\n                    sendSecurity(22, client);\n\n                    expect(spy).to.have.been.calledOnce;\n                    expect(spy.args[0][0].detail.types).to.have.members([\"username\", \"password\", \"target\"]);\n                });\n\n                it('should send user and target separately', function () {\n                    client.addEventListener(\"credentialsrequired\", () => {\n                        client.sendCredentials({ username: 'user',\n                                                 target: 'target',\n                                                 password: 'password' });\n                    });\n                    sendSecurity(22, client);\n                    clock.tick();\n\n                    const expected = [22, 4, 6]; // auth selection, len user, len target\n                    for (let i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }\n\n                    expect(client._sock).to.have.sent(new Uint8Array(expected));\n                });\n            });\n\n            describe('TightVNC authentication (type 16) handler', function () {\n                beforeEach(function () {\n                    sendSecurity(16, client);\n                    client._sock._websocket._getSentData();  // skip the security reply\n                });\n\n                function sendNumStrPairs(pairs, client) {\n                    const data = [];\n                    push32(data, pairs.length);\n\n                    for (let i = 0; i < pairs.length; i++) {\n                        push32(data, pairs[i][0]);\n                        for (let j = 0; j < 4; j++) {\n                            data.push(pairs[i][1].charCodeAt(j));\n                        }\n                        for (let j = 0; j < 8; j++) {\n                            data.push(pairs[i][2].charCodeAt(j));\n                        }\n                    }\n\n                    client._sock._websocket._receiveData(new Uint8Array(data));\n                }\n\n                it('should skip tunnel negotiation if no tunnels are requested', function () {\n                    client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));\n                    expect(client._rfbTightVNC).to.be.true;\n                });\n\n                it('should fail if no supported tunnels are listed', function () {\n                    let callback = sinon.spy();\n                    client.addEventListener(\"disconnect\", callback);\n\n                    sendNumStrPairs([[123, 'OTHR', 'SOMETHNG']], client);\n\n                    expect(callback).to.have.been.calledOnce;\n                    expect(callback.args[0][0].detail.clean).to.be.false;\n                });\n\n                it('should choose the notunnel tunnel type', function () {\n                    sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));\n                });\n\n                it('should choose the notunnel tunnel type for Siemens devices', function () {\n                    sendNumStrPairs([[1, 'SICR', 'SCHANNEL'], [2, 'SICR', 'SCHANLPW']], client);\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));\n                });\n\n                it('should continue to sub-auth negotiation after tunnel negotiation', function () {\n                    sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL']], client);\n                    client._sock._websocket._getSentData();  // skip the tunnel choice here\n                    sendNumStrPairs([[1, 'STDV', 'NOAUTH__']], client);\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));\n                    expect(client._rfbInitState).to.equal('SecurityResult');\n                });\n\n                it('should accept the \"no auth\" auth type and transition to SecurityResult', function () {\n                    sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL']], client);\n                    client._sock._websocket._getSentData();  // skip the tunnel choice here\n                    sendNumStrPairs([[1, 'STDV', 'NOAUTH__']], client);\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));\n                    expect(client._rfbInitState).to.equal('SecurityResult');\n                });\n\n                it('should accept VNC authentication and transition to that', function () {\n                    sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL']], client);\n                    client._sock._websocket._getSentData();  // skip the tunnel choice here\n                    sinon.spy(client, \"_negotiateStdVNCAuth\");\n                    sendNumStrPairs([[2, 'STDV', 'VNCAUTH__']], client);\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));\n                    expect(client._negotiateStdVNCAuth).to.have.been.calledOnce;\n                    expect(client._rfbAuthScheme).to.equal(2);\n                });\n\n                it('should fail if there are no supported auth types', function () {\n                    let callback = sinon.spy();\n                    client.addEventListener(\"disconnect\", callback);\n\n                    sendNumStrPairs([[0, 'TGHT', 'NOTUNNEL']], client);\n                    client._sock._websocket._getSentData();  // skip the tunnel choice here\n                    sendNumStrPairs([[23, 'stdv', 'badval__']], client);\n\n                    expect(callback).to.have.been.calledOnce;\n                    expect(callback.args[0][0].detail.clean).to.be.false;\n                });\n            });\n\n            describe('VeNCrypt authentication (type 19) handler', function () {\n                beforeEach(function () {\n                    sendSecurity(19, client);\n                    expect(client._sock).to.have.sent(new Uint8Array([19]));\n                });\n\n                it('should fail with non-0.2 versions', function () {\n                    let callback = sinon.spy();\n                    client.addEventListener(\"disconnect\", callback);\n\n                    client._sock._websocket._receiveData(new Uint8Array([0, 1]));\n\n                    expect(callback).to.have.been.calledOnce;\n                    expect(callback.args[0][0].detail.clean).to.be.false;\n                });\n\n                it('should fail if there are no supported subtypes', function () {\n                    // VeNCrypt version\n                    client._sock._websocket._receiveData(new Uint8Array([0, 2]));\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 2]));\n                    // Server ACK.\n                    client._sock._websocket._receiveData(new Uint8Array([0]));\n                    // Subtype list\n                    let callback = sinon.spy();\n                    client.addEventListener(\"disconnect\", callback);\n                    client._sock._websocket._receiveData(new Uint8Array([2, 0, 0, 0, 9, 0, 0, 1, 4]));\n                    expect(callback).to.have.been.calledOnce;\n                    expect(callback.args[0][0].detail.clean).to.be.false;\n                });\n\n                it('should support standard types', function () {\n                    // VeNCrypt version\n                    client._sock._websocket._receiveData(new Uint8Array([0, 2]));\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 2]));\n                    // Server ACK.\n                    client._sock._websocket._receiveData(new Uint8Array([0]));\n                    // Subtype list\n                    client._sock._websocket._receiveData(new Uint8Array([2, 0, 0, 0, 2, 0, 0, 1, 4]));\n\n                    let expectedResponse = [];\n                    push32(expectedResponse, 2); // Chosen subtype.\n\n                    expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));\n                });\n\n                it('should respect server preference order', function () {\n                    // VeNCrypt version\n                    client._sock._websocket._receiveData(new Uint8Array([0, 2]));\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 2]));\n                    // Server ACK.\n                    client._sock._websocket._receiveData(new Uint8Array([0]));\n                    // Subtype list\n                    let subtypes = [ 6 ];\n                    push32(subtypes, 79);\n                    push32(subtypes, 30);\n                    push32(subtypes, 188);\n                    push32(subtypes, 256);\n                    push32(subtypes, 6);\n                    push32(subtypes, 1);\n                    client._sock._websocket._receiveData(new Uint8Array(subtypes));\n\n                    let expectedResponse = [];\n                    push32(expectedResponse, 30); // Chosen subtype.\n\n                    expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));\n                });\n\n                it('should ignore redundant VeNCrypt subtype', function () {\n                    // VeNCrypt version\n                    client._sock._websocket._receiveData(new Uint8Array([0, 2]));\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 2]));\n                    // Server ACK.\n                    client._sock._websocket._receiveData(new Uint8Array([0]));\n                    // Subtype list\n                    client._sock._websocket._receiveData(new Uint8Array([2, 0, 0, 0, 19, 0, 0, 0, 2]));\n\n                    let expectedResponse = [];\n                    push32(expectedResponse, 2); // Chosen subtype.\n\n                    expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));\n                });\n            });\n\n            describe('Plain authentication (type 256) handler', function () {\n                beforeEach(function () {\n                    sendSecurity(19, client);\n                    expect(client._sock).to.have.sent(new Uint8Array([19]));\n                    // VeNCrypt version\n                    client._sock._websocket._receiveData(new Uint8Array([0, 2]));\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 2]));\n                    // Server ACK.\n                    client._sock._websocket._receiveData(new Uint8Array([0]));\n                });\n\n                it('should support Plain authentication', function () {\n                    client.addEventListener(\"credentialsrequired\", () => {\n                        client.sendCredentials({ username: 'username', password: 'password' });\n                    });\n                    client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 0, 1, 0]));\n\n                    clock.tick();\n\n                    const expectedResponse = [];\n                    push32(expectedResponse, 8);\n                    push32(expectedResponse, 8);\n                    pushString(expectedResponse, 'username');\n                    pushString(expectedResponse, 'password');\n                    expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));\n                });\n\n                it('should support Plain authentication with an empty password', function () {\n                    client.addEventListener(\"credentialsrequired\", () => {\n                        client.sendCredentials({ username: 'username', password: '' });\n                    });\n                    client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 0, 1, 0]));\n\n                    clock.tick();\n\n                    const expectedResponse = [];\n                    push32(expectedResponse, 8);\n                    push32(expectedResponse, 0);\n                    pushString(expectedResponse, 'username');\n                    pushString(expectedResponse, '');\n                    expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));\n                });\n\n                it('should support Plain authentication with a very long username and password', function () {\n                    client.addEventListener(\"credentialsrequired\", () => {\n                        client.sendCredentials({ username: 'a'.repeat(300), password: 'b'.repeat(300) });\n                    });\n                    client._sock._websocket._receiveData(new Uint8Array([1, 0, 0, 1, 0]));\n                    expect(client._sock).to.have.sent(new Uint8Array([0, 0, 1, 0]));\n\n                    clock.tick();\n\n                    const expectedResponse = [];\n                    push32(expectedResponse, 300);\n                    push32(expectedResponse, 300);\n                    pushString(expectedResponse, 'a'.repeat(300));\n                    pushString(expectedResponse, 'b'.repeat(300));\n                    expect(client._sock).to.have.sent(new Uint8Array(expectedResponse));\n                });\n            });\n        });\n\n        describe('Legacy SecurityResult', function () {\n            it('should not include reason in securityfailure event for versions < 3.7', function () {\n                client.addEventListener(\"credentialsrequired\", () => {\n                    client.sendCredentials({ password: 'passwd' });\n                });\n                const spy = sinon.spy();\n                client.addEventListener(\"securityfailure\", spy);\n                sendVer('003.006\\n', client);\n                client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));\n                const challenge = [];\n                for (let i = 0; i < 16; i++) { challenge[i] = i; }\n                client._sock._websocket._receiveData(new Uint8Array(challenge));\n\n                client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));\n                expect(spy).to.have.been.calledOnce;\n                expect(spy.args[0][0].detail.status).to.equal(2);\n                expect('reason' in spy.args[0][0].detail).to.be.false;\n            });\n\n            it('should not include reason in securityfailure event for versions < 3.8', function () {\n                client.addEventListener(\"credentialsrequired\", () => {\n                    client.sendCredentials({ password: 'passwd' });\n                });\n                const spy = sinon.spy();\n                client.addEventListener(\"securityfailure\", spy);\n                sendVer('003.007\\n', client);\n                sendSecurity(2, client);\n                const challenge = [];\n                for (let i = 0; i < 16; i++) { challenge[i] = i; }\n                client._sock._websocket._receiveData(new Uint8Array(challenge));\n\n                client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 2]));\n                expect(spy).to.have.been.calledOnce;\n                expect(spy.args[0][0].detail.status).to.equal(2);\n                expect('reason' in spy.args[0][0].detail).to.be.false;\n            });\n        });\n\n        describe('SecurityResult', function () {\n            beforeEach(function () {\n                sendVer('003.008\\n', client);\n                client._sock._websocket._getSentData();\n                sendSecurity(1, client);\n                client._sock._websocket._getSentData();\n            });\n\n            it('should fall through to ServerInitialisation on a response code of 0', function () {\n                client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));\n                expect(client._rfbInitState).to.equal('ServerInitialisation');\n            });\n\n            it('should include reason when provided in securityfailure event', function () {\n                const spy = sinon.spy();\n                client.addEventListener(\"securityfailure\", spy);\n                const failureData = [0, 0, 0, 1, 0, 0, 0, 12, 115, 117, 99, 104,\n                                     32, 102, 97, 105, 108, 117, 114, 101];\n                client._sock._websocket._receiveData(new Uint8Array(failureData));\n                expect(spy).to.have.been.calledOnce;\n                expect(spy.args[0][0].detail.status).to.equal(1);\n                expect(spy.args[0][0].detail.reason).to.equal('such failure');\n            });\n\n            it('should not include reason when length is zero in securityfailure event', function () {\n                const spy = sinon.spy();\n                client.addEventListener(\"securityfailure\", spy);\n                const failureData = [0, 0, 0, 1, 0, 0, 0, 0];\n                client._sock._websocket._receiveData(new Uint8Array(failureData));\n                expect(spy).to.have.been.calledOnce;\n                expect(spy.args[0][0].detail.status).to.equal(1);\n                expect('reason' in spy.args[0][0].detail).to.be.false;\n            });\n        });\n\n        describe('ClientInitialisation', function () {\n            it('should transition to the ServerInitialisation state', function () {\n                const client = makeRFB();\n                client._rfbConnectionState = 'connecting';\n                client._rfbInitState = 'SecurityResult';\n                client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));\n                expect(client._rfbInitState).to.equal('ServerInitialisation');\n            });\n\n            it('should send 1 if we are in shared mode', function () {\n                const client = makeRFB('wss://host:8675', { shared: true });\n                client._rfbConnectionState = 'connecting';\n                client._rfbInitState = 'SecurityResult';\n                client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));\n                expect(client._sock).to.have.sent(new Uint8Array([1]));\n            });\n\n            it('should send 0 if we are not in shared mode', function () {\n                const client = makeRFB('wss://host:8675', { shared: false });\n                client._rfbConnectionState = 'connecting';\n                client._rfbInitState = 'SecurityResult';\n                client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 0]));\n                expect(client._sock).to.have.sent(new Uint8Array([0]));\n            });\n        });\n\n        describe('ServerInitialisation', function () {\n            beforeEach(function () {\n                client._rfbInitState = 'ServerInitialisation';\n            });\n\n            function sendServerInit(opts, client) {\n                const fullOpts = { width: 10, height: 12, bpp: 24, depth: 24, bigEndian: 0,\n                                   trueColor: 1, redMax: 255, greenMax: 255, blueMax: 255,\n                                   redShift: 16, greenShift: 8, blueShift: 0, name: 'a name' };\n                for (let opt in opts) {\n                    fullOpts[opt] = opts[opt];\n                }\n                const data = [];\n\n                push16(data, fullOpts.width);\n                push16(data, fullOpts.height);\n\n                data.push(fullOpts.bpp);\n                data.push(fullOpts.depth);\n                data.push(fullOpts.bigEndian);\n                data.push(fullOpts.trueColor);\n\n                push16(data, fullOpts.redMax);\n                push16(data, fullOpts.greenMax);\n                push16(data, fullOpts.blueMax);\n                push8(data, fullOpts.redShift);\n                push8(data, fullOpts.greenShift);\n                push8(data, fullOpts.blueShift);\n\n                // padding\n                push8(data, 0);\n                push8(data, 0);\n                push8(data, 0);\n\n                client._sock._websocket._receiveData(new Uint8Array(data));\n\n                const nameData = [];\n                let nameLen = [];\n                pushString(nameData, fullOpts.name);\n                push32(nameLen, nameData.length);\n\n                client._sock._websocket._receiveData(new Uint8Array(nameLen));\n                client._sock._websocket._receiveData(new Uint8Array(nameData));\n            }\n\n            it('should set the framebuffer width and height', function () {\n                sendServerInit({ width: 32, height: 84 }, client);\n                expect(client._fbWidth).to.equal(32);\n                expect(client._fbHeight).to.equal(84);\n            });\n\n            // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them\n\n            it('should set the framebuffer name and call the callback', function () {\n                const spy = sinon.spy();\n                client.addEventListener(\"desktopname\", spy);\n                sendServerInit({ name: 'som€ nam€' }, client);\n\n                expect(client._fbName).to.equal('som€ nam€');\n                expect(spy).to.have.been.calledOnce;\n                expect(spy.args[0][0].detail.name).to.equal('som€ nam€');\n            });\n\n            it('should handle the extended init message of the tight encoding', function () {\n                // NB(sross): we don't actually do anything with it, so just test that we can\n                //            read it w/o throwing an error\n                client._rfbTightVNC = true;\n                sendServerInit({}, client);\n\n                const tightData = [];\n                push16(tightData, 1);\n                push16(tightData, 2);\n                push16(tightData, 3);\n                push16(tightData, 0);\n                for (let i = 0; i < 16 + 32 + 48; i++) {\n                    tightData.push(i);\n                }\n                client._sock._websocket._receiveData(new Uint8Array(tightData));\n\n                expect(client._rfbConnectionState).to.equal('connected');\n            });\n\n            it('should resize the display', function () {\n                sinon.spy(client._display, 'resize');\n                sendServerInit({ width: 27, height: 32 }, client);\n\n                expect(client._display.resize).to.have.been.calledOnce;\n                expect(client._display.resize).to.have.been.calledWith(27, 32);\n            });\n\n            it('should grab the keyboard', function () {\n                sinon.spy(client._keyboard, 'grab');\n                sendServerInit({}, client);\n                expect(client._keyboard.grab).to.have.been.calledOnce;\n            });\n\n            describe('Initial update request', function () {\n                beforeEach(function () {\n                    sinon.spy(RFB.messages, \"pixelFormat\");\n                    sinon.spy(RFB.messages, \"clientEncodings\");\n                    sinon.spy(RFB.messages, \"fbUpdateRequest\");\n                });\n\n                afterEach(function () {\n                    RFB.messages.pixelFormat.restore();\n                    RFB.messages.clientEncodings.restore();\n                    RFB.messages.fbUpdateRequest.restore();\n                });\n\n                // TODO(directxman12): test the various options in this configuration matrix\n                it('should reply with the pixel format, client encodings, and initial update request', function () {\n                    sendServerInit({ width: 27, height: 32 }, client);\n\n                    expect(RFB.messages.pixelFormat).to.have.been.calledOnce;\n                    expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 24, true);\n                    expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);\n                    expect(RFB.messages.clientEncodings).to.have.been.calledOnce;\n                    expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.encodingTight);\n                    RFB.messages.clientEncodings.getCall(0).args[1].forEach((enc) => {\n                        expect(enc).to.be.a('number');\n                        expect(Number.isInteger(enc)).to.be.true;\n                    });\n                    expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);\n                    expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;\n                    expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);\n                });\n\n                it('should reply with restricted settings for Intel AMT servers', function () {\n                    sendServerInit({ width: 27, height: 32, name: \"Intel(r) AMT KVM\"}, client);\n\n                    expect(RFB.messages.pixelFormat).to.have.been.calledOnce;\n                    expect(RFB.messages.pixelFormat).to.have.been.calledWith(client._sock, 8, true);\n                    expect(RFB.messages.pixelFormat).to.have.been.calledBefore(RFB.messages.clientEncodings);\n                    expect(RFB.messages.clientEncodings).to.have.been.calledOnce;\n                    expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingTight);\n                    expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.not.include(encodings.encodingHextile);\n                    expect(RFB.messages.clientEncodings).to.have.been.calledBefore(RFB.messages.fbUpdateRequest);\n                    expect(RFB.messages.fbUpdateRequest).to.have.been.calledOnce;\n                    expect(RFB.messages.fbUpdateRequest).to.have.been.calledWith(client._sock, false, 0, 0, 27, 32);\n                });\n            });\n\n            it('should send the \"connect\" event', function () {\n                let spy = sinon.spy();\n                client.addEventListener('connect', spy);\n                sendServerInit({}, client);\n                expect(spy).to.have.been.calledOnce;\n            });\n        });\n    });\n\n    describe('Protocol message processing after completing initialization', function () {\n        let client;\n\n        beforeEach(function () {\n            client = makeRFB();\n            client._fbName = 'some device';\n            client._fbWidth = 640;\n            client._fbHeight = 20;\n        });\n\n        describe('Framebuffer update handling', function () {\n            it('should send an update request if there is sufficient data', function () {\n                let esock = new Websock();\n                let ews = new FakeWebSocket();\n                ews._open();\n                esock.attach(ews);\n                RFB.messages.fbUpdateRequest(esock, true, 0, 0, 640, 20);\n                let expected = ews._getSentData();\n\n                client._framebufferUpdate = () => true;\n                client._sock._websocket._receiveData(new Uint8Array([0]));\n\n                expect(client._sock).to.have.sent(expected);\n            });\n\n            it('should not send an update request if we need more data', function () {\n                client._sock._websocket._receiveData(new Uint8Array([0]));\n                expect(client._sock).to.have.sent(new Uint8Array([]));\n            });\n\n            it('should resume receiving an update if we previously did not have enough data', function () {\n                let esock = new Websock();\n                let ews = new FakeWebSocket();\n                ews._open();\n                esock.attach(ews);\n                RFB.messages.fbUpdateRequest(esock, true, 0, 0, 640, 20);\n                let expected = ews._getSentData();\n\n                // just enough to set FBU.rects\n                client._sock._websocket._receiveData(new Uint8Array([0, 0, 0, 3]));\n                expect(client._sock._websocket._getSentData()).to.have.length(0);\n\n                client._framebufferUpdate = function () { this._sock.rQskipBytes(1); return true; };  // we magically have enough data\n                // 247 should *not* be used as the message type here\n                client._sock._websocket._receiveData(new Uint8Array([247]));\n                expect(client._sock).to.have.sent(expected);\n            });\n\n            it('should not send a request in continuous updates mode', function () {\n                client._enabledContinuousUpdates = true;\n                client._framebufferUpdate = () => true;\n                client._sock._websocket._receiveData(new Uint8Array([0]));\n\n                expect(client._sock).to.have.sent(new Uint8Array([]));\n            });\n\n            it('should fail on an unsupported encoding', function () {\n                let callback = sinon.spy();\n                client.addEventListener(\"disconnect\", callback);\n\n                const rectInfo = { x: 8, y: 11, width: 27, height: 32, encoding: 234 };\n                sendFbuMsg([rectInfo], [[]], client);\n\n                expect(callback).to.have.been.calledOnce;\n                expect(callback.args[0][0].detail.clean).to.be.false;\n            });\n\n            describe('Message encoding handlers', function () {\n                beforeEach(function () {\n                    // a really small frame\n                    client._fbWidth = 4;\n                    client._fbHeight = 4;\n                    client._fbDepth = 24;\n                    client._display.resize(4, 4);\n                });\n\n                it('should handle the DesktopSize pseduo-encoding', function () {\n                    sinon.spy(client._display, 'resize');\n                    sendFbuMsg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client);\n\n                    expect(client._fbWidth).to.equal(20);\n                    expect(client._fbHeight).to.equal(50);\n\n                    expect(client._display.resize).to.have.been.calledOnce;\n                    expect(client._display.resize).to.have.been.calledWith(20, 50);\n                });\n\n                describe('the ExtendedDesktopSize pseudo-encoding handler', function () {\n                    beforeEach(function () {\n                        // a really small frame\n                        client._fbWidth = 4;\n                        client._fbHeight = 4;\n                        client._display.resize(4, 4);\n                        sinon.spy(client._display, 'resize');\n                    });\n\n                    function makeScreenData(nrOfScreens) {\n                        const data = [];\n                        push8(data, nrOfScreens);   // number-of-screens\n                        push8(data, 0);               // padding\n                        push16(data, 0);              // padding\n                        for (let i=0; i<nrOfScreens; i += 1) {\n                            push32(data, 0);  // id\n                            push16(data, 0);  // x-position\n                            push16(data, 0);  // y-position\n                            push16(data, 20); // width\n                            push16(data, 50); // height\n                            push32(data, 0);  // flags\n                        }\n                        return data;\n                    }\n\n                    it('should handle a resize requested by this client', function () {\n                        const reasonForChange = 1; // requested by this client\n                        const statusCode      = 0; // No error\n\n                        sendFbuMsg([{ x: reasonForChange, y: statusCode,\n                                      width: 20, height: 50, encoding: -308 }],\n                                   makeScreenData(1), client);\n\n                        expect(client._fbWidth).to.equal(20);\n                        expect(client._fbHeight).to.equal(50);\n\n                        expect(client._display.resize).to.have.been.calledOnce;\n                        expect(client._display.resize).to.have.been.calledWith(20, 50);\n                    });\n\n                    it('should handle a resize requested by another client', function () {\n                        const reasonForChange = 2; // requested by another client\n                        const statusCode      = 0; // No error\n\n                        sendFbuMsg([{ x: reasonForChange, y: statusCode,\n                                      width: 20, height: 50, encoding: -308 }],\n                                   makeScreenData(1), client);\n\n                        expect(client._fbWidth).to.equal(20);\n                        expect(client._fbHeight).to.equal(50);\n\n                        expect(client._display.resize).to.have.been.calledOnce;\n                        expect(client._display.resize).to.have.been.calledWith(20, 50);\n                    });\n\n                    it('should be able to recieve requests which contain data for multiple screens', function () {\n                        const reasonForChange = 2; // requested by another client\n                        const statusCode      = 0; // No error\n\n                        sendFbuMsg([{ x: reasonForChange, y: statusCode,\n                                      width: 60, height: 50, encoding: -308 }],\n                                   makeScreenData(3), client);\n\n                        expect(client._fbWidth).to.equal(60);\n                        expect(client._fbHeight).to.equal(50);\n\n                        expect(client._display.resize).to.have.been.calledOnce;\n                        expect(client._display.resize).to.have.been.calledWith(60, 50);\n                    });\n\n                    it('should not handle a failed request', function () {\n                        const reasonForChange = 1; // requested by this client\n                        const statusCode      = 1; // Resize is administratively prohibited\n\n                        sendFbuMsg([{ x: reasonForChange, y: statusCode,\n                                      width: 20, height: 50, encoding: -308 }],\n                                   makeScreenData(1), client);\n\n                        expect(client._fbWidth).to.equal(4);\n                        expect(client._fbHeight).to.equal(4);\n\n                        expect(client._display.resize).to.not.have.been.called;\n                    });\n                });\n\n                describe('the Cursor pseudo-encoding handler', function () {\n                    beforeEach(function () {\n                        sinon.spy(client._cursor, 'change');\n                    });\n\n                    it('should handle a standard cursor', function () {\n                        const info = { x: 5, y: 7,\n                                       width: 4, height: 4,\n                                       encoding: -239};\n                        let rect = [];\n                        let expected = [];\n\n                        for (let i = 0;i < info.width*info.height;i++) {\n                            push32(rect, 0x11223300);\n                        }\n                        push32(rect, 0xa0a0a0a0);\n\n                        for (let i = 0;i < info.width*info.height/2;i++) {\n                            push32(expected, 0x332211ff);\n                            push32(expected, 0x33221100);\n                        }\n                        expected = new Uint8Array(expected);\n\n                        sendFbuMsg([info], [rect], client);\n\n                        expect(client._cursor.change).to.have.been.calledOnce;\n                        expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);\n                    });\n\n                    it('should handle an empty cursor', function () {\n                        const info = { x: 0, y: 0,\n                                       width: 0, height: 0,\n                                       encoding: -239};\n                        const rect = [];\n\n                        sendFbuMsg([info], [rect], client);\n\n                        expect(client._cursor.change).to.have.been.calledOnce;\n                        expect(client._cursor.change).to.have.been.calledWith(new Uint8Array, 0, 0, 0, 0);\n                    });\n\n                    it('should handle a transparent cursor', function () {\n                        const info = { x: 5, y: 7,\n                                       width: 4, height: 4,\n                                       encoding: -239};\n                        let rect = [];\n                        let expected = [];\n\n                        for (let i = 0;i < info.width*info.height;i++) {\n                            push32(rect, 0x11223300);\n                        }\n                        push32(rect, 0x00000000);\n\n                        for (let i = 0;i < info.width*info.height;i++) {\n                            push32(expected, 0x33221100);\n                        }\n                        expected = new Uint8Array(expected);\n\n                        sendFbuMsg([info], [rect], client);\n\n                        expect(client._cursor.change).to.have.been.calledOnce;\n                        expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);\n                    });\n\n                    describe('dot for empty cursor', function () {\n                        beforeEach(function () {\n                            client.showDotCursor = true;\n                            // Was called when we enabled dot cursor\n                            client._cursor.change.resetHistory();\n                        });\n\n                        it('should show a standard cursor', function () {\n                            const info = { x: 5, y: 7,\n                                           width: 4, height: 4,\n                                           encoding: -239};\n                            let rect = [];\n                            let expected = [];\n\n                            for (let i = 0;i < info.width*info.height;i++) {\n                                push32(rect, 0x11223300);\n                            }\n                            push32(rect, 0xa0a0a0a0);\n\n                            for (let i = 0;i < info.width*info.height/2;i++) {\n                                push32(expected, 0x332211ff);\n                                push32(expected, 0x33221100);\n                            }\n                            expected = new Uint8Array(expected);\n\n                            sendFbuMsg([info], [rect], client);\n\n                            expect(client._cursor.change).to.have.been.calledOnce;\n                            expect(client._cursor.change).to.have.been.calledWith(expected, 5, 7, 4, 4);\n                        });\n\n                        it('should handle an empty cursor', function () {\n                            const info = { x: 0, y: 0,\n                                           width: 0, height: 0,\n                                           encoding: -239};\n                            const rect = [];\n                            const dot = RFB.cursors.dot;\n\n                            sendFbuMsg([info], [rect], client);\n\n                            expect(client._cursor.change).to.have.been.calledOnce;\n                            expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,\n                                                                                  dot.hotx,\n                                                                                  dot.hoty,\n                                                                                  dot.w,\n                                                                                  dot.h);\n                        });\n\n                        it('should handle a transparent cursor', function () {\n                            const info = { x: 5, y: 7,\n                                           width: 4, height: 4,\n                                           encoding: -239};\n                            let rect = [];\n                            const dot = RFB.cursors.dot;\n\n                            for (let i = 0;i < info.width*info.height;i++) {\n                                push32(rect, 0x11223300);\n                            }\n                            push32(rect, 0x00000000);\n\n                            sendFbuMsg([info], [rect], client);\n\n                            expect(client._cursor.change).to.have.been.calledOnce;\n                            expect(client._cursor.change).to.have.been.calledWith(dot.rgbaPixels,\n                                                                                  dot.hotx,\n                                                                                  dot.hoty,\n                                                                                  dot.w,\n                                                                                  dot.h);\n                        });\n                    });\n                });\n\n                describe('the VMware cursor pseudo-encoding handler', function () {\n                    beforeEach(function () {\n                        sinon.spy(client._cursor, 'change');\n                    });\n                    afterEach(function () {\n                        client._cursor.change.resetHistory();\n                    });\n\n                    it('should handle the VMware cursor pseudo-encoding', function () {\n                        let data = [0x00, 0x00, 0xff, 0,\n                                    0x00, 0xff, 0x00, 0,\n                                    0x00, 0xff, 0x00, 0,\n                                    0x00, 0x00, 0xff, 0];\n                        let rect = [];\n                        push8(rect, 0);\n                        push8(rect, 0);\n\n                        //AND-mask\n                        for (let i = 0; i < data.length; i++) {\n                            push8(rect, data[i]);\n                        }\n                        //XOR-mask\n                        for (let i = 0; i < data.length; i++) {\n                            push8(rect, data[i]);\n                        }\n\n                        sendFbuMsg([{ x: 0, y: 0, width: 2, height: 2,\n                                      encoding: 0x574d5664}],\n                                   [rect], client);\n                        expect(client._FBU.rects).to.equal(0);\n                    });\n\n                    it('should handle insufficient cursor pixel data', function () {\n\n                        // Specified 14x23 pixels for the cursor,\n                        // but only send 2x2 pixels worth of data\n                        let w = 14;\n                        let h = 23;\n                        let data = [0x00, 0x00, 0xff, 0,\n                                    0x00, 0xff, 0x00, 0];\n                        let rect = [];\n\n                        push8(rect, 0);\n                        push8(rect, 0);\n\n                        //AND-mask\n                        for (let i = 0; i < data.length; i++) {\n                            push8(rect, data[i]);\n                        }\n                        //XOR-mask\n                        for (let i = 0; i < data.length; i++) {\n                            push8(rect, data[i]);\n                        }\n\n                        sendFbuMsg([{ x: 0, y: 0, width: w, height: h,\n                                      encoding: 0x574d5664}],\n                                   [rect], client);\n\n                        // expect one FBU to remain unhandled\n                        expect(client._FBU.rects).to.equal(1);\n                    });\n\n                    it('should update the cursor when type is classic', function () {\n                        let andMask =\n                            [0xff, 0xff, 0xff, 0xff,  //Transparent\n                             0xff, 0xff, 0xff, 0xff,  //Transparent\n                             0x00, 0x00, 0x00, 0x00,  //Opaque\n                             0xff, 0xff, 0xff, 0xff]; //Inverted\n\n                        let xorMask =\n                            [0x00, 0x00, 0x00, 0x00,  //Transparent\n                             0x00, 0x00, 0x00, 0x00,  //Transparent\n                             0x11, 0x22, 0x33, 0x44,  //Opaque\n                             0xff, 0xff, 0xff, 0x44]; //Inverted\n\n                        let rect = [];\n                        push8(rect, 0); //cursor_type\n                        push8(rect, 0); //padding\n                        let hotx = 0;\n                        let hoty = 0;\n                        let w = 2;\n                        let h = 2;\n\n                        //AND-mask\n                        for (let i = 0; i < andMask.length; i++) {\n                            push8(rect, andMask[i]);\n                        }\n                        //XOR-mask\n                        for (let i = 0; i < xorMask.length; i++) {\n                            push8(rect, xorMask[i]);\n                        }\n\n                        let expectedRgba = [0x00, 0x00, 0x00, 0x00,\n                                            0x00, 0x00, 0x00, 0x00,\n                                            0x33, 0x22, 0x11, 0xff,\n                                            0x00, 0x00, 0x00, 0xff];\n\n                        sendFbuMsg([{ x: hotx, y: hoty,\n                                      width: w, height: h,\n                                      encoding: 0x574d5664}],\n                                   [rect], client);\n\n                        expect(client._cursor.change)\n                            .to.have.been.calledOnce;\n                        expect(client._cursor.change)\n                            .to.have.been.calledWith(expectedRgba,\n                                                     hotx, hoty,\n                                                     w, h);\n                    });\n\n                    it('should update the cursor when type is alpha', function () {\n                        let data = [0xee, 0x55, 0xff, 0x00, // rgba\n                                    0x00, 0xff, 0x00, 0xff,\n                                    0x00, 0xff, 0x00, 0x22,\n                                    0x00, 0xff, 0x00, 0x22,\n                                    0x00, 0xff, 0x00, 0x22,\n                                    0x00, 0x00, 0xff, 0xee];\n                        let rect = [];\n                        push8(rect, 1); //cursor_type\n                        push8(rect, 0); //padding\n                        let hotx = 0;\n                        let hoty = 0;\n                        let w = 3;\n                        let h = 2;\n\n                        for (let i = 0; i < data.length; i++) {\n                            push8(rect, data[i]);\n                        }\n\n                        let expectedRgba = [0xee, 0x55, 0xff, 0x00,\n                                            0x00, 0xff, 0x00, 0xff,\n                                            0x00, 0xff, 0x00, 0x22,\n                                            0x00, 0xff, 0x00, 0x22,\n                                            0x00, 0xff, 0x00, 0x22,\n                                            0x00, 0x00, 0xff, 0xee];\n\n                        sendFbuMsg([{ x: hotx, y: hoty,\n                                      width: w, height: h,\n                                      encoding: 0x574d5664}],\n                                   [rect], client);\n\n                        expect(client._cursor.change)\n                            .to.have.been.calledOnce;\n                        expect(client._cursor.change)\n                            .to.have.been.calledWith(expectedRgba,\n                                                     hotx, hoty,\n                                                     w, h);\n                    });\n\n                    it('should not update cursor when incorrect cursor type given', function () {\n                        let rect = [];\n                        push8(rect, 3); // invalid cursor type\n                        push8(rect, 0); // padding\n\n                        client._cursor.change.resetHistory();\n                        sendFbuMsg([{ x: 0, y: 0, width: 2, height: 2,\n                                      encoding: 0x574d5664}],\n                                   [rect], client);\n\n                        expect(client._cursor.change)\n                            .to.not.have.been.called;\n                    });\n                });\n\n                it('should handle the last_rect pseudo-encoding', function () {\n                    sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100);\n                    // Send a bell message and make sure it is parsed\n                    let spy = sinon.spy();\n                    client.addEventListener(\"bell\", spy);\n                    client._sock._websocket._receiveData(new Uint8Array([0x02]));\n                    expect(spy).to.have.been.calledOnce;\n                });\n\n                it('should handle the DesktopName pseudo-encoding', function () {\n                    let data = [];\n                    push32(data, 13);\n                    pushString(data, \"som€ nam€\");\n\n                    const spy = sinon.spy();\n                    client.addEventListener(\"desktopname\", spy);\n\n                    sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -307 }], [data], client);\n\n                    expect(client._fbName).to.equal('som€ nam€');\n                    expect(spy).to.have.been.calledOnce;\n                    expect(spy.args[0][0].detail.name).to.equal('som€ nam€');\n                });\n\n            });\n\n            describe('Caps Lock and Num Lock remote fixup', function () {\n                function sendLedStateUpdate(state) {\n                    let data = [];\n                    push8(data, state);\n                    sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -261 }], [data], client);\n                }\n\n                let client;\n                beforeEach(function () {\n                    client = makeRFB();\n                    sinon.stub(client, 'sendKey');\n                });\n\n                it('should toggle caps lock if remote caps lock is on and local is off', function () {\n                    sendLedStateUpdate(0b100);\n                    client._handleKeyEvent(0x61, 'KeyA', true, null, false);\n\n                    expect(client.sendKey).to.have.been.calledThrice;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", true);\n                    expect(client.sendKey.secondCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", false);\n                    expect(client.sendKey.thirdCall).to.have.been.calledWith(0x61, \"KeyA\", true);\n                });\n\n                it('should toggle caps lock if remote caps lock is off and local is on', function () {\n                    sendLedStateUpdate(0b011);\n                    client._handleKeyEvent(0x41, 'KeyA', true, null, true);\n\n                    expect(client.sendKey).to.have.been.calledThrice;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", true);\n                    expect(client.sendKey.secondCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", false);\n                    expect(client.sendKey.thirdCall).to.have.been.calledWith(0x41, \"KeyA\", true);\n                });\n\n                it('should not toggle caps lock if remote caps lock is on and local is on', function () {\n                    sendLedStateUpdate(0b100);\n                    client._handleKeyEvent(0x41, 'KeyA', true, null, true);\n\n                    expect(client.sendKey).to.have.been.calledOnce;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0x41, \"KeyA\", true);\n                });\n\n                it('should not toggle caps lock if remote caps lock is off and local is off', function () {\n                    sendLedStateUpdate(0b011);\n                    client._handleKeyEvent(0x61, 'KeyA', true, null, false);\n\n                    expect(client.sendKey).to.have.been.calledOnce;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0x61, \"KeyA\", true);\n                });\n\n                it('should not toggle caps lock if the key is caps lock', function () {\n                    sendLedStateUpdate(0b011);\n                    client._handleKeyEvent(0xFFE5, 'CapsLock', true, null, true);\n\n                    expect(client.sendKey).to.have.been.calledOnce;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", true);\n                });\n\n                it('should toggle caps lock only once', function () {\n                    sendLedStateUpdate(0b100);\n                    client._handleKeyEvent(0x61, 'KeyA', true, null, false);\n                    client._handleKeyEvent(0x61, 'KeyA', true, null, false);\n\n                    expect(client.sendKey).to.have.callCount(4);\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", true);\n                    expect(client.sendKey.secondCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", false);\n                    expect(client.sendKey.thirdCall).to.have.been.calledWith(0x61, \"KeyA\", true);\n                    expect(client.sendKey.lastCall).to.have.been.calledWith(0x61, \"KeyA\", true);\n                });\n\n                it('should retain remote caps lock state on capslock key up', function () {\n                    sendLedStateUpdate(0b100);\n                    client._handleKeyEvent(0xFFE5, 'CapsLock', false, null, true);\n\n                    expect(client.sendKey).to.have.been.calledOnce;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFE5, \"CapsLock\", false);\n                    expect(client._remoteCapsLock).to.equal(true);\n                });\n\n                it('should toggle num lock if remote num lock is on and local is off', function () {\n                    sendLedStateUpdate(0b010);\n                    client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null);\n\n                    expect(client.sendKey).to.have.been.calledThrice;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, \"NumLock\", true);\n                    expect(client.sendKey.secondCall).to.have.been.calledWith(0xFF7F, \"NumLock\", false);\n                    expect(client.sendKey.thirdCall).to.have.been.calledWith(0xFF9C, \"NumPad1\", true);\n                });\n\n                it('should toggle num lock if remote num lock is off and local is on', function () {\n                    sendLedStateUpdate(0b101);\n                    client._handleKeyEvent(0xFFB1, 'NumPad1', true, true, null);\n\n                    expect(client.sendKey).to.have.been.calledThrice;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, \"NumLock\", true);\n                    expect(client.sendKey.secondCall).to.have.been.calledWith(0xFF7F, \"NumLock\", false);\n                    expect(client.sendKey.thirdCall).to.have.been.calledWith(0xFFB1, \"NumPad1\", true);\n                });\n\n                it('should not toggle num lock if remote num lock is on and local is on', function () {\n                    sendLedStateUpdate(0b010);\n                    client._handleKeyEvent(0xFFB1, 'NumPad1', true,  true, null);\n\n                    expect(client.sendKey).to.have.been.calledOnce;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFB1, \"NumPad1\", true);\n                });\n\n                it('should not toggle num lock if remote num lock is off and local is off', function () {\n                    sendLedStateUpdate(0b101);\n                    client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null);\n\n                    expect(client.sendKey).to.have.been.calledOnce;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF9C, \"NumPad1\", true);\n                });\n\n                it('should not toggle num lock if the key is num lock', function () {\n                    sendLedStateUpdate(0b101);\n                    client._handleKeyEvent(0xFF7F, 'NumLock', true, true, null);\n\n                    expect(client.sendKey).to.have.been.calledOnce;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, \"NumLock\", true);\n                });\n\n                it('should not toggle num lock if local state is unknown', function () {\n                    sendLedStateUpdate(0b010);\n                    client._handleKeyEvent(0xFFB1, 'NumPad1', true, null, null);\n\n                    expect(client.sendKey).to.have.been.calledOnce;\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFFB1, \"NumPad1\", true);\n                });\n\n                it('should toggle num lock only once', function () {\n                    sendLedStateUpdate(0b010);\n                    client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null);\n                    client._handleKeyEvent(0xFF9C, 'NumPad1', true, false, null);\n\n                    expect(client.sendKey).to.have.callCount(4);\n                    expect(client.sendKey.firstCall).to.have.been.calledWith(0xFF7F, \"NumLock\", true);\n                    expect(client.sendKey.secondCall).to.have.been.calledWith(0xFF7F, \"NumLock\", false);\n                    expect(client.sendKey.thirdCall).to.have.been.calledWith(0xFF9C, \"NumPad1\", true);\n                    expect(client.sendKey.lastCall).to.have.been.calledWith(0xFF9C, \"NumPad1\", true);\n                });\n            });\n        });\n\n        describe('XVP message handling', function () {\n            it('should set the XVP version and fire the callback with the version on XVP_INIT', function () {\n                const spy = sinon.spy();\n                client.addEventListener(\"capabilities\", spy);\n                client._sock._websocket._receiveData(new Uint8Array([250, 0, 10, 1]));\n                expect(client._rfbXvpVer).to.equal(10);\n                expect(spy).to.have.been.calledOnce;\n                expect(spy.args[0][0].detail.capabilities.power).to.be.true;\n                expect(client.capabilities.power).to.be.true;\n            });\n\n            it('should fail on unknown XVP message types', function () {\n                let callback = sinon.spy();\n                client.addEventListener(\"disconnect\", callback);\n\n                client._sock._websocket._receiveData(new Uint8Array([250, 0, 10, 237]));\n\n                expect(callback).to.have.been.calledOnce;\n                expect(callback.args[0][0].detail.clean).to.be.false;\n            });\n        });\n\n        describe('Normal clipboard handling receive', function () {\n            it('should fire the clipboard callback with the retrieved text on ServerCutText', function () {\n                const expectedStr = 'cheese!';\n                const data = [3, 0, 0, 0];\n                push32(data, expectedStr.length);\n                for (let i = 0; i < expectedStr.length; i++) { data.push(expectedStr.charCodeAt(i)); }\n                const spy = sinon.spy();\n                client.addEventListener(\"clipboard\", spy);\n\n                client._sock._websocket._receiveData(new Uint8Array(data));\n                expect(spy).to.have.been.calledOnce;\n                expect(spy.args[0][0].detail.text).to.equal(expectedStr);\n            });\n        });\n\n        describe('Extended clipboard handling', function () {\n\n            describe('Extended clipboard initialization', function () {\n                beforeEach(function () {\n                    sinon.spy(RFB.messages, 'extendedClipboardCaps');\n                });\n\n                afterEach(function () {\n                    RFB.messages.extendedClipboardCaps.restore();\n                });\n\n                it('should update capabilities when receiving a Caps message', function () {\n                    let data = [3, 0, 0, 0];\n                    const flags = [0x1F, 0x00, 0x00, 0x03];\n                    let fileSizes = [0x00, 0x00, 0x00, 0x1E,\n                                     0x00, 0x00, 0x00, 0x3C];\n\n                    push32(data, toUnsigned32bit(-12));\n                    data = data.concat(flags);\n                    data = data.concat(fileSizes);\n                    client._sock._websocket._receiveData(new Uint8Array(data));\n\n                    // Check that we give an response caps when we receive one\n                    expect(RFB.messages.extendedClipboardCaps).to.have.been.calledOnce;\n\n                    // FIXME: Can we avoid checking internal variables?\n                    expect(client._clipboardServerCapabilitiesFormats[0]).to.not.equal(true);\n                    expect(client._clipboardServerCapabilitiesFormats[1]).to.equal(true);\n                    expect(client._clipboardServerCapabilitiesFormats[2]).to.equal(true);\n                    expect(client._clipboardServerCapabilitiesActions[(1 << 24)]).to.equal(true);\n                });\n\n\n            });\n\n            describe('Extended clipboard handling receive', function () {\n\n                beforeEach(function () {\n                    // Send our capabilities\n                    let data = [3, 0, 0, 0];\n                    const flags = [0x1F, 0x00, 0x00, 0x01];\n                    let fileSizes = [0x00, 0x00, 0x00, 0x1E];\n\n                    push32(data, toUnsigned32bit(-8));\n                    data = data.concat(flags);\n                    data = data.concat(fileSizes);\n                    client._sock._websocket._receiveData(new Uint8Array(data));\n                });\n\n                describe('Handle Provide', function () {\n                    it('should update clipboard with correct Unicode data from a Provide message', function () {\n                        let expectedData = \"Aå漢字!\";\n                        let data = [3, 0, 0, 0];\n                        const flags = [0x10, 0x00, 0x00, 0x01];\n\n                        let text = encodeUTF8(\"Aå漢字!\");\n                        let deflatedText = deflateWithSize(text);\n\n                        // How much data we are sending.\n                        push32(data, toUnsigned32bit(-(4 + deflatedText.length)));\n\n                        data = data.concat(flags);\n                        data = data.concat(Array.from(deflatedText));\n\n                        const spy = sinon.spy();\n                        client.addEventListener(\"clipboard\", spy);\n\n                        client._sock._websocket._receiveData(new Uint8Array(data));\n                        expect(spy).to.have.been.calledOnce;\n                        expect(spy.args[0][0].detail.text).to.equal(expectedData);\n                        client.removeEventListener(\"clipboard\", spy);\n                    });\n\n                    it('should update clipboard with correct escape characters from a Provide message ', function () {\n                        let expectedData = \"Oh\\nmy\\n!\";\n                        let data = [3, 0, 0, 0];\n                        const flags = [0x10, 0x00, 0x00, 0x01];\n\n                        let text = encodeUTF8(\"Oh\\r\\nmy\\r\\n!\\0\");\n\n                        let deflatedText = deflateWithSize(text);\n\n                        // How much data we are sending.\n                        push32(data, toUnsigned32bit(-(4 + deflatedText.length)));\n\n                        data = data.concat(flags);\n                        data = data.concat(Array.from(deflatedText));\n\n                        const spy = sinon.spy();\n                        client.addEventListener(\"clipboard\", spy);\n\n                        client._sock._websocket._receiveData(new Uint8Array(data));\n                        expect(spy).to.have.been.calledOnce;\n                        expect(spy.args[0][0].detail.text).to.equal(expectedData);\n                        client.removeEventListener(\"clipboard\", spy);\n                    });\n\n                    it('should be able to handle large Provide messages', function () {\n                        let expectedData = \"hello\".repeat(100000);\n                        let data = [3, 0, 0, 0];\n                        const flags = [0x10, 0x00, 0x00, 0x01];\n\n                        let text = encodeUTF8(expectedData + \"\\0\");\n\n                        let deflatedText = deflateWithSize(text);\n\n                        // How much data we are sending.\n                        push32(data, toUnsigned32bit(-(4 + deflatedText.length)));\n\n                        data = data.concat(flags);\n                        data = data.concat(Array.from(deflatedText));\n\n                        const spy = sinon.spy();\n                        client.addEventListener(\"clipboard\", spy);\n\n                        client._sock._websocket._receiveData(new Uint8Array(data));\n                        expect(spy).to.have.been.calledOnce;\n                        expect(spy.args[0][0].detail.text).to.equal(expectedData);\n                        client.removeEventListener(\"clipboard\", spy);\n                    });\n\n                });\n\n                describe('Handle Notify', function () {\n                    beforeEach(function () {\n                        sinon.spy(RFB.messages, 'extendedClipboardRequest');\n                    });\n\n                    afterEach(function () {\n                        RFB.messages.extendedClipboardRequest.restore();\n                    });\n\n                    it('should make a request with supported formats when receiving a notify message', function () {\n                        let data = [3, 0, 0, 0];\n                        const flags = [0x08, 0x00, 0x00, 0x07];\n                        push32(data, toUnsigned32bit(-4));\n                        data = data.concat(flags);\n                        let expectedData = [0x01];\n\n                        client._sock._websocket._receiveData(new Uint8Array(data));\n\n                        expect(RFB.messages.extendedClipboardRequest).to.have.been.calledOnce;\n                        expect(RFB.messages.extendedClipboardRequest).to.have.been.calledWith(client._sock, expectedData);\n                    });\n                });\n\n                describe('Handle Peek', function () {\n                    beforeEach(function () {\n                        sinon.spy(RFB.messages, 'extendedClipboardNotify');\n                    });\n\n                    afterEach(function () {\n                        RFB.messages.extendedClipboardNotify.restore();\n                    });\n\n                    it('should send an empty Notify when receiving a Peek and no excisting clipboard data', function () {\n                        let data = [3, 0, 0, 0];\n                        const flags = [0x04, 0x00, 0x00, 0x00];\n                        push32(data, toUnsigned32bit(-4));\n                        data = data.concat(flags);\n                        let expectedData = [];\n\n                        client._sock._websocket._receiveData(new Uint8Array(data));\n\n                        expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;\n                        expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);\n                    });\n\n                    it('should send a Notify message with supported formats when receiving a Peek', function () {\n                        let data = [3, 0, 0, 0];\n                        const flags = [0x04, 0x00, 0x00, 0x00];\n                        push32(data, toUnsigned32bit(-4));\n                        data = data.concat(flags);\n                        let expectedData = [0x01];\n\n                        // Needed to have clipboard data to read.\n                        // This will trigger a call to Notify, reset history\n                        client.clipboardPasteFrom(\"HejHej\");\n                        RFB.messages.extendedClipboardNotify.resetHistory();\n\n                        client._sock._websocket._receiveData(new Uint8Array(data));\n\n                        expect(RFB.messages.extendedClipboardNotify).to.have.been.calledOnce;\n                        expect(RFB.messages.extendedClipboardNotify).to.have.been.calledWith(client._sock, expectedData);\n                    });\n                });\n\n                describe('Handle Request', function () {\n                    beforeEach(function () {\n                        sinon.spy(RFB.messages, 'extendedClipboardProvide');\n                    });\n\n                    afterEach(function () {\n                        RFB.messages.extendedClipboardProvide.restore();\n                    });\n\n                    it('should send a Provide message with supported formats when receiving a Request', function () {\n                        let data = [3, 0, 0, 0];\n                        const flags = [0x02, 0x00, 0x00, 0x01];\n                        push32(data, toUnsigned32bit(-4));\n                        data = data.concat(flags);\n                        let expectedData = [0x01];\n\n                        client.clipboardPasteFrom(\"HejHej\");\n                        expect(RFB.messages.extendedClipboardProvide).to.not.have.been.called;\n\n                        client._sock._websocket._receiveData(new Uint8Array(data));\n\n                        expect(RFB.messages.extendedClipboardProvide).to.have.been.calledOnce;\n                        expect(RFB.messages.extendedClipboardProvide).to.have.been.calledWith(client._sock, expectedData, [\"HejHej\"]);\n                    });\n                });\n            });\n\n        });\n\n        it('should fire the bell callback on Bell', function () {\n            const spy = sinon.spy();\n            client.addEventListener(\"bell\", spy);\n            client._sock._websocket._receiveData(new Uint8Array([2]));\n            expect(spy).to.have.been.calledOnce;\n        });\n\n        it('should respond correctly to ServerFence', function () {\n            const payload = \"foo\\x00ab9\";\n\n            let esock = new Websock();\n            let ews = new FakeWebSocket();\n            ews._open();\n            esock.attach(ews);\n\n            // ClientFence and ServerFence are identical in structure\n            RFB.messages.clientFence(esock, (1<<0) | (1<<1), payload);\n            let expected = ews._getSentData();\n            RFB.messages.clientFence(esock, 0xffffffff, payload);\n            let incoming = ews._getSentData();\n\n            client._sock._websocket._receiveData(incoming);\n\n            expect(client._sock).to.have.sent(expected);\n\n            RFB.messages.clientFence(esock, (1<<0), payload);\n            expected = ews._getSentData();\n            RFB.messages.clientFence(esock, (1<<0) | (1<<31), payload);\n            incoming = ews._getSentData();\n\n            client._sock._websocket._receiveData(incoming);\n\n            expect(client._sock).to.have.sent(expected);\n        });\n\n        it('should enable continuous updates on first EndOfContinousUpdates', function () {\n            let esock = new Websock();\n            let ews = new FakeWebSocket();\n            ews._open();\n            esock.attach(ews);\n            RFB.messages.enableContinuousUpdates(esock, true, 0, 0, 640, 20);\n            let expected = ews._getSentData();\n\n            expect(client._enabledContinuousUpdates).to.be.false;\n\n            client._sock._websocket._receiveData(new Uint8Array([150]));\n\n            expect(client._enabledContinuousUpdates).to.be.true;\n            expect(client._sock).to.have.sent(expected);\n        });\n\n        it('should disable continuous updates on subsequent EndOfContinousUpdates', function () {\n            client._enabledContinuousUpdates = true;\n            client._supportsContinuousUpdates = true;\n\n            client._sock._websocket._receiveData(new Uint8Array([150]));\n\n            expect(client._enabledContinuousUpdates).to.be.false;\n        });\n\n        it('should update continuous updates on resize', function () {\n            let esock = new Websock();\n            let ews = new FakeWebSocket();\n            ews._open();\n            esock.attach(ews);\n            RFB.messages.enableContinuousUpdates(esock, true, 0, 0, 90, 700);\n            let expected = ews._getSentData();\n\n            client._resize(450, 160);\n\n            expect(client._sock).to.have.sent(new Uint8Array([]));\n\n            client._enabledContinuousUpdates = true;\n\n            client._resize(90, 700);\n\n            expect(client._sock).to.have.sent(expected);\n        });\n\n        it('should fail on an unknown message type', function () {\n            let callback = sinon.spy();\n            client.addEventListener(\"disconnect\", callback);\n\n            client._sock._websocket._receiveData(new Uint8Array([87]));\n\n            expect(callback).to.have.been.calledOnce;\n            expect(callback.args[0][0].detail.clean).to.be.false;\n        });\n    });\n\n    describe('Asynchronous events', function () {\n        let client;\n        let pointerEvent;\n        let extendedPointerEvent;\n        let keyEvent;\n        let qemuKeyEvent;\n\n        beforeEach(function () {\n            client = makeRFB();\n            client._display.resize(100, 100);\n\n            // We need to disable this as focusing the canvas will\n            // cause the browser to scoll to it, messing up our\n            // client coordinate calculations\n            client.focusOnClick = false;\n\n            pointerEvent = sinon.spy(RFB.messages, 'pointerEvent');\n            extendedPointerEvent = sinon.spy(RFB.messages, 'extendedPointerEvent');\n            keyEvent = sinon.spy(RFB.messages, 'keyEvent');\n            qemuKeyEvent = sinon.spy(RFB.messages, 'QEMUExtendedKeyEvent');\n        });\n\n        afterEach(function () {\n            pointerEvent.restore();\n            extendedPointerEvent.restore();\n            keyEvent.restore();\n            qemuKeyEvent.restore();\n        });\n\n        describe('Mouse events', function () {\n\n            it('should not send button messages in view-only mode', function () {\n                client._viewOnly = true;\n                sendMouseButtonEvent(10, 10, true, 0x1, client);\n\n                clock.tick(50);\n                expect(pointerEvent).to.not.have.been.called;\n            });\n\n            it('should not send movement messages in view-only mode', function () {\n                client._viewOnly = true;\n                sendMouseMoveEvent(10, 10, 0x0, client);\n\n                clock.tick(50);\n                expect(pointerEvent).to.not.have.been.called;\n            });\n\n            it('should handle left mouse button', function () {\n                sendMouseButtonEvent(10, 10, true, 0x1, client);\n\n                expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                 10, 10, 0x1);\n                pointerEvent.resetHistory();\n\n                sendMouseButtonEvent(10, 10, false, 0x0, client);\n\n                expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                 10, 10, 0x0);\n            });\n\n            it('should handle middle mouse button', function () {\n                sendMouseButtonEvent(10, 10, true, 0x4, client);\n\n                expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                 10, 10, 0x2);\n                pointerEvent.resetHistory();\n\n                sendMouseButtonEvent(10, 10, false, 0x0, client);\n\n                expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                 10, 10, 0x0);\n            });\n\n            it('should handle right mouse button', function () {\n                sendMouseButtonEvent(10, 10, true, 0x2, client);\n\n                expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                 10, 10, 0x4);\n                pointerEvent.resetHistory();\n\n                sendMouseButtonEvent(10, 10, false, 0x0, client);\n\n                expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                 10, 10, 0x0);\n            });\n\n            it('should handle multiple mouse buttons', function () {\n                sendMouseButtonEvent(10, 10, true, 0x1, client);\n                sendMouseButtonEvent(10, 10, true, 0x3, client);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 0x1);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0x5);\n\n                pointerEvent.resetHistory();\n\n\n                sendMouseButtonEvent(10, 10, false, 0x2, client);\n                sendMouseButtonEvent(10, 10, false, 0x0, client);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 0x4);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0x0);\n            });\n\n            it('should handle mouse movement', function () {\n                sendMouseMoveEvent(50, 70, 0x0, client);\n                expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                 50, 70, 0x0);\n            });\n\n            it('should handle click and drag', function () {\n                sendMouseButtonEvent(10, 10, true, 0x1, client);\n                sendMouseMoveEvent(50, 70, 0x1, client);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 0x1);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        50, 70, 0x1);\n\n                pointerEvent.resetHistory();\n\n                sendMouseButtonEvent(50, 70, false, 0x0, client);\n\n                expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                 50, 70, 0x0);\n            });\n\n            it('should send extended pointer event when server supports extended pointer events', function () {\n                // Enable extended pointer events\n                sendFbuMsg([{ x: 0, y: 0, width: 0, height: 0, encoding: -316 }], [[]], client);\n\n                sendMouseButtonEvent(50, 70, true, 0x10, client);\n\n                expect(extendedPointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                         50, 70, 0x100);\n            });\n\n            it('should send normal pointer event when server does not support extended pointer events', function () {\n                sendMouseButtonEvent(50, 70, true, 0x10, client);\n\n                expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                 50, 70, 0x100);\n            });\n\n            describe('Event aggregation', function () {\n                it('should send a single pointer event on mouse movement', function () {\n                    sendMouseMoveEvent(50, 70, 0x0, client);\n                    clock.tick(100);\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     50, 70, 0x0);\n                });\n\n                it('should delay one move if two events are too close', function () {\n                    sendMouseMoveEvent(18, 30, 0x0, client);\n                    sendMouseMoveEvent(20, 50, 0x0, client);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     18, 30, 0x0);\n                    pointerEvent.resetHistory();\n\n                    clock.tick(100);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 50, 0x0);\n                });\n\n                it('should only send first and last move of many close events', function () {\n                    sendMouseMoveEvent(18, 30, 0x0, client);\n                    sendMouseMoveEvent(20, 50, 0x0, client);\n                    sendMouseMoveEvent(21, 55, 0x0, client);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     18, 30, 0x0);\n                    pointerEvent.resetHistory();\n\n                    clock.tick(100);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     21, 55, 0x0);\n                });\n\n                // We selected the 17ms since that is ~60 FPS\n                it('should send move events every 17 ms', function () {\n                    sendMouseMoveEvent(1, 10, 0x0, client);  // instant send\n                    clock.tick(10);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     1, 10, 0x0);\n                    pointerEvent.resetHistory();\n\n                    sendMouseMoveEvent(2, 20, 0x0, client);  // delayed\n                    clock.tick(10);        // timeout send\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     2, 20, 0x0);\n                    pointerEvent.resetHistory();\n\n                    sendMouseMoveEvent(3, 30, 0x0, client);  // delayed\n                    clock.tick(10);\n                    sendMouseMoveEvent(4, 40, 0x0, client);  // delayed\n                    clock.tick(10);        // timeout send\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     4, 40, 0x0);\n                    pointerEvent.resetHistory();\n\n                    sendMouseMoveEvent(5, 50, 0x0, client);  // delayed\n\n                    expect(pointerEvent).to.not.have.been.called;\n                });\n\n                it('should send waiting move events before a button press', function () {\n                    sendMouseMoveEvent(13, 9, 0x0, client);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     13, 9, 0x0);\n                    pointerEvent.resetHistory();\n\n                    sendMouseMoveEvent(20, 70, 0x0, client);\n\n                    expect(pointerEvent).to.not.have.been.called;\n\n                    sendMouseButtonEvent(20, 70, true, 0x1, client);\n\n                    expect(pointerEvent).to.have.been.calledTwice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 70, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 70, 0x1);\n                });\n\n                it('should send move events with enough time apart normally', function () {\n                    sendMouseMoveEvent(58, 60, 0x0, client);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     58, 60, 0x0);\n                    pointerEvent.resetHistory();\n\n                    clock.tick(20);\n\n                    sendMouseMoveEvent(25, 60, 0x0, client);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     25, 60, 0x0);\n                    pointerEvent.resetHistory();\n                });\n\n                it('should not send waiting move events if disconnected', function () {\n                    sendMouseMoveEvent(88, 99, 0x0, client);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     88, 99, 0x0);\n                    pointerEvent.resetHistory();\n\n                    sendMouseMoveEvent(66, 77, 0x0, client);\n                    client.disconnect();\n                    clock.tick(20);\n\n                    expect(pointerEvent).to.not.have.been.called;\n                });\n            });\n\n            it.skip('should block click events', function () {\n                /* FIXME */\n            });\n\n            it.skip('should block contextmenu events', function () {\n                /* FIXME */\n            });\n        });\n\n        describe('Wheel events', function () {\n            function sendWheelEvent(x, y, dx, dy, mode=0, buttons=0) {\n                let pos = elementToClient(x, y, client);\n                let ev;\n\n                ev = new WheelEvent('wheel',\n                                    { 'screenX': pos.x + window.screenX,\n                                      'screenY': pos.y + window.screenY,\n                                      'clientX': pos.x,\n                                      'clientY': pos.y,\n                                      'deltaX': dx,\n                                      'deltaY': dy,\n                                      'deltaMode': mode,\n                                      'buttons': buttons });\n                client._canvas.dispatchEvent(ev);\n            }\n\n            it('should handle wheel up event', function () {\n                sendWheelEvent(10, 10, 0, -50);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 1<<3);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0);\n            });\n\n            it('should handle wheel down event', function () {\n                sendWheelEvent(10, 10, 0, 50);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 1<<4);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0);\n            });\n\n            it('should handle wheel left event', function () {\n                sendWheelEvent(10, 10, -50, 0);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 1<<5);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0);\n            });\n\n            it('should handle wheel right event', function () {\n                sendWheelEvent(10, 10, 50, 0);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 1<<6);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0);\n            });\n\n            it('should ignore wheel when in view only', function () {\n                client._viewOnly = true;\n\n                sendWheelEvent(10, 10, 50, 0);\n\n                expect(pointerEvent).to.not.have.been.called;\n            });\n\n            it('should accumulate wheel events if small enough', function () {\n                sendWheelEvent(10, 10, 0, 20);\n                sendWheelEvent(10, 10, 0, 20);\n\n                expect(pointerEvent).to.not.have.been.called;\n\n                sendWheelEvent(10, 10, 0, 20);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 1<<4);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0);\n            });\n\n            it('should not accumulate large wheel events', function () {\n                sendWheelEvent(10, 10, 0, 400);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 1<<4);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0);\n            });\n\n            it('should handle line based wheel event', function () {\n                sendWheelEvent(10, 10, 0, 3, 1);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 1<<4);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0);\n            });\n\n            it('should handle page based wheel event', function () {\n                sendWheelEvent(10, 10, 0, 3, 2);\n\n                expect(pointerEvent).to.have.been.calledTwice;\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 1<<4);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0);\n            });\n\n            it('should handle wheel event with buttons pressed', function () {\n                sendMouseButtonEvent(10, 10, true, 0x1, client);\n                sendWheelEvent(10, 10, 0, 50, 0, 0x1);\n\n                expect(pointerEvent).to.have.been.called.calledThrice;\n\n                expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 0x1);\n                expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        10, 10, 0x11);\n                expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                       10, 10, 0x1);\n            });\n\n        });\n\n        describe('Keyboard events', function () {\n            it('should send a key message on a key press', function () {\n                let esock = new Websock();\n                let ews = new FakeWebSocket();\n                ews._open();\n                esock.attach(ews);\n                RFB.messages.keyEvent(esock, 0x41, 1);\n                let expected = ews._getSentData();\n\n                client._handleKeyEvent(0x41, 'KeyA', true);\n\n                expect(client._sock).to.have.sent(expected);\n            });\n\n            it('should not send messages in view-only mode', function () {\n                client._viewOnly = true;\n                sinon.spy(client._sock, 'flush');\n                client._handleKeyEvent('a', 'KeyA', true);\n                expect(client._sock.flush).to.not.have.been.called;\n            });\n        });\n\n        describe('Gesture event handlers', function () {\n            describe('Gesture onetap', function () {\n                it('should handle onetap events', function () {\n                    let bmask = 0x1;\n\n                    gestureStart('onetap', 20, 40, client);\n                    gestureEnd('onetap', 20, 40, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                });\n\n                it('should keep same position for multiple onetap events', function () {\n                    let bmask = 0x1;\n\n                    gestureStart('onetap', 20, 40, client);\n                    gestureEnd('onetap', 20, 40, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureStart('onetap', 20, 50, client);\n                    gestureEnd('onetap', 20, 50, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureStart('onetap', 30, 50, client);\n                    gestureEnd('onetap', 30, 50, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                });\n\n                it('should not keep same position for onetap events when too far apart', function () {\n                    let bmask = 0x1;\n\n                    gestureStart('onetap', 20, 40, client);\n                    gestureEnd('onetap', 20, 40, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureStart('onetap', 80, 95, client);\n                    gestureEnd('onetap', 80, 95, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           80, 95, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            80, 95, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           80, 95, 0x0);\n                });\n\n                it('should not keep same position for onetap events when enough time inbetween', function () {\n                    let bmask = 0x1;\n\n                    gestureStart('onetap', 10, 20, client);\n                    gestureEnd('onetap', 10, 20, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           10, 20, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            10, 20, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           10, 20, 0x0);\n\n                    pointerEvent.resetHistory();\n                    this.clock.tick(1500);\n\n                    gestureStart('onetap', 15, 20, client);\n                    gestureEnd('onetap', 15, 20, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           15, 20, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            15, 20, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           15, 20, 0x0);\n\n                    pointerEvent.resetHistory();\n                });\n            });\n\n            describe('Gesture twotap', function () {\n                it('should handle gesture twotap events', function () {\n                    let bmask = 0x4;\n\n                    gestureStart(\"twotap\", 20, 40, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                });\n\n                it('should keep same position for multiple twotap events', function () {\n                    let bmask = 0x4;\n\n                    for (let offset = 0;offset < 30;offset += 10) {\n                        pointerEvent.resetHistory();\n\n                        gestureStart('twotap', 20, 40 + offset, client);\n                        gestureEnd('twotap', 20, 40 + offset, client);\n\n                        expect(pointerEvent).to.have.been.calledThrice;\n                        expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                               20, 40, 0x0);\n                        expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                                20, 40, bmask);\n                        expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                               20, 40, 0x0);\n                    }\n                });\n            });\n\n            describe('Gesture threetap', function () {\n                it('should handle gesture start for threetap events', function () {\n                    let bmask = 0x2;\n\n                    gestureStart(\"threetap\", 20, 40, client);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                });\n\n                it('should keep same position for multiple threetap events', function () {\n                    let bmask = 0x2;\n\n                    for (let offset = 0;offset < 30;offset += 10) {\n                        pointerEvent.resetHistory();\n\n                        gestureStart('threetap', 20, 40 + offset, client);\n                        gestureEnd('threetap', 20, 40 + offset, client);\n\n                        expect(pointerEvent).to.have.been.calledThrice;\n                        expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                               20, 40, 0x0);\n                        expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                                20, 40, bmask);\n                        expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                               20, 40, 0x0);\n                    }\n                });\n            });\n\n            describe('Gesture drag', function () {\n                it('should handle gesture drag events', function () {\n                    let bmask = 0x1;\n\n                    gestureStart('drag', 20, 40, client);\n\n                    expect(pointerEvent).to.have.been.calledTwice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('drag', 30, 50, client);\n                    clock.tick(50);\n\n                    expect(pointerEvent).to.have.been.calledOnce;\n                    expect(pointerEvent).to.have.been.calledWith(client._sock,\n                                                                 30, 50, bmask);\n\n                    pointerEvent.resetHistory();\n\n                    gestureEnd('drag', 30, 50, client);\n\n                    expect(pointerEvent).to.have.been.calledTwice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           30, 50, bmask);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            30, 50, 0x0);\n                });\n            });\n\n            describe('Gesture long press', function () {\n                it('should handle long press events', function () {\n                    let bmask = 0x4;\n\n                    gestureStart('longpress', 20, 40, client);\n\n                    expect(pointerEvent).to.have.been.calledTwice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    pointerEvent.resetHistory();\n\n                    gestureMove('longpress', 40, 60, client);\n                    clock.tick(50);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     40, 60, bmask);\n\n                    pointerEvent.resetHistory();\n\n                    gestureEnd('longpress', 40, 60, client);\n\n                    expect(pointerEvent).to.have.been.calledTwice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           40, 60, bmask);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            40, 60, 0x0);\n                });\n            });\n\n            describe('Gesture twodrag', function () {\n                it('should handle gesture twodrag up events', function () {\n                    let bmask = 0x10; // Button mask for scroll down\n\n                    gestureStart('twodrag', 20, 40, client, 0, 0);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('twodrag', 20, 40, client, 0, -60);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                });\n\n                it('should handle gesture twodrag down events', function () {\n                    let bmask = 0x8; // Button mask for scroll up\n\n                    gestureStart('twodrag', 20, 40, client, 0, 0);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('twodrag', 20, 40, client, 0, 60);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                });\n\n                it('should handle gesture twodrag right events', function () {\n                    let bmask = 0x20; // Button mask for scroll right\n\n                    gestureStart('twodrag', 20, 40, client, 0, 0);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('twodrag', 20, 40, client, 60, 0);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                });\n\n                it('should handle gesture twodrag left events', function () {\n                    let bmask = 0x40; // Button mask for scroll left\n\n                    gestureStart('twodrag', 20, 40, client, 0, 0);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('twodrag', 20, 40, client, -60, 0);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                });\n\n                it('should handle gesture twodrag diag events', function () {\n                    let scrlUp = 0x8; // Button mask for scroll up\n                    let scrlRight = 0x20; // Button mask for scroll right\n\n                    gestureStart('twodrag', 20, 40, client, 0, 0);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('twodrag', 20, 40, client, 60, 60);\n\n                    expect(pointerEvent).to.have.been.callCount(5);\n                    expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, 0x0);\n                    expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, scrlUp);\n                    expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, 0x0);\n                    expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, scrlRight);\n                    expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, 0x0);\n                });\n\n                it('should handle multiple small gesture twodrag events', function () {\n                    let bmask = 0x8; // Button mask for scroll up\n\n                    gestureStart('twodrag', 20, 40, client, 0, 0);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('twodrag', 20, 40, client, 0, 10);\n                    clock.tick(50);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('twodrag', 20, 40, client, 0, 20);\n                    clock.tick(50);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('twodrag', 20, 40, client, 0, 60);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                });\n\n                it('should handle large gesture twodrag events', function () {\n                    let bmask = 0x8; // Button mask for scroll up\n\n                    gestureStart('twodrag', 30, 50, client, 0, 0);\n\n                    expect(pointerEvent).\n                        to.have.been.calledOnceWith(client._sock, 30, 50, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('twodrag', 30, 50, client, 0, 200);\n\n                    expect(pointerEvent).to.have.callCount(7);\n                    expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,\n                                                                            30, 50, 0x0);\n                    expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,\n                                                                            30, 50, bmask);\n                    expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,\n                                                                            30, 50, 0x0);\n                    expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,\n                                                                            30, 50, bmask);\n                    expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,\n                                                                            30, 50, 0x0);\n                    expect(pointerEvent.getCall(5)).to.have.been.calledWith(client._sock,\n                                                                            30, 50, bmask);\n                    expect(pointerEvent.getCall(6)).to.have.been.calledWith(client._sock,\n                                                                            30, 50, 0x0);\n                });\n            });\n\n            describe('Gesture pinch', function () {\n                it('should handle gesture pinch in events', function () {\n                    let keysym = KeyTable.XK_Control_L;\n                    let bmask = 0x10; // Button mask for scroll down\n\n                    gestureStart('pinch', 20, 40, client, 90, 90);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n                    expect(keyEvent).to.not.have.been.called;\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('pinch', 20, 40, client, 30, 30);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n\n                    expect(keyEvent).to.have.been.calledTwice;\n                    expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       keysym, 1);\n                    expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        keysym, 0);\n\n                    expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);\n                    expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);\n\n                    pointerEvent.resetHistory();\n                    keyEvent.resetHistory();\n\n                    gestureEnd('pinch', 20, 40, client);\n\n                    expect(pointerEvent).to.not.have.been.called;\n                    expect(keyEvent).to.not.have.been.called;\n                });\n\n                it('should handle gesture pinch out events', function () {\n                    let keysym = KeyTable.XK_Control_L;\n                    let bmask = 0x8; // Button mask for scroll up\n\n                    gestureStart('pinch', 10, 20, client, 10, 20);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     10, 20, 0x0);\n                    expect(keyEvent).to.not.have.been.called;\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('pinch', 10, 20, client, 70, 80);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           10, 20, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            10, 20, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           10, 20, 0x0);\n\n                    expect(keyEvent).to.have.been.calledTwice;\n                    expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       keysym, 1);\n                    expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        keysym, 0);\n\n                    expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);\n                    expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);\n\n                    pointerEvent.resetHistory();\n                    keyEvent.resetHistory();\n\n                    gestureEnd('pinch', 10, 20, client);\n\n                    expect(pointerEvent).to.not.have.been.called;\n                    expect(keyEvent).to.not.have.been.called;\n                });\n\n                it('should handle large gesture pinch', function () {\n                    let keysym = KeyTable.XK_Control_L;\n                    let bmask = 0x10; // Button mask for scroll down\n\n                    gestureStart('pinch', 20, 40, client, 150, 150);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n                    expect(keyEvent).to.not.have.been.called;\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('pinch', 20, 40, client, 30, 30);\n\n                    expect(pointerEvent).to.have.been.callCount(5);\n                    expect(pointerEvent.getCall(0)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, 0x0);\n                    expect(pointerEvent.getCall(1)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.getCall(2)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, 0x0);\n                    expect(pointerEvent.getCall(3)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.getCall(4)).to.have.been.calledWith(client._sock,\n                                                                            20, 40, 0x0);\n\n                    expect(keyEvent).to.have.been.calledTwice;\n                    expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       keysym, 1);\n                    expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        keysym, 0);\n\n                    expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);\n                    expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);\n\n                    pointerEvent.resetHistory();\n                    keyEvent.resetHistory();\n\n                    gestureEnd('pinch', 20, 40, client);\n\n                    expect(pointerEvent).to.not.have.been.called;\n                    expect(keyEvent).to.not.have.been.called;\n                });\n\n                it('should handle multiple small gesture pinch out events', function () {\n                    let keysym = KeyTable.XK_Control_L;\n                    let bmask = 0x8; // Button mask for scroll down\n\n                    gestureStart('pinch', 20, 40, client, 0, 10);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n                    expect(keyEvent).to.not.have.been.called;\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('pinch', 20, 40, client, 0, 30);\n                    clock.tick(50);\n\n                    expect(pointerEvent).to.have.been.calledWith(client._sock,\n                                                                 20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('pinch', 20, 40, client, 0, 60);\n                    clock.tick(50);\n\n                    expect(pointerEvent).to.have.been.calledWith(client._sock,\n                                                                 20, 40, 0x0);\n\n                    pointerEvent.resetHistory();\n                    keyEvent.resetHistory();\n\n                    gestureMove('pinch', 20, 40, client, 0, 90);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n\n                    expect(keyEvent).to.have.been.calledTwice;\n                    expect(keyEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                       keysym, 1);\n                    expect(keyEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                        keysym, 0);\n\n                    expect(keyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);\n                    expect(keyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);\n\n                    pointerEvent.resetHistory();\n                    keyEvent.resetHistory();\n\n                    gestureEnd('pinch', 20, 40, client);\n\n                    expect(keyEvent).to.not.have.been.called;\n                });\n\n                it('should send correct key control code', function () {\n                    let keysym = KeyTable.XK_Control_L;\n                    let code = 0x1d;\n                    let bmask = 0x10; // Button mask for scroll down\n\n                    client._qemuExtKeyEventSupported = true;\n\n                    gestureStart('pinch', 20, 40, client, 90, 90);\n\n                    expect(pointerEvent).to.have.been.calledOnceWith(client._sock,\n                                                                     20, 40, 0x0);\n                    expect(qemuKeyEvent).to.not.have.been.called;\n\n                    pointerEvent.resetHistory();\n\n                    gestureMove('pinch', 20, 40, client, 30, 30);\n\n                    expect(pointerEvent).to.have.been.calledThrice;\n                    expect(pointerEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n                    expect(pointerEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            20, 40, bmask);\n                    expect(pointerEvent.thirdCall).to.have.been.calledWith(client._sock,\n                                                                           20, 40, 0x0);\n\n                    expect(qemuKeyEvent).to.have.been.calledTwice;\n                    expect(qemuKeyEvent.firstCall).to.have.been.calledWith(client._sock,\n                                                                           keysym,\n                                                                           true,\n                                                                           code);\n                    expect(qemuKeyEvent.secondCall).to.have.been.calledWith(client._sock,\n                                                                            keysym,\n                                                                            false,\n                                                                            code);\n\n                    expect(qemuKeyEvent.firstCall).to.have.been.calledBefore(pointerEvent.secondCall);\n                    expect(qemuKeyEvent.lastCall).to.have.been.calledAfter(pointerEvent.lastCall);\n\n                    pointerEvent.resetHistory();\n                    qemuKeyEvent.resetHistory();\n\n                    gestureEnd('pinch', 20, 40, client);\n\n                    expect(pointerEvent).to.not.have.been.called;\n                    expect(qemuKeyEvent).to.not.have.been.called;\n                });\n            });\n        });\n\n        describe('WebSocket events', function () {\n            // message events\n            it('should do nothing if we receive an empty message and have nothing in the queue', function () {\n                sinon.spy(client, \"_normalMsg\");\n                client._sock._websocket._receiveData(new Uint8Array([]));\n                expect(client._normalMsg).to.not.have.been.called;\n            });\n\n            it('should handle a message in the connected state as a normal message', function () {\n                sinon.spy(client, \"_normalMsg\");\n                client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));\n                expect(client._normalMsg).to.have.been.called;\n            });\n\n            it('should handle a message in any non-disconnected/failed state like an init message', function () {\n                client._rfbConnectionState = 'connecting';\n                client._rfbInitState = 'ProtocolVersion';\n                sinon.spy(client, \"_initMsg\");\n                client._sock._websocket._receiveData(new Uint8Array([1, 2, 3]));\n                expect(client._initMsg).to.have.been.called;\n            });\n\n            it('should process all normal messages directly', function () {\n                const spy = sinon.spy();\n                client.addEventListener(\"bell\", spy);\n                client._sock._websocket._receiveData(new Uint8Array([0x02, 0x02]));\n                expect(spy).to.have.been.calledTwice;\n            });\n\n            // open events\n            it('should update the state to ProtocolVersion on open (if the state is \"connecting\")', function () {\n                client = new RFB(document.createElement('div'), 'wss://host:8675');\n                this.clock.tick();\n                client._sock._websocket._open();\n                expect(client._rfbInitState).to.equal('ProtocolVersion');\n            });\n\n            it('should fail if we are not currently ready to connect and we get an \"open\" event', function () {\n                sinon.spy(client, \"_fail\");\n                client._rfbConnectionState = 'connected';\n                client._sock._websocket._open();\n                expect(client._fail).to.have.been.calledOnce;\n            });\n\n            // close events\n            it('should transition to \"disconnected\" from \"disconnecting\" on a close event', function () {\n                const real = client._sock._websocket.close;\n                client._sock._websocket.close = () => {};\n                client.disconnect();\n                expect(client._rfbConnectionState).to.equal('disconnecting');\n                client._sock._websocket.close = real;\n                client._sock._websocket.close();\n                expect(client._rfbConnectionState).to.equal('disconnected');\n            });\n\n            it('should fail if we get a close event while connecting', function () {\n                sinon.spy(client, \"_fail\");\n                client._rfbConnectionState = 'connecting';\n                client._sock._websocket.close();\n                expect(client._fail).to.have.been.calledOnce;\n            });\n\n            it('should unregister close event handler', function () {\n                sinon.spy(client._sock, 'off');\n                client.disconnect();\n                client._sock._websocket.close();\n                expect(client._sock.off).to.have.been.calledWith('close');\n            });\n\n            // error events do nothing\n        });\n    });\n\n    describe('Quality level setting', function () {\n        const defaultQuality = 6;\n\n        let client;\n\n        beforeEach(function () {\n            client = makeRFB();\n            sinon.spy(RFB.messages, \"clientEncodings\");\n        });\n\n        afterEach(function () {\n            RFB.messages.clientEncodings.restore();\n        });\n\n        it(`should equal ${defaultQuality} by default`, function () {\n            expect(client._qualityLevel).to.equal(defaultQuality);\n        });\n\n        it('should ignore non-integers when set', function () {\n            client.qualityLevel = '1';\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.qualityLevel = 1.5;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.qualityLevel = null;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.qualityLevel = undefined;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.qualityLevel = {};\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n        });\n\n        it('should ignore integers out of range [0, 9]', function () {\n            client.qualityLevel = -1;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.qualityLevel = 10;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n        });\n\n        it('should send clientEncodings with new quality value', function () {\n            let newQuality;\n\n            newQuality = 8;\n            client.qualityLevel = newQuality;\n            expect(client.qualityLevel).to.equal(newQuality);\n            expect(RFB.messages.clientEncodings).to.have.been.calledOnce;\n            expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);\n        });\n\n        it('should not send clientEncodings if quality is the same', function () {\n            let newQuality;\n\n            newQuality = 2;\n            client.qualityLevel = newQuality;\n            expect(RFB.messages.clientEncodings).to.have.been.calledOnce;\n            expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.qualityLevel = newQuality;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n        });\n\n        it('should not send clientEncodings if not in connected state', function () {\n            let newQuality;\n\n            client._rfbConnectionState = '';\n            newQuality = 2;\n            client.qualityLevel = newQuality;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client._rfbConnectionState = 'connnecting';\n            newQuality = 6;\n            client.qualityLevel = newQuality;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client._rfbConnectionState = 'connected';\n            newQuality = 5;\n            client.qualityLevel = newQuality;\n            expect(RFB.messages.clientEncodings).to.have.been.calledOnce;\n            expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingQualityLevel0 + newQuality);\n        });\n    });\n\n    describe('Compression level setting', function () {\n        const defaultCompression = 2;\n\n        let client;\n\n        beforeEach(function () {\n            client = makeRFB();\n            sinon.spy(RFB.messages, \"clientEncodings\");\n        });\n\n        afterEach(function () {\n            RFB.messages.clientEncodings.restore();\n        });\n\n        it(`should equal ${defaultCompression} by default`, function () {\n            expect(client._compressionLevel).to.equal(defaultCompression);\n        });\n\n        it('should ignore non-integers when set', function () {\n            client.compressionLevel = '1';\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.compressionLevel = 1.5;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.compressionLevel = null;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.compressionLevel = undefined;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.compressionLevel = {};\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n        });\n\n        it('should ignore integers out of range [0, 9]', function () {\n            client.compressionLevel = -1;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.compressionLevel = 10;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n        });\n\n        it('should send clientEncodings with new compression value', function () {\n            let newCompression;\n\n            newCompression = 5;\n            client.compressionLevel = newCompression;\n            expect(client.compressionLevel).to.equal(newCompression);\n            expect(RFB.messages.clientEncodings).to.have.been.calledOnce;\n            expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);\n        });\n\n        it('should not send clientEncodings if compression is the same', function () {\n            let newCompression;\n\n            newCompression = 9;\n            client.compressionLevel = newCompression;\n            expect(RFB.messages.clientEncodings).to.have.been.calledOnce;\n            expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client.compressionLevel = newCompression;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n        });\n\n        it('should not send clientEncodings if not in connected state', function () {\n            let newCompression;\n\n            client._rfbConnectionState = '';\n            newCompression = 7;\n            client.compressionLevel = newCompression;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client._rfbConnectionState = 'connnecting';\n            newCompression = 6;\n            client.compressionLevel = newCompression;\n            expect(RFB.messages.clientEncodings).to.not.have.been.called;\n\n            RFB.messages.clientEncodings.resetHistory();\n\n            client._rfbConnectionState = 'connected';\n            newCompression = 5;\n            client.compressionLevel = newCompression;\n            expect(RFB.messages.clientEncodings).to.have.been.calledOnce;\n            expect(RFB.messages.clientEncodings.getCall(0).args[1]).to.include(encodings.pseudoEncodingCompressLevel0 + newCompression);\n        });\n    });\n});\n\ndescribe('RFB messages', function () {\n    let sock;\n\n    beforeEach(function () {\n        let websock = new FakeWebSocket();\n        websock._open();\n        sock = new Websock();\n        sock.attach(websock);\n    });\n\n    describe('Input events', function () {\n        it('should send correct data for keyboard events', function () {\n            // FIXME: down should be boolean\n            RFB.messages.keyEvent(sock, 0x12345678, 0);\n            let expected =\n                [ 4, 0, 0, 0, 0x12, 0x34, 0x56, 0x78];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n\n            RFB.messages.keyEvent(sock, 0x90abcdef, 1);\n            expected =\n                [ 4, 1, 0, 0, 0x90, 0xab, 0xcd, 0xef];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n\n        it('should send correct data for QEMU keyboard events', function () {\n            // FIXME: down should be boolean\n            RFB.messages.QEMUExtendedKeyEvent(sock, 0x12345678, 0, 0x55);\n            let expected =\n                [ 255, 0, 0, 0, 0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x55];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n\n            RFB.messages.QEMUExtendedKeyEvent(sock, 0x90abcdef, 1, 0xe055);\n            expected =\n                [ 255, 0, 0, 1, 0x90, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0xd5];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n\n        it('should send correct data for pointer events', function () {\n            RFB.messages.pointerEvent(sock, 12345, 54321, 0x2b);\n            let expected =\n                [ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n\n        it('should send correct data for pointer events with marker bit set', function () {\n            RFB.messages.pointerEvent(sock, 12345, 54321, 0xab);\n            let expected =\n                [ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n\n        it('should send correct data for pointer events with extended button bits set', function () {\n            RFB.messages.pointerEvent(sock, 12345, 54321, 0x3ab);\n            let expected =\n                [ 5, 0x2b, 0x30, 0x39, 0xd4, 0x31];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n\n        it('should send correct data for extended pointer events', function () {\n            RFB.messages.extendedPointerEvent(sock, 12345, 54321, 0xab);\n            let expected =\n                [ 5, 0xab, 0x30, 0x39, 0xd4, 0x31, 0x1];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n\n        it('should not send invalid data for extended pointer events', function () {\n            expect(() => RFB.messages.extendedPointerEvent(sock, 12345, 54321, 0x3ab)).to.throw(Error);\n        });\n    });\n\n    describe('Clipboard events', function () {\n        it('should send correct data for clipboard events', function () {\n            RFB.messages.clientCutText(sock, new Uint8Array([ 0x01, 0x23, 0x45, 0x67 ]));\n            let expected =\n                [ 6, 0, 0, 0, 0x00, 0x00, 0x00, 0x04,\n                  0x01, 0x23, 0x45, 0x67 ];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n    });\n\n    describe('Extended clipboard handling send', function () {\n        it('should call clientCutText with correct Caps data', function () {\n            let formats = {\n                0: 2,\n                2: 4121\n            };\n            let expectedData = new Uint8Array([0x06, 0x00, 0x00, 0x00,\n                                               0xFF, 0xFF, 0xFF, 0xF4,\n                                               0x1F, 0x00, 0x00, 0x05,\n                                               0x00, 0x00, 0x00, 0x02,\n                                               0x00, 0x00, 0x10, 0x19]);\n            let actions = [\n                1 << 24,  // Caps\n                1 << 25,  // Request\n                1 << 26,  // Peek\n                1 << 27,  // Notify\n                1 << 28   // Provide\n            ];\n\n            RFB.messages.extendedClipboardCaps(sock, actions, formats);\n\n            expect(sock).to.have.sent(expectedData);\n        });\n\n        it('should call clientCutText with correct Request data', function () {\n            let formats = new Uint8Array([0x01]);\n            let expectedData = new Uint8Array([0x06, 0x00, 0x00, 0x00,\n                                               0xFF, 0xFF, 0xFF, 0xFC,\n                                               0x02, 0x00, 0x00, 0x01]);\n\n            RFB.messages.extendedClipboardRequest(sock, formats);\n\n            expect(sock).to.have.sent(expectedData);\n        });\n\n        it('should call clientCutText with correct Notify data', function () {\n            let formats = new Uint8Array([0x01]);\n            let expectedData = new Uint8Array([0x06, 0x00, 0x00, 0x00,\n                                               0xFF, 0xFF, 0xFF, 0xFC,\n                                               0x08, 0x00, 0x00, 0x01]);\n\n            RFB.messages.extendedClipboardNotify(sock, formats);\n\n            expect(sock).to.have.sent(expectedData);\n        });\n\n        it('should call clientCutText with correct Provide data', function () {\n            let testText = \"Test string\";\n            let expectedText = encodeUTF8(testText + \"\\0\");\n\n            let deflatedData =  deflateWithSize(expectedText);\n\n            // Build Expected with flags and deflated data\n            let expectedData = new Uint8Array(8 + 4 + deflatedData.length);\n            expectedData[0] = 0x06; // Message type\n            expectedData[1] = 0x00;\n            expectedData[2] = 0x00;\n            expectedData[3] = 0x00;\n            expectedData[4] = 0xFF; // Size\n            expectedData[5] = 0xFF;\n            expectedData[6] = 0xFF;\n            expectedData[7] = 256 - (4 + deflatedData.length);\n            expectedData[8] = 0x10; // The client capabilities\n            expectedData[9] = 0x00; // Reserved flags\n            expectedData[10] = 0x00; // Reserved flags\n            expectedData[11] = 0x01; // The formats client supports\n            expectedData.set(deflatedData, 12);\n\n            RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);\n\n            expect(sock).to.have.sent(expectedData);\n\n        });\n\n        describe('End of line characters', function () {\n            it('Carriage return', function () {\n\n                let testText = \"Hello\\rworld\\r\\r!\";\n                let expectedText = encodeUTF8(\"Hello\\r\\nworld\\r\\n\\r\\n!\\0\");\n\n                let deflatedData =  deflateWithSize(expectedText);\n\n                // Build Expected with flags and deflated data\n                let expectedData = new Uint8Array(8 + 4 + deflatedData.length);\n                expectedData[0] = 0x06; // Message type\n                expectedData[1] = 0x00;\n                expectedData[2] = 0x00;\n                expectedData[3] = 0x00;\n                expectedData[4] = 0xFF; // Size\n                expectedData[5] = 0xFF;\n                expectedData[6] = 0xFF;\n                expectedData[7] = 256 - (4 + deflatedData.length);\n                expectedData[8] = 0x10; // The client capabilities\n                expectedData[9] = 0x00; // Reserved flags\n                expectedData[10] = 0x00; // Reserved flags\n                expectedData[11] = 0x01; // The formats client supports\n                expectedData.set(deflatedData, 12);\n\n                RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);\n\n                expect(sock).to.have.sent(expectedData);\n            });\n\n            it('Carriage return Line feed', function () {\n\n                let testText = \"Hello\\r\\n\\r\\nworld\\r\\n!\";\n                let expectedText = encodeUTF8(testText + \"\\0\");\n\n                let deflatedData =  deflateWithSize(expectedText);\n\n                // Build Expected with flags and deflated data\n                let expectedData = new Uint8Array(8 + 4 + deflatedData.length);\n                expectedData[0] = 0x06; // Message type\n                expectedData[1] = 0x00;\n                expectedData[2] = 0x00;\n                expectedData[3] = 0x00;\n                expectedData[4] = 0xFF; // Size\n                expectedData[5] = 0xFF;\n                expectedData[6] = 0xFF;\n                expectedData[7] = 256 - (4 + deflatedData.length);\n                expectedData[8] = 0x10; // The client capabilities\n                expectedData[9] = 0x00; // Reserved flags\n                expectedData[10] = 0x00; // Reserved flags\n                expectedData[11] = 0x01; // The formats client supports\n                expectedData.set(deflatedData, 12);\n\n                RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);\n\n                expect(sock).to.have.sent(expectedData);\n            });\n\n            it('Line feed', function () {\n                let testText = \"Hello\\n\\n\\nworld\\n!\";\n                let expectedText = encodeUTF8(\"Hello\\r\\n\\r\\n\\r\\nworld\\r\\n!\\0\");\n\n                let deflatedData =  deflateWithSize(expectedText);\n\n                // Build Expected with flags and deflated data\n                let expectedData = new Uint8Array(8 + 4 + deflatedData.length);\n                expectedData[0] = 0x06; // Message type\n                expectedData[1] = 0x00;\n                expectedData[2] = 0x00;\n                expectedData[3] = 0x00;\n                expectedData[4] = 0xFF; // Size\n                expectedData[5] = 0xFF;\n                expectedData[6] = 0xFF;\n                expectedData[7] = 256 - (4 + deflatedData.length);\n                expectedData[8] = 0x10; // The client capabilities\n                expectedData[9] = 0x00; // Reserved flags\n                expectedData[10] = 0x00; // Reserved flags\n                expectedData[11] = 0x01; // The formats client supports\n                expectedData.set(deflatedData, 12);\n\n                RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);\n\n                expect(sock).to.have.sent(expectedData);\n            });\n\n            it('Carriage return and Line feed mixed', function () {\n                let testText = \"\\rHello\\r\\n\\rworld\\n\\n!\";\n                let expectedText = encodeUTF8(\"\\r\\nHello\\r\\n\\r\\nworld\\r\\n\\r\\n!\\0\");\n\n                let deflatedData =  deflateWithSize(expectedText);\n\n                // Build Expected with flags and deflated data\n                let expectedData = new Uint8Array(8 + 4 + deflatedData.length);\n                expectedData[0] = 0x06; // Message type\n                expectedData[1] = 0x00;\n                expectedData[2] = 0x00;\n                expectedData[3] = 0x00;\n                expectedData[4] = 0xFF; // Size\n                expectedData[5] = 0xFF;\n                expectedData[6] = 0xFF;\n                expectedData[7] = 256 - (4 + deflatedData.length);\n                expectedData[8] = 0x10; // The client capabilities\n                expectedData[9] = 0x00; // Reserved flags\n                expectedData[10] = 0x00; // Reserved flags\n                expectedData[11] = 0x01; // The formats client supports\n                expectedData.set(deflatedData, 12);\n\n                RFB.messages.extendedClipboardProvide(sock, [0x01], [testText]);\n\n                expect(sock).to.have.sent(expectedData);\n            });\n        });\n    });\n\n    describe('Screen layout', function () {\n        it('should send correct data for screen layout changes', function () {\n            RFB.messages.setDesktopSize(sock, 12345, 54321, 0x12345678, 0x90abcdef);\n            let expected =\n                [ 251, 0, 0x30, 0x39, 0xd4, 0x31, 0x01, 0x00,\n                  0x12, 0x34, 0x56, 0x78, 0x00, 0x00, 0x00, 0x00,\n                  0x30, 0x39, 0xd4, 0x31, 0x90, 0xab, 0xcd, 0xef ];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n    });\n\n    describe('Fences', function () {\n        it('should send correct data for fences', function () {\n            // FIXME: Payload should be a byte array\n            RFB.messages.clientFence(sock, 0x12345678, \"text\");\n            let expected =\n                [ 248, 0, 0, 0, 0x12, 0x34, 0x56, 0x78,\n                  4, 0x74, 0x65, 0x78, 0x74 ];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n    });\n\n    describe('Continuous updates', function () {\n        it('should send correct data for continuous updates configuration', function () {\n            // FIXME: enable should be boolean\n            RFB.messages.enableContinuousUpdates(sock, 0, 12345, 54321, 34343, 18181);\n            let expected =\n                [ 150, 0, 0x30, 0x39, 0xd4, 0x31, 0x86, 0x27, 0x47, 0x05 ];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n    });\n\n    describe('Pixel format', function () {\n        it('should send correct data for normal depth', function () {\n            RFB.messages.pixelFormat(sock, 24, true);\n            let expected =\n                [ 0, 0, 0, 0, 32, 24, 0, 1,\n                  0, 255, 0, 255, 0, 255, 0, 8, 16, 0, 0, 0 ];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n\n        it('should send correct data for low depth', function () {\n            RFB.messages.pixelFormat(sock, 8, true);\n            let expected =\n                [ 0, 0, 0, 0, 8, 8, 0, 1,\n                  0, 3, 0, 3, 0, 3, 0, 2, 4, 0, 0, 0 ];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n    });\n\n    describe('Encodings', function () {\n        it('should send correct data for supported encodings', function () {\n            RFB.messages.clientEncodings(sock, [ 0x12345678,\n                                                 0x90abcdef,\n                                                 0x10293847 ]);\n            let expected =\n                [ 2, 0, 0, 3, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd,\n                  0xef, 0x10, 0x29, 0x38, 0x47 ];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n    });\n\n    describe('Update request', function () {\n        it('should send correct data for update request', function () {\n            RFB.messages.fbUpdateRequest(sock, true, 12345, 54321, 34343, 18181);\n            let expected =\n                [ 3, 1, 0x30, 0x39, 0xd4, 0x31, 0x86, 0x27, 0x47, 0x05 ];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n    });\n\n    describe('XVP operations', function () {\n        it('should send correct data for XVP operations', function () {\n            RFB.messages.xvpOp(sock, 123, 45);\n            let expected =\n                [ 250, 0, 123, 45 ];\n            expect(sock).to.have.sent(new Uint8Array(expected));\n        });\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.rre.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport RREDecoder from '../core/decoders/rre.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\nfunction push16(arr, num) {\n    arr.push((num >> 8) & 0xFF,\n             num & 0xFF);\n}\n\nfunction push32(arr, num) {\n    arr.push((num >> 24) & 0xFF,\n             (num >> 16) & 0xFF,\n             (num >>  8) & 0xFF,\n             num & 0xFF);\n}\n\ndescribe('RRE decoder', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        decoder = new RREDecoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(4, 4);\n    });\n\n    // TODO(directxman12): test rre_chunk_sz?\n\n    it('should handle the RRE encoding', function () {\n        let data = [];\n        push32(data, 2); // 2 subrects\n        push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color\n        data.push(0x00); // becomes 0000ff00 --> #0000FF fg color\n        data.push(0x00);\n        data.push(0xff);\n        data.push(0x00);\n        push16(data, 0); // x: 0\n        push16(data, 0); // y: 0\n        push16(data, 2); // width: 2\n        push16(data, 2); // height: 2\n        data.push(0x00); // becomes 0000ff00 --> #0000FF fg color\n        data.push(0x00);\n        data.push(0xff);\n        data.push(0x00);\n        push16(data, 2); // x: 2\n        push16(data, 2); // y: 2\n        push16(data, 2); // width: 2\n        push16(data, 2); // height: 2\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle empty rects', function () {\n        display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n        display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0,\n                                  [ 0x00, 0x00, 0x00, 0x00,\n                                    0xff, 0xff, 0xff, 0xff ],\n                                  display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.tight.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport TightDecoder from '../core/decoders/tight.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\ndescribe('Tight decoder', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        decoder = new TightDecoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(4, 4);\n    });\n\n    it('should handle fill rects', function () {\n        let done = testDecodeRect(decoder, 0, 0, 4, 4,\n                                  [0x80, 0xff, 0x88, 0x44],\n                                  display, 24);\n\n        let targetData = new Uint8Array([\n            0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,\n            0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,\n            0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,\n            0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255,\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle uncompressed copy rects', function () {\n        let done;\n        let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ];\n        let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ];\n\n        done = testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);\n        expect(done).to.be.true;\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle compressed copy rects', function () {\n        let data = [\n            // Control byte\n            0x00,\n            // Pixels (compressed)\n            0x15,\n            0x78, 0x9c, 0x63, 0x60, 0xf8, 0xcf, 0x00, 0x44,\n            0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6,\n            0x00, 0x7e, 0xbf, 0x0f, 0xf1 ];\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle uncompressed mono rects', function () {\n        let data = [\n            // Control bytes\n            0x40, 0x01,\n            // Palette\n            0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00,\n            // Pixels\n            0x30, 0x30, 0xc0, 0xc0 ];\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle compressed mono rects', function () {\n        display.resize(4, 12);\n\n        let data = [\n            // Control bytes\n            0x40, 0x01,\n            // Palette\n            0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00,\n            // Pixels (compressed)\n            0x0e,\n            0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00,\n            0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ];\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle uncompressed palette rects', function () {\n        let done;\n        let data1 = [\n            // Control bytes\n            0x40, 0x01,\n            // Palette\n            0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,\n            // Pixels\n            0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01 ];\n        let data2 = [\n            // Control bytes\n            0x40, 0x01,\n            // Palette\n            0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,\n            // Pixels\n            0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ];\n\n        done = testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24);\n        expect(done).to.be.true;\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle compressed palette rects', function () {\n        let data = [\n            // Control bytes\n            0x40, 0x01,\n            // Palette\n            0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,\n            // Pixels (compressed)\n            0x12,\n            0x78, 0x9c, 0x63, 0x60, 0x60, 0x64, 0x64, 0x00,\n            0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54,\n            0x00, 0x09 ];\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle uncompressed gradient rects', function () {\n        let done;\n        let blueData = [ 0x40, 0x02, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00 ];\n        let greenData = [ 0x40, 0x02, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00 ];\n\n        done = testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24);\n        expect(done).to.be.true;\n        done = testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24);\n        expect(done).to.be.true;\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle compressed gradient rects', function () {\n        let data = [\n            // Control byte\n            0x40, 0x02,\n            // Pixels (compressed)\n            0x18,\n            0x78, 0x9c, 0x62, 0x60, 0xf8, 0xcf, 0x00, 0x04,\n            0xff, 0x19, 0x19, 0xd0, 0x00, 0x44, 0x84, 0xf1,\n            0x3f, 0x9a, 0x30, 0x00, 0x00, 0x00, 0xff, 0xff ];\n\n        let done = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle empty copy rects', function () {\n        display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n        display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0, [ 0x00 ], display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle empty palette rects', function () {\n        display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n        display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0,\n                                  [ 0x40, 0x01, 0x01,\n                                    0xff, 0xff, 0xff,\n                                    0xff, 0xff, 0xff ], display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle empty gradient rects', function () {\n        display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n        display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0,\n                                  [ 0x40, 0x02 ], display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle empty fill rects', function () {\n        display.fillRect(0, 0, 4, 4, [ 0x00, 0x00, 0xff ]);\n        display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);\n        display.fillRect(0, 2, 2, 2, [ 0x00, 0xff, 0x00 ]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0,\n                                  [ 0x80, 0xff, 0xff, 0xff ],\n                                  display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle JPEG rects', async function () {\n        let data = [\n            // Control bytes\n            0x90, 0xd6, 0x05,\n            // JPEG data\n            0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46,\n            0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48,\n            0x00, 0x48, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x13,\n            0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20,\n            0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d,\n            0x50, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0xdb,\n            0x00, 0x43, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n            0x01, 0x01, 0x01, 0xff, 0xc2, 0x00, 0x11, 0x08,\n            0x00, 0x04, 0x00, 0x04, 0x03, 0x01, 0x11, 0x00,\n            0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4,\n            0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x07, 0xff, 0xc4, 0x00, 0x14,\n            0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x08, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01,\n            0x00, 0x02, 0x10, 0x03, 0x10, 0x00, 0x00, 0x01,\n            0x1e, 0x0a, 0xa7, 0x7f, 0xff, 0xc4, 0x00, 0x14,\n            0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,\n            0x00, 0x01, 0x05, 0x02, 0x5d, 0x74, 0x41, 0x47,\n            0xff, 0xc4, 0x00, 0x1f, 0x11, 0x00, 0x01, 0x04,\n            0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x05,\n            0x07, 0x08, 0x14, 0x16, 0x03, 0x15, 0x17, 0x25,\n            0x26, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03, 0x01,\n            0x01, 0x3f, 0x01, 0xad, 0x35, 0xa6, 0x13, 0xb8,\n            0x10, 0x98, 0x5d, 0x8a, 0xb1, 0x41, 0x7e, 0x43,\n            0x99, 0x24, 0x3d, 0x8f, 0x70, 0x30, 0xd8, 0xcb,\n            0x44, 0xbb, 0x7d, 0x48, 0xb5, 0xf8, 0x18, 0x7f,\n            0xe7, 0xc1, 0x9f, 0x86, 0x45, 0x9b, 0xfa, 0xf1,\n            0x61, 0x96, 0x46, 0xbf, 0x56, 0xc8, 0x8b, 0x2b,\n            0x0b, 0x35, 0x6e, 0x4b, 0x8a, 0x95, 0x6a, 0xf9,\n            0xff, 0x00, 0xff, 0xc4, 0x00, 0x1f, 0x11, 0x00,\n            0x01, 0x04, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,\n            0x02, 0x04, 0x05, 0x12, 0x13, 0x14, 0x01, 0x06,\n            0x11, 0x22, 0x23, 0xff, 0xda, 0x00, 0x08, 0x01,\n            0x02, 0x01, 0x01, 0x3f, 0x01, 0x85, 0x85, 0x8c,\n            0xec, 0x31, 0x8d, 0xa6, 0x26, 0x1b, 0x6e, 0x48,\n            0xbc, 0xcd, 0xb0, 0xe3, 0x33, 0x86, 0xf9, 0x35,\n            0xdc, 0x15, 0xa8, 0xbe, 0x4d, 0x4a, 0x10, 0x22,\n            0x80, 0x00, 0x91, 0xe8, 0x24, 0xda, 0xb6, 0x57,\n            0x95, 0xf2, 0xa5, 0x73, 0xff, 0xc4, 0x00, 0x1e,\n            0x10, 0x00, 0x01, 0x04, 0x03, 0x00, 0x03, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x03, 0x01, 0x02, 0x04, 0x12, 0x05, 0x11,\n            0x13, 0x14, 0x22, 0x23, 0xff, 0xda, 0x00, 0x08,\n            0x01, 0x01, 0x00, 0x06, 0x3f, 0x02, 0x91, 0x89,\n            0xc4, 0xc8, 0xf1, 0x60, 0x45, 0xe5, 0xc0, 0x1c,\n            0x80, 0x7a, 0x77, 0x00, 0xe4, 0x97, 0xeb, 0x24,\n            0x66, 0x33, 0xac, 0x63, 0x11, 0xfe, 0xe4, 0x76,\n            0xad, 0x56, 0xe9, 0xa8, 0x88, 0x9f, 0xff, 0xc4,\n            0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, 0x08,\n            0x01, 0x01, 0x00, 0x01, 0x3f, 0x21, 0x68, 0x3f,\n            0x92, 0x17, 0x81, 0x1f, 0x7f, 0xff, 0xda, 0x00,\n            0x0c, 0x03, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00,\n            0x00, 0x00, 0x10, 0x5f, 0xff, 0xc4, 0x00, 0x14,\n            0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03,\n            0x01, 0x01, 0x3f, 0x10, 0x03, 0xeb, 0x11, 0xe4,\n            0xa7, 0xe3, 0xff, 0x00, 0xff, 0xc4, 0x00, 0x14,\n            0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x02,\n            0x01, 0x01, 0x3f, 0x10, 0x6b, 0xd3, 0x02, 0xdc,\n            0x9a, 0xf4, 0xff, 0x00, 0xff, 0xc4, 0x00, 0x14,\n            0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,\n            0x00, 0x01, 0x3f, 0x10, 0x62, 0x7b, 0x3a, 0xd0,\n            0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9,\n        ];\n\n        let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n        expect(decodeDone).to.be.true;\n\n        let targetData = new Uint8Array([\n            0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255\n        ]);\n\n        // Browsers have rounding errors, so we need an approximate\n        // comparing function\n        function almost(a, b) {\n            let diff = Math.abs(a - b);\n            return diff < 5;\n        }\n\n        await display.flush();\n        expect(display).to.have.displayed(targetData, almost);\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.tightpng.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport TightPngDecoder from '../core/decoders/tightpng.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\ndescribe('TightPng decoder', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        decoder = new TightPngDecoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(4, 4);\n    });\n\n    it('should handle the TightPng encoding', async function () {\n        let data = [\n            // Control bytes\n            0xa0, 0xb4, 0x04,\n            // PNG data\n            0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n            0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,\n            0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,\n            0x08, 0x02, 0x00, 0x00, 0x00, 0x26, 0x93, 0x09,\n            0x29, 0x00, 0x00, 0x01, 0x84, 0x69, 0x43, 0x43,\n            0x50, 0x49, 0x43, 0x43, 0x20, 0x70, 0x72, 0x6f,\n            0x66, 0x69, 0x6c, 0x65, 0x00, 0x00, 0x28, 0x91,\n            0x7d, 0x91, 0x3d, 0x48, 0xc3, 0x40, 0x18, 0x86,\n            0xdf, 0xa6, 0x6a, 0x45, 0x2a, 0x0e, 0x76, 0x10,\n            0x71, 0x08, 0x52, 0x9d, 0x2c, 0x88, 0x8a, 0x38,\n            0x6a, 0x15, 0x8a, 0x50, 0x21, 0xd4, 0x0a, 0xad,\n            0x3a, 0x98, 0x5c, 0xfa, 0x07, 0x4d, 0x1a, 0x92,\n            0x14, 0x17, 0x47, 0xc1, 0xb5, 0xe0, 0xe0, 0xcf,\n            0x62, 0xd5, 0xc1, 0xc5, 0x59, 0x57, 0x07, 0x57,\n            0x41, 0x10, 0xfc, 0x01, 0x71, 0x72, 0x74, 0x52,\n            0x74, 0x91, 0x12, 0xbf, 0x4b, 0x0a, 0x2d, 0x62,\n            0xbc, 0xe3, 0xb8, 0x87, 0xf7, 0xbe, 0xf7, 0xe5,\n            0xee, 0x3b, 0x40, 0xa8, 0x97, 0x99, 0x66, 0x75,\n            0x8c, 0x03, 0x9a, 0x6e, 0x9b, 0xa9, 0x44, 0x5c,\n            0xcc, 0x64, 0x57, 0xc5, 0xd0, 0x2b, 0xba, 0x68,\n            0x86, 0x31, 0x8c, 0x2e, 0x99, 0x59, 0xc6, 0x9c,\n            0x24, 0x25, 0xe1, 0x3b, 0xbe, 0xee, 0x11, 0xe0,\n            0xfb, 0x5d, 0x8c, 0x67, 0xf9, 0xd7, 0xfd, 0x39,\n            0x7a, 0xd5, 0x9c, 0xc5, 0x80, 0x80, 0x48, 0x3c,\n            0xcb, 0x0c, 0xd3, 0x26, 0xde, 0x20, 0x9e, 0xde,\n            0xb4, 0x0d, 0xce, 0xfb, 0xc4, 0x11, 0x56, 0x94,\n            0x55, 0xe2, 0x73, 0xe2, 0x31, 0x93, 0x2e, 0x48,\n            0xfc, 0xc8, 0x75, 0xc5, 0xe3, 0x37, 0xce, 0x05,\n            0x97, 0x05, 0x9e, 0x19, 0x31, 0xd3, 0xa9, 0x79,\n            0xe2, 0x08, 0xb1, 0x58, 0x68, 0x63, 0xa5, 0x8d,\n            0x59, 0xd1, 0xd4, 0x88, 0xa7, 0x88, 0xa3, 0xaa,\n            0xa6, 0x53, 0xbe, 0x90, 0xf1, 0x58, 0xe5, 0xbc,\n            0xc5, 0x59, 0x2b, 0x57, 0x59, 0xf3, 0x9e, 0xfc,\n            0x85, 0xe1, 0x9c, 0xbe, 0xb2, 0xcc, 0x75, 0x5a,\n            0x43, 0x48, 0x60, 0x11, 0x4b, 0x90, 0x20, 0x42,\n            0x41, 0x15, 0x25, 0x94, 0x61, 0x23, 0x46, 0xbb,\n            0x4e, 0x8a, 0x85, 0x14, 0x9d, 0xc7, 0x7d, 0xfc,\n            0x83, 0xae, 0x5f, 0x22, 0x97, 0x42, 0xae, 0x12,\n            0x18, 0x39, 0x16, 0x50, 0x81, 0x06, 0xd9, 0xf5,\n            0x83, 0xff, 0xc1, 0xef, 0xde, 0x5a, 0xf9, 0xc9,\n            0x09, 0x2f, 0x29, 0x1c, 0x07, 0x3a, 0x5f, 0x1c,\n            0xe7, 0x63, 0x04, 0x08, 0xed, 0x02, 0x8d, 0x9a,\n            0xe3, 0x7c, 0x1f, 0x3b, 0x4e, 0xe3, 0x04, 0x08,\n            0x3e, 0x03, 0x57, 0x7a, 0xcb, 0x5f, 0xa9, 0x03,\n            0x33, 0x9f, 0xa4, 0xd7, 0x5a, 0x5a, 0xf4, 0x08,\n            0xe8, 0xdb, 0x06, 0x2e, 0xae, 0x5b, 0x9a, 0xb2,\n            0x07, 0x5c, 0xee, 0x00, 0x03, 0x4f, 0x86, 0x6c,\n            0xca, 0xae, 0x14, 0xa4, 0x25, 0xe4, 0xf3, 0xc0,\n            0xfb, 0x19, 0x7d, 0x53, 0x16, 0xe8, 0xbf, 0x05,\n            0x7a, 0xd6, 0xbc, 0xbe, 0x35, 0xcf, 0x71, 0xfa,\n            0x00, 0xa4, 0xa9, 0x57, 0xc9, 0x1b, 0xe0, 0xe0,\n            0x10, 0x18, 0x2d, 0x50, 0xf6, 0xba, 0xcf, 0xbb,\n            0xbb, 0xdb, 0xfb, 0xf6, 0x6f, 0x4d, 0xb3, 0x7f,\n            0x3f, 0x0a, 0x27, 0x72, 0x7d, 0x49, 0x29, 0x8b,\n            0xbb, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59,\n            0x73, 0x00, 0x00, 0x2e, 0x23, 0x00, 0x00, 0x2e,\n            0x23, 0x01, 0x78, 0xa5, 0x3f, 0x76, 0x00, 0x00,\n            0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xe4,\n            0x06, 0x06, 0x0c, 0x23, 0x1d, 0x3f, 0x9f, 0xbb,\n            0x94, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58,\n            0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74,\n            0x00, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,\n            0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49,\n            0x4d, 0x50, 0x57, 0x81, 0x0e, 0x17, 0x00, 0x00,\n            0x00, 0x1e, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7,\n            0x65, 0xc9, 0xb1, 0x0d, 0x00, 0x00, 0x08, 0x03,\n            0x20, 0xea, 0xff, 0x3f, 0xd7, 0xd5, 0x44, 0x56,\n            0x52, 0x90, 0xc2, 0x38, 0xa2, 0xd0, 0xbc, 0x59,\n            0x8a, 0x9f, 0x04, 0x05, 0x6b, 0x38, 0x7b, 0xb2,\n            0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,\n            0xae, 0x42, 0x60, 0x82,\n        ];\n\n        let decodeDone = testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24);\n        expect(decodeDone).to.be.true;\n\n        let targetData = new Uint8Array([\n            0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255\n        ]);\n\n        // Firefox currently has some very odd rounding bug:\n        // https://bugzilla.mozilla.org/show_bug.cgi?id=1667747\n        function almost(a, b) {\n            let diff = Math.abs(a - b);\n            return diff < 30;\n        }\n\n        await display.flush();\n        expect(display).to.have.displayed(targetData, almost);\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.util.js",
    "content": "/* eslint-disable no-console */\nimport * as Log from '../core/util/logging.js';\nimport { encodeUTF8, decodeUTF8 } from '../core/util/strings.js';\n\ndescribe('Utils', function () {\n    \"use strict\";\n\n    describe('logging functions', function () {\n        beforeEach(function () {\n            sinon.spy(console, 'log');\n            sinon.spy(console, 'debug');\n            sinon.spy(console, 'warn');\n            sinon.spy(console, 'error');\n            sinon.spy(console, 'info');\n        });\n\n        afterEach(function () {\n            console.log.restore();\n            console.debug.restore();\n            console.warn.restore();\n            console.error.restore();\n            console.info.restore();\n            Log.initLogging();\n        });\n\n        it('should use noop for levels lower than the min level', function () {\n            Log.initLogging('warn');\n            Log.Debug('hi');\n            Log.Info('hello');\n            expect(console.log).to.not.have.been.called;\n        });\n\n        it('should use console.debug for Debug', function () {\n            Log.initLogging('debug');\n            Log.Debug('dbg');\n            expect(console.debug).to.have.been.calledWith('dbg');\n        });\n\n        it('should use console.info for Info', function () {\n            Log.initLogging('debug');\n            Log.Info('inf');\n            expect(console.info).to.have.been.calledWith('inf');\n        });\n\n        it('should use console.warn for Warn', function () {\n            Log.initLogging('warn');\n            Log.Warn('wrn');\n            expect(console.warn).to.have.been.called;\n            expect(console.warn).to.have.been.calledWith('wrn');\n        });\n\n        it('should use console.error for Error', function () {\n            Log.initLogging('error');\n            Log.Error('err');\n            expect(console.error).to.have.been.called;\n            expect(console.error).to.have.been.calledWith('err');\n        });\n    });\n\n    describe('string functions', function () {\n        it('should decode UTF-8 to DOMString correctly', function () {\n            const utf8string = '\\xd0\\x9f';\n            const domstring = decodeUTF8(utf8string);\n            expect(domstring).to.equal(\"П\");\n        });\n\n        it('should encode DOMString to UTF-8 correctly', function () {\n            const domstring = \"åäöa\";\n            const utf8string = encodeUTF8(domstring);\n            expect(utf8string).to.equal('\\xc3\\xa5\\xc3\\xa4\\xc3\\xb6\\x61');\n        });\n\n        it('should allow Latin-1 strings if allowLatin1 is set when decoding', function () {\n            const latin1string = '\\xe5\\xe4\\xf6';\n            expect(() => decodeUTF8(latin1string)).to.throw(Error);\n            expect(decodeUTF8(latin1string, true)).to.equal('åäö');\n        });\n    });\n\n    // TODO(directxman12): test the conf_default and conf_defaults methods\n    // TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)\n    // TODO(directxman12): figure out a good way to test getPosition and getEventPosition\n    // TODO(directxman12): figure out how to test the browser detection functions properly\n    //                     (we can't really test them against the browsers, except for Gecko\n    //                     via PhantomJS, the default test driver)\n});\n/* eslint-enable no-console */\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.websock.js",
    "content": "import Websock from '../core/websock.js';\nimport FakeWebSocket from './fake.websocket.js';\n\ndescribe('Websock', function () {\n    \"use strict\";\n\n    describe('Receive queue methods', function () {\n        let sock, websock;\n\n        beforeEach(function () {\n            sock = new Websock();\n            websock = new FakeWebSocket();\n            websock._open();\n            sock.attach(websock);\n        });\n\n        describe('rQpeek8', function () {\n            it('should peek at the next byte without poping it off the queue', function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd]));\n                expect(sock.rQpeek8()).to.equal(0xab);\n                expect(sock.rQpeek8()).to.equal(0xab);\n            });\n        });\n\n        describe('rQshift8()', function () {\n            it('should pop a single byte from the receive queue', function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd]));\n                expect(sock.rQshift8()).to.equal(0xab);\n                expect(sock.rQshift8()).to.equal(0xcd);\n            });\n        });\n\n        describe('rQshift16()', function () {\n            it('should pop two bytes from the receive queue and return a single number', function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));\n                expect(sock.rQshift16()).to.equal(0xabcd);\n                expect(sock.rQshift16()).to.equal(0x1234);\n            });\n        });\n\n        describe('rQshift32()', function () {\n            it('should pop four bytes from the receive queue and return a single number', function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,\n                                                     0x88, 0xee, 0x11, 0x33]));\n                expect(sock.rQshift32()).to.equal(0xabcd1234);\n                expect(sock.rQshift32()).to.equal(0x88ee1133);\n            });\n        });\n\n        describe('rQshiftStr', function () {\n            it('should shift the given number of bytes off of the receive queue and return a string', function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,\n                                                     0x88, 0xee, 0x11, 0x33]));\n                expect(sock.rQshiftStr(4)).to.equal('\\xab\\xcd\\x12\\x34');\n                expect(sock.rQshiftStr(4)).to.equal('\\x88\\xee\\x11\\x33');\n            });\n\n            it('should be able to handle very large strings', function () {\n                const BIG_LEN = 500000;\n                const incoming = new Uint8Array(BIG_LEN);\n                let expected = \"\";\n                let letterCode = 'a'.charCodeAt(0);\n                for (let i = 0; i < BIG_LEN; i++) {\n                    incoming[i] = letterCode;\n                    expected += String.fromCharCode(letterCode);\n\n                    if (letterCode < 'z'.charCodeAt(0)) {\n                        letterCode++;\n                    } else {\n                        letterCode = 'a'.charCodeAt(0);\n                    }\n                }\n                websock._receiveData(incoming);\n\n                const shifted = sock.rQshiftStr(BIG_LEN);\n\n                expect(shifted).to.be.equal(expected);\n            });\n        });\n\n        describe('rQshiftBytes', function () {\n            it('should shift the given number of bytes of the receive queue and return an array', function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,\n                                                     0x88, 0xee, 0x11, 0x33]));\n                expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));\n                expect(sock.rQshiftBytes(4)).to.array.equal(new Uint8Array([0x88, 0xee, 0x11, 0x33]));\n            });\n\n            it('should return a shared array if requested', function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,\n                                                     0x88, 0xee, 0x11, 0x33]));\n                const bytes = sock.rQshiftBytes(4, false);\n                expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));\n                expect(bytes.buffer.byteLength).to.not.equal(bytes.length);\n            });\n        });\n\n        describe('rQpeekBytes', function () {\n            it('should not modify the receive queue', function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,\n                                                     0x88, 0xee, 0x11, 0x33]));\n                expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));\n                expect(sock.rQpeekBytes(4)).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));\n            });\n\n            it('should return a shared array if requested', function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,\n                                                     0x88, 0xee, 0x11, 0x33]));\n                const bytes = sock.rQpeekBytes(4, false);\n                expect(bytes).to.array.equal(new Uint8Array([0xab, 0xcd, 0x12, 0x34]));\n                expect(bytes.buffer.byteLength).to.not.equal(bytes.length);\n            });\n        });\n\n        describe('rQwait', function () {\n            beforeEach(function () {\n                websock._receiveData(new Uint8Array([0xab, 0xcd, 0x12, 0x34,\n                                                     0x88, 0xee, 0x11, 0x33]));\n            });\n\n            it('should return true if there are not enough bytes in the receive queue', function () {\n                expect(sock.rQwait('hi', 9)).to.be.true;\n            });\n\n            it('should return false if there are enough bytes in the receive queue', function () {\n                expect(sock.rQwait('hi', 8)).to.be.false;\n            });\n\n            it('should return true and reduce rQi by \"goback\" if there are not enough bytes', function () {\n                expect(sock.rQshift32()).to.equal(0xabcd1234);\n                expect(sock.rQwait('hi', 8, 2)).to.be.true;\n                expect(sock.rQshift32()).to.equal(0x123488ee);\n            });\n\n            it('should raise an error if we try to go back more than possible', function () {\n                expect(sock.rQshift32()).to.equal(0xabcd1234);\n                expect(() => sock.rQwait('hi', 8, 6)).to.throw(Error);\n            });\n\n            it('should not reduce rQi if there are enough bytes', function () {\n                expect(sock.rQshift32()).to.equal(0xabcd1234);\n                expect(sock.rQwait('hi', 4, 2)).to.be.false;\n                expect(sock.rQshift32()).to.equal(0x88ee1133);\n            });\n        });\n    });\n\n    describe('Send queue methods', function () {\n        let sock;\n\n        const bufferSize = 10 * 1024;\n\n        beforeEach(function () {\n            let websock = new FakeWebSocket();\n            websock._open();\n            sock = new Websock();\n            sock.attach(websock);\n        });\n\n        describe('sQpush8()', function () {\n            it('should send a single byte', function () {\n                sock.sQpush8(42);\n                sock.flush();\n                expect(sock).to.have.sent(new Uint8Array([42]));\n            });\n            it('should not send any data until flushing', function () {\n                sock.sQpush8(42);\n                expect(sock).to.have.sent(new Uint8Array([]));\n            });\n            it('should implicitly flush if the queue is full', function () {\n                for (let i = 0;i <= bufferSize;i++) {\n                    sock.sQpush8(42);\n                }\n\n                let expected = [];\n                for (let i = 0;i < bufferSize;i++) {\n                    expected.push(42);\n                }\n\n                expect(sock).to.have.sent(new Uint8Array(expected));\n            });\n        });\n\n        describe('sQpush16()', function () {\n            it('should send a number as two bytes', function () {\n                sock.sQpush16(420);\n                sock.flush();\n                expect(sock).to.have.sent(new Uint8Array([1, 164]));\n            });\n            it('should not send any data until flushing', function () {\n                sock.sQpush16(420);\n                expect(sock).to.have.sent(new Uint8Array([]));\n            });\n            it('should implicitly flush if the queue is full', function () {\n                for (let i = 0;i <= bufferSize/2;i++) {\n                    sock.sQpush16(420);\n                }\n\n                let expected = [];\n                for (let i = 0;i < bufferSize/2;i++) {\n                    expected.push(1);\n                    expected.push(164);\n                }\n\n                expect(sock).to.have.sent(new Uint8Array(expected));\n            });\n        });\n\n        describe('sQpush32()', function () {\n            it('should send a number as two bytes', function () {\n                sock.sQpush32(420420);\n                sock.flush();\n                expect(sock).to.have.sent(new Uint8Array([0, 6, 106, 68]));\n            });\n            it('should not send any data until flushing', function () {\n                sock.sQpush32(420420);\n                expect(sock).to.have.sent(new Uint8Array([]));\n            });\n            it('should implicitly flush if the queue is full', function () {\n                for (let i = 0;i <= bufferSize/4;i++) {\n                    sock.sQpush32(420420);\n                }\n\n                let expected = [];\n                for (let i = 0;i < bufferSize/4;i++) {\n                    expected.push(0);\n                    expected.push(6);\n                    expected.push(106);\n                    expected.push(68);\n                }\n\n                expect(sock).to.have.sent(new Uint8Array(expected));\n            });\n        });\n\n        describe('sQpushString()', function () {\n            it('should send a string buffer', function () {\n                sock.sQpushString('\\x12\\x34\\x56\\x78\\x90');\n                sock.flush();\n                expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));\n            });\n            it('should not send any data until flushing', function () {\n                sock.sQpushString('\\x12\\x34\\x56\\x78\\x90');\n                expect(sock).to.have.sent(new Uint8Array([]));\n            });\n            it('should implicitly flush if the queue is full', function () {\n                for (let i = 0;i <= bufferSize/5;i++) {\n                    sock.sQpushString('\\x12\\x34\\x56\\x78\\x90');\n                }\n\n                let expected = [];\n                for (let i = 0;i < bufferSize/5;i++) {\n                    expected.push(0x12);\n                    expected.push(0x34);\n                    expected.push(0x56);\n                    expected.push(0x78);\n                    expected.push(0x90);\n                }\n\n                expect(sock).to.have.sent(new Uint8Array(expected));\n            });\n            it('should implicitly split a large buffer', function () {\n                let str = '';\n                let expected = [];\n                for (let i = 0;i < bufferSize * 3;i++) {\n                    let byte = Math.random() * 0xff;\n                    str += String.fromCharCode(byte);\n                    expected.push(byte);\n                }\n\n                sock.sQpushString(str);\n                sock.flush();\n\n                expect(sock).to.have.sent(new Uint8Array(expected));\n            });\n        });\n\n        describe('sQpushBytes()', function () {\n            it('should send a byte buffer', function () {\n                sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));\n                sock.flush();\n                expect(sock).to.have.sent(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));\n            });\n            it('should not send any data until flushing', function () {\n                sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));\n                expect(sock).to.have.sent(new Uint8Array([]));\n            });\n            it('should implicitly flush if the queue is full', function () {\n                for (let i = 0;i <= bufferSize/5;i++) {\n                    sock.sQpushBytes(new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90]));\n                }\n\n                let expected = [];\n                for (let i = 0;i < bufferSize/5;i++) {\n                    expected.push(0x12);\n                    expected.push(0x34);\n                    expected.push(0x56);\n                    expected.push(0x78);\n                    expected.push(0x90);\n                }\n\n                expect(sock).to.have.sent(new Uint8Array(expected));\n            });\n            it('should implicitly split a large buffer', function () {\n                let buffer = [];\n                let expected = [];\n                for (let i = 0;i < bufferSize * 3;i++) {\n                    let byte = Math.random() * 0xff;\n                    buffer.push(byte);\n                    expected.push(byte);\n                }\n\n                sock.sQpushBytes(new Uint8Array(buffer));\n                sock.flush();\n\n                expect(sock).to.have.sent(new Uint8Array(expected));\n            });\n        });\n\n        describe('flush', function () {\n            it('should actually send on the websocket', function () {\n                sock._sQ = new Uint8Array([1, 2, 3]);\n                sock._sQlen = 3;\n\n                sock.flush();\n                expect(sock).to.have.sent(new Uint8Array([1, 2, 3]));\n            });\n\n            it('should not call send if we do not have anything queued up', function () {\n                sock._sQlen = 0;\n\n                sock.flush();\n\n                expect(sock).to.have.sent(new Uint8Array([]));\n            });\n        });\n    });\n\n    describe('lifecycle methods', function () {\n        let oldWS;\n        before(function () {\n            oldWS = WebSocket;\n        });\n\n        let sock;\n        beforeEach(function () {\n            sock = new Websock();\n            // eslint-disable-next-line no-global-assign\n            WebSocket = sinon.spy(FakeWebSocket);\n        });\n\n        describe('opening', function () {\n            it('should pick the correct protocols if none are given', function () {\n\n            });\n\n            it('should open the actual websocket', function () {\n                sock.open('ws://localhost:8675', 'binary');\n                expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'binary');\n            });\n\n            // it('should initialize the event handlers')?\n        });\n\n        describe('attaching', function () {\n            it('should attach to an existing websocket', function () {\n                let ws = new FakeWebSocket('ws://localhost:8675');\n                sock.attach(ws);\n                expect(WebSocket).to.not.have.been.called;\n            });\n        });\n\n        describe('closing', function () {\n            beforeEach(function () {\n                sock.open('ws://localhost');\n                sock._websocket.close = sinon.spy();\n            });\n\n            it('should close the actual websocket if it is open', function () {\n                sock._websocket.readyState = WebSocket.OPEN;\n                sock.close();\n                expect(sock._websocket.close).to.have.been.calledOnce;\n            });\n\n            it('should close the actual websocket if it is connecting', function () {\n                sock._websocket.readyState = WebSocket.CONNECTING;\n                sock.close();\n                expect(sock._websocket.close).to.have.been.calledOnce;\n            });\n\n            it('should not try to close the actual websocket if closing', function () {\n                sock._websocket.readyState = WebSocket.CLOSING;\n                sock.close();\n                expect(sock._websocket.close).not.to.have.been.called;\n            });\n\n            it('should not try to close the actual websocket if closed', function () {\n                sock._websocket.readyState = WebSocket.CLOSED;\n                sock.close();\n                expect(sock._websocket.close).not.to.have.been.called;\n            });\n\n            it('should reset onmessage to not call _recvMessage', function () {\n                sinon.spy(sock, '_recvMessage');\n                sock.close();\n                sock._websocket.onmessage(null);\n                try {\n                    expect(sock._recvMessage).not.to.have.been.called;\n                } finally {\n                    sock._recvMessage.restore();\n                }\n            });\n        });\n\n        describe('event handlers', function () {\n            beforeEach(function () {\n                sock._recvMessage = sinon.spy();\n                sock.on('open', sinon.spy());\n                sock.on('close', sinon.spy());\n                sock.on('error', sinon.spy());\n                sock.open('ws://localhost');\n            });\n\n            it('should call _recvMessage on a message', function () {\n                sock._websocket.onmessage(null);\n                expect(sock._recvMessage).to.have.been.calledOnce;\n            });\n\n            it('should call the open event handler on opening', function () {\n                sock._websocket.onopen();\n                expect(sock._eventHandlers.open).to.have.been.calledOnce;\n            });\n\n            it('should call the close event handler on closing', function () {\n                sock._websocket.onclose();\n                expect(sock._eventHandlers.close).to.have.been.calledOnce;\n            });\n\n            it('should call the error event handler on error', function () {\n                sock._websocket.onerror();\n                expect(sock._eventHandlers.error).to.have.been.calledOnce;\n            });\n        });\n\n        describe('ready state', function () {\n            it('should be \"unused\" after construction', function () {\n                let sock = new Websock();\n                expect(sock.readyState).to.equal('unused');\n            });\n\n            it('should be \"connecting\" if WebSocket is connecting', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = WebSocket.CONNECTING;\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('connecting');\n            });\n\n            it('should be \"open\" if WebSocket is open', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = WebSocket.OPEN;\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('open');\n            });\n\n            it('should be \"closing\" if WebSocket is closing', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = WebSocket.CLOSING;\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('closing');\n            });\n\n            it('should be \"closed\" if WebSocket is closed', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = WebSocket.CLOSED;\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('closed');\n            });\n\n            it('should be \"unknown\" if WebSocket state is unknown', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = 666;\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('unknown');\n            });\n\n            it('should be \"connecting\" if RTCDataChannel is connecting', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = 'connecting';\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('connecting');\n            });\n\n            it('should be \"open\" if RTCDataChannel is open', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = 'open';\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('open');\n            });\n\n            it('should be \"closing\" if RTCDataChannel is closing', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = 'closing';\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('closing');\n            });\n\n            it('should be \"closed\" if RTCDataChannel is closed', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = 'closed';\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('closed');\n            });\n\n            it('should be \"unknown\" if RTCDataChannel state is unknown', function () {\n                let sock = new Websock();\n                let ws = new FakeWebSocket();\n                ws.readyState = 'foobar';\n                sock.attach(ws);\n                expect(sock.readyState).to.equal('unknown');\n            });\n        });\n\n        after(function () {\n            // eslint-disable-next-line no-global-assign\n            WebSocket = oldWS;\n        });\n    });\n\n    describe('WebSocket receiving', function () {\n        let sock;\n        beforeEach(function () {\n            sock = new Websock();\n            sock._allocateBuffers();\n        });\n\n        it('should support adding data to the receive queue', function () {\n            const msg = { data: new Uint8Array([1, 2, 3]) };\n            sock._recvMessage(msg);\n            expect(sock.rQshiftStr(3)).to.equal('\\x01\\x02\\x03');\n        });\n\n        it('should call the message event handler if present', function () {\n            sock._eventHandlers.message = sinon.spy();\n            const msg = { data: new Uint8Array([1, 2, 3]).buffer };\n            sock._mode = 'binary';\n            sock._recvMessage(msg);\n            expect(sock._eventHandlers.message).to.have.been.calledOnce;\n        });\n\n        it('should not call the message event handler if there is nothing in the receive queue', function () {\n            sock._eventHandlers.message = sinon.spy();\n            const msg = { data: new Uint8Array([]).buffer };\n            sock._mode = 'binary';\n            sock._recvMessage(msg);\n            expect(sock._eventHandlers.message).not.to.have.been.called;\n        });\n\n        it('should compact the receive queue when fully read', function () {\n            sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);\n            sock._rQlen = 6;\n            sock._rQi = 6;\n            const msg = { data: new Uint8Array([1, 2, 3]).buffer };\n            sock._recvMessage(msg);\n            expect(sock._rQlen).to.equal(3);\n            expect(sock._rQi).to.equal(0);\n        });\n\n        it('should compact the receive queue when we reach the end of the buffer', function () {\n            sock._rQ = new Uint8Array(20);\n            sock._rQbufferSize = 20;\n            sock._rQlen = 20;\n            sock._rQi = 10;\n            const msg = { data: new Uint8Array([1, 2]).buffer };\n            sock._recvMessage(msg);\n            expect(sock._rQlen).to.equal(12);\n            expect(sock._rQi).to.equal(0);\n        });\n\n        it('should automatically resize the receive queue if the incoming message is larger than the buffer', function () {\n            sock._rQ = new Uint8Array(20);\n            sock._rQlen = 0;\n            sock._rQi = 0;\n            sock._rQbufferSize = 20;\n            const msg = { data: new Uint8Array(30).buffer };\n            sock._recvMessage(msg);\n            expect(sock._rQlen).to.equal(30);\n            expect(sock._rQi).to.equal(0);\n            expect(sock._rQ.length).to.equal(240);  // keep the invariant that rQbufferSize / 8 >= rQlen\n        });\n\n        it('should automatically resize the receive queue if the incoming message is larger than 1/8th of the buffer and we reach the end of the buffer', function () {\n            sock._rQ = new Uint8Array(20);\n            sock._rQlen = 16;\n            sock._rQi = 15;\n            sock._rQbufferSize = 20;\n            const msg = { data: new Uint8Array(6).buffer };\n            sock._recvMessage(msg);\n            expect(sock._rQlen).to.equal(7);\n            expect(sock._rQi).to.equal(0);\n            expect(sock._rQ.length).to.equal(56);\n        });\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.webutil.js",
    "content": "/* jshint expr: true */\n\nimport * as WebUtil from '../app/webutil.js';\n\ndescribe('WebUtil', function () {\n    \"use strict\";\n\n    describe('config variables', function () {\n        let origState, origHref;\n        beforeEach(function () {\n            origState = history.state;\n            origHref = location.href;\n        });\n        afterEach(function () {\n            history.replaceState(origState, '', origHref);\n        });\n\n        it('should parse query string variables', function () {\n            // history.pushState() will not cause the browser to attempt loading\n            // the URL, this is exactly what we want here for the tests.\n            history.replaceState({}, '', \"test?myvar=myval\");\n            expect(WebUtil.getConfigVar(\"myvar\")).to.be.equal(\"myval\");\n        });\n        it('should return default value when no query match', function () {\n            history.replaceState({}, '', \"test?myvar=myval\");\n            expect(WebUtil.getConfigVar(\"other\", \"def\")).to.be.equal(\"def\");\n        });\n        it('should handle no query match and no default value', function () {\n            history.replaceState({}, '', \"test?myvar=myval\");\n            expect(WebUtil.getConfigVar(\"other\")).to.be.equal(null);\n        });\n        it('should parse fragment variables', function () {\n            history.replaceState({}, '', \"test#myvar=myval\");\n            expect(WebUtil.getConfigVar(\"myvar\")).to.be.equal(\"myval\");\n        });\n        it('should return default value when no fragment match', function () {\n            history.replaceState({}, '', \"test#myvar=myval\");\n            expect(WebUtil.getConfigVar(\"other\", \"def\")).to.be.equal(\"def\");\n        });\n        it('should handle no fragment match and no default value', function () {\n            history.replaceState({}, '', \"test#myvar=myval\");\n            expect(WebUtil.getConfigVar(\"other\")).to.be.equal(null);\n        });\n        it('should handle both query and fragment', function () {\n            history.replaceState({}, '', \"test?myquery=1#myhash=2\");\n            expect(WebUtil.getConfigVar(\"myquery\")).to.be.equal(\"1\");\n            expect(WebUtil.getConfigVar(\"myhash\")).to.be.equal(\"2\");\n        });\n        it('should prioritize fragment if both provide same var', function () {\n            history.replaceState({}, '', \"test?myvar=1#myvar=2\");\n            expect(WebUtil.getConfigVar(\"myvar\")).to.be.equal(\"2\");\n        });\n    });\n\n    describe('cookies', function () {\n        // TODO\n    });\n\n    describe('settings', function () {\n\n        describe('localStorage', function () {\n            let chrome = window.chrome;\n            before(function () {\n                chrome = window.chrome;\n                window.chrome = null;\n            });\n            after(function () {\n                window.chrome = chrome;\n            });\n\n            let origLocalStorage;\n            beforeEach(function () {\n                origLocalStorage = Object.getOwnPropertyDescriptor(window, \"localStorage\");\n\n                Object.defineProperty(window, \"localStorage\", {value: {}});\n\n                window.localStorage.setItem = sinon.stub();\n                window.localStorage.getItem = sinon.stub();\n                window.localStorage.removeItem = sinon.stub();\n\n                return WebUtil.initSettings();\n            });\n            afterEach(function () {\n                Object.defineProperty(window, \"localStorage\", origLocalStorage);\n            });\n\n            describe('writeSetting', function () {\n                it('should save the setting value to local storage', function () {\n                    WebUtil.writeSetting('test', 'value');\n                    expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                });\n\n                it('should not crash when local storage save fails', function () {\n                    localStorage.setItem.throws(new DOMException());\n                    expect(WebUtil.writeSetting('test', 'value')).to.not.throw;\n                });\n            });\n\n            describe('setSetting', function () {\n                it('should update the setting but not save to local storage', function () {\n                    WebUtil.setSetting('test', 'value');\n                    expect(window.localStorage.setItem).to.not.have.been.called;\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                });\n            });\n\n            describe('readSetting', function () {\n                it('should read the setting value from local storage', function () {\n                    localStorage.getItem.returns('value');\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                });\n\n                it('should return the default value when not in local storage', function () {\n                    expect(WebUtil.readSetting('test', 'default')).to.equal('default');\n                });\n\n                it('should return the cached value even if local storage changed', function () {\n                    localStorage.getItem.returns('value');\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                    localStorage.getItem.returns('something else');\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                });\n\n                it('should cache the value even if it is not initially in local storage', function () {\n                    expect(WebUtil.readSetting('test')).to.be.null;\n                    localStorage.getItem.returns('value');\n                    expect(WebUtil.readSetting('test')).to.be.null;\n                });\n\n                it('should return the default value always if the first read was not in local storage', function () {\n                    expect(WebUtil.readSetting('test', 'default')).to.equal('default');\n                    localStorage.getItem.returns('value');\n                    expect(WebUtil.readSetting('test', 'another default')).to.equal('another default');\n                });\n\n                it('should return the last local written value', function () {\n                    localStorage.getItem.returns('value');\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                    WebUtil.writeSetting('test', 'something else');\n                    expect(WebUtil.readSetting('test')).to.equal('something else');\n                });\n\n                it('should not crash when local storage read fails', function () {\n                    localStorage.getItem.throws(new DOMException());\n                    expect(WebUtil.readSetting('test')).to.not.throw;\n                });\n            });\n\n            // this doesn't appear to be used anywhere\n            describe('eraseSetting', function () {\n                it('should remove the setting from local storage', function () {\n                    WebUtil.eraseSetting('test');\n                    expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');\n                });\n\n                it('should not crash when local storage remove fails', function () {\n                    localStorage.removeItem.throws(new DOMException());\n                    expect(WebUtil.eraseSetting('test')).to.not.throw;\n                });\n            });\n        });\n\n        describe('chrome.storage', function () {\n            let chrome = window.chrome;\n            let settings = {};\n            before(function () {\n                chrome = window.chrome;\n                window.chrome = {\n                    storage: {\n                        sync: {\n                            get(cb) { cb(settings); },\n                            set() {},\n                            remove() {}\n                        }\n                    }\n                };\n            });\n            after(function () {\n                window.chrome = chrome;\n            });\n\n            beforeEach(function () {\n                settings = {};\n                sinon.spy(window.chrome.storage.sync, 'set');\n                sinon.spy(window.chrome.storage.sync, 'remove');\n                return WebUtil.initSettings();\n            });\n            afterEach(function () {\n                window.chrome.storage.sync.set.restore();\n                window.chrome.storage.sync.remove.restore();\n            });\n\n            describe('writeSetting', function () {\n                it('should save the setting value to chrome storage', function () {\n                    WebUtil.writeSetting('test', 'value');\n                    expect(window.chrome.storage.sync.set).to.have.been.calledWithExactly(sinon.match({ test: 'value' }));\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                });\n            });\n\n            describe('setSetting', function () {\n                it('should update the setting but not save to chrome storage', function () {\n                    WebUtil.setSetting('test', 'value');\n                    expect(window.chrome.storage.sync.set).to.not.have.been.called;\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                });\n            });\n\n            describe('readSetting', function () {\n                it('should read the setting value from chrome storage', function () {\n                    settings.test = 'value';\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                });\n\n                it('should return the default value when not in chrome storage', function () {\n                    expect(WebUtil.readSetting('test', 'default')).to.equal('default');\n                });\n\n                it('should return the last local written value', function () {\n                    settings.test = 'value';\n                    expect(WebUtil.readSetting('test')).to.equal('value');\n                    WebUtil.writeSetting('test', 'something else');\n                    expect(WebUtil.readSetting('test')).to.equal('something else');\n                });\n            });\n\n            // this doesn't appear to be used anywhere\n            describe('eraseSetting', function () {\n                it('should remove the setting from chrome storage', function () {\n                    WebUtil.eraseSetting('test');\n                    expect(window.chrome.storage.sync.remove).to.have.been.calledWithExactly('test');\n                });\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.zlib.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport ZlibDecoder from '../core/decoders/zlib.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\ndescribe('Zlib decoder', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        decoder = new ZlibDecoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(4, 4);\n    });\n\n    it('should handle the Zlib encoding', function () {\n        let done;\n\n        let zlibData = new Uint8Array([\n            0x00, 0x00, 0x00, 0x23, /* length */\n            0x78, 0x01, 0xfa, 0xcf, 0x00, 0x04, 0xff, 0x61, 0x04, 0x90, 0x01, 0x41, 0x50, 0xc1, 0xff, 0x0c,\n            0xef, 0x40, 0x02, 0xef, 0xfe, 0x33, 0xac, 0x02, 0xe2, 0xd5, 0x40, 0x8c, 0xce, 0x07, 0x00, 0x00,\n            0x00, 0xff, 0xff,\n        ]);\n        done = testDecodeRect(decoder, 0, 0, 4, 4, zlibData, display, 24);\n        expect(done).to.be.true;\n\n        let targetData = new Uint8ClampedArray([\n            0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255,\n            0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255\n        ]);\n\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle empty rects', function () {\n        display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);\n        display.fillRect(2, 0, 2, 2, [0x00, 0xff, 0x00]);\n        display.fillRect(0, 2, 2, 2, [0x00, 0xff, 0x00]);\n\n        let done = testDecodeRect(decoder, 1, 2, 0, 0, [], display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,\n            0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/test.zrle.js",
    "content": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport ZRLEDecoder from '../core/decoders/zrle.js';\n\nimport FakeWebSocket from './fake.websocket.js';\n\nfunction testDecodeRect(decoder, x, y, width, height, data, display, depth) {\n    let sock;\n    let done = false;\n\n    sock = new Websock;\n    sock.open(\"ws://example.com\");\n\n    sock.on('message', () => {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    });\n\n    // Empty messages are filtered at multiple layers, so we need to\n    // do a direct call\n    if (data.length === 0) {\n        done = decoder.decodeRect(x, y, width, height, sock, display, depth);\n    } else {\n        sock._websocket._receiveData(new Uint8Array(data));\n    }\n\n    display.flip();\n\n    return done;\n}\n\ndescribe('ZRLE decoder', function () {\n    let decoder;\n    let display;\n\n    before(FakeWebSocket.replace);\n    after(FakeWebSocket.restore);\n\n    beforeEach(function () {\n        decoder = new ZRLEDecoder();\n        display = new Display(document.createElement('canvas'));\n        display.resize(4, 4);\n    });\n\n    it('should handle the Raw subencoding', function () {\n        let done = testDecodeRect(decoder, 0, 0, 4, 4,\n                                  [0x00, 0x00, 0x00, 0x0e, 0x78, 0x5e,\n                                   0x62, 0x60, 0x60, 0xf8, 0x4f, 0x12,\n                                   0x02, 0x00, 0x00, 0x00, 0xff, 0xff],\n                                  display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle the Solid subencoding', function () {\n        let done = testDecodeRect(decoder, 0, 0, 4, 4,\n                                  [0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e,\n                                   0x62, 0x64, 0x60, 0xf8, 0x0f, 0x00,\n                                   0x00, 0x00, 0xff, 0xff],\n                                  display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n\n    it('should handle the Palette Tile subencoding', function () {\n        let done = testDecodeRect(decoder, 0, 0, 4, 4,\n                                  [0x00, 0x00, 0x00, 0x12, 0x78, 0x5E,\n                                   0x62, 0x62, 0x60,  248, 0xff, 0x9F,\n                                   0x01, 0x08, 0x3E, 0x7C, 0x00, 0x00,\n                                   0x00, 0x00, 0xff, 0xff],\n                                  display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,\n            0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle the RLE Tile subencoding', function () {\n        let done = testDecodeRect(decoder, 0, 0, 4, 4,\n                                  [0x00, 0x00, 0x00, 0x0d, 0x78, 0x5e,\n                                   0x6a, 0x60, 0x60, 0xf8, 0x2f, 0x00,\n                                   0x00, 0x00, 0x00, 0xff, 0xff],\n                                  display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should handle the RLE Palette Tile subencoding', function () {\n        let done = testDecodeRect(decoder, 0, 0, 4, 4,\n                                  [0x00, 0x00, 0x00, 0x11, 0x78, 0x5e,\n                                   0x6a, 0x62, 0x60, 0xf8, 0xff, 0x9f,\n                                   0x81, 0xa1, 0x81, 0x1f, 0x00, 0x00,\n                                   0x00, 0xff, 0xff],\n                                  display, 24);\n\n        let targetData = new Uint8Array([\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,\n            0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff\n        ]);\n\n        expect(done).to.be.true;\n        expect(display).to.have.displayed(targetData);\n    });\n\n    it('should fail on an invalid subencoding', function () {\n        let data = [0x00, 0x00, 0x00, 0x0c, 0x78, 0x5e, 0x6a, 0x64, 0x60, 0xf8, 0x0f, 0x00, 0x00, 0x00, 0xff, 0xff];\n        expect(() => testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24)).to.throw();\n    });\n});\n"
  },
  {
    "path": "services/gateway/noVNC/tests/vnc_playback.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <title>VNC playback</title>\n        <script type=\"module\" src=\"./playback-ui.js\"></script>\n    </head>\n    <body>\n\n        Iterations: <input id='iterations'>&nbsp;\n        Perftest:<input type='radio' id='mode1' name='mode' checked>&nbsp;\n        Realtime:<input type='radio' id='mode2' name='mode'>&nbsp;&nbsp;\n\n        <input id='startButton' type='button' value='Start' disabled>&nbsp;\n\n        <br><br>\n\n        Results:<br>\n        <textarea id=\"messages\" cols=80 rows=25></textarea>\n\n        <br><br>\n\n        <div id=\"VNC_screen\">\n            <div id=\"VNC_status\">Loading</div>\n        </div>\n    </body>\n</html>\n"
  },
  {
    "path": "services/gateway/noVNC/utils/README.md",
    "content": "## WebSockets Proxy/Bridge\n\nWebsockify has been forked out into its own project. `novnc_proxy` will\nautomatically download it here if it is not already present and not\ninstalled as system-wide.\n\nFor more detailed description and usage information please refer to\nthe [websockify README](https://github.com/novnc/websockify/blob/master/README.md).\n\nThe other versions of websockify (C, Node.js) and the associated test\nprograms have been moved to\n[websockify](https://github.com/novnc/websockify).  Websockify was\nformerly named wsproxy.\n\n"
  },
  {
    "path": "services/gateway/noVNC/utils/b64-to-binary.pl",
    "content": "#!/usr/bin/env perl\nuse MIME::Base64;\n\nfor (<>) {\n    unless (/^'([{}])(\\d+)\\1(.+?)',$/) {\n        print;\n        next;\n    }\n\n    my ($dir, $amt, $b64) = ($1, $2, $3);\n\n    my $decoded = MIME::Base64::decode($b64) or die \"Could not base64-decode line `$_`\";\n\n    my $decoded_escaped = join \"\", map { \"\\\\x$_\" } unpack(\"(H2)*\", $decoded);\n\n    print \"'${dir}${amt}${dir}${decoded_escaped}',\\n\";\n}\n"
  },
  {
    "path": "services/gateway/noVNC/utils/convert.js",
    "content": "#!/usr/bin/env node\n\nconst path = require('path');\nconst { program } = require('commander');\nconst fs = require('fs');\nconst fse = require('fs-extra');\nconst babel = require('@babel/core');\n\nprogram\n    .option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')\n    .option('--clean', 'clear the lib folder before building')\n    .parse(process.argv);\n\n// the various important paths\nconst paths = {\n    main: path.resolve(__dirname, '..'),\n    core: path.resolve(__dirname, '..', 'core'),\n    vendor: path.resolve(__dirname, '..', 'vendor'),\n    libDirBase: path.resolve(__dirname, '..', 'lib'),\n};\n\n// util.promisify requires Node.js 8.x, so we have our own\nfunction promisify(original) {\n    return function promiseWrap() {\n        const args = Array.prototype.slice.call(arguments);\n        return new Promise((resolve, reject) => {\n            original.apply(this, args.concat((err, value) => {\n                if (err) return reject(err);\n                resolve(value);\n            }));\n        });\n    };\n}\n\nconst writeFile = promisify(fs.writeFile);\n\nconst readdir = promisify(fs.readdir);\nconst lstat = promisify(fs.lstat);\n\nconst ensureDir = promisify(fse.ensureDir);\n\nconst babelTransformFile = promisify(babel.transformFile);\n\n// walkDir *recursively* walks directories trees,\n// calling the callback for all normal files found.\nfunction walkDir(basePath, cb, filter) {\n    return readdir(basePath)\n        .then((files) => {\n            const paths = files.map(filename => path.join(basePath, filename));\n            return Promise.all(paths.map(filepath => lstat(filepath)\n                .then((stats) => {\n                    if (filter !== undefined && !filter(filepath, stats)) return;\n\n                    if (stats.isSymbolicLink()) return;\n                    if (stats.isFile()) return cb(filepath);\n                    if (stats.isDirectory()) return walkDir(filepath, cb, filter);\n                })));\n        });\n}\n\nfunction makeLibFiles(sourceMaps) {\n    // NB: we need to make a copy of babelOpts, since babel sets some defaults on it\n    const babelOpts = () => ({\n        plugins: [],\n        presets: [\n            [ '@babel/preset-env',\n              { modules: 'commonjs' } ]\n        ],\n        ast: false,\n        sourceMaps: sourceMaps,\n    });\n\n    fse.ensureDirSync(paths.libDirBase);\n\n    const outFiles = [];\n\n    const handleDir = (vendorRewrite, inPathBase, filename) => Promise.resolve()\n        .then(() => {\n            const outPath = path.join(paths.libDirBase, path.relative(inPathBase, filename));\n\n            if (path.extname(filename) !== '.js') {\n                return;  // skip non-javascript files\n            }\n            return Promise.resolve()\n                .then(() => ensureDir(path.dirname(outPath)))\n                .then(() => {\n                    const opts = babelOpts();\n            // Adjust for the fact that we move the core files relative\n            // to the vendor directory\n                    if (vendorRewrite) {\n                        opts.plugins.push([\"import-redirect\",\n                                           {\"root\": paths.libDirBase,\n                                            \"redirect\": { \"vendor/(.+)\": \"./vendor/$1\"}}]);\n                    }\n\n                    return babelTransformFile(filename, opts)\n                        .then((res) => {\n                            console.log(`Writing ${outPath}`);\n                            const {map} = res;\n                            let {code} = res;\n                            if (sourceMaps === true) {\n                    // append URL for external source map\n                                code += `\\n//# sourceMappingURL=${path.basename(outPath)}.map\\n`;\n                            }\n                            outFiles.push(`${outPath}`);\n                            return writeFile(outPath, code)\n                                .then(() => {\n                                    if (sourceMaps === true || sourceMaps === 'both') {\n                                        console.log(`  and ${outPath}.map`);\n                                        outFiles.push(`${outPath}.map`);\n                                        return writeFile(`${outPath}.map`, JSON.stringify(map));\n                                    }\n                                });\n                        });\n                });\n        });\n\n    Promise.resolve()\n        .then(() => {\n            const handler = handleDir.bind(null, false, paths.main);\n            return walkDir(paths.vendor, handler);\n        })\n        .then(() => {\n            const handler = handleDir.bind(null, true, paths.core);\n            return walkDir(paths.core, handler);\n        })\n        .catch((err) => {\n            console.error(`Failure converting modules: ${err}`);\n            process.exit(1);\n        });\n}\n\nlet options = program.opts();\n\nif (options.clean) {\n    console.log(`Removing ${paths.libDirBase}`);\n    fse.removeSync(paths.libDirBase);\n}\n\nmakeLibFiles(options.withSourceMaps);\n"
  },
  {
    "path": "services/gateway/noVNC/utils/genkeysymdef.js",
    "content": "#!/usr/bin/env node\n/*\n * genkeysymdef: X11 keysymdef.h to JavaScript converter\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n */\n\n\"use strict\";\n\nconst fs = require('fs');\n\nlet showHelp = process.argv.length === 2;\nlet filename;\n\nfor (let i = 2; i < process.argv.length; ++i) {\n    switch (process.argv[i]) {\n        case \"--help\":\n        case \"-h\":\n            showHelp = true;\n            break;\n        case \"--file\":\n        case \"-f\":\n        default:\n            filename = process.argv[i];\n    }\n}\n\nif (!filename) {\n    showHelp = true;\n    console.log(\"Error: No filename specified\\n\");\n}\n\nif (showHelp) {\n    console.log(\"Parses a *nix keysymdef.h to generate Unicode code point mappings\");\n    console.log(\"Usage: node parse.js [options] filename:\");\n    console.log(\"  -h [ --help ]                 Produce this help message\");\n    console.log(\"  filename                      The keysymdef.h file to parse\");\n    process.exit(0);\n}\n\nconst buf = fs.readFileSync(filename);\nconst str = buf.toString('utf8');\n\nconst re = /^#define XK_([a-zA-Z_0-9]+)\\s+0x([0-9a-fA-F]+)\\s*(\\/\\*\\s*(.*)\\s*\\*\\/)?\\s*$/m;\n\nconst arr = str.split('\\n');\n\nconst codepoints = {};\n\nfor (let i = 0; i < arr.length; ++i) {\n    const result = re.exec(arr[i]);\n    if (result) {\n        const keyname = result[1];\n        const keysym = parseInt(result[2], 16);\n        const remainder = result[3];\n\n        const unicodeRes = /U\\+([0-9a-fA-F]+)/.exec(remainder);\n        if (unicodeRes) {\n            const unicode = parseInt(unicodeRes[1], 16);\n            // The first entry is the preferred one\n            if (!codepoints[unicode]) {\n                codepoints[unicode] = { keysym: keysym, name: keyname };\n            }\n        }\n    }\n}\n\nlet out =\n\"/*\\n\" +\n\" * Mapping from Unicode codepoints to X11/RFB keysyms\\n\" +\n\" *\\n\" +\n\" * This file was automatically generated from keysymdef.h\\n\" +\n\" * DO NOT EDIT!\\n\" +\n\" */\\n\" +\n\"\\n\" +\n\"/* Functions at the bottom */\\n\" +\n\"\\n\" +\n\"const codepoints = {\\n\";\n\nfunction toHex(num) {\n    let s = num.toString(16);\n    if (s.length < 4) {\n        s = (\"0000\" + s).slice(-4);\n    }\n    return \"0x\" + s;\n}\n\nfor (let codepoint in codepoints) {\n    codepoint = parseInt(codepoint);\n\n    // Latin-1?\n    if ((codepoint >= 0x20) && (codepoint <= 0xff)) {\n        continue;\n    }\n\n    // Handled by the general Unicode mapping?\n    if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) {\n        continue;\n    }\n\n    out += \"    \" + toHex(codepoint) + \": \" +\n           toHex(codepoints[codepoint].keysym) +\n           \", // XK_\" + codepoints[codepoint].name + \"\\n\";\n}\n\nout +=\n\"};\\n\" +\n\"\\n\" +\n\"export default {\\n\" +\n\"    lookup(u) {\\n\" +\n\"        // Latin-1 is one-to-one mapping\\n\" +\n\"        if ((u >= 0x20) && (u <= 0xff)) {\\n\" +\n\"            return u;\\n\" +\n\"        }\\n\" +\n\"\\n\" +\n\"        // Lookup table (fairly random)\\n\" +\n\"        const keysym = codepoints[u];\\n\" +\n\"        if (keysym !== undefined) {\\n\" +\n\"            return keysym;\\n\" +\n\"        }\\n\" +\n\"\\n\" +\n\"        // General mapping as final fallback\\n\" +\n\"        return 0x01000000 | u;\\n\" +\n\"    },\\n\" +\n\"};\";\n\nconsole.log(out);\n"
  },
  {
    "path": "services/gateway/noVNC/utils/novnc_proxy",
    "content": "#!/usr/bin/env bash\n\n# Copyright (C) 2018 The noVNC authors\n# Licensed under MPL 2.0 or any later version (see LICENSE.txt)\n\nusage() {\n    if [ \"$*\" ]; then\n        echo \"$*\"\n        echo\n    fi\n    echo \"Usage: ${NAME} [--listen [HOST:]PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]\"\n    echo\n    echo \"Starts the WebSockets proxy and a mini-webserver and \"\n    echo \"provides a cut-and-paste URL to go to.\"\n    echo\n    echo \"    --listen [HOST:]PORT  Port for proxy/webserver to listen on\"\n    echo \"                          Default: 6080 (on all interfaces)\"\n    echo \"    --vnc VNC_HOST:PORT   VNC server host:port proxy target\"\n    echo \"                          Default: localhost:5900\"\n    echo \"    --cert CERT           Path to combined cert/key file, or just\"\n    echo \"                          the cert file if used with --key\"\n    echo \"                          Default: self.pem\"\n    echo \"    --key KEY             Path to key file, when not combined with cert\"\n    echo \"    --web WEB             Path to web files (e.g. vnc.html)\"\n    echo \"                          Default: ./\"\n    echo \"    --ssl-only            Disable non-https connections.\"\n    echo \"                                    \"\n    echo \"    --file-only           Disable directory listing in web server.\"\n    echo \"                                    \"\n    echo \"    --record FILE         Record traffic to FILE.session.js\"\n    echo \"                                    \"\n    echo \"    --syslog SERVER       Can be local socket such as /dev/log, or a UDP host:port pair.\"\n    echo \"                                    \"\n    echo \"    --heartbeat SEC       send a ping to the client every SEC seconds\"\n    echo \"    --timeout SEC         after SEC seconds exit when not connected\"\n    echo \"    --idle-timeout SEC    server exits after SEC seconds if there are no\"\n    echo \"                                    \"\n    echo \"    --web-auth            enable authentication\"\n    echo \"    --auth-plugin CLASS   authentication plugin to use\"\n    echo \"    --auth-source ARG     plugin configuration\"\n    echo \"                                    \"\n    echo \"                          active connections\"\n    echo \"                                    \"\n    exit 2\n}\n\nNAME=\"$(basename $0)\"\nREAL_NAME=\"$(readlink -f $0)\"\nHERE=\"$(cd \"$(dirname \"$REAL_NAME\")\" && pwd)\"\nHOST=\"\"\nPORT=\"6080\"\nLISTEN=\"$PORT\"\nVNC_DEST=\"localhost:5900\"\nCERT=\"\"\nKEY=\"\"\nWEB=\"\"\nproxy_pid=\"\"\nSSLONLY=\"\"\nRECORD=\"\"\nSYSLOG_ARG=\"\"\nHEARTBEAT_ARG=\"\"\nIDLETIMEOUT_ARG=\"\"\nTIMEOUT_ARG=\"\"\nWEBAUTH_ARG=\"\"\nAUTHPLUGIN_ARG=\"\"\nAUTHSOURCE_ARG=\"\"\nFILEONLY_ARG=\"\"\n\n\ndie() {\n    echo \"$*\"\n    exit 1\n}\n\ncleanup() {\n    trap - TERM QUIT INT EXIT\n    trap \"true\" CHLD   # Ignore cleanup messages\n    echo\n    if [ -n \"${proxy_pid}\" ]; then\n        echo \"Terminating WebSockets proxy (${proxy_pid})\"\n        kill ${proxy_pid}\n    fi\n}\n\n# Process arguments\n\n# Arguments that only apply to chrooter itself\nwhile [ \"$*\" ]; do\n    param=$1; shift; OPTARG=$1\n    case $param in\n    --listen)  LISTEN=\"${OPTARG}\"; shift            ;;\n    --vnc)     VNC_DEST=\"${OPTARG}\"; shift        ;;\n    --cert)    CERT=\"${OPTARG}\"; shift            ;;\n    --key)     KEY=\"${OPTARG}\"; shift             ;;\n    --web)     WEB=\"${OPTARG}\"; shift            ;;\n    --ssl-only) SSLONLY=\"--ssl-only\"             ;;\n    --file-only) FILEONLY_ARG=\"--file-only\"      ;;\n    --record) RECORD=\"${OPTARG}\"; shift ;;\n    --syslog) SYSLOG_ARG=\"--syslog ${OPTARG}\"; shift ;;\n    --heartbeat) HEARTBEAT_ARG=\"--heartbeat ${OPTARG}\"; shift ;;\n    --idle-timeout) IDLETIMEOUT_ARG=\"--idle-timeout ${OPTARG}\"; shift ;;\n    --timeout) TIMEOUT_ARG=\"--timeout ${OPTARG}\"; shift ;;\n    --web-auth) WEBAUTH_ARG=\"--web-auth\"                ;;\n    --auth-plugin) AUTHPLUGIN_ARG=\"--auth-plugin ${OPTARG}\"; shift ;;\n    --auth-source) AUTHSOURCE_ARG=\"--auth-source ${OPTARG}\"; shift ;;\n    -h|--help) usage                              ;;\n    -*) usage \"Unknown chrooter option: ${param}\" ;;\n    *) break                                      ;;\n    esac\ndone\n\nif [ \"$LISTEN\" != \"$PORT\" ]; then\n    HOST=${LISTEN%:*}\n    PORT=${LISTEN##*:}\n    # if no host was given, restore\n    [ \"$HOST\" = \"$PORT\" ] && HOST=\"\"\nfi\n\n# Sanity checks\nif [ -z \"${HOST}\" ]; then\n    if bash -c \"exec 7<>/dev/tcp/localhost/${PORT}\" &> /dev/null; then\n        exec 7<&-\n        exec 7>&-\n        die \"Port ${PORT} in use. Try --listen PORT\"\n    else\n        exec 7<&-\n        exec 7>&-\n    fi\nfi\n\ntrap \"cleanup\" TERM QUIT INT EXIT\n\n# Find vnc.html\nif [ -n \"${WEB}\" ]; then\n    if [ ! -e \"${WEB}/vnc.html\" ]; then\n        die \"Could not find ${WEB}/vnc.html\"\n    fi\nelif [ -e \"$(pwd)/vnc.html\" ]; then\n    WEB=$(pwd)\nelif [ -e \"${HERE}/../vnc.html\" ]; then\n    WEB=${HERE}/../\nelif [ -e \"${HERE}/vnc.html\" ]; then\n    WEB=${HERE}\nelif [ -e \"${HERE}/../share/novnc/vnc.html\" ]; then\n    WEB=${HERE}/../share/novnc/\nelse\n    die \"Could not find vnc.html\"\nfi\n\n# Find self.pem\nif [ -n \"${CERT}\" ]; then\n    if [ ! -e \"${CERT}\" ]; then\n        die \"Could not find ${CERT}\"\n    fi\nelif [ -e \"$(pwd)/self.pem\" ]; then\n    CERT=\"$(pwd)/self.pem\"\nelif [ -e \"${HERE}/../self.pem\" ]; then\n    CERT=\"${HERE}/../self.pem\"\nelif [ -e \"${HERE}/self.pem\" ]; then\n    CERT=\"${HERE}/self.pem\"\nelse\n    echo \"Warning: could not find self.pem\"\nfi\n\n# Check key file\nif [ -n \"${KEY}\" ]; then\n    if [ ! -e \"${KEY}\" ]; then\n        die \"Could not find ${KEY}\"\n    fi\nfi\n\n# try to find websockify (prefer local, try global, then download local)\nif [[ -d ${HERE}/websockify ]]; then\n    WEBSOCKIFY=${HERE}/websockify/run\n\n    if [[ ! -x $WEBSOCKIFY ]]; then\n        echo \"The path ${HERE}/websockify exists, but $WEBSOCKIFY either does not exist or is not executable.\"\n        echo \"If you intended to use an installed websockify package, please remove ${HERE}/websockify.\"\n        exit 1\n    fi\n\n    echo \"Using local websockify at $WEBSOCKIFY\"\nelse\n    WEBSOCKIFY_FROMSYSTEM=$(which websockify 2>/dev/null)\n    WEBSOCKIFY_FROMSNAP=${HERE}/../usr/bin/python2-websockify\n    [ -f $WEBSOCKIFY_FROMSYSTEM ] && WEBSOCKIFY=$WEBSOCKIFY_FROMSYSTEM\n    [ -f $WEBSOCKIFY_FROMSNAP ] && WEBSOCKIFY=$WEBSOCKIFY_FROMSNAP\n\n    if [ ! -f \"$WEBSOCKIFY\" ]; then\n        echo \"No installed websockify, attempting to clone websockify...\"\n        WEBSOCKIFY=${HERE}/websockify/run\n        git clone https://github.com/novnc/websockify ${HERE}/websockify\n\n        if [[ ! -e $WEBSOCKIFY ]]; then\n            echo \"Unable to locate ${HERE}/websockify/run after downloading\"\n            exit 1\n        fi\n\n        echo \"Using local websockify at $WEBSOCKIFY\"\n    else\n        echo \"Using installed websockify at $WEBSOCKIFY\"\n    fi\nfi\n\n# Make all file paths absolute as websockify changes working directory\nWEB=`realpath \"${WEB}\"`\n[ -n \"${CERT}\" ] && CERT=`realpath \"${CERT}\"`\n[ -n \"${KEY}\" ] && KEY=`realpath \"${KEY}\"`\n[ -n \"${RECORD}\" ] && RECORD=`realpath \"${RECORD}\"`\n\necho \"Starting webserver and WebSockets proxy on${HOST:+ host ${HOST}} port ${PORT}\"\n${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${LISTEN} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD:+--record ${RECORD}} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} &\nproxy_pid=\"$!\"\nsleep 1\nif [ -z \"$proxy_pid\" ] || ! ps -eo pid= | grep -w \"$proxy_pid\" > /dev/null; then\n    proxy_pid=\n    echo \"Failed to start WebSockets proxy\"\n    exit 1\nfi\n\nif [ -z \"$HOST\" ]; then\n    HOST=$(hostname)\nfi\n\necho -e \"\\n\\nNavigate to this URL:\\n\"\nif [ \"x$SSLONLY\" == \"x\" ]; then\n    echo -e \"    http://${HOST}:${PORT}/vnc.html?host=${HOST}&port=${PORT}\\n\"\nelse\n    echo -e \"    https://${HOST}:${PORT}/vnc.html?host=${HOST}&port=${PORT}\\n\"\nfi\n\necho -e \"Press Ctrl-C to exit\\n\\n\"\n\nwait ${proxy_pid}\n"
  },
  {
    "path": "services/gateway/noVNC/utils/u2x11",
    "content": "#!/usr/bin/env bash\n#\n# Convert \"U+...\" commented entries in /usr/include/X11/keysymdef.h\n# into JavaScript for use by noVNC.  Note this is likely to produce\n# a few duplicate properties with clashing values, that will need\n# resolving manually.\n#\n# Colin Dean <colin@xvpsource.org>\n#\n\nregex=\"^#define[ \\t]+XK_[A-Za-z0-9_]+[ \\t]+0x([0-9a-fA-F]+)[ \\t]+\\/\\*[ \\t]+U\\+([0-9a-fA-F]+)[ \\t]+[^*]+.[ \\t]+\\*\\/[ \\t]*$\"\necho \"unicodeTable = {\"\nwhile read line; do\n    if echo \"${line}\" | egrep -qs \"${regex}\"; then\n\n        x11=$(echo \"${line}\" | sed -r \"s/${regex}/\\1/\")\n        vnc=$(echo \"${line}\" | sed -r \"s/${regex}/\\2/\")\n\t\n\tif echo \"${vnc}\" | egrep -qs \"^00[2-9A-F][0-9A-F]$\"; then\n\t    : # skip ISO Latin-1 (U+0020 to U+00FF) as 1-to-1 mapping\n\telse\n\t    # note 1-to-1 is possible (e.g. for Euro symbol, U+20AC)\n\t    echo \"    0x${vnc} : 0x${x11},\"\n\tfi\n    fi\ndone < /usr/include/X11/keysymdef.h | uniq\necho \"};\"\n\n"
  },
  {
    "path": "services/gateway/noVNC/utils/validate",
    "content": "#!/bin/bash\n\nset -e\n\nRET=0\n\nOUT=`mktemp`\n\nfor fn in \"$@\"; do\n\techo \"Validating $fn...\"\n\techo\n\n\tcase $fn in\n\t\t*.html)\n\t\t\ttype=\"text/html\"\n\t\t\t;;\n\t\t*.css)\n\t\t\ttype=\"text/css\"\n\t\t\t;;\n\t\t*)\n\t\t\techo \"Unknown format!\"\n\t\t\techo\n\t\t\tRET=1\n\t\t\tcontinue\n\t\t\t;;\n\tesac\n\n\tcurl --silent \\\n\t\t--header \"Content-Type: ${type}; charset=utf-8\" \\\n\t\t--data-binary @${fn} \\\n\t\t\"https://validator.w3.org/nu/?out=gnu&level=error&asciiquotes=yes\" \\\n\t\t> $OUT\n\n\t# We don't fail the check for warnings as some warnings are\n\t# not relevant for us, and we don't currently have a way to\n\t# ignore just those\n\twhile read -r line; do\n\t\techo\n\n\t\tline_info=$(echo $line | cut -d \":\" -f 2)\n\t\tstart_info=$(echo $line_info | cut -d \"-\" -f 1)\n\t\tend_info=$(echo $line_info | cut -d \"-\" -f 2)\n\n\t\tline_start=$(echo $start_info | cut -d \".\" -f 1)\n\t\tcol_start=$(echo $start_info | cut -d \".\" -f 2)\n\n\t\tline_end=$(echo $end_info | cut -d \".\" -f 1)\n\t\tcol_end=$(echo $end_info | cut -d \".\" -f 2)\n\n\t\terror=$(echo $line | cut -d \":\" -f 4-)\n\n\t\tcase $error in\n\t\t\t*\"\\\"scrollbar-gutter\\\": Property \\\"scrollbar-gutter\\\" doesn't exist.\")\n\t\t\t\t# FIXME: https://github.com/validator/validator/issues/1788\n\t\t\t\techo \"Ignoring below error on line ${line_start},\" \\\n\t\t\t\t     \"the scrollbar-gutter property actually exist and is widely\" \\\n\t\t\t\t     \"supported:\"\n\t\t\t\techo $error\n\t\t\t\tcontinue\n\t\t\t\t;;\n\t\t\t*\"\\\"clip-path\\\": \\\"path(\"*)\n\t\t\t\t# FIXME: https://github.com/validator/validator/issues/1786\n\t\t\t\techo \"Ignoring below error on line ${line_start},\" \\\n\t\t\t\t     \"the path() function is valid for clip-path and is\" \\\n\t\t\t\t     \"widely supported:\"\n\t\t\t\techo $error\n\t\t\t\tcontinue\n\t\t\t\t;;\n\t\t\t*\"Parse Error.\")\n\t\t\t\t# FIXME: https://github.com/validator/validator/issues/1786\n\t\t\t\tlineofselector=$(grep -n \"@supports selector(\" $fn | cut -d \":\" -f 1)\n\t\t\t\tlinediff=$((lineofselector-line_start))\n\t\t\t\t# Only ignore if parse error is within 50 lines of \"selector()\"\n\t\t\t\tif [ ${linediff#-} -lt 50 ]; then\n\t\t\t\t\techo \"Ignoring below error on line ${line_start},\" \\\n\t\t\t\t\t     \"the @supports selector() function should not give a parse\" \\\n\t\t\t\t\t     \"error:\"\n\t\t\t\t\techo $error\n\t\t\t\t\tcontinue\n\t\t\t\tfi\n\t\t\t\t;;\n\t\tesac\n\t\techo \"ERROR between line ${line_start} (col ${col_start})\" \\\n\t\t     \"and line ${line_end} (col ${col_end}):\"\n\t\techo $error\n\t\tRET=1\n\tdone < \"$OUT\"\ndone\n\nrm $OUT\n\nexit $RET\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/LICENSE",
    "content": "(The MIT License)\n\nCopyright (C) 2014-2016 by Vitaly Puzrin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/README.md",
    "content": "This is an ES6-modules-compatible version of\nhttps://github.com/nodeca/pako, based on pako version 1.0.3.\n\nIt's more-or-less a direct translation of the original, with unused parts\nremoved, and the dynamic support for non-typed arrays removed (since ES6\nmodules don't work well with dynamic exports).\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/utils/common.js",
    "content": "// reduce buffer size, avoiding mem copy\nexport function shrinkBuf (buf, size) {\n  if (buf.length === size) { return buf; }\n  if (buf.subarray) { return buf.subarray(0, size); }\n  buf.length = size;\n  return buf;\n};\n\n\nexport function arraySet (dest, src, src_offs, len, dest_offs) {\n  if (src.subarray && dest.subarray) {\n    dest.set(src.subarray(src_offs, src_offs + len), dest_offs);\n    return;\n  }\n  // Fallback to ordinary array\n  for (var i = 0; i < len; i++) {\n    dest[dest_offs + i] = src[src_offs + i];\n  }\n}\n\n// Join array of chunks to single array.\nexport function flattenChunks (chunks) {\n  var i, l, len, pos, chunk, result;\n\n  // calculate data length\n  len = 0;\n  for (i = 0, l = chunks.length; i < l; i++) {\n    len += chunks[i].length;\n  }\n\n  // join chunks\n  result = new Uint8Array(len);\n  pos = 0;\n  for (i = 0, l = chunks.length; i < l; i++) {\n    chunk = chunks[i];\n    result.set(chunk, pos);\n    pos += chunk.length;\n  }\n\n  return result;\n}\n\nexport var Buf8  = Uint8Array;\nexport var Buf16 = Uint16Array;\nexport var Buf32 = Int32Array;\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/adler32.js",
    "content": "// Note: adler32 takes 12% for level 0 and 2% for level 6.\n// It doesn't worth to make additional optimizationa as in original.\n// Small size is preferable.\n\nexport default function adler32(adler, buf, len, pos) {\n  var s1 = (adler & 0xffff) |0,\n      s2 = ((adler >>> 16) & 0xffff) |0,\n      n = 0;\n\n  while (len !== 0) {\n    // Set limit ~ twice less than 5552, to keep\n    // s2 in 31-bits, because we force signed ints.\n    // in other case %= will fail.\n    n = len > 2000 ? 2000 : len;\n    len -= n;\n\n    do {\n      s1 = (s1 + buf[pos++]) |0;\n      s2 = (s2 + s1) |0;\n    } while (--n);\n\n    s1 %= 65521;\n    s2 %= 65521;\n  }\n\n  return (s1 | (s2 << 16)) |0;\n}\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/constants.js",
    "content": "export default {\n\n  /* Allowed flush values; see deflate() and inflate() below for details */\n  Z_NO_FLUSH:         0,\n  Z_PARTIAL_FLUSH:    1,\n  Z_SYNC_FLUSH:       2,\n  Z_FULL_FLUSH:       3,\n  Z_FINISH:           4,\n  Z_BLOCK:            5,\n  Z_TREES:            6,\n\n  /* Return codes for the compression/decompression functions. Negative values\n  * are errors, positive values are used for special but normal events.\n  */\n  Z_OK:               0,\n  Z_STREAM_END:       1,\n  Z_NEED_DICT:        2,\n  Z_ERRNO:           -1,\n  Z_STREAM_ERROR:    -2,\n  Z_DATA_ERROR:      -3,\n  //Z_MEM_ERROR:     -4,\n  Z_BUF_ERROR:       -5,\n  //Z_VERSION_ERROR: -6,\n\n  /* compression levels */\n  Z_NO_COMPRESSION:         0,\n  Z_BEST_SPEED:             1,\n  Z_BEST_COMPRESSION:       9,\n  Z_DEFAULT_COMPRESSION:   -1,\n\n\n  Z_FILTERED:               1,\n  Z_HUFFMAN_ONLY:           2,\n  Z_RLE:                    3,\n  Z_FIXED:                  4,\n  Z_DEFAULT_STRATEGY:       0,\n\n  /* Possible values of the data_type field (though see inflate()) */\n  Z_BINARY:                 0,\n  Z_TEXT:                   1,\n  //Z_ASCII:                1, // = Z_TEXT (deprecated)\n  Z_UNKNOWN:                2,\n\n  /* The deflate compression method */\n  Z_DEFLATED:               8\n  //Z_NULL:                 null // Use -1 or null inline, depending on var type\n};\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/crc32.js",
    "content": "// Note: we can't get significant speed boost here.\n// So write code to minimize size - no pregenerated tables\n// and array tools dependencies.\n\n\n// Use ordinary array, since untyped makes no boost here\nexport default function makeTable() {\n  var c, table = [];\n\n  for (var n = 0; n < 256; n++) {\n    c = n;\n    for (var k = 0; k < 8; k++) {\n      c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));\n    }\n    table[n] = c;\n  }\n\n  return table;\n}\n\n// Create table on load. Just 255 signed longs. Not a problem.\nvar crcTable = makeTable();\n\n\nfunction crc32(crc, buf, len, pos) {\n  var t = crcTable,\n      end = pos + len;\n\n  crc ^= -1;\n\n  for (var i = pos; i < end; i++) {\n    crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];\n  }\n\n  return (crc ^ (-1)); // >>> 0;\n}\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/deflate.js",
    "content": "import * as utils from \"../utils/common.js\";\nimport * as trees from \"./trees.js\";\nimport adler32 from \"./adler32.js\";\nimport crc32 from \"./crc32.js\";\nimport msg from \"./messages.js\";\n\n/* Public constants ==========================================================*/\n/* ===========================================================================*/\n\n\n/* Allowed flush values; see deflate() and inflate() below for details */\nexport const Z_NO_FLUSH      = 0;\nexport const Z_PARTIAL_FLUSH = 1;\n//export const Z_SYNC_FLUSH    = 2;\nexport const Z_FULL_FLUSH    = 3;\nexport const Z_FINISH        = 4;\nexport const Z_BLOCK         = 5;\n//export const Z_TREES         = 6;\n\n\n/* Return codes for the compression/decompression functions. Negative values\n * are errors, positive values are used for special but normal events.\n */\nexport const Z_OK            = 0;\nexport const Z_STREAM_END    = 1;\n//export const Z_NEED_DICT     = 2;\n//export const Z_ERRNO         = -1;\nexport const Z_STREAM_ERROR  = -2;\nexport const Z_DATA_ERROR    = -3;\n//export const Z_MEM_ERROR     = -4;\nexport const Z_BUF_ERROR     = -5;\n//export const Z_VERSION_ERROR = -6;\n\n\n/* compression levels */\n//export const Z_NO_COMPRESSION      = 0;\n//export const Z_BEST_SPEED          = 1;\n//export const Z_BEST_COMPRESSION    = 9;\nexport const Z_DEFAULT_COMPRESSION = -1;\n\n\nexport const Z_FILTERED            = 1;\nexport const Z_HUFFMAN_ONLY        = 2;\nexport const Z_RLE                 = 3;\nexport const Z_FIXED               = 4;\nexport const Z_DEFAULT_STRATEGY    = 0;\n\n/* Possible values of the data_type field (though see inflate()) */\n//export const Z_BINARY              = 0;\n//export const Z_TEXT                = 1;\n//export const Z_ASCII               = 1; // = Z_TEXT\nexport const Z_UNKNOWN             = 2;\n\n\n/* The deflate compression method */\nexport const Z_DEFLATED  = 8;\n\n/*============================================================================*/\n\n\nvar MAX_MEM_LEVEL = 9;\n/* Maximum value for memLevel in deflateInit2 */\nvar MAX_WBITS = 15;\n/* 32K LZ77 window */\nvar DEF_MEM_LEVEL = 8;\n\n\nvar LENGTH_CODES  = 29;\n/* number of length codes, not counting the special END_BLOCK code */\nvar LITERALS      = 256;\n/* number of literal bytes 0..255 */\nvar L_CODES       = LITERALS + 1 + LENGTH_CODES;\n/* number of Literal or Length codes, including the END_BLOCK code */\nvar D_CODES       = 30;\n/* number of distance codes */\nvar BL_CODES      = 19;\n/* number of codes used to transfer the bit lengths */\nvar HEAP_SIZE     = 2 * L_CODES + 1;\n/* maximum heap size */\nvar MAX_BITS  = 15;\n/* All codes must not exceed MAX_BITS bits */\n\nvar MIN_MATCH = 3;\nvar MAX_MATCH = 258;\nvar MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);\n\nvar PRESET_DICT = 0x20;\n\nvar INIT_STATE = 42;\nvar EXTRA_STATE = 69;\nvar NAME_STATE = 73;\nvar COMMENT_STATE = 91;\nvar HCRC_STATE = 103;\nvar BUSY_STATE = 113;\nvar FINISH_STATE = 666;\n\nvar BS_NEED_MORE      = 1; /* block not completed, need more input or more output */\nvar BS_BLOCK_DONE     = 2; /* block flush performed */\nvar BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */\nvar BS_FINISH_DONE    = 4; /* finish done, accept no more input or output */\n\nvar OS_CODE = 0x03; // Unix :) . Don't detect, use this default.\n\nfunction err(strm, errorCode) {\n  strm.msg = msg[errorCode];\n  return errorCode;\n}\n\nfunction rank(f) {\n  return ((f) << 1) - ((f) > 4 ? 9 : 0);\n}\n\nfunction zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }\n\n\n/* =========================================================================\n * Flush as much pending output as possible. All deflate() output goes\n * through this function so some applications may wish to modify it\n * to avoid allocating a large strm->output buffer and copying into it.\n * (See also read_buf()).\n */\nfunction flush_pending(strm) {\n  var s = strm.state;\n\n  //_tr_flush_bits(s);\n  var len = s.pending;\n  if (len > strm.avail_out) {\n    len = strm.avail_out;\n  }\n  if (len === 0) { return; }\n\n  utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out);\n  strm.next_out += len;\n  s.pending_out += len;\n  strm.total_out += len;\n  strm.avail_out -= len;\n  s.pending -= len;\n  if (s.pending === 0) {\n    s.pending_out = 0;\n  }\n}\n\n\nfunction flush_block_only(s, last) {\n  trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);\n  s.block_start = s.strstart;\n  flush_pending(s.strm);\n}\n\n\nfunction put_byte(s, b) {\n  s.pending_buf[s.pending++] = b;\n}\n\n\n/* =========================================================================\n * Put a short in the pending buffer. The 16-bit value is put in MSB order.\n * IN assertion: the stream state is correct and there is enough room in\n * pending_buf.\n */\nfunction putShortMSB(s, b) {\n//  put_byte(s, (Byte)(b >> 8));\n//  put_byte(s, (Byte)(b & 0xff));\n  s.pending_buf[s.pending++] = (b >>> 8) & 0xff;\n  s.pending_buf[s.pending++] = b & 0xff;\n}\n\n\n/* ===========================================================================\n * Read a new buffer from the current input stream, update the adler32\n * and total number of bytes read.  All deflate() input goes through\n * this function so some applications may wish to modify it to avoid\n * allocating a large strm->input buffer and copying from it.\n * (See also flush_pending()).\n */\nfunction read_buf(strm, buf, start, size) {\n  var len = strm.avail_in;\n\n  if (len > size) { len = size; }\n  if (len === 0) { return 0; }\n\n  strm.avail_in -= len;\n\n  // zmemcpy(buf, strm->next_in, len);\n  utils.arraySet(buf, strm.input, strm.next_in, len, start);\n  if (strm.state.wrap === 1) {\n    strm.adler = adler32(strm.adler, buf, len, start);\n  }\n\n  else if (strm.state.wrap === 2) {\n    strm.adler = crc32(strm.adler, buf, len, start);\n  }\n\n  strm.next_in += len;\n  strm.total_in += len;\n\n  return len;\n}\n\n\n/* ===========================================================================\n * Set match_start to the longest match starting at the given string and\n * return its length. Matches shorter or equal to prev_length are discarded,\n * in which case the result is equal to prev_length and match_start is\n * garbage.\n * IN assertions: cur_match is the head of the hash chain for the current\n *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1\n * OUT assertion: the match length is not greater than s->lookahead.\n */\nfunction longest_match(s, cur_match) {\n  var chain_length = s.max_chain_length;      /* max hash chain length */\n  var scan = s.strstart; /* current string */\n  var match;                       /* matched string */\n  var len;                           /* length of current match */\n  var best_len = s.prev_length;              /* best match length so far */\n  var nice_match = s.nice_match;             /* stop if match long enough */\n  var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ?\n      s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/;\n\n  var _win = s.window; // shortcut\n\n  var wmask = s.w_mask;\n  var prev  = s.prev;\n\n  /* Stop when cur_match becomes <= limit. To simplify the code,\n   * we prevent matches with the string of window index 0.\n   */\n\n  var strend = s.strstart + MAX_MATCH;\n  var scan_end1  = _win[scan + best_len - 1];\n  var scan_end   = _win[scan + best_len];\n\n  /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.\n   * It is easy to get rid of this optimization if necessary.\n   */\n  // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, \"Code too clever\");\n\n  /* Do not waste too much time if we already have a good match: */\n  if (s.prev_length >= s.good_match) {\n    chain_length >>= 2;\n  }\n  /* Do not look for matches beyond the end of the input. This is necessary\n   * to make deflate deterministic.\n   */\n  if (nice_match > s.lookahead) { nice_match = s.lookahead; }\n\n  // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, \"need lookahead\");\n\n  do {\n    // Assert(cur_match < s->strstart, \"no future\");\n    match = cur_match;\n\n    /* Skip to next match if the match length cannot increase\n     * or if the match length is less than 2.  Note that the checks below\n     * for insufficient lookahead only occur occasionally for performance\n     * reasons.  Therefore uninitialized memory will be accessed, and\n     * conditional jumps will be made that depend on those values.\n     * However the length of the match is limited to the lookahead, so\n     * the output of deflate is not affected by the uninitialized values.\n     */\n\n    if (_win[match + best_len]     !== scan_end  ||\n        _win[match + best_len - 1] !== scan_end1 ||\n        _win[match]                !== _win[scan] ||\n        _win[++match]              !== _win[scan + 1]) {\n      continue;\n    }\n\n    /* The check at best_len-1 can be removed because it will be made\n     * again later. (This heuristic is not always a win.)\n     * It is not necessary to compare scan[2] and match[2] since they\n     * are always equal when the other bytes match, given that\n     * the hash keys are equal and that HASH_BITS >= 8.\n     */\n    scan += 2;\n    match++;\n    // Assert(*scan == *match, \"match[2]?\");\n\n    /* We check for insufficient lookahead only every 8th comparison;\n     * the 256th check will be made at strstart+258.\n     */\n    do {\n      // Do nothing\n    } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&\n             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&\n             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&\n             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&\n             scan < strend);\n\n    // Assert(scan <= s->window+(unsigned)(s->window_size-1), \"wild scan\");\n\n    len = MAX_MATCH - (strend - scan);\n    scan = strend - MAX_MATCH;\n\n    if (len > best_len) {\n      s.match_start = cur_match;\n      best_len = len;\n      if (len >= nice_match) {\n        break;\n      }\n      scan_end1  = _win[scan + best_len - 1];\n      scan_end   = _win[scan + best_len];\n    }\n  } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);\n\n  if (best_len <= s.lookahead) {\n    return best_len;\n  }\n  return s.lookahead;\n}\n\n\n/* ===========================================================================\n * Fill the window when the lookahead becomes insufficient.\n * Updates strstart and lookahead.\n *\n * IN assertion: lookahead < MIN_LOOKAHEAD\n * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD\n *    At least one byte has been read, or avail_in == 0; reads are\n *    performed for at least two bytes (required for the zip translate_eol\n *    option -- not supported here).\n */\nfunction fill_window(s) {\n  var _w_size = s.w_size;\n  var p, n, m, more, str;\n\n  //Assert(s->lookahead < MIN_LOOKAHEAD, \"already enough lookahead\");\n\n  do {\n    more = s.window_size - s.lookahead - s.strstart;\n\n    // JS ints have 32 bit, block below not needed\n    /* Deal with !@#$% 64K limit: */\n    //if (sizeof(int) <= 2) {\n    //    if (more == 0 && s->strstart == 0 && s->lookahead == 0) {\n    //        more = wsize;\n    //\n    //  } else if (more == (unsigned)(-1)) {\n    //        /* Very unlikely, but possible on 16 bit machine if\n    //         * strstart == 0 && lookahead == 1 (input done a byte at time)\n    //         */\n    //        more--;\n    //    }\n    //}\n\n\n    /* If the window is almost full and there is insufficient lookahead,\n     * move the upper half to the lower one to make room in the upper half.\n     */\n    if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {\n\n      utils.arraySet(s.window, s.window, _w_size, _w_size, 0);\n      s.match_start -= _w_size;\n      s.strstart -= _w_size;\n      /* we now have strstart >= MAX_DIST */\n      s.block_start -= _w_size;\n\n      /* Slide the hash table (could be avoided with 32 bit values\n       at the expense of memory usage). We slide even when level == 0\n       to keep the hash table consistent if we switch back to level > 0\n       later. (Using level 0 permanently is not an optimal usage of\n       zlib, so we don't care about this pathological case.)\n       */\n\n      n = s.hash_size;\n      p = n;\n      do {\n        m = s.head[--p];\n        s.head[p] = (m >= _w_size ? m - _w_size : 0);\n      } while (--n);\n\n      n = _w_size;\n      p = n;\n      do {\n        m = s.prev[--p];\n        s.prev[p] = (m >= _w_size ? m - _w_size : 0);\n        /* If n is not on any hash chain, prev[n] is garbage but\n         * its value will never be used.\n         */\n      } while (--n);\n\n      more += _w_size;\n    }\n    if (s.strm.avail_in === 0) {\n      break;\n    }\n\n    /* If there was no sliding:\n     *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&\n     *    more == window_size - lookahead - strstart\n     * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)\n     * => more >= window_size - 2*WSIZE + 2\n     * In the BIG_MEM or MMAP case (not yet supported),\n     *   window_size == input_size + MIN_LOOKAHEAD  &&\n     *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.\n     * Otherwise, window_size == 2*WSIZE so more >= 2.\n     * If there was sliding, more >= WSIZE. So in all cases, more >= 2.\n     */\n    //Assert(more >= 2, \"more < 2\");\n    n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);\n    s.lookahead += n;\n\n    /* Initialize the hash value now that we have some input: */\n    if (s.lookahead + s.insert >= MIN_MATCH) {\n      str = s.strstart - s.insert;\n      s.ins_h = s.window[str];\n\n      /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */\n      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask;\n//#if MIN_MATCH != 3\n//        Call update_hash() MIN_MATCH-3 more times\n//#endif\n      while (s.insert) {\n        /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */\n        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;\n\n        s.prev[str & s.w_mask] = s.head[s.ins_h];\n        s.head[s.ins_h] = str;\n        str++;\n        s.insert--;\n        if (s.lookahead + s.insert < MIN_MATCH) {\n          break;\n        }\n      }\n    }\n    /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,\n     * but this is not important since only literal bytes will be emitted.\n     */\n\n  } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);\n\n  /* If the WIN_INIT bytes after the end of the current data have never been\n   * written, then zero those bytes in order to avoid memory check reports of\n   * the use of uninitialized (or uninitialised as Julian writes) bytes by\n   * the longest match routines.  Update the high water mark for the next\n   * time through here.  WIN_INIT is set to MAX_MATCH since the longest match\n   * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.\n   */\n//  if (s.high_water < s.window_size) {\n//    var curr = s.strstart + s.lookahead;\n//    var init = 0;\n//\n//    if (s.high_water < curr) {\n//      /* Previous high water mark below current data -- zero WIN_INIT\n//       * bytes or up to end of window, whichever is less.\n//       */\n//      init = s.window_size - curr;\n//      if (init > WIN_INIT)\n//        init = WIN_INIT;\n//      zmemzero(s->window + curr, (unsigned)init);\n//      s->high_water = curr + init;\n//    }\n//    else if (s->high_water < (ulg)curr + WIN_INIT) {\n//      /* High water mark at or above current data, but below current data\n//       * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up\n//       * to end of window, whichever is less.\n//       */\n//      init = (ulg)curr + WIN_INIT - s->high_water;\n//      if (init > s->window_size - s->high_water)\n//        init = s->window_size - s->high_water;\n//      zmemzero(s->window + s->high_water, (unsigned)init);\n//      s->high_water += init;\n//    }\n//  }\n//\n//  Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,\n//    \"not enough room for search\");\n}\n\n/* ===========================================================================\n * Copy without compression as much as possible from the input stream, return\n * the current block state.\n * This function does not insert new strings in the dictionary since\n * uncompressible data is probably not useful. This function is used\n * only for the level=0 compression option.\n * NOTE: this function should be optimized to avoid extra copying from\n * window to pending_buf.\n */\nfunction deflate_stored(s, flush) {\n  /* Stored blocks are limited to 0xffff bytes, pending_buf is limited\n   * to pending_buf_size, and each stored block has a 5 byte header:\n   */\n  var max_block_size = 0xffff;\n\n  if (max_block_size > s.pending_buf_size - 5) {\n    max_block_size = s.pending_buf_size - 5;\n  }\n\n  /* Copy as much as possible from input to output: */\n  for (;;) {\n    /* Fill the window as much as possible: */\n    if (s.lookahead <= 1) {\n\n      //Assert(s->strstart < s->w_size+MAX_DIST(s) ||\n      //  s->block_start >= (long)s->w_size, \"slide too late\");\n//      if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||\n//        s.block_start >= s.w_size)) {\n//        throw  new Error(\"slide too late\");\n//      }\n\n      fill_window(s);\n      if (s.lookahead === 0 && flush === Z_NO_FLUSH) {\n        return BS_NEED_MORE;\n      }\n\n      if (s.lookahead === 0) {\n        break;\n      }\n      /* flush the current block */\n    }\n    //Assert(s->block_start >= 0L, \"block gone\");\n//    if (s.block_start < 0) throw new Error(\"block gone\");\n\n    s.strstart += s.lookahead;\n    s.lookahead = 0;\n\n    /* Emit a stored block if pending_buf will be full: */\n    var max_start = s.block_start + max_block_size;\n\n    if (s.strstart === 0 || s.strstart >= max_start) {\n      /* strstart == 0 is possible when wraparound on 16-bit machine */\n      s.lookahead = s.strstart - max_start;\n      s.strstart = max_start;\n      /*** FLUSH_BLOCK(s, 0); ***/\n      flush_block_only(s, false);\n      if (s.strm.avail_out === 0) {\n        return BS_NEED_MORE;\n      }\n      /***/\n\n\n    }\n    /* Flush if we may have to slide, otherwise block_start may become\n     * negative and the data will be gone:\n     */\n    if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) {\n      /*** FLUSH_BLOCK(s, 0); ***/\n      flush_block_only(s, false);\n      if (s.strm.avail_out === 0) {\n        return BS_NEED_MORE;\n      }\n      /***/\n    }\n  }\n\n  s.insert = 0;\n\n  if (flush === Z_FINISH) {\n    /*** FLUSH_BLOCK(s, 1); ***/\n    flush_block_only(s, true);\n    if (s.strm.avail_out === 0) {\n      return BS_FINISH_STARTED;\n    }\n    /***/\n    return BS_FINISH_DONE;\n  }\n\n  if (s.strstart > s.block_start) {\n    /*** FLUSH_BLOCK(s, 0); ***/\n    flush_block_only(s, false);\n    if (s.strm.avail_out === 0) {\n      return BS_NEED_MORE;\n    }\n    /***/\n  }\n\n  return BS_NEED_MORE;\n}\n\n/* ===========================================================================\n * Compress as much as possible from the input stream, return the current\n * block state.\n * This function does not perform lazy evaluation of matches and inserts\n * new strings in the dictionary only for unmatched strings or for short\n * matches. It is used only for the fast compression options.\n */\nfunction deflate_fast(s, flush) {\n  var hash_head;        /* head of the hash chain */\n  var bflush;           /* set if current block must be flushed */\n\n  for (;;) {\n    /* Make sure that we always have enough lookahead, except\n     * at the end of the input file. We need MAX_MATCH bytes\n     * for the next match, plus MIN_MATCH bytes to insert the\n     * string following the next match.\n     */\n    if (s.lookahead < MIN_LOOKAHEAD) {\n      fill_window(s);\n      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {\n        return BS_NEED_MORE;\n      }\n      if (s.lookahead === 0) {\n        break; /* flush the current block */\n      }\n    }\n\n    /* Insert the string window[strstart .. strstart+2] in the\n     * dictionary, and set hash_head to the head of the hash chain:\n     */\n    hash_head = 0/*NIL*/;\n    if (s.lookahead >= MIN_MATCH) {\n      /*** INSERT_STRING(s, s.strstart, hash_head); ***/\n      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;\n      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];\n      s.head[s.ins_h] = s.strstart;\n      /***/\n    }\n\n    /* Find the longest match, discarding those <= prev_length.\n     * At this point we have always match_length < MIN_MATCH\n     */\n    if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) {\n      /* To simplify the code, we prevent matches with the string\n       * of window index 0 (in particular we have to avoid a match\n       * of the string with itself at the start of the input file).\n       */\n      s.match_length = longest_match(s, hash_head);\n      /* longest_match() sets match_start */\n    }\n    if (s.match_length >= MIN_MATCH) {\n      // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only\n\n      /*** _tr_tally_dist(s, s.strstart - s.match_start,\n                     s.match_length - MIN_MATCH, bflush); ***/\n      bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);\n\n      s.lookahead -= s.match_length;\n\n      /* Insert new strings in the hash table only if the match length\n       * is not too large. This saves time but degrades compression.\n       */\n      if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) {\n        s.match_length--; /* string at strstart already in table */\n        do {\n          s.strstart++;\n          /*** INSERT_STRING(s, s.strstart, hash_head); ***/\n          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;\n          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];\n          s.head[s.ins_h] = s.strstart;\n          /***/\n          /* strstart never exceeds WSIZE-MAX_MATCH, so there are\n           * always MIN_MATCH bytes ahead.\n           */\n        } while (--s.match_length !== 0);\n        s.strstart++;\n      } else\n      {\n        s.strstart += s.match_length;\n        s.match_length = 0;\n        s.ins_h = s.window[s.strstart];\n        /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */\n        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask;\n\n//#if MIN_MATCH != 3\n//                Call UPDATE_HASH() MIN_MATCH-3 more times\n//#endif\n        /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not\n         * matter since it will be recomputed at next deflate call.\n         */\n      }\n    } else {\n      /* No match, output a literal byte */\n      //Tracevv((stderr,\"%c\", s.window[s.strstart]));\n      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/\n      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);\n\n      s.lookahead--;\n      s.strstart++;\n    }\n    if (bflush) {\n      /*** FLUSH_BLOCK(s, 0); ***/\n      flush_block_only(s, false);\n      if (s.strm.avail_out === 0) {\n        return BS_NEED_MORE;\n      }\n      /***/\n    }\n  }\n  s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1);\n  if (flush === Z_FINISH) {\n    /*** FLUSH_BLOCK(s, 1); ***/\n    flush_block_only(s, true);\n    if (s.strm.avail_out === 0) {\n      return BS_FINISH_STARTED;\n    }\n    /***/\n    return BS_FINISH_DONE;\n  }\n  if (s.last_lit) {\n    /*** FLUSH_BLOCK(s, 0); ***/\n    flush_block_only(s, false);\n    if (s.strm.avail_out === 0) {\n      return BS_NEED_MORE;\n    }\n    /***/\n  }\n  return BS_BLOCK_DONE;\n}\n\n/* ===========================================================================\n * Same as above, but achieves better compression. We use a lazy\n * evaluation for matches: a match is finally adopted only if there is\n * no better match at the next window position.\n */\nfunction deflate_slow(s, flush) {\n  var hash_head;          /* head of hash chain */\n  var bflush;              /* set if current block must be flushed */\n\n  var max_insert;\n\n  /* Process the input block. */\n  for (;;) {\n    /* Make sure that we always have enough lookahead, except\n     * at the end of the input file. We need MAX_MATCH bytes\n     * for the next match, plus MIN_MATCH bytes to insert the\n     * string following the next match.\n     */\n    if (s.lookahead < MIN_LOOKAHEAD) {\n      fill_window(s);\n      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {\n        return BS_NEED_MORE;\n      }\n      if (s.lookahead === 0) { break; } /* flush the current block */\n    }\n\n    /* Insert the string window[strstart .. strstart+2] in the\n     * dictionary, and set hash_head to the head of the hash chain:\n     */\n    hash_head = 0/*NIL*/;\n    if (s.lookahead >= MIN_MATCH) {\n      /*** INSERT_STRING(s, s.strstart, hash_head); ***/\n      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;\n      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];\n      s.head[s.ins_h] = s.strstart;\n      /***/\n    }\n\n    /* Find the longest match, discarding those <= prev_length.\n     */\n    s.prev_length = s.match_length;\n    s.prev_match = s.match_start;\n    s.match_length = MIN_MATCH - 1;\n\n    if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match &&\n        s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) {\n      /* To simplify the code, we prevent matches with the string\n       * of window index 0 (in particular we have to avoid a match\n       * of the string with itself at the start of the input file).\n       */\n      s.match_length = longest_match(s, hash_head);\n      /* longest_match() sets match_start */\n\n      if (s.match_length <= 5 &&\n         (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) {\n\n        /* If prev_match is also MIN_MATCH, match_start is garbage\n         * but we will ignore the current match anyway.\n         */\n        s.match_length = MIN_MATCH - 1;\n      }\n    }\n    /* If there was a match at the previous step and the current\n     * match is not better, output the previous match:\n     */\n    if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {\n      max_insert = s.strstart + s.lookahead - MIN_MATCH;\n      /* Do not insert strings in hash table beyond this. */\n\n      //check_match(s, s.strstart-1, s.prev_match, s.prev_length);\n\n      /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,\n                     s.prev_length - MIN_MATCH, bflush);***/\n      bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);\n      /* Insert in hash table all strings up to the end of the match.\n       * strstart-1 and strstart are already inserted. If there is not\n       * enough lookahead, the last two strings are not inserted in\n       * the hash table.\n       */\n      s.lookahead -= s.prev_length - 1;\n      s.prev_length -= 2;\n      do {\n        if (++s.strstart <= max_insert) {\n          /*** INSERT_STRING(s, s.strstart, hash_head); ***/\n          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;\n          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];\n          s.head[s.ins_h] = s.strstart;\n          /***/\n        }\n      } while (--s.prev_length !== 0);\n      s.match_available = 0;\n      s.match_length = MIN_MATCH - 1;\n      s.strstart++;\n\n      if (bflush) {\n        /*** FLUSH_BLOCK(s, 0); ***/\n        flush_block_only(s, false);\n        if (s.strm.avail_out === 0) {\n          return BS_NEED_MORE;\n        }\n        /***/\n      }\n\n    } else if (s.match_available) {\n      /* If there was no match at the previous position, output a\n       * single literal. If there was a match but the current match\n       * is longer, truncate the previous match to a single literal.\n       */\n      //Tracevv((stderr,\"%c\", s->window[s->strstart-1]));\n      /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/\n      bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);\n\n      if (bflush) {\n        /*** FLUSH_BLOCK_ONLY(s, 0) ***/\n        flush_block_only(s, false);\n        /***/\n      }\n      s.strstart++;\n      s.lookahead--;\n      if (s.strm.avail_out === 0) {\n        return BS_NEED_MORE;\n      }\n    } else {\n      /* There is no previous match to compare with, wait for\n       * the next step to decide.\n       */\n      s.match_available = 1;\n      s.strstart++;\n      s.lookahead--;\n    }\n  }\n  //Assert (flush != Z_NO_FLUSH, \"no flush?\");\n  if (s.match_available) {\n    //Tracevv((stderr,\"%c\", s->window[s->strstart-1]));\n    /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/\n    bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);\n\n    s.match_available = 0;\n  }\n  s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;\n  if (flush === Z_FINISH) {\n    /*** FLUSH_BLOCK(s, 1); ***/\n    flush_block_only(s, true);\n    if (s.strm.avail_out === 0) {\n      return BS_FINISH_STARTED;\n    }\n    /***/\n    return BS_FINISH_DONE;\n  }\n  if (s.last_lit) {\n    /*** FLUSH_BLOCK(s, 0); ***/\n    flush_block_only(s, false);\n    if (s.strm.avail_out === 0) {\n      return BS_NEED_MORE;\n    }\n    /***/\n  }\n\n  return BS_BLOCK_DONE;\n}\n\n\n/* ===========================================================================\n * For Z_RLE, simply look for runs of bytes, generate matches only of distance\n * one.  Do not maintain a hash table.  (It will be regenerated if this run of\n * deflate switches away from Z_RLE.)\n */\nfunction deflate_rle(s, flush) {\n  var bflush;            /* set if current block must be flushed */\n  var prev;              /* byte at distance one to match */\n  var scan, strend;      /* scan goes up to strend for length of run */\n\n  var _win = s.window;\n\n  for (;;) {\n    /* Make sure that we always have enough lookahead, except\n     * at the end of the input file. We need MAX_MATCH bytes\n     * for the longest run, plus one for the unrolled loop.\n     */\n    if (s.lookahead <= MAX_MATCH) {\n      fill_window(s);\n      if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) {\n        return BS_NEED_MORE;\n      }\n      if (s.lookahead === 0) { break; } /* flush the current block */\n    }\n\n    /* See how many times the previous byte repeats */\n    s.match_length = 0;\n    if (s.lookahead >= MIN_MATCH && s.strstart > 0) {\n      scan = s.strstart - 1;\n      prev = _win[scan];\n      if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {\n        strend = s.strstart + MAX_MATCH;\n        do {\n          // Do nothing\n        } while (prev === _win[++scan] && prev === _win[++scan] &&\n                 prev === _win[++scan] && prev === _win[++scan] &&\n                 prev === _win[++scan] && prev === _win[++scan] &&\n                 prev === _win[++scan] && prev === _win[++scan] &&\n                 scan < strend);\n        s.match_length = MAX_MATCH - (strend - scan);\n        if (s.match_length > s.lookahead) {\n          s.match_length = s.lookahead;\n        }\n      }\n      //Assert(scan <= s->window+(uInt)(s->window_size-1), \"wild scan\");\n    }\n\n    /* Emit match if have run of MIN_MATCH or longer, else emit literal */\n    if (s.match_length >= MIN_MATCH) {\n      //check_match(s, s.strstart, s.strstart - 1, s.match_length);\n\n      /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/\n      bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH);\n\n      s.lookahead -= s.match_length;\n      s.strstart += s.match_length;\n      s.match_length = 0;\n    } else {\n      /* No match, output a literal byte */\n      //Tracevv((stderr,\"%c\", s->window[s->strstart]));\n      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/\n      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);\n\n      s.lookahead--;\n      s.strstart++;\n    }\n    if (bflush) {\n      /*** FLUSH_BLOCK(s, 0); ***/\n      flush_block_only(s, false);\n      if (s.strm.avail_out === 0) {\n        return BS_NEED_MORE;\n      }\n      /***/\n    }\n  }\n  s.insert = 0;\n  if (flush === Z_FINISH) {\n    /*** FLUSH_BLOCK(s, 1); ***/\n    flush_block_only(s, true);\n    if (s.strm.avail_out === 0) {\n      return BS_FINISH_STARTED;\n    }\n    /***/\n    return BS_FINISH_DONE;\n  }\n  if (s.last_lit) {\n    /*** FLUSH_BLOCK(s, 0); ***/\n    flush_block_only(s, false);\n    if (s.strm.avail_out === 0) {\n      return BS_NEED_MORE;\n    }\n    /***/\n  }\n  return BS_BLOCK_DONE;\n}\n\n/* ===========================================================================\n * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.\n * (It will be regenerated if this run of deflate switches away from Huffman.)\n */\nfunction deflate_huff(s, flush) {\n  var bflush;             /* set if current block must be flushed */\n\n  for (;;) {\n    /* Make sure that we have a literal to write. */\n    if (s.lookahead === 0) {\n      fill_window(s);\n      if (s.lookahead === 0) {\n        if (flush === Z_NO_FLUSH) {\n          return BS_NEED_MORE;\n        }\n        break;      /* flush the current block */\n      }\n    }\n\n    /* Output a literal byte */\n    s.match_length = 0;\n    //Tracevv((stderr,\"%c\", s->window[s->strstart]));\n    /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/\n    bflush = trees._tr_tally(s, 0, s.window[s.strstart]);\n    s.lookahead--;\n    s.strstart++;\n    if (bflush) {\n      /*** FLUSH_BLOCK(s, 0); ***/\n      flush_block_only(s, false);\n      if (s.strm.avail_out === 0) {\n        return BS_NEED_MORE;\n      }\n      /***/\n    }\n  }\n  s.insert = 0;\n  if (flush === Z_FINISH) {\n    /*** FLUSH_BLOCK(s, 1); ***/\n    flush_block_only(s, true);\n    if (s.strm.avail_out === 0) {\n      return BS_FINISH_STARTED;\n    }\n    /***/\n    return BS_FINISH_DONE;\n  }\n  if (s.last_lit) {\n    /*** FLUSH_BLOCK(s, 0); ***/\n    flush_block_only(s, false);\n    if (s.strm.avail_out === 0) {\n      return BS_NEED_MORE;\n    }\n    /***/\n  }\n  return BS_BLOCK_DONE;\n}\n\n/* Values for max_lazy_match, good_match and max_chain_length, depending on\n * the desired pack level (0..9). The values given below have been tuned to\n * exclude worst case performance for pathological files. Better values may be\n * found for specific files.\n */\nfunction Config(good_length, max_lazy, nice_length, max_chain, func) {\n  this.good_length = good_length;\n  this.max_lazy = max_lazy;\n  this.nice_length = nice_length;\n  this.max_chain = max_chain;\n  this.func = func;\n}\n\nvar configuration_table;\n\nconfiguration_table = [\n  /*      good lazy nice chain */\n  new Config(0, 0, 0, 0, deflate_stored),          /* 0 store only */\n  new Config(4, 4, 8, 4, deflate_fast),            /* 1 max speed, no lazy matches */\n  new Config(4, 5, 16, 8, deflate_fast),           /* 2 */\n  new Config(4, 6, 32, 32, deflate_fast),          /* 3 */\n\n  new Config(4, 4, 16, 16, deflate_slow),          /* 4 lazy matches */\n  new Config(8, 16, 32, 32, deflate_slow),         /* 5 */\n  new Config(8, 16, 128, 128, deflate_slow),       /* 6 */\n  new Config(8, 32, 128, 256, deflate_slow),       /* 7 */\n  new Config(32, 128, 258, 1024, deflate_slow),    /* 8 */\n  new Config(32, 258, 258, 4096, deflate_slow)     /* 9 max compression */\n];\n\n\n/* ===========================================================================\n * Initialize the \"longest match\" routines for a new zlib stream\n */\nfunction lm_init(s) {\n  s.window_size = 2 * s.w_size;\n\n  /*** CLEAR_HASH(s); ***/\n  zero(s.head); // Fill with NIL (= 0);\n\n  /* Set the default configuration parameters:\n   */\n  s.max_lazy_match = configuration_table[s.level].max_lazy;\n  s.good_match = configuration_table[s.level].good_length;\n  s.nice_match = configuration_table[s.level].nice_length;\n  s.max_chain_length = configuration_table[s.level].max_chain;\n\n  s.strstart = 0;\n  s.block_start = 0;\n  s.lookahead = 0;\n  s.insert = 0;\n  s.match_length = s.prev_length = MIN_MATCH - 1;\n  s.match_available = 0;\n  s.ins_h = 0;\n}\n\n\nfunction DeflateState() {\n  this.strm = null;            /* pointer back to this zlib stream */\n  this.status = 0;            /* as the name implies */\n  this.pending_buf = null;      /* output still pending */\n  this.pending_buf_size = 0;  /* size of pending_buf */\n  this.pending_out = 0;       /* next pending byte to output to the stream */\n  this.pending = 0;           /* nb of bytes in the pending buffer */\n  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */\n  this.gzhead = null;         /* gzip header information to write */\n  this.gzindex = 0;           /* where in extra, name, or comment */\n  this.method = Z_DEFLATED; /* can only be DEFLATED */\n  this.last_flush = -1;   /* value of flush param for previous deflate call */\n\n  this.w_size = 0;  /* LZ77 window size (32K by default) */\n  this.w_bits = 0;  /* log2(w_size)  (8..16) */\n  this.w_mask = 0;  /* w_size - 1 */\n\n  this.window = null;\n  /* Sliding window. Input bytes are read into the second half of the window,\n   * and move to the first half later to keep a dictionary of at least wSize\n   * bytes. With this organization, matches are limited to a distance of\n   * wSize-MAX_MATCH bytes, but this ensures that IO is always\n   * performed with a length multiple of the block size.\n   */\n\n  this.window_size = 0;\n  /* Actual size of window: 2*wSize, except when the user input buffer\n   * is directly used as sliding window.\n   */\n\n  this.prev = null;\n  /* Link to older string with same hash index. To limit the size of this\n   * array to 64K, this link is maintained only for the last 32K strings.\n   * An index in this array is thus a window index modulo 32K.\n   */\n\n  this.head = null;   /* Heads of the hash chains or NIL. */\n\n  this.ins_h = 0;       /* hash index of string to be inserted */\n  this.hash_size = 0;   /* number of elements in hash table */\n  this.hash_bits = 0;   /* log2(hash_size) */\n  this.hash_mask = 0;   /* hash_size-1 */\n\n  this.hash_shift = 0;\n  /* Number of bits by which ins_h must be shifted at each input\n   * step. It must be such that after MIN_MATCH steps, the oldest\n   * byte no longer takes part in the hash key, that is:\n   *   hash_shift * MIN_MATCH >= hash_bits\n   */\n\n  this.block_start = 0;\n  /* Window position at the beginning of the current output block. Gets\n   * negative when the window is moved backwards.\n   */\n\n  this.match_length = 0;      /* length of best match */\n  this.prev_match = 0;        /* previous match */\n  this.match_available = 0;   /* set if previous match exists */\n  this.strstart = 0;          /* start of string to insert */\n  this.match_start = 0;       /* start of matching string */\n  this.lookahead = 0;         /* number of valid bytes ahead in window */\n\n  this.prev_length = 0;\n  /* Length of the best match at previous step. Matches not greater than this\n   * are discarded. This is used in the lazy match evaluation.\n   */\n\n  this.max_chain_length = 0;\n  /* To speed up deflation, hash chains are never searched beyond this\n   * length.  A higher limit improves compression ratio but degrades the\n   * speed.\n   */\n\n  this.max_lazy_match = 0;\n  /* Attempt to find a better match only when the current match is strictly\n   * smaller than this value. This mechanism is used only for compression\n   * levels >= 4.\n   */\n  // That's alias to max_lazy_match, don't use directly\n  //this.max_insert_length = 0;\n  /* Insert new strings in the hash table only if the match length is not\n   * greater than this length. This saves time but degrades compression.\n   * max_insert_length is used only for compression levels <= 3.\n   */\n\n  this.level = 0;     /* compression level (1..9) */\n  this.strategy = 0;  /* favor or force Huffman coding*/\n\n  this.good_match = 0;\n  /* Use a faster search when the previous match is longer than this */\n\n  this.nice_match = 0; /* Stop searching when current match exceeds this */\n\n              /* used by trees.c: */\n\n  /* Didn't use ct_data typedef below to suppress compiler warning */\n\n  // struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */\n  // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */\n  // struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */\n\n  // Use flat array of DOUBLE size, with interleaved fata,\n  // because JS does not support effective\n  this.dyn_ltree  = new utils.Buf16(HEAP_SIZE * 2);\n  this.dyn_dtree  = new utils.Buf16((2 * D_CODES + 1) * 2);\n  this.bl_tree    = new utils.Buf16((2 * BL_CODES + 1) * 2);\n  zero(this.dyn_ltree);\n  zero(this.dyn_dtree);\n  zero(this.bl_tree);\n\n  this.l_desc   = null;         /* desc. for literal tree */\n  this.d_desc   = null;         /* desc. for distance tree */\n  this.bl_desc  = null;         /* desc. for bit length tree */\n\n  //ush bl_count[MAX_BITS+1];\n  this.bl_count = new utils.Buf16(MAX_BITS + 1);\n  /* number of codes at each bit length for an optimal tree */\n\n  //int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */\n  this.heap = new utils.Buf16(2 * L_CODES + 1);  /* heap used to build the Huffman trees */\n  zero(this.heap);\n\n  this.heap_len = 0;               /* number of elements in the heap */\n  this.heap_max = 0;               /* element of largest frequency */\n  /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.\n   * The same heap array is used to build all trees.\n   */\n\n  this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1];\n  zero(this.depth);\n  /* Depth of each subtree used as tie breaker for trees of equal frequency\n   */\n\n  this.l_buf = 0;          /* buffer index for literals or lengths */\n\n  this.lit_bufsize = 0;\n  /* Size of match buffer for literals/lengths.  There are 4 reasons for\n   * limiting lit_bufsize to 64K:\n   *   - frequencies can be kept in 16 bit counters\n   *   - if compression is not successful for the first block, all input\n   *     data is still in the window so we can still emit a stored block even\n   *     when input comes from standard input.  (This can also be done for\n   *     all blocks if lit_bufsize is not greater than 32K.)\n   *   - if compression is not successful for a file smaller than 64K, we can\n   *     even emit a stored file instead of a stored block (saving 5 bytes).\n   *     This is applicable only for zip (not gzip or zlib).\n   *   - creating new Huffman trees less frequently may not provide fast\n   *     adaptation to changes in the input data statistics. (Take for\n   *     example a binary file with poorly compressible code followed by\n   *     a highly compressible string table.) Smaller buffer sizes give\n   *     fast adaptation but have of course the overhead of transmitting\n   *     trees more frequently.\n   *   - I can't count above 4\n   */\n\n  this.last_lit = 0;      /* running index in l_buf */\n\n  this.d_buf = 0;\n  /* Buffer index for distances. To simplify the code, d_buf and l_buf have\n   * the same number of elements. To use different lengths, an extra flag\n   * array would be necessary.\n   */\n\n  this.opt_len = 0;       /* bit length of current block with optimal trees */\n  this.static_len = 0;    /* bit length of current block with static trees */\n  this.matches = 0;       /* number of string matches in current block */\n  this.insert = 0;        /* bytes at end of window left to insert */\n\n\n  this.bi_buf = 0;\n  /* Output buffer. bits are inserted starting at the bottom (least\n   * significant bits).\n   */\n  this.bi_valid = 0;\n  /* Number of valid bits in bi_buf.  All bits above the last valid bit\n   * are always zero.\n   */\n\n  // Used for window memory init. We safely ignore it for JS. That makes\n  // sense only for pointers and memory check tools.\n  //this.high_water = 0;\n  /* High water mark offset in window for initialized bytes -- bytes above\n   * this are set to zero in order to avoid memory check warnings when\n   * longest match routines access bytes past the input.  This is then\n   * updated to the new high water mark.\n   */\n}\n\n\nfunction deflateResetKeep(strm) {\n  var s;\n\n  if (!strm || !strm.state) {\n    return err(strm, Z_STREAM_ERROR);\n  }\n\n  strm.total_in = strm.total_out = 0;\n  strm.data_type = Z_UNKNOWN;\n\n  s = strm.state;\n  s.pending = 0;\n  s.pending_out = 0;\n\n  if (s.wrap < 0) {\n    s.wrap = -s.wrap;\n    /* was made negative by deflate(..., Z_FINISH); */\n  }\n  s.status = (s.wrap ? INIT_STATE : BUSY_STATE);\n  strm.adler = (s.wrap === 2) ?\n    0  // crc32(0, Z_NULL, 0)\n  :\n    1; // adler32(0, Z_NULL, 0)\n  s.last_flush = Z_NO_FLUSH;\n  trees._tr_init(s);\n  return Z_OK;\n}\n\n\nfunction deflateReset(strm) {\n  var ret = deflateResetKeep(strm);\n  if (ret === Z_OK) {\n    lm_init(strm.state);\n  }\n  return ret;\n}\n\n\nfunction deflateSetHeader(strm, head) {\n  if (!strm || !strm.state) { return Z_STREAM_ERROR; }\n  if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }\n  strm.state.gzhead = head;\n  return Z_OK;\n}\n\n\nfunction deflateInit2(strm, level, method, windowBits, memLevel, strategy) {\n  if (!strm) { // === Z_NULL\n    return Z_STREAM_ERROR;\n  }\n  var wrap = 1;\n\n  if (level === Z_DEFAULT_COMPRESSION) {\n    level = 6;\n  }\n\n  if (windowBits < 0) { /* suppress zlib wrapper */\n    wrap = 0;\n    windowBits = -windowBits;\n  }\n\n  else if (windowBits > 15) {\n    wrap = 2;           /* write gzip wrapper instead */\n    windowBits -= 16;\n  }\n\n\n  if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||\n    windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||\n    strategy < 0 || strategy > Z_FIXED) {\n    return err(strm, Z_STREAM_ERROR);\n  }\n\n\n  if (windowBits === 8) {\n    windowBits = 9;\n  }\n  /* until 256-byte window bug fixed */\n\n  var s = new DeflateState();\n\n  strm.state = s;\n  s.strm = strm;\n\n  s.wrap = wrap;\n  s.gzhead = null;\n  s.w_bits = windowBits;\n  s.w_size = 1 << s.w_bits;\n  s.w_mask = s.w_size - 1;\n\n  s.hash_bits = memLevel + 7;\n  s.hash_size = 1 << s.hash_bits;\n  s.hash_mask = s.hash_size - 1;\n  s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);\n\n  s.window = new utils.Buf8(s.w_size * 2);\n  s.head = new utils.Buf16(s.hash_size);\n  s.prev = new utils.Buf16(s.w_size);\n\n  // Don't need mem init magic for JS.\n  //s.high_water = 0;  /* nothing written to s->window yet */\n\n  s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */\n\n  s.pending_buf_size = s.lit_bufsize * 4;\n\n  //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);\n  //s->pending_buf = (uchf *) overlay;\n  s.pending_buf = new utils.Buf8(s.pending_buf_size);\n\n  // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)\n  //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);\n  s.d_buf = 1 * s.lit_bufsize;\n\n  //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;\n  s.l_buf = (1 + 2) * s.lit_bufsize;\n\n  s.level = level;\n  s.strategy = strategy;\n  s.method = method;\n\n  return deflateReset(strm);\n}\n\nfunction deflateInit(strm, level) {\n  return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);\n}\n\n\nfunction deflate(strm, flush) {\n  var old_flush, s;\n  var beg, val; // for gzip header write only\n\n  if (!strm || !strm.state ||\n    flush > Z_BLOCK || flush < 0) {\n    return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;\n  }\n\n  s = strm.state;\n\n  if (!strm.output ||\n      (!strm.input && strm.avail_in !== 0) ||\n      (s.status === FINISH_STATE && flush !== Z_FINISH)) {\n    return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR);\n  }\n\n  s.strm = strm; /* just in case */\n  old_flush = s.last_flush;\n  s.last_flush = flush;\n\n  /* Write the header */\n  if (s.status === INIT_STATE) {\n\n    if (s.wrap === 2) { // GZIP header\n      strm.adler = 0;  //crc32(0L, Z_NULL, 0);\n      put_byte(s, 31);\n      put_byte(s, 139);\n      put_byte(s, 8);\n      if (!s.gzhead) { // s->gzhead == Z_NULL\n        put_byte(s, 0);\n        put_byte(s, 0);\n        put_byte(s, 0);\n        put_byte(s, 0);\n        put_byte(s, 0);\n        put_byte(s, s.level === 9 ? 2 :\n                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?\n                     4 : 0));\n        put_byte(s, OS_CODE);\n        s.status = BUSY_STATE;\n      }\n      else {\n        put_byte(s, (s.gzhead.text ? 1 : 0) +\n                    (s.gzhead.hcrc ? 2 : 0) +\n                    (!s.gzhead.extra ? 0 : 4) +\n                    (!s.gzhead.name ? 0 : 8) +\n                    (!s.gzhead.comment ? 0 : 16)\n                );\n        put_byte(s, s.gzhead.time & 0xff);\n        put_byte(s, (s.gzhead.time >> 8) & 0xff);\n        put_byte(s, (s.gzhead.time >> 16) & 0xff);\n        put_byte(s, (s.gzhead.time >> 24) & 0xff);\n        put_byte(s, s.level === 9 ? 2 :\n                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?\n                     4 : 0));\n        put_byte(s, s.gzhead.os & 0xff);\n        if (s.gzhead.extra && s.gzhead.extra.length) {\n          put_byte(s, s.gzhead.extra.length & 0xff);\n          put_byte(s, (s.gzhead.extra.length >> 8) & 0xff);\n        }\n        if (s.gzhead.hcrc) {\n          strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0);\n        }\n        s.gzindex = 0;\n        s.status = EXTRA_STATE;\n      }\n    }\n    else // DEFLATE header\n    {\n      var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;\n      var level_flags = -1;\n\n      if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {\n        level_flags = 0;\n      } else if (s.level < 6) {\n        level_flags = 1;\n      } else if (s.level === 6) {\n        level_flags = 2;\n      } else {\n        level_flags = 3;\n      }\n      header |= (level_flags << 6);\n      if (s.strstart !== 0) { header |= PRESET_DICT; }\n      header += 31 - (header % 31);\n\n      s.status = BUSY_STATE;\n      putShortMSB(s, header);\n\n      /* Save the adler32 of the preset dictionary: */\n      if (s.strstart !== 0) {\n        putShortMSB(s, strm.adler >>> 16);\n        putShortMSB(s, strm.adler & 0xffff);\n      }\n      strm.adler = 1; // adler32(0L, Z_NULL, 0);\n    }\n  }\n\n//#ifdef GZIP\n  if (s.status === EXTRA_STATE) {\n    if (s.gzhead.extra/* != Z_NULL*/) {\n      beg = s.pending;  /* start of bytes to update crc */\n\n      while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {\n        if (s.pending === s.pending_buf_size) {\n          if (s.gzhead.hcrc && s.pending > beg) {\n            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);\n          }\n          flush_pending(strm);\n          beg = s.pending;\n          if (s.pending === s.pending_buf_size) {\n            break;\n          }\n        }\n        put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);\n        s.gzindex++;\n      }\n      if (s.gzhead.hcrc && s.pending > beg) {\n        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);\n      }\n      if (s.gzindex === s.gzhead.extra.length) {\n        s.gzindex = 0;\n        s.status = NAME_STATE;\n      }\n    }\n    else {\n      s.status = NAME_STATE;\n    }\n  }\n  if (s.status === NAME_STATE) {\n    if (s.gzhead.name/* != Z_NULL*/) {\n      beg = s.pending;  /* start of bytes to update crc */\n      //int val;\n\n      do {\n        if (s.pending === s.pending_buf_size) {\n          if (s.gzhead.hcrc && s.pending > beg) {\n            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);\n          }\n          flush_pending(strm);\n          beg = s.pending;\n          if (s.pending === s.pending_buf_size) {\n            val = 1;\n            break;\n          }\n        }\n        // JS specific: little magic to add zero terminator to end of string\n        if (s.gzindex < s.gzhead.name.length) {\n          val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;\n        } else {\n          val = 0;\n        }\n        put_byte(s, val);\n      } while (val !== 0);\n\n      if (s.gzhead.hcrc && s.pending > beg) {\n        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);\n      }\n      if (val === 0) {\n        s.gzindex = 0;\n        s.status = COMMENT_STATE;\n      }\n    }\n    else {\n      s.status = COMMENT_STATE;\n    }\n  }\n  if (s.status === COMMENT_STATE) {\n    if (s.gzhead.comment/* != Z_NULL*/) {\n      beg = s.pending;  /* start of bytes to update crc */\n      //int val;\n\n      do {\n        if (s.pending === s.pending_buf_size) {\n          if (s.gzhead.hcrc && s.pending > beg) {\n            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);\n          }\n          flush_pending(strm);\n          beg = s.pending;\n          if (s.pending === s.pending_buf_size) {\n            val = 1;\n            break;\n          }\n        }\n        // JS specific: little magic to add zero terminator to end of string\n        if (s.gzindex < s.gzhead.comment.length) {\n          val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;\n        } else {\n          val = 0;\n        }\n        put_byte(s, val);\n      } while (val !== 0);\n\n      if (s.gzhead.hcrc && s.pending > beg) {\n        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);\n      }\n      if (val === 0) {\n        s.status = HCRC_STATE;\n      }\n    }\n    else {\n      s.status = HCRC_STATE;\n    }\n  }\n  if (s.status === HCRC_STATE) {\n    if (s.gzhead.hcrc) {\n      if (s.pending + 2 > s.pending_buf_size) {\n        flush_pending(strm);\n      }\n      if (s.pending + 2 <= s.pending_buf_size) {\n        put_byte(s, strm.adler & 0xff);\n        put_byte(s, (strm.adler >> 8) & 0xff);\n        strm.adler = 0; //crc32(0L, Z_NULL, 0);\n        s.status = BUSY_STATE;\n      }\n    }\n    else {\n      s.status = BUSY_STATE;\n    }\n  }\n//#endif\n\n  /* Flush as much pending output as possible */\n  if (s.pending !== 0) {\n    flush_pending(strm);\n    if (strm.avail_out === 0) {\n      /* Since avail_out is 0, deflate will be called again with\n       * more output space, but possibly with both pending and\n       * avail_in equal to zero. There won't be anything to do,\n       * but this is not an error situation so make sure we\n       * return OK instead of BUF_ERROR at next call of deflate:\n       */\n      s.last_flush = -1;\n      return Z_OK;\n    }\n\n    /* Make sure there is something to do and avoid duplicate consecutive\n     * flushes. For repeated and useless calls with Z_FINISH, we keep\n     * returning Z_STREAM_END instead of Z_BUF_ERROR.\n     */\n  } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&\n    flush !== Z_FINISH) {\n    return err(strm, Z_BUF_ERROR);\n  }\n\n  /* User must not provide more input after the first FINISH: */\n  if (s.status === FINISH_STATE && strm.avail_in !== 0) {\n    return err(strm, Z_BUF_ERROR);\n  }\n\n  /* Start a new block or continue the current one.\n   */\n  if (strm.avail_in !== 0 || s.lookahead !== 0 ||\n    (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) {\n    var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) :\n      (s.strategy === Z_RLE ? deflate_rle(s, flush) :\n        configuration_table[s.level].func(s, flush));\n\n    if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {\n      s.status = FINISH_STATE;\n    }\n    if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {\n      if (strm.avail_out === 0) {\n        s.last_flush = -1;\n        /* avoid BUF_ERROR next call, see above */\n      }\n      return Z_OK;\n      /* If flush != Z_NO_FLUSH && avail_out == 0, the next call\n       * of deflate should use the same flush parameter to make sure\n       * that the flush is complete. So we don't have to output an\n       * empty block here, this will be done at next call. This also\n       * ensures that for a very small output buffer, we emit at most\n       * one empty block.\n       */\n    }\n    if (bstate === BS_BLOCK_DONE) {\n      if (flush === Z_PARTIAL_FLUSH) {\n        trees._tr_align(s);\n      }\n      else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */\n\n        trees._tr_stored_block(s, 0, 0, false);\n        /* For a full flush, this empty block will be recognized\n         * as a special marker by inflate_sync().\n         */\n        if (flush === Z_FULL_FLUSH) {\n          /*** CLEAR_HASH(s); ***/             /* forget history */\n          zero(s.head); // Fill with NIL (= 0);\n\n          if (s.lookahead === 0) {\n            s.strstart = 0;\n            s.block_start = 0;\n            s.insert = 0;\n          }\n        }\n      }\n      flush_pending(strm);\n      if (strm.avail_out === 0) {\n        s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */\n        return Z_OK;\n      }\n    }\n  }\n  //Assert(strm->avail_out > 0, \"bug2\");\n  //if (strm.avail_out <= 0) { throw new Error(\"bug2\");}\n\n  if (flush !== Z_FINISH) { return Z_OK; }\n  if (s.wrap <= 0) { return Z_STREAM_END; }\n\n  /* Write the trailer */\n  if (s.wrap === 2) {\n    put_byte(s, strm.adler & 0xff);\n    put_byte(s, (strm.adler >> 8) & 0xff);\n    put_byte(s, (strm.adler >> 16) & 0xff);\n    put_byte(s, (strm.adler >> 24) & 0xff);\n    put_byte(s, strm.total_in & 0xff);\n    put_byte(s, (strm.total_in >> 8) & 0xff);\n    put_byte(s, (strm.total_in >> 16) & 0xff);\n    put_byte(s, (strm.total_in >> 24) & 0xff);\n  }\n  else\n  {\n    putShortMSB(s, strm.adler >>> 16);\n    putShortMSB(s, strm.adler & 0xffff);\n  }\n\n  flush_pending(strm);\n  /* If avail_out is zero, the application will call deflate again\n   * to flush the rest.\n   */\n  if (s.wrap > 0) { s.wrap = -s.wrap; }\n  /* write the trailer only once! */\n  return s.pending !== 0 ? Z_OK : Z_STREAM_END;\n}\n\nfunction deflateEnd(strm) {\n  var status;\n\n  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {\n    return Z_STREAM_ERROR;\n  }\n\n  status = strm.state.status;\n  if (status !== INIT_STATE &&\n    status !== EXTRA_STATE &&\n    status !== NAME_STATE &&\n    status !== COMMENT_STATE &&\n    status !== HCRC_STATE &&\n    status !== BUSY_STATE &&\n    status !== FINISH_STATE\n  ) {\n    return err(strm, Z_STREAM_ERROR);\n  }\n\n  strm.state = null;\n\n  return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;\n}\n\n\n/* =========================================================================\n * Initializes the compression dictionary from the given byte\n * sequence without producing any compressed output.\n */\nfunction deflateSetDictionary(strm, dictionary) {\n  var dictLength = dictionary.length;\n\n  var s;\n  var str, n;\n  var wrap;\n  var avail;\n  var next;\n  var input;\n  var tmpDict;\n\n  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {\n    return Z_STREAM_ERROR;\n  }\n\n  s = strm.state;\n  wrap = s.wrap;\n\n  if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {\n    return Z_STREAM_ERROR;\n  }\n\n  /* when using zlib wrappers, compute Adler-32 for provided dictionary */\n  if (wrap === 1) {\n    /* adler32(strm->adler, dictionary, dictLength); */\n    strm.adler = adler32(strm.adler, dictionary, dictLength, 0);\n  }\n\n  s.wrap = 0;   /* avoid computing Adler-32 in read_buf */\n\n  /* if dictionary would fill window, just replace the history */\n  if (dictLength >= s.w_size) {\n    if (wrap === 0) {            /* already empty otherwise */\n      /*** CLEAR_HASH(s); ***/\n      zero(s.head); // Fill with NIL (= 0);\n      s.strstart = 0;\n      s.block_start = 0;\n      s.insert = 0;\n    }\n    /* use the tail */\n    // dictionary = dictionary.slice(dictLength - s.w_size);\n    tmpDict = new utils.Buf8(s.w_size);\n    utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0);\n    dictionary = tmpDict;\n    dictLength = s.w_size;\n  }\n  /* insert dictionary into window and hash */\n  avail = strm.avail_in;\n  next = strm.next_in;\n  input = strm.input;\n  strm.avail_in = dictLength;\n  strm.next_in = 0;\n  strm.input = dictionary;\n  fill_window(s);\n  while (s.lookahead >= MIN_MATCH) {\n    str = s.strstart;\n    n = s.lookahead - (MIN_MATCH - 1);\n    do {\n      /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */\n      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;\n\n      s.prev[str & s.w_mask] = s.head[s.ins_h];\n\n      s.head[s.ins_h] = str;\n      str++;\n    } while (--n);\n    s.strstart = str;\n    s.lookahead = MIN_MATCH - 1;\n    fill_window(s);\n  }\n  s.strstart += s.lookahead;\n  s.block_start = s.strstart;\n  s.insert = s.lookahead;\n  s.lookahead = 0;\n  s.match_length = s.prev_length = MIN_MATCH - 1;\n  s.match_available = 0;\n  strm.next_in = next;\n  strm.input = input;\n  strm.avail_in = avail;\n  s.wrap = wrap;\n  return Z_OK;\n}\n\n\nexport { deflateInit, deflateInit2, deflateReset, deflateResetKeep, deflateSetHeader, deflate, deflateEnd, deflateSetDictionary };\nexport var deflateInfo = 'pako deflate (from Nodeca project)';\n\n/* Not implemented\nexports.deflateBound = deflateBound;\nexports.deflateCopy = deflateCopy;\nexports.deflateParams = deflateParams;\nexports.deflatePending = deflatePending;\nexports.deflatePrime = deflatePrime;\nexports.deflateTune = deflateTune;\n*/\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/gzheader.js",
    "content": "export default function GZheader() {\n  /* true if compressed data believed to be text */\n  this.text       = 0;\n  /* modification time */\n  this.time       = 0;\n  /* extra flags (not used when writing a gzip file) */\n  this.xflags     = 0;\n  /* operating system */\n  this.os         = 0;\n  /* pointer to extra field or Z_NULL if none */\n  this.extra      = null;\n  /* extra field length (valid if extra != Z_NULL) */\n  this.extra_len  = 0; // Actually, we don't need it in JS,\n                       // but leave for few code modifications\n\n  //\n  // Setup limits is not necessary because in js we should not preallocate memory\n  // for inflate use constant limit in 65536 bytes\n  //\n\n  /* space at extra (only when reading header) */\n  // this.extra_max  = 0;\n  /* pointer to zero-terminated file name or Z_NULL */\n  this.name       = '';\n  /* space at name (only when reading header) */\n  // this.name_max   = 0;\n  /* pointer to zero-terminated comment or Z_NULL */\n  this.comment    = '';\n  /* space at comment (only when reading header) */\n  // this.comm_max   = 0;\n  /* true if there was or will be a header crc */\n  this.hcrc       = 0;\n  /* true when done reading gzip header (not used when writing a gzip file) */\n  this.done       = false;\n}\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/inffast.js",
    "content": "// See state defs from inflate.js\nvar BAD = 30;       /* got a data error -- remain here until reset */\nvar TYPE = 12;      /* i: waiting for type bits, including last-flag bit */\n\n/*\n   Decode literal, length, and distance codes and write out the resulting\n   literal and match bytes until either not enough input or output is\n   available, an end-of-block is encountered, or a data error is encountered.\n   When large enough input and output buffers are supplied to inflate(), for\n   example, a 16K input buffer and a 64K output buffer, more than 95% of the\n   inflate execution time is spent in this routine.\n\n   Entry assumptions:\n\n        state.mode === LEN\n        strm.avail_in >= 6\n        strm.avail_out >= 258\n        start >= strm.avail_out\n        state.bits < 8\n\n   On return, state.mode is one of:\n\n        LEN -- ran out of enough output space or enough available input\n        TYPE -- reached end of block code, inflate() to interpret next block\n        BAD -- error in block data\n\n   Notes:\n\n    - The maximum input bits used by a length/distance pair is 15 bits for the\n      length code, 5 bits for the length extra, 15 bits for the distance code,\n      and 13 bits for the distance extra.  This totals 48 bits, or six bytes.\n      Therefore if strm.avail_in >= 6, then there is enough input to avoid\n      checking for available input while decoding.\n\n    - The maximum bytes that a single length/distance pair can output is 258\n      bytes, which is the maximum length that can be coded.  inflate_fast()\n      requires strm.avail_out >= 258 for each loop to avoid checking for\n      output space.\n */\nexport default function inflate_fast(strm, start) {\n  var state;\n  var _in;                    /* local strm.input */\n  var last;                   /* have enough input while in < last */\n  var _out;                   /* local strm.output */\n  var beg;                    /* inflate()'s initial strm.output */\n  var end;                    /* while out < end, enough space available */\n//#ifdef INFLATE_STRICT\n  var dmax;                   /* maximum distance from zlib header */\n//#endif\n  var wsize;                  /* window size or zero if not using window */\n  var whave;                  /* valid bytes in the window */\n  var wnext;                  /* window write index */\n  // Use `s_window` instead `window`, avoid conflict with instrumentation tools\n  var s_window;               /* allocated sliding window, if wsize != 0 */\n  var hold;                   /* local strm.hold */\n  var bits;                   /* local strm.bits */\n  var lcode;                  /* local strm.lencode */\n  var dcode;                  /* local strm.distcode */\n  var lmask;                  /* mask for first level of length codes */\n  var dmask;                  /* mask for first level of distance codes */\n  var here;                   /* retrieved table entry */\n  var op;                     /* code bits, operation, extra bits, or */\n                              /*  window position, window bytes to copy */\n  var len;                    /* match length, unused bytes */\n  var dist;                   /* match distance */\n  var from;                   /* where to copy match from */\n  var from_source;\n\n\n  var input, output; // JS specific, because we have no pointers\n\n  /* copy state to local variables */\n  state = strm.state;\n  //here = state.here;\n  _in = strm.next_in;\n  input = strm.input;\n  last = _in + (strm.avail_in - 5);\n  _out = strm.next_out;\n  output = strm.output;\n  beg = _out - (start - strm.avail_out);\n  end = _out + (strm.avail_out - 257);\n//#ifdef INFLATE_STRICT\n  dmax = state.dmax;\n//#endif\n  wsize = state.wsize;\n  whave = state.whave;\n  wnext = state.wnext;\n  s_window = state.window;\n  hold = state.hold;\n  bits = state.bits;\n  lcode = state.lencode;\n  dcode = state.distcode;\n  lmask = (1 << state.lenbits) - 1;\n  dmask = (1 << state.distbits) - 1;\n\n\n  /* decode literals and length/distances until end-of-block or not enough\n     input data or output space */\n\n  top:\n  do {\n    if (bits < 15) {\n      hold += input[_in++] << bits;\n      bits += 8;\n      hold += input[_in++] << bits;\n      bits += 8;\n    }\n\n    here = lcode[hold & lmask];\n\n    dolen:\n    for (;;) { // Goto emulation\n      op = here >>> 24/*here.bits*/;\n      hold >>>= op;\n      bits -= op;\n      op = (here >>> 16) & 0xff/*here.op*/;\n      if (op === 0) {                          /* literal */\n        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?\n        //        \"inflate:         literal '%c'\\n\" :\n        //        \"inflate:         literal 0x%02x\\n\", here.val));\n        output[_out++] = here & 0xffff/*here.val*/;\n      }\n      else if (op & 16) {                     /* length base */\n        len = here & 0xffff/*here.val*/;\n        op &= 15;                           /* number of extra bits */\n        if (op) {\n          if (bits < op) {\n            hold += input[_in++] << bits;\n            bits += 8;\n          }\n          len += hold & ((1 << op) - 1);\n          hold >>>= op;\n          bits -= op;\n        }\n        //Tracevv((stderr, \"inflate:         length %u\\n\", len));\n        if (bits < 15) {\n          hold += input[_in++] << bits;\n          bits += 8;\n          hold += input[_in++] << bits;\n          bits += 8;\n        }\n        here = dcode[hold & dmask];\n\n        dodist:\n        for (;;) { // goto emulation\n          op = here >>> 24/*here.bits*/;\n          hold >>>= op;\n          bits -= op;\n          op = (here >>> 16) & 0xff/*here.op*/;\n\n          if (op & 16) {                      /* distance base */\n            dist = here & 0xffff/*here.val*/;\n            op &= 15;                       /* number of extra bits */\n            if (bits < op) {\n              hold += input[_in++] << bits;\n              bits += 8;\n              if (bits < op) {\n                hold += input[_in++] << bits;\n                bits += 8;\n              }\n            }\n            dist += hold & ((1 << op) - 1);\n//#ifdef INFLATE_STRICT\n            if (dist > dmax) {\n              strm.msg = 'invalid distance too far back';\n              state.mode = BAD;\n              break top;\n            }\n//#endif\n            hold >>>= op;\n            bits -= op;\n            //Tracevv((stderr, \"inflate:         distance %u\\n\", dist));\n            op = _out - beg;                /* max distance in output */\n            if (dist > op) {                /* see if copy from window */\n              op = dist - op;               /* distance back in window */\n              if (op > whave) {\n                if (state.sane) {\n                  strm.msg = 'invalid distance too far back';\n                  state.mode = BAD;\n                  break top;\n                }\n\n// (!) This block is disabled in zlib defailts,\n// don't enable it for binary compatibility\n//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR\n//                if (len <= op - whave) {\n//                  do {\n//                    output[_out++] = 0;\n//                  } while (--len);\n//                  continue top;\n//                }\n//                len -= op - whave;\n//                do {\n//                  output[_out++] = 0;\n//                } while (--op > whave);\n//                if (op === 0) {\n//                  from = _out - dist;\n//                  do {\n//                    output[_out++] = output[from++];\n//                  } while (--len);\n//                  continue top;\n//                }\n//#endif\n              }\n              from = 0; // window index\n              from_source = s_window;\n              if (wnext === 0) {           /* very common case */\n                from += wsize - op;\n                if (op < len) {         /* some from window */\n                  len -= op;\n                  do {\n                    output[_out++] = s_window[from++];\n                  } while (--op);\n                  from = _out - dist;  /* rest from output */\n                  from_source = output;\n                }\n              }\n              else if (wnext < op) {      /* wrap around window */\n                from += wsize + wnext - op;\n                op -= wnext;\n                if (op < len) {         /* some from end of window */\n                  len -= op;\n                  do {\n                    output[_out++] = s_window[from++];\n                  } while (--op);\n                  from = 0;\n                  if (wnext < len) {  /* some from start of window */\n                    op = wnext;\n                    len -= op;\n                    do {\n                      output[_out++] = s_window[from++];\n                    } while (--op);\n                    from = _out - dist;      /* rest from output */\n                    from_source = output;\n                  }\n                }\n              }\n              else {                      /* contiguous in window */\n                from += wnext - op;\n                if (op < len) {         /* some from window */\n                  len -= op;\n                  do {\n                    output[_out++] = s_window[from++];\n                  } while (--op);\n                  from = _out - dist;  /* rest from output */\n                  from_source = output;\n                }\n              }\n              while (len > 2) {\n                output[_out++] = from_source[from++];\n                output[_out++] = from_source[from++];\n                output[_out++] = from_source[from++];\n                len -= 3;\n              }\n              if (len) {\n                output[_out++] = from_source[from++];\n                if (len > 1) {\n                  output[_out++] = from_source[from++];\n                }\n              }\n            }\n            else {\n              from = _out - dist;          /* copy direct from output */\n              do {                        /* minimum length is three */\n                output[_out++] = output[from++];\n                output[_out++] = output[from++];\n                output[_out++] = output[from++];\n                len -= 3;\n              } while (len > 2);\n              if (len) {\n                output[_out++] = output[from++];\n                if (len > 1) {\n                  output[_out++] = output[from++];\n                }\n              }\n            }\n          }\n          else if ((op & 64) === 0) {          /* 2nd level distance code */\n            here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];\n            continue dodist;\n          }\n          else {\n            strm.msg = 'invalid distance code';\n            state.mode = BAD;\n            break top;\n          }\n\n          break; // need to emulate goto via \"continue\"\n        }\n      }\n      else if ((op & 64) === 0) {              /* 2nd level length code */\n        here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];\n        continue dolen;\n      }\n      else if (op & 32) {                     /* end-of-block */\n        //Tracevv((stderr, \"inflate:         end of block\\n\"));\n        state.mode = TYPE;\n        break top;\n      }\n      else {\n        strm.msg = 'invalid literal/length code';\n        state.mode = BAD;\n        break top;\n      }\n\n      break; // need to emulate goto via \"continue\"\n    }\n  } while (_in < last && _out < end);\n\n  /* return unused bytes (on entry, bits < 8, so in won't go too far back) */\n  len = bits >> 3;\n  _in -= len;\n  bits -= len << 3;\n  hold &= (1 << bits) - 1;\n\n  /* update state and return */\n  strm.next_in = _in;\n  strm.next_out = _out;\n  strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));\n  strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));\n  state.hold = hold;\n  state.bits = bits;\n  return;\n};\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/inflate.js",
    "content": "import * as utils from \"../utils/common.js\";\nimport adler32 from \"./adler32.js\";\nimport crc32 from \"./crc32.js\";\nimport inflate_fast from \"./inffast.js\";\nimport inflate_table from \"./inftrees.js\";\n\nvar CODES = 0;\nvar LENS = 1;\nvar DISTS = 2;\n\n/* Public constants ==========================================================*/\n/* ===========================================================================*/\n\n\n/* Allowed flush values; see deflate() and inflate() below for details */\n//export const Z_NO_FLUSH      = 0;\n//export const Z_PARTIAL_FLUSH = 1;\n//export const Z_SYNC_FLUSH    = 2;\n//export const Z_FULL_FLUSH    = 3;\nexport const Z_FINISH        = 4;\nexport const Z_BLOCK         = 5;\nexport const Z_TREES         = 6;\n\n\n/* Return codes for the compression/decompression functions. Negative values\n * are errors, positive values are used for special but normal events.\n */\nexport const Z_OK            = 0;\nexport const Z_STREAM_END    = 1;\nexport const Z_NEED_DICT     = 2;\n//export const Z_ERRNO         = -1;\nexport const Z_STREAM_ERROR  = -2;\nexport const Z_DATA_ERROR    = -3;\nexport const Z_MEM_ERROR     = -4;\nexport const Z_BUF_ERROR     = -5;\n//export const Z_VERSION_ERROR = -6;\n\n/* The deflate compression method */\nexport const Z_DEFLATED  = 8;\n\n\n/* STATES ====================================================================*/\n/* ===========================================================================*/\n\n\nvar    HEAD = 1;       /* i: waiting for magic header */\nvar    FLAGS = 2;      /* i: waiting for method and flags (gzip) */\nvar    TIME = 3;       /* i: waiting for modification time (gzip) */\nvar    OS = 4;         /* i: waiting for extra flags and operating system (gzip) */\nvar    EXLEN = 5;      /* i: waiting for extra length (gzip) */\nvar    EXTRA = 6;      /* i: waiting for extra bytes (gzip) */\nvar    NAME = 7;       /* i: waiting for end of file name (gzip) */\nvar    COMMENT = 8;    /* i: waiting for end of comment (gzip) */\nvar    HCRC = 9;       /* i: waiting for header crc (gzip) */\nvar    DICTID = 10;    /* i: waiting for dictionary check value */\nvar    DICT = 11;      /* waiting for inflateSetDictionary() call */\nvar        TYPE = 12;      /* i: waiting for type bits, including last-flag bit */\nvar        TYPEDO = 13;    /* i: same, but skip check to exit inflate on new block */\nvar        STORED = 14;    /* i: waiting for stored size (length and complement) */\nvar        COPY_ = 15;     /* i/o: same as COPY below, but only first time in */\nvar        COPY = 16;      /* i/o: waiting for input or output to copy stored block */\nvar        TABLE = 17;     /* i: waiting for dynamic block table lengths */\nvar        LENLENS = 18;   /* i: waiting for code length code lengths */\nvar        CODELENS = 19;  /* i: waiting for length/lit and distance code lengths */\nvar            LEN_ = 20;      /* i: same as LEN below, but only first time in */\nvar            LEN = 21;       /* i: waiting for length/lit/eob code */\nvar            LENEXT = 22;    /* i: waiting for length extra bits */\nvar            DIST = 23;      /* i: waiting for distance code */\nvar            DISTEXT = 24;   /* i: waiting for distance extra bits */\nvar            MATCH = 25;     /* o: waiting for output space to copy string */\nvar            LIT = 26;       /* o: waiting for output space to write literal */\nvar    CHECK = 27;     /* i: waiting for 32-bit check value */\nvar    LENGTH = 28;    /* i: waiting for 32-bit length (gzip) */\nvar    DONE = 29;      /* finished check, done -- remain here until reset */\nvar    BAD = 30;       /* got a data error -- remain here until reset */\nvar    MEM = 31;       /* got an inflate() memory error -- remain here until reset */\nvar    SYNC = 32;      /* looking for synchronization bytes to restart inflate() */\n\n/* ===========================================================================*/\n\n\n\nvar ENOUGH_LENS = 852;\nvar ENOUGH_DISTS = 592;\n//var ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);\n\nvar MAX_WBITS = 15;\n/* 32K LZ77 window */\nvar DEF_WBITS = MAX_WBITS;\n\n\nfunction zswap32(q) {\n  return  (((q >>> 24) & 0xff) +\n          ((q >>> 8) & 0xff00) +\n          ((q & 0xff00) << 8) +\n          ((q & 0xff) << 24));\n}\n\n\nfunction InflateState() {\n  this.mode = 0;             /* current inflate mode */\n  this.last = false;          /* true if processing last block */\n  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */\n  this.havedict = false;      /* true if dictionary provided */\n  this.flags = 0;             /* gzip header method and flags (0 if zlib) */\n  this.dmax = 0;              /* zlib header max distance (INFLATE_STRICT) */\n  this.check = 0;             /* protected copy of check value */\n  this.total = 0;             /* protected copy of output count */\n  // TODO: may be {}\n  this.head = null;           /* where to save gzip header information */\n\n  /* sliding window */\n  this.wbits = 0;             /* log base 2 of requested window size */\n  this.wsize = 0;             /* window size or zero if not using window */\n  this.whave = 0;             /* valid bytes in the window */\n  this.wnext = 0;             /* window write index */\n  this.window = null;         /* allocated sliding window, if needed */\n\n  /* bit accumulator */\n  this.hold = 0;              /* input bit accumulator */\n  this.bits = 0;              /* number of bits in \"in\" */\n\n  /* for string and stored block copying */\n  this.length = 0;            /* literal or length of data to copy */\n  this.offset = 0;            /* distance back to copy string from */\n\n  /* for table and code decoding */\n  this.extra = 0;             /* extra bits needed */\n\n  /* fixed and dynamic code tables */\n  this.lencode = null;          /* starting table for length/literal codes */\n  this.distcode = null;         /* starting table for distance codes */\n  this.lenbits = 0;           /* index bits for lencode */\n  this.distbits = 0;          /* index bits for distcode */\n\n  /* dynamic table building */\n  this.ncode = 0;             /* number of code length code lengths */\n  this.nlen = 0;              /* number of length code lengths */\n  this.ndist = 0;             /* number of distance code lengths */\n  this.have = 0;              /* number of code lengths in lens[] */\n  this.next = null;              /* next available space in codes[] */\n\n  this.lens = new utils.Buf16(320); /* temporary storage for code lengths */\n  this.work = new utils.Buf16(288); /* work area for code table building */\n\n  /*\n   because we don't have pointers in js, we use lencode and distcode directly\n   as buffers so we don't need codes\n  */\n  //this.codes = new utils.Buf32(ENOUGH);       /* space for code tables */\n  this.lendyn = null;              /* dynamic table for length/literal codes (JS specific) */\n  this.distdyn = null;             /* dynamic table for distance codes (JS specific) */\n  this.sane = 0;                   /* if false, allow invalid distance too far */\n  this.back = 0;                   /* bits back of last unprocessed length/lit */\n  this.was = 0;                    /* initial length of match */\n}\n\nfunction inflateResetKeep(strm) {\n  var state;\n\n  if (!strm || !strm.state) { return Z_STREAM_ERROR; }\n  state = strm.state;\n  strm.total_in = strm.total_out = state.total = 0;\n  strm.msg = ''; /*Z_NULL*/\n  if (state.wrap) {       /* to support ill-conceived Java test suite */\n    strm.adler = state.wrap & 1;\n  }\n  state.mode = HEAD;\n  state.last = 0;\n  state.havedict = 0;\n  state.dmax = 32768;\n  state.head = null/*Z_NULL*/;\n  state.hold = 0;\n  state.bits = 0;\n  //state.lencode = state.distcode = state.next = state.codes;\n  state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS);\n  state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);\n\n  state.sane = 1;\n  state.back = -1;\n  //Tracev((stderr, \"inflate: reset\\n\"));\n  return Z_OK;\n}\n\nfunction inflateReset(strm) {\n  var state;\n\n  if (!strm || !strm.state) { return Z_STREAM_ERROR; }\n  state = strm.state;\n  state.wsize = 0;\n  state.whave = 0;\n  state.wnext = 0;\n  return inflateResetKeep(strm);\n\n}\n\nfunction inflateReset2(strm, windowBits) {\n  var wrap;\n  var state;\n\n  /* get the state */\n  if (!strm || !strm.state) { return Z_STREAM_ERROR; }\n  state = strm.state;\n\n  /* extract wrap request from windowBits parameter */\n  if (windowBits < 0) {\n    wrap = 0;\n    windowBits = -windowBits;\n  }\n  else {\n    wrap = (windowBits >> 4) + 1;\n    if (windowBits < 48) {\n      windowBits &= 15;\n    }\n  }\n\n  /* set number of window bits, free window if different */\n  if (windowBits && (windowBits < 8 || windowBits > 15)) {\n    return Z_STREAM_ERROR;\n  }\n  if (state.window !== null && state.wbits !== windowBits) {\n    state.window = null;\n  }\n\n  /* update state and reset the rest of it */\n  state.wrap = wrap;\n  state.wbits = windowBits;\n  return inflateReset(strm);\n}\n\nfunction inflateInit2(strm, windowBits) {\n  var ret;\n  var state;\n\n  if (!strm) { return Z_STREAM_ERROR; }\n  //strm.msg = Z_NULL;                 /* in case we return an error */\n\n  state = new InflateState();\n\n  //if (state === Z_NULL) return Z_MEM_ERROR;\n  //Tracev((stderr, \"inflate: allocated\\n\"));\n  strm.state = state;\n  state.window = null/*Z_NULL*/;\n  ret = inflateReset2(strm, windowBits);\n  if (ret !== Z_OK) {\n    strm.state = null/*Z_NULL*/;\n  }\n  return ret;\n}\n\nfunction inflateInit(strm) {\n  return inflateInit2(strm, DEF_WBITS);\n}\n\n\n/*\n Return state with length and distance decoding tables and index sizes set to\n fixed code decoding.  Normally this returns fixed tables from inffixed.h.\n If BUILDFIXED is defined, then instead this routine builds the tables the\n first time it's called, and returns those tables the first time and\n thereafter.  This reduces the size of the code by about 2K bytes, in\n exchange for a little execution time.  However, BUILDFIXED should not be\n used for threaded applications, since the rewriting of the tables and virgin\n may not be thread-safe.\n */\nvar virgin = true;\n\nvar lenfix, distfix; // We have no pointers in JS, so keep tables separate\n\nfunction fixedtables(state) {\n  /* build fixed huffman tables if first call (may not be thread safe) */\n  if (virgin) {\n    var sym;\n\n    lenfix = new utils.Buf32(512);\n    distfix = new utils.Buf32(32);\n\n    /* literal/length table */\n    sym = 0;\n    while (sym < 144) { state.lens[sym++] = 8; }\n    while (sym < 256) { state.lens[sym++] = 9; }\n    while (sym < 280) { state.lens[sym++] = 7; }\n    while (sym < 288) { state.lens[sym++] = 8; }\n\n    inflate_table(LENS,  state.lens, 0, 288, lenfix,   0, state.work, { bits: 9 });\n\n    /* distance table */\n    sym = 0;\n    while (sym < 32) { state.lens[sym++] = 5; }\n\n    inflate_table(DISTS, state.lens, 0, 32,   distfix, 0, state.work, { bits: 5 });\n\n    /* do this just once */\n    virgin = false;\n  }\n\n  state.lencode = lenfix;\n  state.lenbits = 9;\n  state.distcode = distfix;\n  state.distbits = 5;\n}\n\n\n/*\n Update the window with the last wsize (normally 32K) bytes written before\n returning.  If window does not exist yet, create it.  This is only called\n when a window is already in use, or when output has been written during this\n inflate call, but the end of the deflate stream has not been reached yet.\n It is also called to create a window for dictionary data when a dictionary\n is loaded.\n\n Providing output buffers larger than 32K to inflate() should provide a speed\n advantage, since only the last 32K of output is copied to the sliding window\n upon return from inflate(), and since all distances after the first 32K of\n output will fall in the output data, making match copies simpler and faster.\n The advantage may be dependent on the size of the processor's data caches.\n */\nfunction updatewindow(strm, src, end, copy) {\n  var dist;\n  var state = strm.state;\n\n  /* if it hasn't been done already, allocate space for the window */\n  if (state.window === null) {\n    state.wsize = 1 << state.wbits;\n    state.wnext = 0;\n    state.whave = 0;\n\n    state.window = new utils.Buf8(state.wsize);\n  }\n\n  /* copy state->wsize or less output bytes into the circular window */\n  if (copy >= state.wsize) {\n    utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0);\n    state.wnext = 0;\n    state.whave = state.wsize;\n  }\n  else {\n    dist = state.wsize - state.wnext;\n    if (dist > copy) {\n      dist = copy;\n    }\n    //zmemcpy(state->window + state->wnext, end - copy, dist);\n    utils.arraySet(state.window, src, end - copy, dist, state.wnext);\n    copy -= dist;\n    if (copy) {\n      //zmemcpy(state->window, end - copy, copy);\n      utils.arraySet(state.window, src, end - copy, copy, 0);\n      state.wnext = copy;\n      state.whave = state.wsize;\n    }\n    else {\n      state.wnext += dist;\n      if (state.wnext === state.wsize) { state.wnext = 0; }\n      if (state.whave < state.wsize) { state.whave += dist; }\n    }\n  }\n  return 0;\n}\n\nfunction inflate(strm, flush) {\n  var state;\n  var input, output;          // input/output buffers\n  var next;                   /* next input INDEX */\n  var put;                    /* next output INDEX */\n  var have, left;             /* available input and output */\n  var hold;                   /* bit buffer */\n  var bits;                   /* bits in bit buffer */\n  var _in, _out;              /* save starting available input and output */\n  var copy;                   /* number of stored or match bytes to copy */\n  var from;                   /* where to copy match bytes from */\n  var from_source;\n  var here = 0;               /* current decoding table entry */\n  var here_bits, here_op, here_val; // paked \"here\" denormalized (JS specific)\n  //var last;                   /* parent table entry */\n  var last_bits, last_op, last_val; // paked \"last\" denormalized (JS specific)\n  var len;                    /* length to copy for repeats, bits to drop */\n  var ret;                    /* return code */\n  var hbuf = new utils.Buf8(4);    /* buffer for gzip header crc calculation */\n  var opts;\n\n  var n; // temporary var for NEED_BITS\n\n  var order = /* permutation of code lengths */\n    [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];\n\n\n  if (!strm || !strm.state || !strm.output ||\n      (!strm.input && strm.avail_in !== 0)) {\n    return Z_STREAM_ERROR;\n  }\n\n  state = strm.state;\n  if (state.mode === TYPE) { state.mode = TYPEDO; }    /* skip check */\n\n\n  //--- LOAD() ---\n  put = strm.next_out;\n  output = strm.output;\n  left = strm.avail_out;\n  next = strm.next_in;\n  input = strm.input;\n  have = strm.avail_in;\n  hold = state.hold;\n  bits = state.bits;\n  //---\n\n  _in = have;\n  _out = left;\n  ret = Z_OK;\n\n  inf_leave: // goto emulation\n  for (;;) {\n    switch (state.mode) {\n    case HEAD:\n      if (state.wrap === 0) {\n        state.mode = TYPEDO;\n        break;\n      }\n      //=== NEEDBITS(16);\n      while (bits < 16) {\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n      }\n      //===//\n      if ((state.wrap & 2) && hold === 0x8b1f) {  /* gzip header */\n        state.check = 0/*crc32(0L, Z_NULL, 0)*/;\n        //=== CRC2(state.check, hold);\n        hbuf[0] = hold & 0xff;\n        hbuf[1] = (hold >>> 8) & 0xff;\n        state.check = crc32(state.check, hbuf, 2, 0);\n        //===//\n\n        //=== INITBITS();\n        hold = 0;\n        bits = 0;\n        //===//\n        state.mode = FLAGS;\n        break;\n      }\n      state.flags = 0;           /* expect zlib header */\n      if (state.head) {\n        state.head.done = false;\n      }\n      if (!(state.wrap & 1) ||   /* check if zlib header allowed */\n        (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {\n        strm.msg = 'incorrect header check';\n        state.mode = BAD;\n        break;\n      }\n      if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) {\n        strm.msg = 'unknown compression method';\n        state.mode = BAD;\n        break;\n      }\n      //--- DROPBITS(4) ---//\n      hold >>>= 4;\n      bits -= 4;\n      //---//\n      len = (hold & 0x0f)/*BITS(4)*/ + 8;\n      if (state.wbits === 0) {\n        state.wbits = len;\n      }\n      else if (len > state.wbits) {\n        strm.msg = 'invalid window size';\n        state.mode = BAD;\n        break;\n      }\n      state.dmax = 1 << len;\n      //Tracev((stderr, \"inflate:   zlib header ok\\n\"));\n      strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;\n      state.mode = hold & 0x200 ? DICTID : TYPE;\n      //=== INITBITS();\n      hold = 0;\n      bits = 0;\n      //===//\n      break;\n    case FLAGS:\n      //=== NEEDBITS(16); */\n      while (bits < 16) {\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n      }\n      //===//\n      state.flags = hold;\n      if ((state.flags & 0xff) !== Z_DEFLATED) {\n        strm.msg = 'unknown compression method';\n        state.mode = BAD;\n        break;\n      }\n      if (state.flags & 0xe000) {\n        strm.msg = 'unknown header flags set';\n        state.mode = BAD;\n        break;\n      }\n      if (state.head) {\n        state.head.text = ((hold >> 8) & 1);\n      }\n      if (state.flags & 0x0200) {\n        //=== CRC2(state.check, hold);\n        hbuf[0] = hold & 0xff;\n        hbuf[1] = (hold >>> 8) & 0xff;\n        state.check = crc32(state.check, hbuf, 2, 0);\n        //===//\n      }\n      //=== INITBITS();\n      hold = 0;\n      bits = 0;\n      //===//\n      state.mode = TIME;\n      /* falls through */\n    case TIME:\n      //=== NEEDBITS(32); */\n      while (bits < 32) {\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n      }\n      //===//\n      if (state.head) {\n        state.head.time = hold;\n      }\n      if (state.flags & 0x0200) {\n        //=== CRC4(state.check, hold)\n        hbuf[0] = hold & 0xff;\n        hbuf[1] = (hold >>> 8) & 0xff;\n        hbuf[2] = (hold >>> 16) & 0xff;\n        hbuf[3] = (hold >>> 24) & 0xff;\n        state.check = crc32(state.check, hbuf, 4, 0);\n        //===\n      }\n      //=== INITBITS();\n      hold = 0;\n      bits = 0;\n      //===//\n      state.mode = OS;\n      /* falls through */\n    case OS:\n      //=== NEEDBITS(16); */\n      while (bits < 16) {\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n      }\n      //===//\n      if (state.head) {\n        state.head.xflags = (hold & 0xff);\n        state.head.os = (hold >> 8);\n      }\n      if (state.flags & 0x0200) {\n        //=== CRC2(state.check, hold);\n        hbuf[0] = hold & 0xff;\n        hbuf[1] = (hold >>> 8) & 0xff;\n        state.check = crc32(state.check, hbuf, 2, 0);\n        //===//\n      }\n      //=== INITBITS();\n      hold = 0;\n      bits = 0;\n      //===//\n      state.mode = EXLEN;\n      /* falls through */\n    case EXLEN:\n      if (state.flags & 0x0400) {\n        //=== NEEDBITS(16); */\n        while (bits < 16) {\n          if (have === 0) { break inf_leave; }\n          have--;\n          hold += input[next++] << bits;\n          bits += 8;\n        }\n        //===//\n        state.length = hold;\n        if (state.head) {\n          state.head.extra_len = hold;\n        }\n        if (state.flags & 0x0200) {\n          //=== CRC2(state.check, hold);\n          hbuf[0] = hold & 0xff;\n          hbuf[1] = (hold >>> 8) & 0xff;\n          state.check = crc32(state.check, hbuf, 2, 0);\n          //===//\n        }\n        //=== INITBITS();\n        hold = 0;\n        bits = 0;\n        //===//\n      }\n      else if (state.head) {\n        state.head.extra = null/*Z_NULL*/;\n      }\n      state.mode = EXTRA;\n      /* falls through */\n    case EXTRA:\n      if (state.flags & 0x0400) {\n        copy = state.length;\n        if (copy > have) { copy = have; }\n        if (copy) {\n          if (state.head) {\n            len = state.head.extra_len - state.length;\n            if (!state.head.extra) {\n              // Use untyped array for more conveniend processing later\n              state.head.extra = new Array(state.head.extra_len);\n            }\n            utils.arraySet(\n              state.head.extra,\n              input,\n              next,\n              // extra field is limited to 65536 bytes\n              // - no need for additional size check\n              copy,\n              /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/\n              len\n            );\n            //zmemcpy(state.head.extra + len, next,\n            //        len + copy > state.head.extra_max ?\n            //        state.head.extra_max - len : copy);\n          }\n          if (state.flags & 0x0200) {\n            state.check = crc32(state.check, input, copy, next);\n          }\n          have -= copy;\n          next += copy;\n          state.length -= copy;\n        }\n        if (state.length) { break inf_leave; }\n      }\n      state.length = 0;\n      state.mode = NAME;\n      /* falls through */\n    case NAME:\n      if (state.flags & 0x0800) {\n        if (have === 0) { break inf_leave; }\n        copy = 0;\n        do {\n          // TODO: 2 or 1 bytes?\n          len = input[next + copy++];\n          /* use constant limit because in js we should not preallocate memory */\n          if (state.head && len &&\n              (state.length < 65536 /*state.head.name_max*/)) {\n            state.head.name += String.fromCharCode(len);\n          }\n        } while (len && copy < have);\n\n        if (state.flags & 0x0200) {\n          state.check = crc32(state.check, input, copy, next);\n        }\n        have -= copy;\n        next += copy;\n        if (len) { break inf_leave; }\n      }\n      else if (state.head) {\n        state.head.name = null;\n      }\n      state.length = 0;\n      state.mode = COMMENT;\n      /* falls through */\n    case COMMENT:\n      if (state.flags & 0x1000) {\n        if (have === 0) { break inf_leave; }\n        copy = 0;\n        do {\n          len = input[next + copy++];\n          /* use constant limit because in js we should not preallocate memory */\n          if (state.head && len &&\n              (state.length < 65536 /*state.head.comm_max*/)) {\n            state.head.comment += String.fromCharCode(len);\n          }\n        } while (len && copy < have);\n        if (state.flags & 0x0200) {\n          state.check = crc32(state.check, input, copy, next);\n        }\n        have -= copy;\n        next += copy;\n        if (len) { break inf_leave; }\n      }\n      else if (state.head) {\n        state.head.comment = null;\n      }\n      state.mode = HCRC;\n      /* falls through */\n    case HCRC:\n      if (state.flags & 0x0200) {\n        //=== NEEDBITS(16); */\n        while (bits < 16) {\n          if (have === 0) { break inf_leave; }\n          have--;\n          hold += input[next++] << bits;\n          bits += 8;\n        }\n        //===//\n        if (hold !== (state.check & 0xffff)) {\n          strm.msg = 'header crc mismatch';\n          state.mode = BAD;\n          break;\n        }\n        //=== INITBITS();\n        hold = 0;\n        bits = 0;\n        //===//\n      }\n      if (state.head) {\n        state.head.hcrc = ((state.flags >> 9) & 1);\n        state.head.done = true;\n      }\n      strm.adler = state.check = 0;\n      state.mode = TYPE;\n      break;\n    case DICTID:\n      //=== NEEDBITS(32); */\n      while (bits < 32) {\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n      }\n      //===//\n      strm.adler = state.check = zswap32(hold);\n      //=== INITBITS();\n      hold = 0;\n      bits = 0;\n      //===//\n      state.mode = DICT;\n      /* falls through */\n    case DICT:\n      if (state.havedict === 0) {\n        //--- RESTORE() ---\n        strm.next_out = put;\n        strm.avail_out = left;\n        strm.next_in = next;\n        strm.avail_in = have;\n        state.hold = hold;\n        state.bits = bits;\n        //---\n        return Z_NEED_DICT;\n      }\n      strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;\n      state.mode = TYPE;\n      /* falls through */\n    case TYPE:\n      if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; }\n      /* falls through */\n    case TYPEDO:\n      if (state.last) {\n        //--- BYTEBITS() ---//\n        hold >>>= bits & 7;\n        bits -= bits & 7;\n        //---//\n        state.mode = CHECK;\n        break;\n      }\n      //=== NEEDBITS(3); */\n      while (bits < 3) {\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n      }\n      //===//\n      state.last = (hold & 0x01)/*BITS(1)*/;\n      //--- DROPBITS(1) ---//\n      hold >>>= 1;\n      bits -= 1;\n      //---//\n\n      switch ((hold & 0x03)/*BITS(2)*/) {\n      case 0:                             /* stored block */\n        //Tracev((stderr, \"inflate:     stored block%s\\n\",\n        //        state.last ? \" (last)\" : \"\"));\n        state.mode = STORED;\n        break;\n      case 1:                             /* fixed block */\n        fixedtables(state);\n        //Tracev((stderr, \"inflate:     fixed codes block%s\\n\",\n        //        state.last ? \" (last)\" : \"\"));\n        state.mode = LEN_;             /* decode codes */\n        if (flush === Z_TREES) {\n          //--- DROPBITS(2) ---//\n          hold >>>= 2;\n          bits -= 2;\n          //---//\n          break inf_leave;\n        }\n        break;\n      case 2:                             /* dynamic block */\n        //Tracev((stderr, \"inflate:     dynamic codes block%s\\n\",\n        //        state.last ? \" (last)\" : \"\"));\n        state.mode = TABLE;\n        break;\n      case 3:\n        strm.msg = 'invalid block type';\n        state.mode = BAD;\n      }\n      //--- DROPBITS(2) ---//\n      hold >>>= 2;\n      bits -= 2;\n      //---//\n      break;\n    case STORED:\n      //--- BYTEBITS() ---// /* go to byte boundary */\n      hold >>>= bits & 7;\n      bits -= bits & 7;\n      //---//\n      //=== NEEDBITS(32); */\n      while (bits < 32) {\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n      }\n      //===//\n      if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {\n        strm.msg = 'invalid stored block lengths';\n        state.mode = BAD;\n        break;\n      }\n      state.length = hold & 0xffff;\n      //Tracev((stderr, \"inflate:       stored length %u\\n\",\n      //        state.length));\n      //=== INITBITS();\n      hold = 0;\n      bits = 0;\n      //===//\n      state.mode = COPY_;\n      if (flush === Z_TREES) { break inf_leave; }\n      /* falls through */\n    case COPY_:\n      state.mode = COPY;\n      /* falls through */\n    case COPY:\n      copy = state.length;\n      if (copy) {\n        if (copy > have) { copy = have; }\n        if (copy > left) { copy = left; }\n        if (copy === 0) { break inf_leave; }\n        //--- zmemcpy(put, next, copy); ---\n        utils.arraySet(output, input, next, copy, put);\n        //---//\n        have -= copy;\n        next += copy;\n        left -= copy;\n        put += copy;\n        state.length -= copy;\n        break;\n      }\n      //Tracev((stderr, \"inflate:       stored end\\n\"));\n      state.mode = TYPE;\n      break;\n    case TABLE:\n      //=== NEEDBITS(14); */\n      while (bits < 14) {\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n      }\n      //===//\n      state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;\n      //--- DROPBITS(5) ---//\n      hold >>>= 5;\n      bits -= 5;\n      //---//\n      state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;\n      //--- DROPBITS(5) ---//\n      hold >>>= 5;\n      bits -= 5;\n      //---//\n      state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;\n      //--- DROPBITS(4) ---//\n      hold >>>= 4;\n      bits -= 4;\n      //---//\n//#ifndef PKZIP_BUG_WORKAROUND\n      if (state.nlen > 286 || state.ndist > 30) {\n        strm.msg = 'too many length or distance symbols';\n        state.mode = BAD;\n        break;\n      }\n//#endif\n      //Tracev((stderr, \"inflate:       table sizes ok\\n\"));\n      state.have = 0;\n      state.mode = LENLENS;\n      /* falls through */\n    case LENLENS:\n      while (state.have < state.ncode) {\n        //=== NEEDBITS(3);\n        while (bits < 3) {\n          if (have === 0) { break inf_leave; }\n          have--;\n          hold += input[next++] << bits;\n          bits += 8;\n        }\n        //===//\n        state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);\n        //--- DROPBITS(3) ---//\n        hold >>>= 3;\n        bits -= 3;\n        //---//\n      }\n      while (state.have < 19) {\n        state.lens[order[state.have++]] = 0;\n      }\n      // We have separate tables & no pointers. 2 commented lines below not needed.\n      //state.next = state.codes;\n      //state.lencode = state.next;\n      // Switch to use dynamic table\n      state.lencode = state.lendyn;\n      state.lenbits = 7;\n\n      opts = { bits: state.lenbits };\n      ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);\n      state.lenbits = opts.bits;\n\n      if (ret) {\n        strm.msg = 'invalid code lengths set';\n        state.mode = BAD;\n        break;\n      }\n      //Tracev((stderr, \"inflate:       code lengths ok\\n\"));\n      state.have = 0;\n      state.mode = CODELENS;\n      /* falls through */\n    case CODELENS:\n      while (state.have < state.nlen + state.ndist) {\n        for (;;) {\n          here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/\n          here_bits = here >>> 24;\n          here_op = (here >>> 16) & 0xff;\n          here_val = here & 0xffff;\n\n          if ((here_bits) <= bits) { break; }\n          //--- PULLBYTE() ---//\n          if (have === 0) { break inf_leave; }\n          have--;\n          hold += input[next++] << bits;\n          bits += 8;\n          //---//\n        }\n        if (here_val < 16) {\n          //--- DROPBITS(here.bits) ---//\n          hold >>>= here_bits;\n          bits -= here_bits;\n          //---//\n          state.lens[state.have++] = here_val;\n        }\n        else {\n          if (here_val === 16) {\n            //=== NEEDBITS(here.bits + 2);\n            n = here_bits + 2;\n            while (bits < n) {\n              if (have === 0) { break inf_leave; }\n              have--;\n              hold += input[next++] << bits;\n              bits += 8;\n            }\n            //===//\n            //--- DROPBITS(here.bits) ---//\n            hold >>>= here_bits;\n            bits -= here_bits;\n            //---//\n            if (state.have === 0) {\n              strm.msg = 'invalid bit length repeat';\n              state.mode = BAD;\n              break;\n            }\n            len = state.lens[state.have - 1];\n            copy = 3 + (hold & 0x03);//BITS(2);\n            //--- DROPBITS(2) ---//\n            hold >>>= 2;\n            bits -= 2;\n            //---//\n          }\n          else if (here_val === 17) {\n            //=== NEEDBITS(here.bits + 3);\n            n = here_bits + 3;\n            while (bits < n) {\n              if (have === 0) { break inf_leave; }\n              have--;\n              hold += input[next++] << bits;\n              bits += 8;\n            }\n            //===//\n            //--- DROPBITS(here.bits) ---//\n            hold >>>= here_bits;\n            bits -= here_bits;\n            //---//\n            len = 0;\n            copy = 3 + (hold & 0x07);//BITS(3);\n            //--- DROPBITS(3) ---//\n            hold >>>= 3;\n            bits -= 3;\n            //---//\n          }\n          else {\n            //=== NEEDBITS(here.bits + 7);\n            n = here_bits + 7;\n            while (bits < n) {\n              if (have === 0) { break inf_leave; }\n              have--;\n              hold += input[next++] << bits;\n              bits += 8;\n            }\n            //===//\n            //--- DROPBITS(here.bits) ---//\n            hold >>>= here_bits;\n            bits -= here_bits;\n            //---//\n            len = 0;\n            copy = 11 + (hold & 0x7f);//BITS(7);\n            //--- DROPBITS(7) ---//\n            hold >>>= 7;\n            bits -= 7;\n            //---//\n          }\n          if (state.have + copy > state.nlen + state.ndist) {\n            strm.msg = 'invalid bit length repeat';\n            state.mode = BAD;\n            break;\n          }\n          while (copy--) {\n            state.lens[state.have++] = len;\n          }\n        }\n      }\n\n      /* handle error breaks in while */\n      if (state.mode === BAD) { break; }\n\n      /* check for end-of-block code (better have one) */\n      if (state.lens[256] === 0) {\n        strm.msg = 'invalid code -- missing end-of-block';\n        state.mode = BAD;\n        break;\n      }\n\n      /* build code tables -- note: do not change the lenbits or distbits\n         values here (9 and 6) without reading the comments in inftrees.h\n         concerning the ENOUGH constants, which depend on those values */\n      state.lenbits = 9;\n\n      opts = { bits: state.lenbits };\n      ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);\n      // We have separate tables & no pointers. 2 commented lines below not needed.\n      // state.next_index = opts.table_index;\n      state.lenbits = opts.bits;\n      // state.lencode = state.next;\n\n      if (ret) {\n        strm.msg = 'invalid literal/lengths set';\n        state.mode = BAD;\n        break;\n      }\n\n      state.distbits = 6;\n      //state.distcode.copy(state.codes);\n      // Switch to use dynamic table\n      state.distcode = state.distdyn;\n      opts = { bits: state.distbits };\n      ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);\n      // We have separate tables & no pointers. 2 commented lines below not needed.\n      // state.next_index = opts.table_index;\n      state.distbits = opts.bits;\n      // state.distcode = state.next;\n\n      if (ret) {\n        strm.msg = 'invalid distances set';\n        state.mode = BAD;\n        break;\n      }\n      //Tracev((stderr, 'inflate:       codes ok\\n'));\n      state.mode = LEN_;\n      if (flush === Z_TREES) { break inf_leave; }\n      /* falls through */\n    case LEN_:\n      state.mode = LEN;\n      /* falls through */\n    case LEN:\n      if (have >= 6 && left >= 258) {\n        //--- RESTORE() ---\n        strm.next_out = put;\n        strm.avail_out = left;\n        strm.next_in = next;\n        strm.avail_in = have;\n        state.hold = hold;\n        state.bits = bits;\n        //---\n        inflate_fast(strm, _out);\n        //--- LOAD() ---\n        put = strm.next_out;\n        output = strm.output;\n        left = strm.avail_out;\n        next = strm.next_in;\n        input = strm.input;\n        have = strm.avail_in;\n        hold = state.hold;\n        bits = state.bits;\n        //---\n\n        if (state.mode === TYPE) {\n          state.back = -1;\n        }\n        break;\n      }\n      state.back = 0;\n      for (;;) {\n        here = state.lencode[hold & ((1 << state.lenbits) - 1)];  /*BITS(state.lenbits)*/\n        here_bits = here >>> 24;\n        here_op = (here >>> 16) & 0xff;\n        here_val = here & 0xffff;\n\n        if (here_bits <= bits) { break; }\n        //--- PULLBYTE() ---//\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n        //---//\n      }\n      if (here_op && (here_op & 0xf0) === 0) {\n        last_bits = here_bits;\n        last_op = here_op;\n        last_val = here_val;\n        for (;;) {\n          here = state.lencode[last_val +\n                  ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];\n          here_bits = here >>> 24;\n          here_op = (here >>> 16) & 0xff;\n          here_val = here & 0xffff;\n\n          if ((last_bits + here_bits) <= bits) { break; }\n          //--- PULLBYTE() ---//\n          if (have === 0) { break inf_leave; }\n          have--;\n          hold += input[next++] << bits;\n          bits += 8;\n          //---//\n        }\n        //--- DROPBITS(last.bits) ---//\n        hold >>>= last_bits;\n        bits -= last_bits;\n        //---//\n        state.back += last_bits;\n      }\n      //--- DROPBITS(here.bits) ---//\n      hold >>>= here_bits;\n      bits -= here_bits;\n      //---//\n      state.back += here_bits;\n      state.length = here_val;\n      if (here_op === 0) {\n        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?\n        //        \"inflate:         literal '%c'\\n\" :\n        //        \"inflate:         literal 0x%02x\\n\", here.val));\n        state.mode = LIT;\n        break;\n      }\n      if (here_op & 32) {\n        //Tracevv((stderr, \"inflate:         end of block\\n\"));\n        state.back = -1;\n        state.mode = TYPE;\n        break;\n      }\n      if (here_op & 64) {\n        strm.msg = 'invalid literal/length code';\n        state.mode = BAD;\n        break;\n      }\n      state.extra = here_op & 15;\n      state.mode = LENEXT;\n      /* falls through */\n    case LENEXT:\n      if (state.extra) {\n        //=== NEEDBITS(state.extra);\n        n = state.extra;\n        while (bits < n) {\n          if (have === 0) { break inf_leave; }\n          have--;\n          hold += input[next++] << bits;\n          bits += 8;\n        }\n        //===//\n        state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;\n        //--- DROPBITS(state.extra) ---//\n        hold >>>= state.extra;\n        bits -= state.extra;\n        //---//\n        state.back += state.extra;\n      }\n      //Tracevv((stderr, \"inflate:         length %u\\n\", state.length));\n      state.was = state.length;\n      state.mode = DIST;\n      /* falls through */\n    case DIST:\n      for (;;) {\n        here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/\n        here_bits = here >>> 24;\n        here_op = (here >>> 16) & 0xff;\n        here_val = here & 0xffff;\n\n        if ((here_bits) <= bits) { break; }\n        //--- PULLBYTE() ---//\n        if (have === 0) { break inf_leave; }\n        have--;\n        hold += input[next++] << bits;\n        bits += 8;\n        //---//\n      }\n      if ((here_op & 0xf0) === 0) {\n        last_bits = here_bits;\n        last_op = here_op;\n        last_val = here_val;\n        for (;;) {\n          here = state.distcode[last_val +\n                  ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];\n          here_bits = here >>> 24;\n          here_op = (here >>> 16) & 0xff;\n          here_val = here & 0xffff;\n\n          if ((last_bits + here_bits) <= bits) { break; }\n          //--- PULLBYTE() ---//\n          if (have === 0) { break inf_leave; }\n          have--;\n          hold += input[next++] << bits;\n          bits += 8;\n          //---//\n        }\n        //--- DROPBITS(last.bits) ---//\n        hold >>>= last_bits;\n        bits -= last_bits;\n        //---//\n        state.back += last_bits;\n      }\n      //--- DROPBITS(here.bits) ---//\n      hold >>>= here_bits;\n      bits -= here_bits;\n      //---//\n      state.back += here_bits;\n      if (here_op & 64) {\n        strm.msg = 'invalid distance code';\n        state.mode = BAD;\n        break;\n      }\n      state.offset = here_val;\n      state.extra = (here_op) & 15;\n      state.mode = DISTEXT;\n      /* falls through */\n    case DISTEXT:\n      if (state.extra) {\n        //=== NEEDBITS(state.extra);\n        n = state.extra;\n        while (bits < n) {\n          if (have === 0) { break inf_leave; }\n          have--;\n          hold += input[next++] << bits;\n          bits += 8;\n        }\n        //===//\n        state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;\n        //--- DROPBITS(state.extra) ---//\n        hold >>>= state.extra;\n        bits -= state.extra;\n        //---//\n        state.back += state.extra;\n      }\n//#ifdef INFLATE_STRICT\n      if (state.offset > state.dmax) {\n        strm.msg = 'invalid distance too far back';\n        state.mode = BAD;\n        break;\n      }\n//#endif\n      //Tracevv((stderr, \"inflate:         distance %u\\n\", state.offset));\n      state.mode = MATCH;\n      /* falls through */\n    case MATCH:\n      if (left === 0) { break inf_leave; }\n      copy = _out - left;\n      if (state.offset > copy) {         /* copy from window */\n        copy = state.offset - copy;\n        if (copy > state.whave) {\n          if (state.sane) {\n            strm.msg = 'invalid distance too far back';\n            state.mode = BAD;\n            break;\n          }\n// (!) This block is disabled in zlib defailts,\n// don't enable it for binary compatibility\n//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR\n//          Trace((stderr, \"inflate.c too far\\n\"));\n//          copy -= state.whave;\n//          if (copy > state.length) { copy = state.length; }\n//          if (copy > left) { copy = left; }\n//          left -= copy;\n//          state.length -= copy;\n//          do {\n//            output[put++] = 0;\n//          } while (--copy);\n//          if (state.length === 0) { state.mode = LEN; }\n//          break;\n//#endif\n        }\n        if (copy > state.wnext) {\n          copy -= state.wnext;\n          from = state.wsize - copy;\n        }\n        else {\n          from = state.wnext - copy;\n        }\n        if (copy > state.length) { copy = state.length; }\n        from_source = state.window;\n      }\n      else {                              /* copy from output */\n        from_source = output;\n        from = put - state.offset;\n        copy = state.length;\n      }\n      if (copy > left) { copy = left; }\n      left -= copy;\n      state.length -= copy;\n      do {\n        output[put++] = from_source[from++];\n      } while (--copy);\n      if (state.length === 0) { state.mode = LEN; }\n      break;\n    case LIT:\n      if (left === 0) { break inf_leave; }\n      output[put++] = state.length;\n      left--;\n      state.mode = LEN;\n      break;\n    case CHECK:\n      if (state.wrap) {\n        //=== NEEDBITS(32);\n        while (bits < 32) {\n          if (have === 0) { break inf_leave; }\n          have--;\n          // Use '|' insdead of '+' to make sure that result is signed\n          hold |= input[next++] << bits;\n          bits += 8;\n        }\n        //===//\n        _out -= left;\n        strm.total_out += _out;\n        state.total += _out;\n        if (_out) {\n          strm.adler = state.check =\n              /*UPDATE(state.check, put - _out, _out);*/\n              (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out));\n\n        }\n        _out = left;\n        // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too\n        if ((state.flags ? hold : zswap32(hold)) !== state.check) {\n          strm.msg = 'incorrect data check';\n          state.mode = BAD;\n          break;\n        }\n        //=== INITBITS();\n        hold = 0;\n        bits = 0;\n        //===//\n        //Tracev((stderr, \"inflate:   check matches trailer\\n\"));\n      }\n      state.mode = LENGTH;\n      /* falls through */\n    case LENGTH:\n      if (state.wrap && state.flags) {\n        //=== NEEDBITS(32);\n        while (bits < 32) {\n          if (have === 0) { break inf_leave; }\n          have--;\n          hold += input[next++] << bits;\n          bits += 8;\n        }\n        //===//\n        if (hold !== (state.total & 0xffffffff)) {\n          strm.msg = 'incorrect length check';\n          state.mode = BAD;\n          break;\n        }\n        //=== INITBITS();\n        hold = 0;\n        bits = 0;\n        //===//\n        //Tracev((stderr, \"inflate:   length matches trailer\\n\"));\n      }\n      state.mode = DONE;\n      /* falls through */\n    case DONE:\n      ret = Z_STREAM_END;\n      break inf_leave;\n    case BAD:\n      ret = Z_DATA_ERROR;\n      break inf_leave;\n    case MEM:\n      return Z_MEM_ERROR;\n    case SYNC:\n      /* falls through */\n    default:\n      return Z_STREAM_ERROR;\n    }\n  }\n\n  // inf_leave <- here is real place for \"goto inf_leave\", emulated via \"break inf_leave\"\n\n  /*\n     Return from inflate(), updating the total counts and the check value.\n     If there was no progress during the inflate() call, return a buffer\n     error.  Call updatewindow() to create and/or update the window state.\n     Note: a memory error from inflate() is non-recoverable.\n   */\n\n  //--- RESTORE() ---\n  strm.next_out = put;\n  strm.avail_out = left;\n  strm.next_in = next;\n  strm.avail_in = have;\n  state.hold = hold;\n  state.bits = bits;\n  //---\n\n  if (state.wsize || (_out !== strm.avail_out && state.mode < BAD &&\n                      (state.mode < CHECK || flush !== Z_FINISH))) {\n    if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) {\n      state.mode = MEM;\n      return Z_MEM_ERROR;\n    }\n  }\n  _in -= strm.avail_in;\n  _out -= strm.avail_out;\n  strm.total_in += _in;\n  strm.total_out += _out;\n  state.total += _out;\n  if (state.wrap && _out) {\n    strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/\n      (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out));\n  }\n  strm.data_type = state.bits + (state.last ? 64 : 0) +\n                    (state.mode === TYPE ? 128 : 0) +\n                    (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);\n  if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) {\n    ret = Z_BUF_ERROR;\n  }\n  return ret;\n}\n\nfunction inflateEnd(strm) {\n\n  if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {\n    return Z_STREAM_ERROR;\n  }\n\n  var state = strm.state;\n  if (state.window) {\n    state.window = null;\n  }\n  strm.state = null;\n  return Z_OK;\n}\n\nfunction inflateGetHeader(strm, head) {\n  var state;\n\n  /* check state */\n  if (!strm || !strm.state) { return Z_STREAM_ERROR; }\n  state = strm.state;\n  if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; }\n\n  /* save header structure */\n  state.head = head;\n  head.done = false;\n  return Z_OK;\n}\n\nfunction inflateSetDictionary(strm, dictionary) {\n  var dictLength = dictionary.length;\n\n  var state;\n  var dictid;\n  var ret;\n\n  /* check state */\n  if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; }\n  state = strm.state;\n\n  if (state.wrap !== 0 && state.mode !== DICT) {\n    return Z_STREAM_ERROR;\n  }\n\n  /* check for correct dictionary identifier */\n  if (state.mode === DICT) {\n    dictid = 1; /* adler32(0, null, 0)*/\n    /* dictid = adler32(dictid, dictionary, dictLength); */\n    dictid = adler32(dictid, dictionary, dictLength, 0);\n    if (dictid !== state.check) {\n      return Z_DATA_ERROR;\n    }\n  }\n  /* copy dictionary to window using updatewindow(), which will amend the\n   existing dictionary if appropriate */\n  ret = updatewindow(strm, dictionary, dictLength, dictLength);\n  if (ret) {\n    state.mode = MEM;\n    return Z_MEM_ERROR;\n  }\n  state.havedict = 1;\n  // Tracev((stderr, \"inflate:   dictionary set\\n\"));\n  return Z_OK;\n}\n\nexport { inflateReset, inflateReset2, inflateResetKeep, inflateInit, inflateInit2, inflate, inflateEnd, inflateGetHeader, inflateSetDictionary };\nexport var inflateInfo = 'pako inflate (from Nodeca project)';\n\n/* Not implemented\nexports.inflateCopy = inflateCopy;\nexports.inflateGetDictionary = inflateGetDictionary;\nexports.inflateMark = inflateMark;\nexports.inflatePrime = inflatePrime;\nexports.inflateSync = inflateSync;\nexports.inflateSyncPoint = inflateSyncPoint;\nexports.inflateUndermine = inflateUndermine;\n*/\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/inftrees.js",
    "content": "import * as utils from \"../utils/common.js\";\n\nvar MAXBITS = 15;\nvar ENOUGH_LENS = 852;\nvar ENOUGH_DISTS = 592;\n//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);\n\nvar CODES = 0;\nvar LENS = 1;\nvar DISTS = 2;\n\nvar lbase = [ /* Length codes 257..285 base */\n  3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,\n  35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0\n];\n\nvar lext = [ /* Length codes 257..285 extra */\n  16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,\n  19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78\n];\n\nvar dbase = [ /* Distance codes 0..29 base */\n  1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,\n  257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,\n  8193, 12289, 16385, 24577, 0, 0\n];\n\nvar dext = [ /* Distance codes 0..29 extra */\n  16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,\n  23, 23, 24, 24, 25, 25, 26, 26, 27, 27,\n  28, 28, 29, 29, 64, 64\n];\n\nexport default function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts)\n{\n  var bits = opts.bits;\n      //here = opts.here; /* table entry for duplication */\n\n  var len = 0;               /* a code's length in bits */\n  var sym = 0;               /* index of code symbols */\n  var min = 0, max = 0;          /* minimum and maximum code lengths */\n  var root = 0;              /* number of index bits for root table */\n  var curr = 0;              /* number of index bits for current table */\n  var drop = 0;              /* code bits to drop for sub-table */\n  var left = 0;                   /* number of prefix codes available */\n  var used = 0;              /* code entries in table used */\n  var huff = 0;              /* Huffman code */\n  var incr;              /* for incrementing code, index */\n  var fill;              /* index for replicating entries */\n  var low;               /* low bits for current root entry */\n  var mask;              /* mask for low root bits */\n  var next;             /* next available space in table */\n  var base = null;     /* base value table to use */\n  var base_index = 0;\n//  var shoextra;    /* extra bits table to use */\n  var end;                    /* use base and extra for symbol > end */\n  var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];    /* number of codes of each length */\n  var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];     /* offsets in table for each length */\n  var extra = null;\n  var extra_index = 0;\n\n  var here_bits, here_op, here_val;\n\n  /*\n   Process a set of code lengths to create a canonical Huffman code.  The\n   code lengths are lens[0..codes-1].  Each length corresponds to the\n   symbols 0..codes-1.  The Huffman code is generated by first sorting the\n   symbols by length from short to long, and retaining the symbol order\n   for codes with equal lengths.  Then the code starts with all zero bits\n   for the first code of the shortest length, and the codes are integer\n   increments for the same length, and zeros are appended as the length\n   increases.  For the deflate format, these bits are stored backwards\n   from their more natural integer increment ordering, and so when the\n   decoding tables are built in the large loop below, the integer codes\n   are incremented backwards.\n\n   This routine assumes, but does not check, that all of the entries in\n   lens[] are in the range 0..MAXBITS.  The caller must assure this.\n   1..MAXBITS is interpreted as that code length.  zero means that that\n   symbol does not occur in this code.\n\n   The codes are sorted by computing a count of codes for each length,\n   creating from that a table of starting indices for each length in the\n   sorted table, and then entering the symbols in order in the sorted\n   table.  The sorted table is work[], with that space being provided by\n   the caller.\n\n   The length counts are used for other purposes as well, i.e. finding\n   the minimum and maximum length codes, determining if there are any\n   codes at all, checking for a valid set of lengths, and looking ahead\n   at length counts to determine sub-table sizes when building the\n   decoding tables.\n   */\n\n  /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */\n  for (len = 0; len <= MAXBITS; len++) {\n    count[len] = 0;\n  }\n  for (sym = 0; sym < codes; sym++) {\n    count[lens[lens_index + sym]]++;\n  }\n\n  /* bound code lengths, force root to be within code lengths */\n  root = bits;\n  for (max = MAXBITS; max >= 1; max--) {\n    if (count[max] !== 0) { break; }\n  }\n  if (root > max) {\n    root = max;\n  }\n  if (max === 0) {                     /* no symbols to code at all */\n    //table.op[opts.table_index] = 64;  //here.op = (var char)64;    /* invalid code marker */\n    //table.bits[opts.table_index] = 1;   //here.bits = (var char)1;\n    //table.val[opts.table_index++] = 0;   //here.val = (var short)0;\n    table[table_index++] = (1 << 24) | (64 << 16) | 0;\n\n\n    //table.op[opts.table_index] = 64;\n    //table.bits[opts.table_index] = 1;\n    //table.val[opts.table_index++] = 0;\n    table[table_index++] = (1 << 24) | (64 << 16) | 0;\n\n    opts.bits = 1;\n    return 0;     /* no symbols, but wait for decoding to report error */\n  }\n  for (min = 1; min < max; min++) {\n    if (count[min] !== 0) { break; }\n  }\n  if (root < min) {\n    root = min;\n  }\n\n  /* check for an over-subscribed or incomplete set of lengths */\n  left = 1;\n  for (len = 1; len <= MAXBITS; len++) {\n    left <<= 1;\n    left -= count[len];\n    if (left < 0) {\n      return -1;\n    }        /* over-subscribed */\n  }\n  if (left > 0 && (type === CODES || max !== 1)) {\n    return -1;                      /* incomplete set */\n  }\n\n  /* generate offsets into symbol table for each length for sorting */\n  offs[1] = 0;\n  for (len = 1; len < MAXBITS; len++) {\n    offs[len + 1] = offs[len] + count[len];\n  }\n\n  /* sort symbols by length, by symbol order within each length */\n  for (sym = 0; sym < codes; sym++) {\n    if (lens[lens_index + sym] !== 0) {\n      work[offs[lens[lens_index + sym]]++] = sym;\n    }\n  }\n\n  /*\n   Create and fill in decoding tables.  In this loop, the table being\n   filled is at next and has curr index bits.  The code being used is huff\n   with length len.  That code is converted to an index by dropping drop\n   bits off of the bottom.  For codes where len is less than drop + curr,\n   those top drop + curr - len bits are incremented through all values to\n   fill the table with replicated entries.\n\n   root is the number of index bits for the root table.  When len exceeds\n   root, sub-tables are created pointed to by the root entry with an index\n   of the low root bits of huff.  This is saved in low to check for when a\n   new sub-table should be started.  drop is zero when the root table is\n   being filled, and drop is root when sub-tables are being filled.\n\n   When a new sub-table is needed, it is necessary to look ahead in the\n   code lengths to determine what size sub-table is needed.  The length\n   counts are used for this, and so count[] is decremented as codes are\n   entered in the tables.\n\n   used keeps track of how many table entries have been allocated from the\n   provided *table space.  It is checked for LENS and DIST tables against\n   the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in\n   the initial root table size constants.  See the comments in inftrees.h\n   for more information.\n\n   sym increments through all symbols, and the loop terminates when\n   all codes of length max, i.e. all codes, have been processed.  This\n   routine permits incomplete codes, so another loop after this one fills\n   in the rest of the decoding tables with invalid code markers.\n   */\n\n  /* set up for code type */\n  // poor man optimization - use if-else instead of switch,\n  // to avoid deopts in old v8\n  if (type === CODES) {\n    base = extra = work;    /* dummy value--not used */\n    end = 19;\n\n  } else if (type === LENS) {\n    base = lbase;\n    base_index -= 257;\n    extra = lext;\n    extra_index -= 257;\n    end = 256;\n\n  } else {                    /* DISTS */\n    base = dbase;\n    extra = dext;\n    end = -1;\n  }\n\n  /* initialize opts for loop */\n  huff = 0;                   /* starting code */\n  sym = 0;                    /* starting code symbol */\n  len = min;                  /* starting code length */\n  next = table_index;              /* current table to fill in */\n  curr = root;                /* current table index bits */\n  drop = 0;                   /* current bits to drop from code for index */\n  low = -1;                   /* trigger new sub-table when len > root */\n  used = 1 << root;          /* use root table entries */\n  mask = used - 1;            /* mask for comparing low */\n\n  /* check available table space */\n  if ((type === LENS && used > ENOUGH_LENS) ||\n    (type === DISTS && used > ENOUGH_DISTS)) {\n    return 1;\n  }\n\n  /* process all codes and make table entries */\n  for (;;) {\n    /* create table entry */\n    here_bits = len - drop;\n    if (work[sym] < end) {\n      here_op = 0;\n      here_val = work[sym];\n    }\n    else if (work[sym] > end) {\n      here_op = extra[extra_index + work[sym]];\n      here_val = base[base_index + work[sym]];\n    }\n    else {\n      here_op = 32 + 64;         /* end of block */\n      here_val = 0;\n    }\n\n    /* replicate for those indices with low len bits equal to huff */\n    incr = 1 << (len - drop);\n    fill = 1 << curr;\n    min = fill;                 /* save offset to next table */\n    do {\n      fill -= incr;\n      table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;\n    } while (fill !== 0);\n\n    /* backwards increment the len-bit code huff */\n    incr = 1 << (len - 1);\n    while (huff & incr) {\n      incr >>= 1;\n    }\n    if (incr !== 0) {\n      huff &= incr - 1;\n      huff += incr;\n    } else {\n      huff = 0;\n    }\n\n    /* go to next symbol, update count, len */\n    sym++;\n    if (--count[len] === 0) {\n      if (len === max) { break; }\n      len = lens[lens_index + work[sym]];\n    }\n\n    /* create new sub-table if needed */\n    if (len > root && (huff & mask) !== low) {\n      /* if first time, transition to sub-tables */\n      if (drop === 0) {\n        drop = root;\n      }\n\n      /* increment past last table */\n      next += min;            /* here min is 1 << curr */\n\n      /* determine length of next table */\n      curr = len - drop;\n      left = 1 << curr;\n      while (curr + drop < max) {\n        left -= count[curr + drop];\n        if (left <= 0) { break; }\n        curr++;\n        left <<= 1;\n      }\n\n      /* check for enough space */\n      used += 1 << curr;\n      if ((type === LENS && used > ENOUGH_LENS) ||\n        (type === DISTS && used > ENOUGH_DISTS)) {\n        return 1;\n      }\n\n      /* point entry in root table to sub-table */\n      low = huff & mask;\n      /*table.op[low] = curr;\n      table.bits[low] = root;\n      table.val[low] = next - opts.table_index;*/\n      table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;\n    }\n  }\n\n  /* fill in remaining table entry if code is incomplete (guaranteed to have\n   at most one remaining entry, since if the code is incomplete, the\n   maximum code length that was allowed to get this far is one bit) */\n  if (huff !== 0) {\n    //table.op[next + huff] = 64;            /* invalid code marker */\n    //table.bits[next + huff] = len - drop;\n    //table.val[next + huff] = 0;\n    table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;\n  }\n\n  /* set return parameters */\n  //opts.table_index += used;\n  opts.bits = root;\n  return 0;\n};\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/messages.js",
    "content": "export default {\n  2:      'need dictionary',     /* Z_NEED_DICT       2  */\n  1:      'stream end',          /* Z_STREAM_END      1  */\n  0:      '',                    /* Z_OK              0  */\n  '-1':   'file error',          /* Z_ERRNO         (-1) */\n  '-2':   'stream error',        /* Z_STREAM_ERROR  (-2) */\n  '-3':   'data error',          /* Z_DATA_ERROR    (-3) */\n  '-4':   'insufficient memory', /* Z_MEM_ERROR     (-4) */\n  '-5':   'buffer error',        /* Z_BUF_ERROR     (-5) */\n  '-6':   'incompatible version' /* Z_VERSION_ERROR (-6) */\n};\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/trees.js",
    "content": "import * as utils from \"../utils/common.js\";\n\n/* Public constants ==========================================================*/\n/* ===========================================================================*/\n\n\n//var Z_FILTERED          = 1;\n//var Z_HUFFMAN_ONLY      = 2;\n//var Z_RLE               = 3;\nvar Z_FIXED               = 4;\n//var Z_DEFAULT_STRATEGY  = 0;\n\n/* Possible values of the data_type field (though see inflate()) */\nvar Z_BINARY              = 0;\nvar Z_TEXT                = 1;\n//var Z_ASCII             = 1; // = Z_TEXT\nvar Z_UNKNOWN             = 2;\n\n/*============================================================================*/\n\n\nfunction zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }\n\n// From zutil.h\n\nvar STORED_BLOCK = 0;\nvar STATIC_TREES = 1;\nvar DYN_TREES    = 2;\n/* The three kinds of block type */\n\nvar MIN_MATCH    = 3;\nvar MAX_MATCH    = 258;\n/* The minimum and maximum match lengths */\n\n// From deflate.h\n/* ===========================================================================\n * Internal compression state.\n */\n\nvar LENGTH_CODES  = 29;\n/* number of length codes, not counting the special END_BLOCK code */\n\nvar LITERALS      = 256;\n/* number of literal bytes 0..255 */\n\nvar L_CODES       = LITERALS + 1 + LENGTH_CODES;\n/* number of Literal or Length codes, including the END_BLOCK code */\n\nvar D_CODES       = 30;\n/* number of distance codes */\n\nvar BL_CODES      = 19;\n/* number of codes used to transfer the bit lengths */\n\nvar HEAP_SIZE     = 2 * L_CODES + 1;\n/* maximum heap size */\n\nvar MAX_BITS      = 15;\n/* All codes must not exceed MAX_BITS bits */\n\nvar Buf_size      = 16;\n/* size of bit buffer in bi_buf */\n\n\n/* ===========================================================================\n * Constants\n */\n\nvar MAX_BL_BITS = 7;\n/* Bit length codes must not exceed MAX_BL_BITS bits */\n\nvar END_BLOCK   = 256;\n/* end of block literal code */\n\nvar REP_3_6     = 16;\n/* repeat previous bit length 3-6 times (2 bits of repeat count) */\n\nvar REPZ_3_10   = 17;\n/* repeat a zero length 3-10 times  (3 bits of repeat count) */\n\nvar REPZ_11_138 = 18;\n/* repeat a zero length 11-138 times  (7 bits of repeat count) */\n\n/* eslint-disable comma-spacing,array-bracket-spacing */\nvar extra_lbits =   /* extra bits for each length code */\n  [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];\n\nvar extra_dbits =   /* extra bits for each distance code */\n  [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];\n\nvar extra_blbits =  /* extra bits for each bit length code */\n  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7];\n\nvar bl_order =\n  [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];\n/* eslint-enable comma-spacing,array-bracket-spacing */\n\n/* The lengths of the bit length codes are sent in order of decreasing\n * probability, to avoid transmitting the lengths for unused bit length codes.\n */\n\n/* ===========================================================================\n * Local data. These are initialized only once.\n */\n\n// We pre-fill arrays with 0 to avoid uninitialized gaps\n\nvar DIST_CODE_LEN = 512; /* see definition of array dist_code below */\n\n// !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1\nvar static_ltree  = new Array((L_CODES + 2) * 2);\nzero(static_ltree);\n/* The static literal tree. Since the bit lengths are imposed, there is no\n * need for the L_CODES extra codes used during heap construction. However\n * The codes 286 and 287 are needed to build a canonical tree (see _tr_init\n * below).\n */\n\nvar static_dtree  = new Array(D_CODES * 2);\nzero(static_dtree);\n/* The static distance tree. (Actually a trivial tree since all codes use\n * 5 bits.)\n */\n\nvar _dist_code    = new Array(DIST_CODE_LEN);\nzero(_dist_code);\n/* Distance codes. The first 256 values correspond to the distances\n * 3 .. 258, the last 256 values correspond to the top 8 bits of\n * the 15 bit distances.\n */\n\nvar _length_code  = new Array(MAX_MATCH - MIN_MATCH + 1);\nzero(_length_code);\n/* length code for each normalized match length (0 == MIN_MATCH) */\n\nvar base_length   = new Array(LENGTH_CODES);\nzero(base_length);\n/* First normalized length for each code (0 = MIN_MATCH) */\n\nvar base_dist     = new Array(D_CODES);\nzero(base_dist);\n/* First normalized distance for each code (0 = distance of 1) */\n\n\nfunction StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {\n\n  this.static_tree  = static_tree;  /* static tree or NULL */\n  this.extra_bits   = extra_bits;   /* extra bits for each code or NULL */\n  this.extra_base   = extra_base;   /* base index for extra_bits */\n  this.elems        = elems;        /* max number of elements in the tree */\n  this.max_length   = max_length;   /* max bit length for the codes */\n\n  // show if `static_tree` has data or dummy - needed for monomorphic objects\n  this.has_stree    = static_tree && static_tree.length;\n}\n\n\nvar static_l_desc;\nvar static_d_desc;\nvar static_bl_desc;\n\n\nfunction TreeDesc(dyn_tree, stat_desc) {\n  this.dyn_tree = dyn_tree;     /* the dynamic tree */\n  this.max_code = 0;            /* largest code with non zero frequency */\n  this.stat_desc = stat_desc;   /* the corresponding static tree */\n}\n\n\n\nfunction d_code(dist) {\n  return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];\n}\n\n\n/* ===========================================================================\n * Output a short LSB first on the stream.\n * IN assertion: there is enough room in pendingBuf.\n */\nfunction put_short(s, w) {\n//    put_byte(s, (uch)((w) & 0xff));\n//    put_byte(s, (uch)((ush)(w) >> 8));\n  s.pending_buf[s.pending++] = (w) & 0xff;\n  s.pending_buf[s.pending++] = (w >>> 8) & 0xff;\n}\n\n\n/* ===========================================================================\n * Send a value on a given number of bits.\n * IN assertion: length <= 16 and value fits in length bits.\n */\nfunction send_bits(s, value, length) {\n  if (s.bi_valid > (Buf_size - length)) {\n    s.bi_buf |= (value << s.bi_valid) & 0xffff;\n    put_short(s, s.bi_buf);\n    s.bi_buf = value >> (Buf_size - s.bi_valid);\n    s.bi_valid += length - Buf_size;\n  } else {\n    s.bi_buf |= (value << s.bi_valid) & 0xffff;\n    s.bi_valid += length;\n  }\n}\n\n\nfunction send_code(s, c, tree) {\n  send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/);\n}\n\n\n/* ===========================================================================\n * Reverse the first len bits of a code, using straightforward code (a faster\n * method would use a table)\n * IN assertion: 1 <= len <= 15\n */\nfunction bi_reverse(code, len) {\n  var res = 0;\n  do {\n    res |= code & 1;\n    code >>>= 1;\n    res <<= 1;\n  } while (--len > 0);\n  return res >>> 1;\n}\n\n\n/* ===========================================================================\n * Flush the bit buffer, keeping at most 7 bits in it.\n */\nfunction bi_flush(s) {\n  if (s.bi_valid === 16) {\n    put_short(s, s.bi_buf);\n    s.bi_buf = 0;\n    s.bi_valid = 0;\n\n  } else if (s.bi_valid >= 8) {\n    s.pending_buf[s.pending++] = s.bi_buf & 0xff;\n    s.bi_buf >>= 8;\n    s.bi_valid -= 8;\n  }\n}\n\n\n/* ===========================================================================\n * Compute the optimal bit lengths for a tree and update the total bit length\n * for the current block.\n * IN assertion: the fields freq and dad are set, heap[heap_max] and\n *    above are the tree nodes sorted by increasing frequency.\n * OUT assertions: the field len is set to the optimal bit length, the\n *     array bl_count contains the frequencies for each bit length.\n *     The length opt_len is updated; static_len is also updated if stree is\n *     not null.\n */\nfunction gen_bitlen(s, desc)\n//    deflate_state *s;\n//    tree_desc *desc;    /* the tree descriptor */\n{\n  var tree            = desc.dyn_tree;\n  var max_code        = desc.max_code;\n  var stree           = desc.stat_desc.static_tree;\n  var has_stree       = desc.stat_desc.has_stree;\n  var extra           = desc.stat_desc.extra_bits;\n  var base            = desc.stat_desc.extra_base;\n  var max_length      = desc.stat_desc.max_length;\n  var h;              /* heap index */\n  var n, m;           /* iterate over the tree elements */\n  var bits;           /* bit length */\n  var xbits;          /* extra bits */\n  var f;              /* frequency */\n  var overflow = 0;   /* number of elements with bit length too large */\n\n  for (bits = 0; bits <= MAX_BITS; bits++) {\n    s.bl_count[bits] = 0;\n  }\n\n  /* In a first pass, compute the optimal bit lengths (which may\n   * overflow in the case of the bit length tree).\n   */\n  tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */\n\n  for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {\n    n = s.heap[h];\n    bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;\n    if (bits > max_length) {\n      bits = max_length;\n      overflow++;\n    }\n    tree[n * 2 + 1]/*.Len*/ = bits;\n    /* We overwrite tree[n].Dad which is no longer needed */\n\n    if (n > max_code) { continue; } /* not a leaf node */\n\n    s.bl_count[bits]++;\n    xbits = 0;\n    if (n >= base) {\n      xbits = extra[n - base];\n    }\n    f = tree[n * 2]/*.Freq*/;\n    s.opt_len += f * (bits + xbits);\n    if (has_stree) {\n      s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits);\n    }\n  }\n  if (overflow === 0) { return; }\n\n  // Trace((stderr,\"\\nbit length overflow\\n\"));\n  /* This happens for example on obj2 and pic of the Calgary corpus */\n\n  /* Find the first bit length which could increase: */\n  do {\n    bits = max_length - 1;\n    while (s.bl_count[bits] === 0) { bits--; }\n    s.bl_count[bits]--;      /* move one leaf down the tree */\n    s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */\n    s.bl_count[max_length]--;\n    /* The brother of the overflow item also moves one step up,\n     * but this does not affect bl_count[max_length]\n     */\n    overflow -= 2;\n  } while (overflow > 0);\n\n  /* Now recompute all bit lengths, scanning in increasing frequency.\n   * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all\n   * lengths instead of fixing only the wrong ones. This idea is taken\n   * from 'ar' written by Haruhiko Okumura.)\n   */\n  for (bits = max_length; bits !== 0; bits--) {\n    n = s.bl_count[bits];\n    while (n !== 0) {\n      m = s.heap[--h];\n      if (m > max_code) { continue; }\n      if (tree[m * 2 + 1]/*.Len*/ !== bits) {\n        // Trace((stderr,\"code %d bits %d->%d\\n\", m, tree[m].Len, bits));\n        s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/;\n        tree[m * 2 + 1]/*.Len*/ = bits;\n      }\n      n--;\n    }\n  }\n}\n\n\n/* ===========================================================================\n * Generate the codes for a given tree and bit counts (which need not be\n * optimal).\n * IN assertion: the array bl_count contains the bit length statistics for\n * the given tree and the field len is set for all tree elements.\n * OUT assertion: the field code is set for all tree elements of non\n *     zero code length.\n */\nfunction gen_codes(tree, max_code, bl_count)\n//    ct_data *tree;             /* the tree to decorate */\n//    int max_code;              /* largest code with non zero frequency */\n//    ushf *bl_count;            /* number of codes at each bit length */\n{\n  var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */\n  var code = 0;              /* running code value */\n  var bits;                  /* bit index */\n  var n;                     /* code index */\n\n  /* The distribution counts are first used to generate the code values\n   * without bit reversal.\n   */\n  for (bits = 1; bits <= MAX_BITS; bits++) {\n    next_code[bits] = code = (code + bl_count[bits - 1]) << 1;\n  }\n  /* Check that the bit counts in bl_count are consistent. The last code\n   * must be all ones.\n   */\n  //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,\n  //        \"inconsistent bit counts\");\n  //Tracev((stderr,\"\\ngen_codes: max_code %d \", max_code));\n\n  for (n = 0;  n <= max_code; n++) {\n    var len = tree[n * 2 + 1]/*.Len*/;\n    if (len === 0) { continue; }\n    /* Now reverse the bits */\n    tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len);\n\n    //Tracecv(tree != static_ltree, (stderr,\"\\nn %3d %c l %2d c %4x (%x) \",\n    //     n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));\n  }\n}\n\n\n/* ===========================================================================\n * Initialize the various 'constant' tables.\n */\nfunction tr_static_init() {\n  var n;        /* iterates over tree elements */\n  var bits;     /* bit counter */\n  var length;   /* length value */\n  var code;     /* code value */\n  var dist;     /* distance index */\n  var bl_count = new Array(MAX_BITS + 1);\n  /* number of codes at each bit length for an optimal tree */\n\n  // do check in _tr_init()\n  //if (static_init_done) return;\n\n  /* For some embedded targets, global variables are not initialized: */\n/*#ifdef NO_INIT_GLOBAL_POINTERS\n  static_l_desc.static_tree = static_ltree;\n  static_l_desc.extra_bits = extra_lbits;\n  static_d_desc.static_tree = static_dtree;\n  static_d_desc.extra_bits = extra_dbits;\n  static_bl_desc.extra_bits = extra_blbits;\n#endif*/\n\n  /* Initialize the mapping length (0..255) -> length code (0..28) */\n  length = 0;\n  for (code = 0; code < LENGTH_CODES - 1; code++) {\n    base_length[code] = length;\n    for (n = 0; n < (1 << extra_lbits[code]); n++) {\n      _length_code[length++] = code;\n    }\n  }\n  //Assert (length == 256, \"tr_static_init: length != 256\");\n  /* Note that the length 255 (match length 258) can be represented\n   * in two different ways: code 284 + 5 bits or code 285, so we\n   * overwrite length_code[255] to use the best encoding:\n   */\n  _length_code[length - 1] = code;\n\n  /* Initialize the mapping dist (0..32K) -> dist code (0..29) */\n  dist = 0;\n  for (code = 0; code < 16; code++) {\n    base_dist[code] = dist;\n    for (n = 0; n < (1 << extra_dbits[code]); n++) {\n      _dist_code[dist++] = code;\n    }\n  }\n  //Assert (dist == 256, \"tr_static_init: dist != 256\");\n  dist >>= 7; /* from now on, all distances are divided by 128 */\n  for (; code < D_CODES; code++) {\n    base_dist[code] = dist << 7;\n    for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {\n      _dist_code[256 + dist++] = code;\n    }\n  }\n  //Assert (dist == 256, \"tr_static_init: 256+dist != 512\");\n\n  /* Construct the codes of the static literal tree */\n  for (bits = 0; bits <= MAX_BITS; bits++) {\n    bl_count[bits] = 0;\n  }\n\n  n = 0;\n  while (n <= 143) {\n    static_ltree[n * 2 + 1]/*.Len*/ = 8;\n    n++;\n    bl_count[8]++;\n  }\n  while (n <= 255) {\n    static_ltree[n * 2 + 1]/*.Len*/ = 9;\n    n++;\n    bl_count[9]++;\n  }\n  while (n <= 279) {\n    static_ltree[n * 2 + 1]/*.Len*/ = 7;\n    n++;\n    bl_count[7]++;\n  }\n  while (n <= 287) {\n    static_ltree[n * 2 + 1]/*.Len*/ = 8;\n    n++;\n    bl_count[8]++;\n  }\n  /* Codes 286 and 287 do not exist, but we must include them in the\n   * tree construction to get a canonical Huffman tree (longest code\n   * all ones)\n   */\n  gen_codes(static_ltree, L_CODES + 1, bl_count);\n\n  /* The static distance tree is trivial: */\n  for (n = 0; n < D_CODES; n++) {\n    static_dtree[n * 2 + 1]/*.Len*/ = 5;\n    static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5);\n  }\n\n  // Now data ready and we can init static trees\n  static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);\n  static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0,          D_CODES, MAX_BITS);\n  static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0,         BL_CODES, MAX_BL_BITS);\n\n  //static_init_done = true;\n}\n\n\n/* ===========================================================================\n * Initialize a new block.\n */\nfunction init_block(s) {\n  var n; /* iterates over tree elements */\n\n  /* Initialize the trees. */\n  for (n = 0; n < L_CODES;  n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }\n  for (n = 0; n < D_CODES;  n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }\n  for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }\n\n  s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;\n  s.opt_len = s.static_len = 0;\n  s.last_lit = s.matches = 0;\n}\n\n\n/* ===========================================================================\n * Flush the bit buffer and align the output on a byte boundary\n */\nfunction bi_windup(s)\n{\n  if (s.bi_valid > 8) {\n    put_short(s, s.bi_buf);\n  } else if (s.bi_valid > 0) {\n    //put_byte(s, (Byte)s->bi_buf);\n    s.pending_buf[s.pending++] = s.bi_buf;\n  }\n  s.bi_buf = 0;\n  s.bi_valid = 0;\n}\n\n/* ===========================================================================\n * Copy a stored block, storing first the length and its\n * one's complement if requested.\n */\nfunction copy_block(s, buf, len, header)\n//DeflateState *s;\n//charf    *buf;    /* the input data */\n//unsigned len;     /* its length */\n//int      header;  /* true if block header must be written */\n{\n  bi_windup(s);        /* align on byte boundary */\n\n  if (header) {\n    put_short(s, len);\n    put_short(s, ~len);\n  }\n//  while (len--) {\n//    put_byte(s, *buf++);\n//  }\n  utils.arraySet(s.pending_buf, s.window, buf, len, s.pending);\n  s.pending += len;\n}\n\n/* ===========================================================================\n * Compares to subtrees, using the tree depth as tie breaker when\n * the subtrees have equal frequency. This minimizes the worst case length.\n */\nfunction smaller(tree, n, m, depth) {\n  var _n2 = n * 2;\n  var _m2 = m * 2;\n  return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ ||\n         (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m]));\n}\n\n/* ===========================================================================\n * Restore the heap property by moving down the tree starting at node k,\n * exchanging a node with the smallest of its two sons if necessary, stopping\n * when the heap property is re-established (each father smaller than its\n * two sons).\n */\nfunction pqdownheap(s, tree, k)\n//    deflate_state *s;\n//    ct_data *tree;  /* the tree to restore */\n//    int k;               /* node to move down */\n{\n  var v = s.heap[k];\n  var j = k << 1;  /* left son of k */\n  while (j <= s.heap_len) {\n    /* Set j to the smallest of the two sons: */\n    if (j < s.heap_len &&\n      smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {\n      j++;\n    }\n    /* Exit if v is smaller than both sons */\n    if (smaller(tree, v, s.heap[j], s.depth)) { break; }\n\n    /* Exchange v with the smallest son */\n    s.heap[k] = s.heap[j];\n    k = j;\n\n    /* And continue down the tree, setting j to the left son of k */\n    j <<= 1;\n  }\n  s.heap[k] = v;\n}\n\n\n// inlined manually\n// var SMALLEST = 1;\n\n/* ===========================================================================\n * Send the block data compressed using the given Huffman trees\n */\nfunction compress_block(s, ltree, dtree)\n//    deflate_state *s;\n//    const ct_data *ltree; /* literal tree */\n//    const ct_data *dtree; /* distance tree */\n{\n  var dist;           /* distance of matched string */\n  var lc;             /* match length or unmatched char (if dist == 0) */\n  var lx = 0;         /* running index in l_buf */\n  var code;           /* the code to send */\n  var extra;          /* number of extra bits to send */\n\n  if (s.last_lit !== 0) {\n    do {\n      dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]);\n      lc = s.pending_buf[s.l_buf + lx];\n      lx++;\n\n      if (dist === 0) {\n        send_code(s, lc, ltree); /* send a literal byte */\n        //Tracecv(isgraph(lc), (stderr,\" '%c' \", lc));\n      } else {\n        /* Here, lc is the match length - MIN_MATCH */\n        code = _length_code[lc];\n        send_code(s, code + LITERALS + 1, ltree); /* send the length code */\n        extra = extra_lbits[code];\n        if (extra !== 0) {\n          lc -= base_length[code];\n          send_bits(s, lc, extra);       /* send the extra length bits */\n        }\n        dist--; /* dist is now the match distance - 1 */\n        code = d_code(dist);\n        //Assert (code < D_CODES, \"bad d_code\");\n\n        send_code(s, code, dtree);       /* send the distance code */\n        extra = extra_dbits[code];\n        if (extra !== 0) {\n          dist -= base_dist[code];\n          send_bits(s, dist, extra);   /* send the extra distance bits */\n        }\n      } /* literal or match pair ? */\n\n      /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */\n      //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,\n      //       \"pendingBuf overflow\");\n\n    } while (lx < s.last_lit);\n  }\n\n  send_code(s, END_BLOCK, ltree);\n}\n\n\n/* ===========================================================================\n * Construct one Huffman tree and assigns the code bit strings and lengths.\n * Update the total bit length for the current block.\n * IN assertion: the field freq is set for all tree elements.\n * OUT assertions: the fields len and code are set to the optimal bit length\n *     and corresponding code. The length opt_len is updated; static_len is\n *     also updated if stree is not null. The field max_code is set.\n */\nfunction build_tree(s, desc)\n//    deflate_state *s;\n//    tree_desc *desc; /* the tree descriptor */\n{\n  var tree     = desc.dyn_tree;\n  var stree    = desc.stat_desc.static_tree;\n  var has_stree = desc.stat_desc.has_stree;\n  var elems    = desc.stat_desc.elems;\n  var n, m;          /* iterate over heap elements */\n  var max_code = -1; /* largest code with non zero frequency */\n  var node;          /* new node being created */\n\n  /* Construct the initial heap, with least frequent element in\n   * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].\n   * heap[0] is not used.\n   */\n  s.heap_len = 0;\n  s.heap_max = HEAP_SIZE;\n\n  for (n = 0; n < elems; n++) {\n    if (tree[n * 2]/*.Freq*/ !== 0) {\n      s.heap[++s.heap_len] = max_code = n;\n      s.depth[n] = 0;\n\n    } else {\n      tree[n * 2 + 1]/*.Len*/ = 0;\n    }\n  }\n\n  /* The pkzip format requires that at least one distance code exists,\n   * and that at least one bit should be sent even if there is only one\n   * possible code. So to avoid special checks later on we force at least\n   * two codes of non zero frequency.\n   */\n  while (s.heap_len < 2) {\n    node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);\n    tree[node * 2]/*.Freq*/ = 1;\n    s.depth[node] = 0;\n    s.opt_len--;\n\n    if (has_stree) {\n      s.static_len -= stree[node * 2 + 1]/*.Len*/;\n    }\n    /* node is 0 or 1 so it does not have extra bits */\n  }\n  desc.max_code = max_code;\n\n  /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,\n   * establish sub-heaps of increasing lengths:\n   */\n  for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); }\n\n  /* Construct the Huffman tree by repeatedly combining the least two\n   * frequent nodes.\n   */\n  node = elems;              /* next internal node of the tree */\n  do {\n    //pqremove(s, tree, n);  /* n = node of least frequency */\n    /*** pqremove ***/\n    n = s.heap[1/*SMALLEST*/];\n    s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--];\n    pqdownheap(s, tree, 1/*SMALLEST*/);\n    /***/\n\n    m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */\n\n    s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */\n    s.heap[--s.heap_max] = m;\n\n    /* Create a new node father of n and m */\n    tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/;\n    s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;\n    tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node;\n\n    /* and insert the new node in the heap */\n    s.heap[1/*SMALLEST*/] = node++;\n    pqdownheap(s, tree, 1/*SMALLEST*/);\n\n  } while (s.heap_len >= 2);\n\n  s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/];\n\n  /* At this point, the fields freq and dad are set. We can now\n   * generate the bit lengths.\n   */\n  gen_bitlen(s, desc);\n\n  /* The field len is now set, we can generate the bit codes */\n  gen_codes(tree, max_code, s.bl_count);\n}\n\n\n/* ===========================================================================\n * Scan a literal or distance tree to determine the frequencies of the codes\n * in the bit length tree.\n */\nfunction scan_tree(s, tree, max_code)\n//    deflate_state *s;\n//    ct_data *tree;   /* the tree to be scanned */\n//    int max_code;    /* and its largest code of non zero frequency */\n{\n  var n;                     /* iterates over all tree elements */\n  var prevlen = -1;          /* last emitted length */\n  var curlen;                /* length of current code */\n\n  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */\n\n  var count = 0;             /* repeat count of the current code */\n  var max_count = 7;         /* max repeat count */\n  var min_count = 4;         /* min repeat count */\n\n  if (nextlen === 0) {\n    max_count = 138;\n    min_count = 3;\n  }\n  tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */\n\n  for (n = 0; n <= max_code; n++) {\n    curlen = nextlen;\n    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;\n\n    if (++count < max_count && curlen === nextlen) {\n      continue;\n\n    } else if (count < min_count) {\n      s.bl_tree[curlen * 2]/*.Freq*/ += count;\n\n    } else if (curlen !== 0) {\n\n      if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; }\n      s.bl_tree[REP_3_6 * 2]/*.Freq*/++;\n\n    } else if (count <= 10) {\n      s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++;\n\n    } else {\n      s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++;\n    }\n\n    count = 0;\n    prevlen = curlen;\n\n    if (nextlen === 0) {\n      max_count = 138;\n      min_count = 3;\n\n    } else if (curlen === nextlen) {\n      max_count = 6;\n      min_count = 3;\n\n    } else {\n      max_count = 7;\n      min_count = 4;\n    }\n  }\n}\n\n\n/* ===========================================================================\n * Send a literal or distance tree in compressed form, using the codes in\n * bl_tree.\n */\nfunction send_tree(s, tree, max_code)\n//    deflate_state *s;\n//    ct_data *tree; /* the tree to be scanned */\n//    int max_code;       /* and its largest code of non zero frequency */\n{\n  var n;                     /* iterates over all tree elements */\n  var prevlen = -1;          /* last emitted length */\n  var curlen;                /* length of current code */\n\n  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */\n\n  var count = 0;             /* repeat count of the current code */\n  var max_count = 7;         /* max repeat count */\n  var min_count = 4;         /* min repeat count */\n\n  /* tree[max_code+1].Len = -1; */  /* guard already set */\n  if (nextlen === 0) {\n    max_count = 138;\n    min_count = 3;\n  }\n\n  for (n = 0; n <= max_code; n++) {\n    curlen = nextlen;\n    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;\n\n    if (++count < max_count && curlen === nextlen) {\n      continue;\n\n    } else if (count < min_count) {\n      do { send_code(s, curlen, s.bl_tree); } while (--count !== 0);\n\n    } else if (curlen !== 0) {\n      if (curlen !== prevlen) {\n        send_code(s, curlen, s.bl_tree);\n        count--;\n      }\n      //Assert(count >= 3 && count <= 6, \" 3_6?\");\n      send_code(s, REP_3_6, s.bl_tree);\n      send_bits(s, count - 3, 2);\n\n    } else if (count <= 10) {\n      send_code(s, REPZ_3_10, s.bl_tree);\n      send_bits(s, count - 3, 3);\n\n    } else {\n      send_code(s, REPZ_11_138, s.bl_tree);\n      send_bits(s, count - 11, 7);\n    }\n\n    count = 0;\n    prevlen = curlen;\n    if (nextlen === 0) {\n      max_count = 138;\n      min_count = 3;\n\n    } else if (curlen === nextlen) {\n      max_count = 6;\n      min_count = 3;\n\n    } else {\n      max_count = 7;\n      min_count = 4;\n    }\n  }\n}\n\n\n/* ===========================================================================\n * Construct the Huffman tree for the bit lengths and return the index in\n * bl_order of the last bit length code to send.\n */\nfunction build_bl_tree(s) {\n  var max_blindex;  /* index of last bit length code of non zero freq */\n\n  /* Determine the bit length frequencies for literal and distance trees */\n  scan_tree(s, s.dyn_ltree, s.l_desc.max_code);\n  scan_tree(s, s.dyn_dtree, s.d_desc.max_code);\n\n  /* Build the bit length tree: */\n  build_tree(s, s.bl_desc);\n  /* opt_len now includes the length of the tree representations, except\n   * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.\n   */\n\n  /* Determine the number of bit length codes to send. The pkzip format\n   * requires that at least 4 bit length codes be sent. (appnote.txt says\n   * 3 but the actual value used is 4.)\n   */\n  for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {\n    if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {\n      break;\n    }\n  }\n  /* Update opt_len to include the bit length tree and counts */\n  s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;\n  //Tracev((stderr, \"\\ndyn trees: dyn %ld, stat %ld\",\n  //        s->opt_len, s->static_len));\n\n  return max_blindex;\n}\n\n\n/* ===========================================================================\n * Send the header for a block using dynamic Huffman trees: the counts, the\n * lengths of the bit length codes, the literal tree and the distance tree.\n * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.\n */\nfunction send_all_trees(s, lcodes, dcodes, blcodes)\n//    deflate_state *s;\n//    int lcodes, dcodes, blcodes; /* number of codes for each tree */\n{\n  var rank;                    /* index in bl_order */\n\n  //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, \"not enough codes\");\n  //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,\n  //        \"too many codes\");\n  //Tracev((stderr, \"\\nbl counts: \"));\n  send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */\n  send_bits(s, dcodes - 1,   5);\n  send_bits(s, blcodes - 4,  4); /* not -3 as stated in appnote.txt */\n  for (rank = 0; rank < blcodes; rank++) {\n    //Tracev((stderr, \"\\nbl code %2d \", bl_order[rank]));\n    send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3);\n  }\n  //Tracev((stderr, \"\\nbl tree: sent %ld\", s->bits_sent));\n\n  send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */\n  //Tracev((stderr, \"\\nlit tree: sent %ld\", s->bits_sent));\n\n  send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */\n  //Tracev((stderr, \"\\ndist tree: sent %ld\", s->bits_sent));\n}\n\n\n/* ===========================================================================\n * Check if the data type is TEXT or BINARY, using the following algorithm:\n * - TEXT if the two conditions below are satisfied:\n *    a) There are no non-portable control characters belonging to the\n *       \"black list\" (0..6, 14..25, 28..31).\n *    b) There is at least one printable character belonging to the\n *       \"white list\" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).\n * - BINARY otherwise.\n * - The following partially-portable control characters form a\n *   \"gray list\" that is ignored in this detection algorithm:\n *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).\n * IN assertion: the fields Freq of dyn_ltree are set.\n */\nfunction detect_data_type(s) {\n  /* black_mask is the bit mask of black-listed bytes\n   * set bits 0..6, 14..25, and 28..31\n   * 0xf3ffc07f = binary 11110011111111111100000001111111\n   */\n  var black_mask = 0xf3ffc07f;\n  var n;\n\n  /* Check for non-textual (\"black-listed\") bytes. */\n  for (n = 0; n <= 31; n++, black_mask >>>= 1) {\n    if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) {\n      return Z_BINARY;\n    }\n  }\n\n  /* Check for textual (\"white-listed\") bytes. */\n  if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 ||\n      s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {\n    return Z_TEXT;\n  }\n  for (n = 32; n < LITERALS; n++) {\n    if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {\n      return Z_TEXT;\n    }\n  }\n\n  /* There are no \"black-listed\" or \"white-listed\" bytes:\n   * this stream either is empty or has tolerated (\"gray-listed\") bytes only.\n   */\n  return Z_BINARY;\n}\n\n\nvar static_init_done = false;\n\n/* ===========================================================================\n * Initialize the tree data structures for a new zlib stream.\n */\nfunction _tr_init(s)\n{\n\n  if (!static_init_done) {\n    tr_static_init();\n    static_init_done = true;\n  }\n\n  s.l_desc  = new TreeDesc(s.dyn_ltree, static_l_desc);\n  s.d_desc  = new TreeDesc(s.dyn_dtree, static_d_desc);\n  s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);\n\n  s.bi_buf = 0;\n  s.bi_valid = 0;\n\n  /* Initialize the first block of the first file: */\n  init_block(s);\n}\n\n\n/* ===========================================================================\n * Send a stored block\n */\nfunction _tr_stored_block(s, buf, stored_len, last)\n//DeflateState *s;\n//charf *buf;       /* input block */\n//ulg stored_len;   /* length of input block */\n//int last;         /* one if this is the last block for a file */\n{\n  send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3);    /* send block type */\n  copy_block(s, buf, stored_len, true); /* with header */\n}\n\n\n/* ===========================================================================\n * Send one empty static block to give enough lookahead for inflate.\n * This takes 10 bits, of which 7 may remain in the bit buffer.\n */\nfunction _tr_align(s) {\n  send_bits(s, STATIC_TREES << 1, 3);\n  send_code(s, END_BLOCK, static_ltree);\n  bi_flush(s);\n}\n\n\n/* ===========================================================================\n * Determine the best encoding for the current block: dynamic trees, static\n * trees or store, and output the encoded block to the zip file.\n */\nfunction _tr_flush_block(s, buf, stored_len, last)\n//DeflateState *s;\n//charf *buf;       /* input block, or NULL if too old */\n//ulg stored_len;   /* length of input block */\n//int last;         /* one if this is the last block for a file */\n{\n  var opt_lenb, static_lenb;  /* opt_len and static_len in bytes */\n  var max_blindex = 0;        /* index of last bit length code of non zero freq */\n\n  /* Build the Huffman trees unless a stored block is forced */\n  if (s.level > 0) {\n\n    /* Check if the file is binary or text */\n    if (s.strm.data_type === Z_UNKNOWN) {\n      s.strm.data_type = detect_data_type(s);\n    }\n\n    /* Construct the literal and distance trees */\n    build_tree(s, s.l_desc);\n    // Tracev((stderr, \"\\nlit data: dyn %ld, stat %ld\", s->opt_len,\n    //        s->static_len));\n\n    build_tree(s, s.d_desc);\n    // Tracev((stderr, \"\\ndist data: dyn %ld, stat %ld\", s->opt_len,\n    //        s->static_len));\n    /* At this point, opt_len and static_len are the total bit lengths of\n     * the compressed block data, excluding the tree representations.\n     */\n\n    /* Build the bit length tree for the above two trees, and get the index\n     * in bl_order of the last bit length code to send.\n     */\n    max_blindex = build_bl_tree(s);\n\n    /* Determine the best encoding. Compute the block lengths in bytes. */\n    opt_lenb = (s.opt_len + 3 + 7) >>> 3;\n    static_lenb = (s.static_len + 3 + 7) >>> 3;\n\n    // Tracev((stderr, \"\\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u \",\n    //        opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,\n    //        s->last_lit));\n\n    if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; }\n\n  } else {\n    // Assert(buf != (char*)0, \"lost buf\");\n    opt_lenb = static_lenb = stored_len + 5; /* force a stored block */\n  }\n\n  if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) {\n    /* 4: two words for the lengths */\n\n    /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.\n     * Otherwise we can't have processed more than WSIZE input bytes since\n     * the last block flush, because compression would have been\n     * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to\n     * transform a block into a stored block.\n     */\n    _tr_stored_block(s, buf, stored_len, last);\n\n  } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {\n\n    send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);\n    compress_block(s, static_ltree, static_dtree);\n\n  } else {\n    send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);\n    send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);\n    compress_block(s, s.dyn_ltree, s.dyn_dtree);\n  }\n  // Assert (s->compressed_len == s->bits_sent, \"bad compressed size\");\n  /* The above check is made mod 2^32, for files larger than 512 MB\n   * and uLong implemented on 32 bits.\n   */\n  init_block(s);\n\n  if (last) {\n    bi_windup(s);\n  }\n  // Tracev((stderr,\"\\ncomprlen %lu(%lu) \", s->compressed_len>>3,\n  //       s->compressed_len-7*last));\n}\n\n/* ===========================================================================\n * Save the match info and tally the frequency counts. Return true if\n * the current block must be flushed.\n */\nfunction _tr_tally(s, dist, lc)\n//    deflate_state *s;\n//    unsigned dist;  /* distance of matched string */\n//    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */\n{\n  //var out_length, in_length, dcode;\n\n  s.pending_buf[s.d_buf + s.last_lit * 2]     = (dist >>> 8) & 0xff;\n  s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;\n\n  s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;\n  s.last_lit++;\n\n  if (dist === 0) {\n    /* lc is the unmatched char */\n    s.dyn_ltree[lc * 2]/*.Freq*/++;\n  } else {\n    s.matches++;\n    /* Here, lc is the match length - MIN_MATCH */\n    dist--;             /* dist = match distance - 1 */\n    //Assert((ush)dist < (ush)MAX_DIST(s) &&\n    //       (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&\n    //       (ush)d_code(dist) < (ush)D_CODES,  \"_tr_tally: bad match\");\n\n    s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++;\n    s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;\n  }\n\n// (!) This block is disabled in zlib defailts,\n// don't enable it for binary compatibility\n\n//#ifdef TRUNCATE_BLOCK\n//  /* Try to guess if it is profitable to stop the current block here */\n//  if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {\n//    /* Compute an upper bound for the compressed length */\n//    out_length = s.last_lit*8;\n//    in_length = s.strstart - s.block_start;\n//\n//    for (dcode = 0; dcode < D_CODES; dcode++) {\n//      out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);\n//    }\n//    out_length >>>= 3;\n//    //Tracev((stderr,\"\\nlast_lit %u, in %ld, out ~%ld(%ld%%) \",\n//    //       s->last_lit, in_length, out_length,\n//    //       100L - out_length*100L/in_length));\n//    if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {\n//      return true;\n//    }\n//  }\n//#endif\n\n  return (s.last_lit === s.lit_bufsize - 1);\n  /* We avoid equality with lit_bufsize because of wraparound at 64K\n   * on 16 bit machines and because stored blocks are restricted to\n   * 64K-1 bytes.\n   */\n}\n\nexport { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align };\n"
  },
  {
    "path": "services/gateway/noVNC/vendor/pako/lib/zlib/zstream.js",
    "content": "export default function ZStream() {\n  /* next input byte */\n  this.input = null; // JS specific, because we have no pointers\n  this.next_in = 0;\n  /* number of bytes available at input */\n  this.avail_in = 0;\n  /* total number of input bytes read so far */\n  this.total_in = 0;\n  /* next output byte should be put there */\n  this.output = null; // JS specific, because we have no pointers\n  this.next_out = 0;\n  /* remaining free space at output */\n  this.avail_out = 0;\n  /* total number of bytes output so far */\n  this.total_out = 0;\n  /* last error message, NULL if no error */\n  this.msg = ''/*Z_NULL*/;\n  /* not visible by applications */\n  this.state = null;\n  /* best guess about the data type: binary or text */\n  this.data_type = 2/*Z_UNKNOWN*/;\n  /* adler32 value of the uncompressed data */\n  this.adler = 0;\n}\n"
  },
  {
    "path": "services/gateway/noVNC/vnc.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" class=\"noVNC_loading\">\n<head>\n    <base href=\"/static/\">\n    <!--\n    noVNC example: simple example using default UI\n    Copyright (C) 2019 The noVNC authors\n    noVNC is licensed under the MPL 2.0 (see LICENSE.txt)\n    This file is licensed under the 2-Clause BSD license (see LICENSE.txt).\n\n    Connect parameters are provided in query string:\n        http://example.com/?host=HOST&port=PORT&encrypt=1\n    or the fragment:\n        http://example.com/#host=HOST&port=PORT&encrypt=1\n    -->\n    <title>noVNC</title>\n\n    <link rel=\"icon\" type=\"image/x-icon\" href=\"app/images/icons/novnc.ico\">\n    <meta name=\"theme-color\" content=\"#313131\">\n\n    <!-- Apple iOS Safari settings -->\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">\n\n    <!-- @2x -->\n    <link rel=\"apple-touch-icon\" sizes=\"40x40\" type=\"image/png\" href=\"app/images/icons/novnc-ios-40.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"58x58\" type=\"image/png\" href=\"app/images/icons/novnc-ios-58.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"80x80\" type=\"image/png\" href=\"app/images/icons/novnc-ios-80.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"120x120\" type=\"image/png\" href=\"app/images/icons/novnc-ios-120.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"152x152\" type=\"image/png\" href=\"app/images/icons/novnc-ios-152.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"167x167\" type=\"image/png\" href=\"app/images/icons/novnc-ios-167.png\">\n    <!-- @3x -->\n    <link rel=\"apple-touch-icon\" sizes=\"60x60\" type=\"image/png\" href=\"app/images/icons/novnc-ios-60.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"87x87\" type=\"image/png\" href=\"app/images/icons/novnc-ios-87.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"120x120\" type=\"image/png\" href=\"app/images/icons/novnc-ios-120.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" type=\"image/png\" href=\"app/images/icons/novnc-ios-180.png\">\n\n    <!-- Stylesheets -->\n    <link rel=\"stylesheet\" href=\"app/styles/constants.css\">\n    <link rel=\"stylesheet\" href=\"app/styles/base.css\">\n    <link rel=\"stylesheet\" href=\"app/styles/input.css\">\n\n    <!-- Images that will later appear via CSS -->\n    <link rel=\"preload\" as=\"image\" href=\"app/images/info.svg\">\n    <link rel=\"preload\" as=\"image\" href=\"app/images/error.svg\">\n    <link rel=\"preload\" as=\"image\" href=\"app/images/warning.svg\">\n\n    <script type=\"module\" crossorigin=\"anonymous\" src=\"app/error-handler.js\"></script>\n\n    <script type=\"module\">\n        import UI from \"./app/ui.js\";\n        import * as Log from './core/util/logging.js';\n\n        let response;\n\n        let defaults = {};\n        let mandatory = {};\n\n        // Default settings will be loaded from defaults.json. Mandatory\n        // settings will be loaded from mandatory.json, which the user\n        // cannot change.\n\n        try {\n            response = await fetch('./defaults.json');\n            if (!response.ok) {\n                throw Error(\"\" + response.status + \" \" + response.statusText);\n            }\n\n            defaults = await response.json();\n        } catch (err) {\n            Log.Error(\"Couldn't fetch defaults.json: \" + err);\n        }\n\n        try {\n            response = await fetch('./mandatory.json');\n            if (!response.ok) {\n                throw Error(\"\" + response.status + \" \" + response.statusText);\n            }\n\n            mandatory = await response.json();\n        } catch (err) {\n            Log.Error(\"Couldn't fetch mandatory.json: \" + err);\n        }\n\n        // You can also override any defaults you need here:\n        //\n        // defaults['host'] = 'vnc.example.com';\n\n        // Or force a specific setting, preventing the user from\n        // changing it:\n        //\n        // mandatory['view_only'] = true;\n\n        // See docs/EMBEDDING.md for a list of possible settings.\n\n        UI.start({ settings: { defaults: defaults,\n                               mandatory: mandatory } });\n    </script>\n</head>\n\n<body>\n\n    <div id=\"noVNC_fallback_error\" class=\"noVNC_center\">\n        <div>\n            <div>noVNC encountered an error:</div>\n            <br>\n            <div id=\"noVNC_fallback_errormsg\"></div>\n        </div>\n    </div>\n\n    <!-- noVNC control bar -->\n    <div id=\"noVNC_control_bar_anchor\" class=\"noVNC_vcenter\">\n\n        <div id=\"noVNC_control_bar\">\n            <div id=\"noVNC_control_bar_handle\" title=\"Hide/Show the control bar\"><div></div></div>\n\n            <div class=\"noVNC_scroll\">\n\n            <h1 class=\"noVNC_logo\" translate=\"no\"><span>no</span><br>VNC</h1>\n\n            <hr>\n\n            <!-- Drag/Pan the viewport -->\n            <input type=\"image\" alt=\"Drag\" src=\"app/images/drag.svg\"\n                id=\"noVNC_view_drag_button\" class=\"noVNC_button noVNC_hidden\"\n                title=\"Move/Drag viewport\">\n\n            <!--noVNC touch device only buttons-->\n            <div id=\"noVNC_mobile_buttons\">\n                <input type=\"image\" alt=\"Keyboard\" src=\"app/images/keyboard.svg\"\n                    id=\"noVNC_keyboard_button\" class=\"noVNC_button\" title=\"Show keyboard\">\n            </div>\n\n            <!-- Extra manual keys -->\n            <input type=\"image\" alt=\"Extra keys\" src=\"app/images/toggleextrakeys.svg\"\n                id=\"noVNC_toggle_extra_keys_button\" class=\"noVNC_button\"\n                title=\"Show extra keys\">\n            <div class=\"noVNC_vcenter\">\n            <div id=\"noVNC_modifiers\" class=\"noVNC_panel\">\n                <input type=\"image\" alt=\"Ctrl\" src=\"app/images/ctrl.svg\"\n                    id=\"noVNC_toggle_ctrl_button\" class=\"noVNC_button\"\n                    title=\"Toggle Ctrl\">\n                <input type=\"image\" alt=\"Alt\" src=\"app/images/alt.svg\"\n                    id=\"noVNC_toggle_alt_button\" class=\"noVNC_button\"\n                    title=\"Toggle Alt\">\n                <input type=\"image\" alt=\"Windows\" src=\"app/images/windows.svg\"\n                    id=\"noVNC_toggle_windows_button\" class=\"noVNC_button\"\n                    title=\"Toggle Windows\">\n                <input type=\"image\" alt=\"Tab\" src=\"app/images/tab.svg\"\n                    id=\"noVNC_send_tab_button\" class=\"noVNC_button\"\n                    title=\"Send Tab\">\n                <input type=\"image\" alt=\"Esc\" src=\"app/images/esc.svg\"\n                    id=\"noVNC_send_esc_button\" class=\"noVNC_button\"\n                    title=\"Send Escape\">\n                <input type=\"image\" alt=\"Ctrl+Alt+Del\" src=\"app/images/ctrlaltdel.svg\"\n                    id=\"noVNC_send_ctrl_alt_del_button\" class=\"noVNC_button\"\n                    title=\"Send Ctrl-Alt-Del\">\n            </div>\n            </div>\n\n            <!-- Shutdown/Reboot -->\n            <input type=\"image\" alt=\"Shutdown/Reboot\" src=\"app/images/power.svg\"\n                id=\"noVNC_power_button\" class=\"noVNC_button\"\n                title=\"Shutdown/Reboot...\">\n            <div class=\"noVNC_vcenter\">\n            <div id=\"noVNC_power\" class=\"noVNC_panel\">\n                <div class=\"noVNC_heading\">\n                    <img alt=\"\" src=\"app/images/power.svg\"> Power\n                </div>\n                <input type=\"button\" id=\"noVNC_shutdown_button\" value=\"Shutdown\">\n                <input type=\"button\" id=\"noVNC_reboot_button\" value=\"Reboot\">\n                <input type=\"button\" id=\"noVNC_reset_button\" value=\"Reset\">\n            </div>\n            </div>\n\n            <!-- Clipboard -->\n            <input type=\"image\" alt=\"Clipboard\" src=\"app/images/clipboard.svg\"\n                id=\"noVNC_clipboard_button\" class=\"noVNC_button\"\n                title=\"Clipboard\">\n            <div class=\"noVNC_vcenter\">\n            <div id=\"noVNC_clipboard\" class=\"noVNC_panel\">\n                <div class=\"noVNC_heading\">\n                    <img alt=\"\" src=\"app/images/clipboard.svg\"> Clipboard\n                </div>\n                <p class=\"noVNC_subheading\">\n                    Edit clipboard content in the textarea below.\n                </p>\n                <textarea id=\"noVNC_clipboard_text\" rows=5></textarea>\n            </div>\n            </div>\n\n            <!-- Toggle fullscreen -->\n            <input type=\"image\" alt=\"Full screen\" src=\"app/images/fullscreen.svg\"\n                id=\"noVNC_fullscreen_button\" class=\"noVNC_button noVNC_hidden\"\n                title=\"Full screen\">\n\n            <!-- Settings -->\n            <input type=\"image\" alt=\"Settings\" src=\"app/images/settings.svg\"\n                id=\"noVNC_settings_button\" class=\"noVNC_button\"\n                title=\"Settings\">\n            <div class=\"noVNC_vcenter\">\n            <div id=\"noVNC_settings\" class=\"noVNC_panel\">\n                <div class=\"noVNC_heading\">\n                    <img alt=\"\" src=\"app/images/settings.svg\"> Settings\n                </div>\n                <ul>\n                    <li>\n                        <label>\n                            <input id=\"noVNC_setting_shared\" type=\"checkbox\"\n                                   class=\"toggle\">\n                            Shared mode\n                        </label>\n                    </li>\n                    <li>\n                        <label>\n                            <input id=\"noVNC_setting_view_only\" type=\"checkbox\"\n                                   class=\"toggle\">\n                            View only\n                        </label>\n                    </li>\n                    <li><hr></li>\n                    <li>\n                        <label>\n                            <input id=\"noVNC_setting_view_clip\" type=\"checkbox\"\n                                   class=\"toggle\">\n                            Clip to window\n                        </label>\n                    </li>\n                    <li>\n                        <label for=\"noVNC_setting_resize\">Scaling mode:</label>\n                        <select id=\"noVNC_setting_resize\" name=\"vncResize\">\n                            <option value=\"off\">None</option>\n                            <option value=\"scale\">Local scaling</option>\n                            <option value=\"remote\">Remote resizing</option>\n                        </select>\n                    </li>\n                    <li><hr></li>\n                    <li>\n                        <div class=\"noVNC_expander\">Advanced</div>\n                        <div><ul>\n                            <li>\n                                <label for=\"noVNC_setting_quality\">Quality:</label>\n                                <input id=\"noVNC_setting_quality\" type=\"range\" min=\"0\" max=\"9\" value=\"6\">\n                            </li>\n                            <li>\n                                <label for=\"noVNC_setting_compression\">Compression level:</label>\n                                <input id=\"noVNC_setting_compression\" type=\"range\" min=\"0\" max=\"9\" value=\"2\">\n                            </li>\n                            <li><hr></li>\n                            <li>\n                                <label for=\"noVNC_setting_repeaterID\">Repeater ID:</label>\n                                <input id=\"noVNC_setting_repeaterID\" type=\"text\" value=\"\">\n                            </li>\n                            <li>\n                                <div class=\"noVNC_expander\">WebSocket</div>\n                                <div><ul>\n                                    <li>\n                                        <label>\n                                            <input id=\"noVNC_setting_encrypt\" type=\"checkbox\"\n                                                   class=\"toggle\">\n                                            Encrypt\n                                        </label>\n                                    </li>\n                                    <li>\n                                        <label for=\"noVNC_setting_host\">Host:</label>\n                                        <input id=\"noVNC_setting_host\">\n                                    </li>\n                                    <li>\n                                        <label for=\"noVNC_setting_port\">Port:</label>\n                                        <input id=\"noVNC_setting_port\" type=\"number\">\n                                    </li>\n                                    <li>\n                                        <label for=\"noVNC_setting_path\">Path:</label>\n                                        <input id=\"noVNC_setting_path\" type=\"text\" value=\"websockify\">\n                                    </li>\n                                </ul></div>\n                            </li>\n                            <li><hr></li>\n                            <li>\n                                <label>\n                                    <input id=\"noVNC_setting_reconnect\" type=\"checkbox\"\n                                           class=\"toggle\">\n                                    Automatic reconnect\n                                </label>\n                            </li>\n                            <li>\n                                <label for=\"noVNC_setting_reconnect_delay\">Reconnect delay (ms):</label>\n                                <input id=\"noVNC_setting_reconnect_delay\" type=\"number\">\n                            </li>\n                            <li><hr></li>\n                            <li>\n                                <label>\n                                    <input id=\"noVNC_setting_show_dot\" type=\"checkbox\"\n                                           class=\"toggle\">\n                                    Show dot when no cursor\n                                </label>\n                            </li>\n                            <li><hr></li>\n                            <!-- Logging selection dropdown -->\n                            <li>\n                                <label>Logging:\n                                    <select id=\"noVNC_setting_logging\" name=\"vncLogging\">\n                                    </select>\n                                </label>\n                            </li>\n                        </ul></div>\n                    </li>\n                    <li class=\"noVNC_version_separator\"><hr></li>\n                    <li class=\"noVNC_version_wrapper\">\n                        <span>Version:</span>\n                        <span class=\"noVNC_version\"></span>\n                    </li>\n                </ul>\n            </div>\n            </div>\n\n            <!-- Connection controls -->\n            <input type=\"image\" alt=\"Disconnect\" src=\"app/images/disconnect.svg\"\n                id=\"noVNC_disconnect_button\" class=\"noVNC_button\"\n                title=\"Disconnect\">\n\n            </div>\n        </div>\n\n    </div> <!-- End of noVNC_control_bar -->\n\n    <div id=\"noVNC_hint_anchor\" class=\"noVNC_vcenter\">\n        <div id=\"noVNC_control_bar_hint\">\n        </div>\n    </div>\n\n    <!-- Status dialog -->\n    <div id=\"noVNC_status\"></div>\n\n    <!-- Connect button -->\n    <div class=\"noVNC_center\">\n        <div id=\"noVNC_connect_dlg\">\n            <p class=\"noVNC_logo\" translate=\"no\"><span>no</span>VNC</p>\n            <div>\n                <button id=\"noVNC_connect_button\">\n                    <img alt=\"\" src=\"app/images/connect.svg\"> Connect\n                </button>\n            </div>\n        </div>\n    </div>\n\n    <!-- Server key verification dialog -->\n    <div class=\"noVNC_center noVNC_connect_layer\">\n    <div id=\"noVNC_verify_server_dlg\" class=\"noVNC_panel\"><form>\n        <div class=\"noVNC_heading\">\n            Server identity\n        </div>\n        <div>\n            The server has provided the following identifying information:\n        </div>\n        <div id=\"noVNC_fingerprint_block\">\n            Fingerprint:\n            <span id=\"noVNC_fingerprint\"></span>\n        </div>\n        <div>\n            Please verify that the information is correct and press\n            \"Approve\". Otherwise press \"Reject\".\n        </div>\n        <div class=\"button_row\">\n            <input id=\"noVNC_approve_server_button\" type=\"submit\" value=\"Approve\">\n            <input id=\"noVNC_reject_server_button\" type=\"button\" value=\"Reject\">\n        </div>\n    </form></div>\n    </div>\n\n    <!-- Password dialog -->\n    <div class=\"noVNC_center noVNC_connect_layer\">\n    <div id=\"noVNC_credentials_dlg\" class=\"noVNC_panel\"><form>\n        <div class=\"noVNC_heading\">\n            Credentials\n        </div>\n        <div id=\"noVNC_username_block\">\n            <label for=\"noVNC_username_input\">Username:</label>\n            <input id=\"noVNC_username_input\">\n        </div>\n        <div id=\"noVNC_password_block\">\n            <label for=\"noVNC_password_input\">Password:</label>\n            <input id=\"noVNC_password_input\" type=\"password\">\n        </div>\n        <div class=\"button_row\">\n            <input id=\"noVNC_credentials_button\" type=\"submit\" value=\"Send credentials\">\n        </div>\n    </form></div>\n    </div>\n\n    <!-- Transition screens -->\n    <div id=\"noVNC_transition\">\n        <div id=\"noVNC_transition_text\"></div>\n        <div>\n        <input type=\"button\" id=\"noVNC_cancel_reconnect_button\" value=\"Cancel\">\n        </div>\n        <div class=\"noVNC_spinner\"></div>\n    </div>\n\n    <!-- This is where the RFB elements will attach -->\n    <div id=\"noVNC_container\">\n        <!-- Note that Google Chrome on Android doesn't respect any of these,\n             html attributes which attempt to disable text suggestions on the\n             on-screen keyboard. Let's hope Chrome implements the ime-mode\n             style for example -->\n        <textarea id=\"noVNC_keyboardinput\" autocapitalize=\"off\"\n            autocomplete=\"off\" spellcheck=\"false\" tabindex=\"-1\"></textarea>\n    </div>\n\n    <audio id=\"noVNC_bell\">\n        <source src=\"app/sounds/bell.oga\" type=\"audio/ogg\">\n        <source src=\"app/sounds/bell.mp3\" type=\"audio/mpeg\">\n    </audio>\n </body>\n</html>\n"
  },
  {
    "path": "services/gateway/noVNC/vnc_lite.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <base href=\"/static/\">\n    <!--\n    noVNC example: lightweight example using minimal UI and features\n\n    This is a self-contained file which doesn't import WebUtil or external CSS.\n\n    Copyright (C) 2019 The noVNC authors\n    noVNC is licensed under the MPL 2.0 (see LICENSE.txt)\n    This file is licensed under the 2-Clause BSD license (see LICENSE.txt).\n\n    Connect parameters are provided in query string:\n        http://example.com/?host=HOST&port=PORT&scale=true\n    -->\n    <title>noVNC</title>\n\n    <style>\n\n        body {\n            margin: 0;\n            background-color: dimgrey;\n            height: 100%;\n            display: flex;\n            flex-direction: column;\n        }\n        html {\n            height: 100%;\n        }\n\n        #top_bar {\n            background-color: #6e84a3;\n            color: white;\n            font: bold 12px Helvetica;\n            padding: 6px 5px 4px 5px;\n            border-bottom: 1px outset;\n        }\n        #status {\n            text-align: center;\n        }\n        #sendCtrlAltDelButton {\n            position: fixed;\n            top: 0px;\n            right: 0px;\n            border: 1px outset;\n            padding: 5px 5px 4px 5px;\n            cursor: pointer;\n        }\n\n        #screen {\n            flex: 1; /* fill remaining space */\n            overflow: hidden;\n        }\n\n    </style>\n\n    <script type=\"module\" crossorigin=\"anonymous\">\n        // RFB holds the API to connect and communicate with a VNC server\n        import RFB from './core/rfb.js';\n\n        let rfb;\n        let desktopName;\n\n        // When this function is called we have\n        // successfully connected to a server\n        function connectedToServer(e) {\n            status(\"Connected to \" + desktopName);\n        }\n\n        // This function is called when we are disconnected\n        function disconnectedFromServer(e) {\n            if (e.detail.clean) {\n                status(\"Disconnected\");\n            } else {\n                status(\"Something went wrong, connection is closed\");\n            }\n        }\n\n        // When this function is called, the server requires\n        // credentials to authenticate\n        function credentialsAreRequired(e) {\n            const password = prompt(\"Password required:\");\n            rfb.sendCredentials({ password: password });\n        }\n\n        // When this function is called we have received\n        // a desktop name from the server\n        function updateDesktopName(e) {\n            desktopName = e.detail.name;\n        }\n\n        // Since most operating systems will catch Ctrl+Alt+Del\n        // before they get a chance to be intercepted by the browser,\n        // we provide a way to emulate this key sequence.\n        function sendCtrlAltDel() {\n            rfb.sendCtrlAltDel();\n            return false;\n        }\n\n        // Show a status text in the top bar\n        function status(text) {\n            document.getElementById('status').textContent = text;\n        }\n\n        // This function extracts the value of one variable from the\n        // query string. If the variable isn't defined in the URL\n        // it returns the default value instead.\n        function readQueryVariable(name, defaultValue) {\n            // A URL with a query parameter can look like this:\n            // https://www.example.com?myqueryparam=myvalue\n            //\n            // Note that we use location.href instead of location.search\n            // because Firefox < 53 has a bug w.r.t location.search\n            const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),\n                  match = document.location.href.match(re);\n\n            if (match) {\n                // We have to decode the URL since want the cleartext value\n                return decodeURIComponent(match[1]);\n            }\n\n            return defaultValue;\n        }\n\n        document.getElementById('sendCtrlAltDelButton')\n            .onclick = sendCtrlAltDel;\n\n        // Read parameters specified in the URL query string\n        // By default, use the host and port of server that served this file\n        const host = readQueryVariable('host', window.location.hostname);\n        let port = readQueryVariable('port', window.location.port);\n        const password = readQueryVariable('password');\n        const path = readQueryVariable('path', 'websockify');\n\n        // | | |         | | |\n        // | | | Connect | | |\n        // v v v         v v v\n\n        status(\"Connecting\");\n\n        // Build the websocket URL used to connect\n        let url;\n        if (window.location.protocol === \"https:\") {\n            url = 'wss';\n        } else {\n            url = 'ws';\n        }\n        url += '://' + host;\n        if(port) {\n            url += ':' + port;\n        }\n        url += '/' + path;\n\n        // Creating a new RFB object will start a new connection\n        rfb = new RFB(document.getElementById('screen'), url,\n                      { credentials: { password: password } });\n\n        // Add listeners to important events from the RFB module\n        rfb.addEventListener(\"connect\",  connectedToServer);\n        rfb.addEventListener(\"disconnect\", disconnectedFromServer);\n        rfb.addEventListener(\"credentialsrequired\", credentialsAreRequired);\n        rfb.addEventListener(\"desktopname\", updateDesktopName);\n\n        // Set parameters that can be changed on an active connection\n        rfb.viewOnly = readQueryVariable('view_only', false);\n        rfb.scaleViewport = readQueryVariable('scale', false);\n    </script>\n</head>\n\n<body>\n    <div id=\"top_bar\">\n        <div id=\"status\">Loading</div>\n        <div id=\"sendCtrlAltDelButton\">Send CtrlAltDel</div>\n    </div>\n    <div id=\"screen\">\n        <!-- This is where the remote screen will appear -->\n    </div>\n</body>\n</html>\n"
  },
  {
    "path": "services/gateway/requirements.txt",
    "content": "annotated-types==0.7.0\nanyio==4.9.0\ncachetools==5.5.2\ncertifi==2025.1.31\ncharset-normalizer==3.4.1\nclick==8.1.8\ncolorama==0.4.6\ndnspython==2.7.0\ndurationpy==0.9\nemail-validator==2.2.0\nfastapi==0.115.12\nfastapi-cli==0.0.7\ngoogle-auth==2.39.0\nh11==0.14.0\nhttpcore==1.0.8\nhttptools==0.6.4\nhttpx==0.28.1\nidna==3.10\njinja2==3.1.6\nkubernetes==32.0.1\nmarkdown-it-py==3.0.0\nmarkupsafe==3.0.2\nmdurl==0.1.2\noauthlib==3.2.2\npyasn1==0.6.1\npyasn1-modules==0.4.2\npydantic==2.11.3\npydantic-core==2.33.1\npygments==2.19.1\npython-dateutil==2.9.0.post0\npython-dotenv==1.1.0\npython-multipart==0.0.20\npyyaml==6.0.2\nrequests==2.32.3\nrequests-oauthlib==2.0.0\nrich==14.0.0\nrich-toolkit==0.14.1\nrsa==4.9.1\nshellingham==1.5.4\nsix==1.17.0\nsniffio==1.3.1\nstarlette==0.46.2\nsupabase==2.15.0\ntyper==0.15.2\ntyping-extensions==4.13.2\ntyping-inspection==0.4.0\nurllib3==2.4.0\nuvicorn==0.34.2\nwatchfiles==1.0.5\nwebsocket-client==1.8.0\nwebsockets\n"
  }
]