diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000..058a593e --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,10 @@ +/* eslint-env node */ +module.exports = { + extends: ['plugin:@typescript-eslint/recommended'], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + root: true, + rules: { + "@typescript-eslint/ban-ts-comment": "off" + } +}; diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 722947c4..1cdec8d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Node setup - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: - node-version: 16 + node-version: latest - name: Install yarn run: npm install --global yarn - name: Install dependecies @@ -31,7 +31,10 @@ jobs: - name: Unit Tests run: yarn test - name: Acceptance Tests - run: PEERBOOK_REF="${{ github.event.inputs.pbRef }}" WEBEXEC_REF="${{ github.event.inputs.weRef }}" bash -x aatp/run + run: > + PEERBOOK_REF="${{ github.event.inputs.pbRef }}" + WEBEXEC_REF="${{ github.event.inputs.weRef }}" + bash -x aatp/run - name: Upload acceptance test results if: ${{ failure() }} uses: actions/upload-artifact@v3 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index e21c4d8d..aec034ec 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1,17 +1,17 @@ name: Validate -on: push +on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Node setup - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20.8.1 - name: Install yarn run: npm install --global yarn - name: Install dependecies diff --git a/.gitignore b/.gitignore index 260a4da3..1b798851 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ aatp/**/authorized_fingerprints # Local Netlify folder .netlify + +# idea +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 63eae69e..4fe4045c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.8.1] - 2023/9/14 + +### Fixed + +- PeerBook status indicator is always an emoji, no kanji chars + +## [1.8.0] - 2023/9/10 + +### Added + +- dashed underline to link on the map - just like in TWR +- Progressive Web App support +- synchronizing layout across all connected client +- the login command for web clients +- peerbook spinner and indicator + ## 1.7.1 - 2023/8/27 ### Fixed diff --git a/__mocks__/@revenuecat/purchases-capacitor.ts b/__mocks__/@revenuecat/purchases-capacitor.ts new file mode 100644 index 00000000..e5472179 --- /dev/null +++ b/__mocks__/@revenuecat/purchases-capacitor.ts @@ -0,0 +1,7 @@ +import { vi } from "vitest" + +export const configure = vi.fn().mockResolvedValue(undefined); +export const setMockWebResults = vi.fn().mockResolvedValue(undefined); +export const getOfferings = vi.fn().mockResolvedValue({ offerings: [] }); +export const getProducts = vi.fn().mockResolvedValue({ products: [] }); +// ...mock other methods as needed diff --git a/__mocks__/xterm.ts b/__mocks__/xterm.ts index 12387eb0..29daed19 100644 --- a/__mocks__/xterm.ts +++ b/__mocks__/xterm.ts @@ -9,6 +9,10 @@ export class Terminal { } element = { addEventListener: vi.fn(), + parentElement: { + clientHeight: 480, + clientWidth: 640 + } } constructor (props) { this.out = "" @@ -62,5 +66,18 @@ export class Terminal { const ev = new KeyboardEvent("keydown", { key }) this.keyHandler( { domEvent: ev } ) } + resize:(columns: number, rows: number) => void = vi.fn(); + _core = { + _renderService: { + dimensions: { + css: { + cell: { + width: 5, + height: 11 + } + } + } + } + } } diff --git a/aatp/http_webrtc/http_webrtc.spec.ts b/aatp/http_webrtc/http_webrtc.spec.ts index 256dd238..ebf276ce 100644 --- a/aatp/http_webrtc/http_webrtc.spec.ts +++ b/aatp/http_webrtc/http_webrtc.spec.ts @@ -100,8 +100,8 @@ pinch_max_y_velocity = 0.1` await page.screenshot({ path: `/result/2.png` }) await expect(page.locator('.pane')).toHaveCount(2) }) - test('a pane can be close', async() => { - sleep(500) + test('a pane can be closed', async() => { + await sleep(500) const exitState = await page.evaluate(() => { try { window.terminal7.activeG.activeW.activeP.d.send("exit\n") @@ -120,7 +120,7 @@ pinch_max_y_velocity = 0.1` await page.screenshot({ path: `/result/second.png` }) const lines = await page.evaluate(() => window.terminal7.activeG.activeW.activeP.t.buffer.active.length) - expect(lines).toEqual(38) + await expect(lines).toEqual(38) await page.evaluate(async() => { const gate = window.terminal7.activeG gate.disengage().then(() => { diff --git a/aatp/infra/webexec/docker_entry.sh b/aatp/infra/webexec/docker_entry.sh index ac775f82..5dfd6797 100644 --- a/aatp/infra/webexec/docker_entry.sh +++ b/aatp/infra/webexec/docker_entry.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -x -EXE="/usr/local/bin/webexec" + HOME=/home/runner CONF=/conf @@ -19,8 +19,7 @@ if [[ $PEERBOOK == "1" ]] then /scripts/wait-for-it.sh -h peerbook -p 17777 fi -su -c "$EXE start --debug" runner while true do - sleep 1 + su - runner -c '/usr/local/bin/webexec start --debug' done diff --git a/aatp/peerbook_webrtc/admin.spec.ts b/aatp/peerbook_webrtc/admin.spec.ts index 40691ed8..8ab4f1ee 100644 --- a/aatp/peerbook_webrtc/admin.spec.ts +++ b/aatp/peerbook_webrtc/admin.spec.ts @@ -80,7 +80,7 @@ test.describe('peerbook administration', () => { expect(pbOpen).toBeFalsy() }) test('purchase update with an active subscription and bad otp', async () => { - await sleep(500) + await page.keyboard.press("Enter") await redisClient.set("tempid:$ValidBearer", "1") await sleep(1500) await page.evaluate(async () => { diff --git a/aatp/peerbook_webrtc/lab.yaml b/aatp/peerbook_webrtc/lab.yaml index 234f72f2..48e7de51 100644 --- a/aatp/peerbook_webrtc/lab.yaml +++ b/aatp/peerbook_webrtc/lab.yaml @@ -54,7 +54,6 @@ services: PB_HOME_URL: http://peerbook:17777 redis: image: "redis:alpine" - command: [sh, -c, "rm -f /data/dump.rdb && redis-server"] # disable persistence revenuecat: image: "mockserver/mockserver" environment: @@ -65,8 +64,7 @@ services: volumes: - ./aatp/peerbook_webrtc:/config smtp: - image: mailhog:local - build: https://github.com/mailhog/MailHog.git + image: jcalonso/mailhog expose: - 1025 - 8025 diff --git a/aatp/peerbook_webrtc/session.spec.ts b/aatp/peerbook_webrtc/session.spec.ts index b6ae081b..4ead6234 100644 --- a/aatp/peerbook_webrtc/session.spec.ts +++ b/aatp/peerbook_webrtc/session.spec.ts @@ -144,32 +144,27 @@ insecure = true await expect(page.locator('.pane')).toHaveCount(1) }) test('disengage and reconnect', async() => { - await page.evaluate(async() => { - const gate = window.terminal7.activeG - gate.activeW.activeP.d.send("seq 10; sleep 1; seq 10 20\n") + await page.evaluate(async() => + await window.terminal7.activeG.activeW.activeP.d.send( + "seq 10; sleep 1; seq 10 20\n")) + await page.screenshot({ path: `/result/first.png` }) + const y1 = await page.evaluate(async() => { + const ret = window.terminal7.activeG.activeW.activeP.t.buffer.active.cursorY + window.terminal7.onAppStateChange({isActive: false}) + return ret }) - await sleep(100) await page.screenshot({ path: `/result/second.png` }) - const lines1 = await page.evaluate(async() => { - const gate = window.terminal7.activeG - await gate.disengage().then(() => { - window.terminal7.clearTimeouts() - window.terminal7.activeG.session = null - }) - return gate.activeW.activeP.t.buffer.active.length - }) await sleep(1000) await page.screenshot({ path: `/result/third.png` }) - await page.evaluate(async() => { - window.terminal7.activeG.connect() - }) + await page.evaluate(async() => + window.terminal7.onAppStateChange({isActive: true})) await expect(page.locator('.pane')).toHaveCount(1) await sleep(1500) - const lines2 = await page.evaluate(() => - window.terminal7.activeG.activeW.activeP.t.buffer.active.length) + const y2 = await page.evaluate(() => + window.terminal7.activeG.activeW.activeP.t.buffer.active.cursorY) await page.screenshot({ path: `/result/fourth.png` }) - console.log(lines1, lines2) - expect(lines2-lines1).toEqual(11) + console.log(y1, y2) + expect(y2-y1).toEqual(11) }) test('after disengage & reconnect, a a pane can be close', async() => { await page.screenshot({ path: `/result/fifth.png` }) diff --git a/aatp/ui/ui.spec.ts b/aatp/ui/ui.spec.ts index 891f9246..3bc0717b 100644 --- a/aatp/ui/ui.spec.ts +++ b/aatp/ui/ui.spec.ts @@ -103,7 +103,7 @@ insecure=true`) await page.screenshot({ path: `/result/2.png` }) await page.locator('.tabbar .reset').click() await expect(page.locator('#t0')).toBeVisible() - sleep(20) + await sleep(20) await page.keyboard.press('Enter') await expect(page.locator('#t0')).toBeHidden() await expect(page.locator('.pane')).toHaveCount(1) diff --git a/android/.idea/kotlinc.xml b/android/.idea/kotlinc.xml index 0e65ceac..69e86158 100644 --- a/android/.idea/kotlinc.xml +++ b/android/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml index c986c771..89efa49f 100644 --- a/android/.idea/misc.xml +++ b/android/.idea/misc.xml @@ -1,9 +1,12 @@ - + + + \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 5be69bf2..347b6ffa 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "dev.terminal7" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 5 - versionName "1.7.1" + versionCode 6 + versionName "1.8.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index d3591c12..4ad47812 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -18,7 +18,7 @@ dependencies { implementation project(':capacitor-network') implementation project(':capacitor-preferences') implementation project(':capacitor-status-bar') - implementation project(':capgo-capacitor-purchases') + implementation project(':revenuecat-purchases-capacitor') implementation project(':capacitor-native-biometric') implementation project(':capacitor-rate-app') implementation project(':capacitor-ssh-plugin') diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json index e9aa8c0c..4657a022 100644 --- a/android/app/src/main/assets/capacitor.plugins.json +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -36,8 +36,8 @@ "classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin" }, { - "pkg": "@capgo/capacitor-purchases", - "classpath": "ee.forgr.plugin.capacitor_purchases.CapacitorPurchasesPlugin" + "pkg": "@revenuecat/purchases-capacitor", + "classpath": "com.revenuecat.purchases.capacitor.PurchasesPlugin" }, { "pkg": "capacitor-native-biometric", diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index f8a1cd8d..d94843c7 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -29,8 +29,8 @@ project(':capacitor-preferences').projectDir = new File('../node_modules/@capaci include ':capacitor-status-bar' project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android') -include ':capgo-capacitor-purchases' -project(':capgo-capacitor-purchases').projectDir = new File('../node_modules/@capgo/capacitor-purchases/android') +include ':revenuecat-purchases-capacitor' +project(':revenuecat-purchases-capacitor').projectDir = new File('../node_modules/@revenuecat/purchases-capacitor/android') include ':capacitor-native-biometric' project(':capacitor-native-biometric').projectDir = new File('../node_modules/capacitor-native-biometric/android') diff --git a/css/terminal7.css b/css/terminal7.css index 2c305248..1d6711d6 100644 --- a/css/terminal7.css +++ b/css/terminal7.css @@ -367,7 +367,6 @@ button.rename-close { position: absolute; top: 128px; left: 20px; - max-width: 35vw; padding: 10px; border: 1px dotted; border-radius: 4px; @@ -375,9 +374,20 @@ button.rename-close { line-height: 12px; } #version a { - text-decoration: underline; + text-decoration-style: dashed; + text-decoration-line: underline; cursor: pointer; } +#version ul { + list-style: none; + text-align: left; + padding-left: 0; + +} +#version li .status { + float: right; + padding-left: 0; +} #version footer { font-size: 10px; } @@ -475,10 +485,30 @@ label { height: 100%; width: 100%; } +@media (min-width: 600px) { + #title-short { + display: none; + } + #title-long { + display: block; + } + #version { + max-width: 35vw; + } +} @media (max-width: 600px) { #log { width: 90vw; } + #title-short { + display: block; + } + #title-long { + display: none; + } + #version { + max-width: 35vw; + } } .search-container { margin: 0 auto; @@ -787,17 +817,8 @@ div.boarding { text-decoration: none; } #title-short { - display: none; letter-spacing: 0.1em; } -@media (max-width: 600px) { - #title-short { - display: block; - } - #title-long { - display: none; - } -} .expand-gate { display: block; position: absolute; diff --git a/index.html b/index.html index 3e40d2cd..c3d32ab8 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,8 @@ - + +
@@ -49,13 +50,23 @@

Terminal 7

T7

-

version 1.7.1

+

version 1.8.1

+ + +
@@ -179,16 +190,20 @@

Network is down

diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index 015c388b..1f2031e7 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -375,7 +375,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = VB5MV6CCSY; INFOPLIST_FILE = App/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Terminal7; @@ -385,7 +385,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7.1; + MARKETING_VERSION = 1.8.0; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = dev.terminal7; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -406,7 +406,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = VB5MV6CCSY; INFOPLIST_FILE = App/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Terminal7; @@ -416,7 +416,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7.1; + MARKETING_VERSION = 1.8.0; PRODUCT_BUNDLE_IDENTIFIER = dev.terminal7; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/ios/App/Podfile b/ios/App/Podfile index 63096e57..47c4d0b7 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -1,4 +1,15 @@ -require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' +def assertDeploymentTarget(installer) + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + # ensure IPHONEOS_DEPLOYMENT_TARGET is at least 13.0 + deployment_target = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f + should_upgrade = deployment_target < 13.0 && deployment_target != 0.0 + if should_upgrade + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + end + end + end +end platform :ios, '15.4' use_frameworks! @@ -19,7 +30,7 @@ def capacitor_pods pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network' pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences' pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar' - pod 'CapgoCapacitorPurchases', :path => '../../node_modules/@capgo/capacitor-purchases' + pod 'RevenuecatPurchasesCapacitor', :path => '../../node_modules/@revenuecat/purchases-capacitor' pod 'CapacitorNativeBiometric', :path => '../../node_modules/capacitor-native-biometric' pod 'CapacitorRateApp', :path => '../../node_modules/capacitor-rate-app' pod 'CapacitorSshPlugin', :path => '../../node_modules/capacitor-ssh-plugin' diff --git a/ios/App/Podfile.lock b/ios/App/Podfile.lock index 39d23dd3..0bd8c381 100644 --- a/ios/App/Podfile.lock +++ b/ios/App/Podfile.lock @@ -1,15 +1,15 @@ PODS: - - Capacitor (5.2.2): + - Capacitor (5.3.0): - CapacitorCordova - CapacitorApp (5.0.6): - Capacitor - CapacitorBrowser (5.0.6): - Capacitor - - CapacitorCamera (5.0.6): + - CapacitorCamera (5.0.7): - Capacitor - CapacitorClipboard (5.0.6): - Capacitor - - CapacitorCordova (5.2.2) + - CapacitorCordova (5.3.0) - CapacitorDevice (5.0.6): - Capacitor - CapacitorKeyboard (5.0.6): @@ -27,14 +27,13 @@ PODS: - NMSSHT7 (~> 2.9) - CapacitorStatusBar (5.0.6): - Capacitor - - CapgoCapacitorPurchases (5.0.8): - - Capacitor - - PurchasesHybridCommon (= 4.8.0) - - RevenueCat (>= 4.16.0) - NMSSHT7 (2.9.1) - - PurchasesHybridCommon (4.8.0): - - RevenueCat (= 4.16.0) - - RevenueCat (4.16.0) + - PurchasesHybridCommon (7.0.0): + - RevenueCat (= 4.27.0) + - RevenueCat (4.27.0) + - RevenuecatPurchasesCapacitor (7.0.0): + - Capacitor + - PurchasesHybridCommon (= 7.0.0) DEPENDENCIES: - "Capacitor (from `../../node_modules/@capacitor/ios`)" @@ -51,7 +50,7 @@ DEPENDENCIES: - CapacitorRateApp (from `../../node_modules/capacitor-rate-app`) - CapacitorSshPlugin (from `../../node_modules/capacitor-ssh-plugin`) - "CapacitorStatusBar (from `../../node_modules/@capacitor/status-bar`)" - - "CapgoCapacitorPurchases (from `../../node_modules/@capgo/capacitor-purchases`)" + - "RevenuecatPurchasesCapacitor (from `../../node_modules/@revenuecat/purchases-capacitor`)" SPEC REPOS: trunk: @@ -88,16 +87,16 @@ EXTERNAL SOURCES: :path: "../../node_modules/capacitor-ssh-plugin" CapacitorStatusBar: :path: "../../node_modules/@capacitor/status-bar" - CapgoCapacitorPurchases: - :path: "../../node_modules/@capgo/capacitor-purchases" + RevenuecatPurchasesCapacitor: + :path: "../../node_modules/@revenuecat/purchases-capacitor" SPEC CHECKSUMS: - Capacitor: 070b18988e0f566728ae9a5eb3a7a974595f1626 + Capacitor: 1ac9165943bc4f2137642d218c5ba05df811de69 CapacitorApp: 024e1b1bea5f883d79f6330d309bc441c88ad04a CapacitorBrowser: 6192948e0ce804fd72aaf77f4114a3ad2e08c760 - CapacitorCamera: 4a95204d13a05b0b726bf9086b44124349ab1952 + CapacitorCamera: 084b0b228bba7d00587910337b1c89e93b1d32ab CapacitorClipboard: 77edf49827ea21da2a9c05c690a4a6a4d07199c4 - CapacitorCordova: 3773395d5331add072300ff6041ca2cf7b93cb0b + CapacitorCordova: b9374d68e63ce29e96ab5db994cf14fbefd722c9 CapacitorDevice: 2c968f98a1ec4d22357418c1521e7ddc46c675e6 CapacitorKeyboard: b978154b024a5f65e044908e37d15b7de58b9d12 CapacitorNativeBiometric: b47637a8cd349bdac014424bb4ddcae9ee5d4919 @@ -106,11 +105,11 @@ SPEC CHECKSUMS: CapacitorRateApp: 9c63b7a25f281ce24bac929e8e8ed29a5c25252a CapacitorSshPlugin: 79f67cb26d40bd0c07cec6dda93d93d5faf7b614 CapacitorStatusBar: 565c0a1ebd79bb40d797606a8992b4a105885309 - CapgoCapacitorPurchases: 8351f236aaa2329c1a172d8f762d7f6faddaaea9 NMSSHT7: 67f9d8f43b40b997728761b4cf2aa7ea660ba7c2 - PurchasesHybridCommon: bd1e1d3476afd3f95332b0ae160e4ce1bd1efd99 - RevenueCat: c12e14f5e3dc5732db6d5095ac4ce6ff08d7eeb0 + PurchasesHybridCommon: af3b2413f9cb999bc1fdca44770bdaf39dfb89fa + RevenueCat: 84fbe2eb9bbf63e1abf346ccd3ff9ee45d633e3b + RevenuecatPurchasesCapacitor: c0de310959a58b3217acd7ec5ce981175bb050b1 -PODFILE CHECKSUM: ade1f161fe406ec0b829d2febee88fb9b125b5ab +PODFILE CHECKSUM: 4e9654adb0765b7ee27622f314473ebfafd489a8 -COCOAPODS: 1.12.1 +COCOAPODS: 1.11.3 diff --git a/main.js b/main.ts similarity index 89% rename from main.js rename to main.ts index cb75b7c7..2609809d 100644 --- a/main.js +++ b/main.ts @@ -3,7 +3,7 @@ import "./css/xterm.css" import "./css/framework7-icons.css" import "./css/codemirror.css" import "./css/dialog.css" -import { Terminal7 } from "./src/terminal7.js" +import { Terminal7 } from "./src/terminal7" import { registerSW } from "virtual:pwa-register"; import { StatusBar, Style } from '@capacitor/status-bar'; @@ -22,8 +22,8 @@ document.addEventListener("DOMContentLoaded", async () => { await StatusBar.show(); await StatusBar.setOverlaysWebView({ overlay: true }); } catch(e) {} - window.terminal7 = new Terminal7() - terminal7.open() + terminal7 = new Terminal7() + await terminal7.open() } }) window.addEventListener('beforeinstallprompt', e => { diff --git a/package.json b/package.json index baa468c8..15703ada 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "terminal7", - "version": "1.7.1", + "version": "1.8.1", "description": "A touchable terminal emulator & multiplexer tablet app", "main": "src/index.js", "homepage": "https://terminal7.dev", @@ -30,8 +30,8 @@ "@capacitor/network": "^5.0.0", "@capacitor/preferences": "^5.0.0", "@capacitor/status-bar": "^5.0.0", - "@capgo/capacitor-purchases": "^5.0.0", "@liveconfig/xterm-webfont": "^2.1.0", + "@revenuecat/purchases-capacitor": "^7.0.0", "@tuzig/codemirror": "5.65.5-mods", "@tuzig/toml": "^3.0.0-browser", "@types/marked": "^4.0.7", @@ -54,6 +54,8 @@ "devDependencies": { "@capacitor/assets": "^2.0.4", "@playwright/test": "^1.20.1", + "@types/hammerjs": "^2.0.42", + "@types/xterm": "^3.0.0", "@typescript-eslint/eslint-plugin": "<6", "@typescript-eslint/parser": "<6", "eslint": "<9", @@ -76,7 +78,7 @@ "build": "vite build -c vite.dev.config.js", "clean": "rm -rf dist ios/App/App/public/*", "aatp": "bash ./aatp/run", - "lint": "npx eslint aatp src --ext .js,.jsx,.ts,.tsx", + "lint": "npx eslint aatp src --ext .js,.ts && npx tsc --noEmit --target es2022 --moduleResolution node src/*.ts", "capacitor:copy:before": "npm run build" }, "repository": { diff --git a/public/virtual-webgl2.js b/public/virtual-webgl2.js new file mode 100644 index 00000000..accfdce5 --- /dev/null +++ b/public/virtual-webgl2.js @@ -0,0 +1,1408 @@ +/* + * Copyright 2018, Gregg Tavares. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Gregg Tavares. nor the names of his + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* eslint-env browser */ + +(function() { + const settings = { + disableWebGL1: false, + compositorCreator() { + }, + }; + const canvasToVirtualContextMap = new Map(); + const extensionInfo = {}; + const extensionSaveRestoreHelpersArray = []; + const extensionSaveRestoreHelpers = {}; + + let currentVirtualContext = null; + let someContextsNeedRendering; + + const sharedWebGLContext = document.createElement('canvas').getContext('webgl2'); + const numAttribs = sharedWebGLContext.getParameter(sharedWebGLContext.MAX_VERTEX_ATTRIBS); + const numTextureUnits = sharedWebGLContext.getParameter(sharedWebGLContext.MAX_COMBINED_TEXTURE_IMAGE_UNITS); + const numUniformBufferBindings = sharedWebGLContext.getParameter(sharedWebGLContext.MAX_UNIFORM_BUFFER_BINDINGS); + const baseState = makeDefaultState(sharedWebGLContext, 300, 150); + + const INT = 0x1404 + const UNSIGNED_INT = 0x1405; + const FLOAT = 0x1406; + + const vs = ` + attribute vec4 position; + varying vec2 v_texcoord; + void main() { + gl_Position = position; + v_texcoord = position.xy * .5 + .5; + } + `; + + const fs = ` + precision mediump float; + varying vec2 v_texcoord; + uniform sampler2D u_tex; + void main() { + gl_FragColor = texture2D(u_tex, v_texcoord); + } + `; + + const fs2 = ` + precision mediump float; + varying vec2 v_texcoord; + uniform sampler2D u_tex; + void main() { + gl_FragColor = texture2D(u_tex, v_texcoord); + gl_FragColor.rgb *= gl_FragColor.a; + } + `; + + const premultiplyAlphaTrueProgram = createProgram(sharedWebGLContext, [vs, fs]); + const premultiplyAlphaFalseProgram = createProgram(sharedWebGLContext, [vs, fs2]); + + { + const gl = sharedWebGLContext; + const positionLoc = 0; // hard coded in createProgram + + const buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + -1, -1, + 1, -1, + -1, 1, + -1, 1, + 1, -1, + 1, 1, + ]), gl.STATIC_DRAW); + + gl.enableVertexAttribArray(positionLoc); + gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0); + + } + + saveAllState(baseState); + + HTMLCanvasElement.prototype.getContext = (function(origFn) { + return function(type, contextAttributes) { + if (type === 'webgl' || type === 'experimental-webgl') { + return createOrGetVirtualWebGLContext(this, type, contextAttributes); + } else if (type === 'webgl2') { + return createOrGetVirtualWebGLContext(this, type, contextAttributes); + } + return origFn.call(this, type, contextAttributes); + }; + + }(HTMLCanvasElement.prototype.getContext)); + + function valueOrDefault(value, defaultValue) { + return value === undefined ? defaultValue : value; + } + + function errorDisposedContext(fnName) { + return function() { + throw new Error(`tried to call ${fnName} on disposed context`); + }; + } + + class DefaultCompositor { + constructor(canvas) { + this._ctx = canvas.getContext('2d'); + } + composite(gl, texture, canvas, contextAttributes) { + // note: not entirely sure what to do here. We need this canvas to be at least as large + // as the canvas we're drawing to. Resizing a canvas is slow so I think just making + // sure we never get smaller than the largest canvas. At the moment though I'm too lazy + // to make it smaller. + const ctx = this._ctx; + const width = canvas.width; + const height = canvas.height; + const maxWidth = Math.max(gl.canvas.width, width); + const maxHeight = Math.max(gl.canvas.height, height); + if (gl.canvas.width !== maxWidth || gl.canvas.height !== maxHeight) { + gl.canvas.width = maxWidth; + gl.canvas.height = maxHeight; + } + + gl.viewport(0, 0, width, height); + + gl.useProgram(contextAttributes.premultipliedAlpha ? premultiplyAlphaTrueProgram : premultiplyAlphaFalseProgram); + + // draw the drawingbuffer's texture to the offscreen canvas + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + // copy it to target canvas + ctx.globalCompositeOperation = 'copy'; + ctx.drawImage( + gl.canvas, + 0, maxHeight - height, width, height, // src rect + 0, 0, width, height); // dest rect + } + dispose() { + } + } + + function virtualGLConstruct(canvas, contextAttributes = {}, compositor, disposeHelper) { + const gl = sharedWebGLContext; + this._canvas = canvas; + // Should use Symbols or something to hide these variables from the outside. + + this._compositor = compositor; + this._disposeHelper = disposeHelper; + this._extensions = {}; + // based on context attributes and canvas.width, canvas.height + // create a texture and framebuffer + this._drawingbufferTexture = gl.createTexture(); + this._drawingbufferFramebuffer = gl.createFramebuffer(); + this._contextAttributes = { + alpha: valueOrDefault(contextAttributes.alpha, true), + antialias: false, + depth: valueOrDefault(contextAttributes.depth, true), + failIfMajorPerformanceCaveat: false, + premultipliedAlpha: valueOrDefault(contextAttributes.premultipliedAlpha, true), + stencil: valueOrDefault(contextAttributes.stencil, false), + }; + this._preserveDrawingbuffer = valueOrDefault(contextAttributes.preserveDrawingBuffer, false); + + const oldTexture = gl.getParameter(gl.TEXTURE_BINDING_2D); + const oldFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); + + gl.bindTexture(gl.TEXTURE_2D, this._drawingbufferTexture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + // this._drawingbufferTexture.id = canvas.id; + // this._drawingbufferFramebuffer.id = canvas.id; + + gl.bindFramebuffer(gl.FRAMEBUFFER, this._drawingbufferFramebuffer); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this._drawingbufferTexture, 0); + + if (this._contextAttributes.depth) { + const oldRenderbuffer = gl.getParameter(gl.RENDERBUFFER_BINDING); + this._depthRenderbuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, this._depthRenderbuffer); + const attachmentPoint = this._contextAttributes.stencil ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachmentPoint, gl.RENDERBUFFER, this._depthRenderbuffer); + gl.bindRenderbuffer(gl.RENDERBUFFER, oldRenderbuffer); + } + + gl.bindTexture(gl.TEXTURE_2D, oldTexture); + gl.bindFramebuffer(gl.FRAMEBUFFER, oldFramebuffer); + + // remember all WebGL state (default bindings, default texture units, + // default attributes and/or vertex shade object, default program, + // default blend, stencil, zBuffer, culling, viewport etc... state + this._state = makeDefaultState(gl, canvas.width, canvas.height); + this._state.readFramebuffer = this._drawingbufferFramebuffer; + this._state.drawFramebuffer = this._drawingbufferFramebuffer; + this._state.readBuffer = gl.COLOR_ATTACHMENT0; + + this._state.vertexArray = gl.createVertexArray(); + this._defaultVertexArray = this._state.vertexArray; + } + + function virtualGLDispose() { + this._disposeHelper(); + const gl = sharedWebGLContext; + gl.deleteFramebuffer(this._drawingbufferFramebuffer); + gl.deleteTexture(this._drawingbufferTexture); + if (this._depthRenderbuffer) { + gl.deleteRenderbuffer(this._depthRenderbuffer); + } + if (this._compositor.dispose) { + this._compositor.dispose(); + } + for (const [key, value] of Object.entries(this)) { + if (typeof value === 'function') { + this[key] = errorDisposedContext(key); + } + } + for (const [key, value] of Object.entries(this.prototype)) { + if (typeof value === 'function') { + this[key] = errorDisposedContext(key); + } + } + } + + function virtualGLComposite(gl) { + this._compositor.composite(gl, this._drawingbufferTexture, this.canvas, this._contextAttributes); + if (!this._preserveDrawingbuffer) { + this._needClear = true; + } + } + + // Base exists so VirtualWebGLContext has a base class we can replace + // because otherwise it's base is Object which we can't replace. + class Base {} + class VirtualWebGLContext extends Base { + constructor(canvas, contextAttributes = {}, compositor, disposeHelper) { + super(); + this.dispose = virtualGLDispose; + this.composite = virtualGLComposite; + virtualGLConstruct.call(this, canvas, contextAttributes, compositor, disposeHelper); + } + get canvas() { + return this._canvas; + } + get drawingBufferWidth() { + return this.canvas.width; + } + get drawingBufferHeight() { + return this.canvas.height; + } + } + class Base2 {} + class VirtualWebGL2Context extends Base2 { + constructor(canvas, contextAttributes = {}, compositor, disposeHelper) { + super(); + this.dispose = virtualGLDispose; + this.composite = virtualGLComposite; + virtualGLConstruct.call(this, canvas, contextAttributes, compositor, disposeHelper); + } + get canvas() { + return this._canvas; + } + get drawingBufferWidth() { + return this.canvas.width; + } + get drawingBufferHeight() { + return this.canvas.height; + } + } + + // Replace the prototype with WebGL2RenderingContext so that someCtx instanceof WebGL2RenderingContext returns true + Object.setPrototypeOf(Object.getPrototypeOf(VirtualWebGLContext.prototype), WebGLRenderingContext.prototype); + Object.setPrototypeOf(Object.getPrototypeOf(VirtualWebGL2Context.prototype), WebGL2RenderingContext.prototype); + + function makeDefaultState(gl, width, height) { + const vao = gl.createVertexArray(); + gl.bindVertexArray(vao); + + const state = { + arrayBuffer: null, + renderbuffer: null, + drawFramebuffer: null, + readFramebuffer: null, + copyReadBuffer: null, + copyWriteBuffer: null, + pixelPackBuffer: null, + pixelUnpackBuffer: null, + transformFeedbackBuffer: null, + uniformBuffer: null, + + readBuffer: gl.BACK, + + enable: new Map([ + [ gl.BLEND, false ], + [ gl.CULL_FACE, false ], + [ gl.DEPTH_TEST, false ], + [ gl.DITHER, false ], + [ gl.POLYGON_OFFSET_FILL, false ], + [ gl.RASTERIZER_DISCARD, false ], + [ gl.SAMPLE_ALPHA_TO_COVERAGE, false ], + [ gl.SAMPLE_COVERAGE, false ], + [ gl.SCISSOR_TEST, false ], + [ gl.STENCIL_TEST, false ], + ]), + + // This is a place the spec gets wrong! This data should have been part of a VertexArray + attribValues: new Array(numAttribs).fill(0).map(() => { + return { + type: gl.FLOAT, + value: [0, 0, 0, 1], + }; + }), + + vertexArray: vao, + activeTexture: gl.TEXTURE0, + transformFeedback: null, + + pack: new Map([ + [ gl.PACK_ALIGNMENT, 4], + [ gl.UNPACK_ALIGNMENT, 4], + [ gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.BROWSER_DEFAULT_WEBGL], + [ gl.UNPACK_FLIP_Y_WEBGL, 0], + [ gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0], + [ gl.UNPACK_ROW_LENGTH , 0], + [ gl.UNPACK_SKIP_ROWS , 0], + [ gl.UNPACK_SKIP_PIXELS , 0], + [ gl.UNPACK_SKIP_IMAGES , 0], + [ gl.UNPACK_IMAGE_HEIGHT , 0], + [ gl.PACK_ROW_LENGTH , 0], + [ gl.PACK_SKIP_ROWS , 0], + [ gl.PACK_SKIP_PIXELS , 0], + ]), + + currentProgram: null, + viewport: [0, 0, width, height], + scissor: [0, 0, width, height], + blendSrcRgb: gl.ONE, + blendDstRgb: gl.ZERO, + blendSrcAlpha: gl.ONE, + blendDstAlpha: gl.ZERO, + blendEquationRgb: gl.FUNC_ADD, + blendEquationAlpha: gl.FUNC_ADD, + blendColor: [0, 0, 0, 0], + clearColor: [0, 0, 0, 0], + colorMask: [true, true, true, true], + cullFaceMode: gl.BACK, + clearDepth: 1, + depthFunc: gl.LESS, + depthRange: [0, 1], + depthMask: true, + frontFace: gl.CCW, + generateMipmapHint: gl.DONT_CARE, + lineWidth: 1, + polygonOffsetFactor: 0, + polygonOffsetUnits: 0, + sampleCoverageValue: 1, + sampleCoverageUnits: false, + stencilFront: { + fail: gl.KEEP, + func: gl.ALWAYS, + depthFail: gl.KEEP, + depthPass: gl.KEEP, + ref: 0, + valueMask: 0xFFFFFFFF, + writeMask: 0xFFFFFFFF, + }, + stencilBack: { + fail: gl.KEEP, + func: gl.ALWAYS, + depthFail: gl.KEEP, + depthPass: gl.KEEP, + ref: 0, + valueMask: 0xFFFFFFFF, + writeMask: 0xFFFFFFFF, + }, + stencilClearValue: 0, + + textureUnits: new Array(numTextureUnits).fill(0).map(() => { + return { + texture2D: null, + textureCubemap: null, + texture2DArray: null, + texture3D: null, + sampler: null, + }; + }), + uniformBufferBindings: new Array(numUniformBufferBindings).fill(0).map(() => { + return { + buffer: null, + size: 0, + start: 0, + }; + }), + }; + + return state; + } + + function isFramebufferBindingNull(vCtx) { + return vCtx._state.drawFramebuffer === vCtx._drawingbufferFramebuffer; + } + + function createWrapper(origFn/*, name*/) { + // lots of optimization could happen here depending on specific functions + return function(...args) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + return origFn.call(sharedWebGLContext, ...args); + }; + } + + function clearIfNeeded(vCtx) { + if (vCtx._needClear) { + vCtx._needClear = false; + const gl = sharedWebGLContext; + gl.bindFramebuffer(gl.FRAMEBUFFER, vCtx._drawingbufferFramebuffer); + gl.disable(gl.SCISSOR_TEST); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); + enableDisable(gl, gl.SCISSOR_TEST, vCtx._state.scissorTest); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, vCtx._state.drawFramebuffer); + } + } + + function beforeDraw(vCtx) { + makeCurrentContext(vCtx); + resizeCanvasIfChanged(vCtx); + clearIfNeeded(vCtx); + } + + function afterDraw(vCtx) { + if (isFramebufferBindingNull(vCtx)) { + vCtx._needComposite = true; + if (!someContextsNeedRendering) { + someContextsNeedRendering = true; + setTimeout(renderAllDirtyVirtualCanvases, 0); + } + } + } + + function createDrawWrapper(origFn) { + return function(...args) { + // a rendering function was called so we need to copy are drawingBuffer + // to the canvas for this context after the current event. + beforeDraw(this); + const result = origFn.call(sharedWebGLContext, ...args); + afterDraw(this); + return result; + }; + } + + function createStateArgsSaverFn(fnName) { + return function(...args) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl[fnName](...args); + const v = this._state[fnName]; + for (let i = 0; i < args.length; ++i) { + v[i] = args[i]; + } + }; + } + + function createSaveStateNamedArgs(fnName, argsToStateProps) { + return function(...args) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl[fnName](...args); + for (let i = 0; i < argsToStateProps.length; ++i) { + this._state[argsToStateProps[i]] = args[i]; + } + }; + } + + function createVertexAttribWrapper(origFn, fn) { + return function(loc, ...args) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + origFn.call(gl, loc, ...args); + const [type, value] = fn(args); + this._state.attribValues[loc] = {type, value}; + }; + } + + function saveStencilMaskImpl(st, mask) { + st.writeMask = mask; + } + + function saveStencilMask(state, face, mask) { + if (face === sharedWebGLContext.FRONT || face === sharedWebGLContext.FRONT_AND_BACK) { + saveStencilMaskImpl(state.stencilFront, mask); + } + if (face === sharedWebGLContext.BACK || face === sharedWebGLContext.FRONT_AND_BACK) { + saveStencilMaskImpl(state.stencilBack, mask); + } + } + + function saveStencilFuncImpl(st, func, ref, mask) { + st.func = func; + st.ref = ref; + st.valueMask = mask; + } + + function saveStencilFunc(state, face, func, ref, mask) { + if (face === sharedWebGLContext.FRONT || face === sharedWebGLContext.FRONT_AND_BACK) { + saveStencilFuncImpl(state.stencilFront, func, ref, mask); + } + if (face === sharedWebGLContext.BACK || face === sharedWebGLContext.FRONT_AND_BACK) { + saveStencilFuncImpl(state.stencilBack, func, ref, mask); + } + } + + function saveStencilOpImpl(st, fail, zfail, zpass) { + st.fail = fail; + st.depthFail = zfail; + st.depthPass = zpass; + } + + function saveStencilOp(state, face, fail, zfail, zpass) { + if (face === sharedWebGLContext.FRONT || face === sharedWebGLContext.FRONT_AND_BACK) { + saveStencilOpImpl(state.stencilFront, fail, zfail, zpass); + } + if (face === sharedWebGLContext.BACK || face === sharedWebGLContext.FRONT_AND_BACK) { + saveStencilOpImpl(state.stencilBack, fail, zfail, zpass); + } + } + + const virtualFns = { + getExtension(extensionName) { + extensionName = extensionName.toLowerCase(); + // just like the real context each extension needs a virtual class because each use + // of the extension might be modified (as in people adding properties to it) + const existingExt = this._extensions[extensionName]; + if (existingExt) { + return existingExt; + } + + const ext = sharedWebGLContext.getExtension(extensionName); + if (!ext) { + return null; + } + const wrapperInfo = extensionInfo[extensionName] || {}; + const wrapperFnMakerFn = wrapperInfo.wrapperFnMakerFn || (() => { + console.log('trying to get extension:', extensionName); + }); + const saveRestoreHelper = extensionSaveRestoreHelpers[extensionName]; + if (!saveRestoreHelper) { + const saveRestoreMakerFn = wrapperInfo.saveRestoreMakerFn; + if (saveRestoreMakerFn) { + const saveRestore = saveRestoreMakerFn(ext); + extensionSaveRestoreHelpers[extensionName] = saveRestore; + extensionSaveRestoreHelpersArray.push(saveRestore); + } + } + + const wrapper = { + _context: this, + }; + for (const key in ext) { + let value = ext[key]; + if (typeof value === 'function') { + value = wrapperFnMakerFn(ext, value, extensionName); + } + wrapper[key] = value; + } + + this._extensions[extensionName] = wrapper; + + return wrapper; + }, + activeTexture(unit) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.activeTexture(unit); + this._state.activeTexture = unit; + }, + enable(pname) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.enable(pname); + this._state.enable.set(pname, true); + }, + disable(pname) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.disable(pname); + this._state.enable.set(pname, false); + }, + viewport: createStateArgsSaverFn('viewport'), + scissor: createStateArgsSaverFn('scissor'), + blendColor: createStateArgsSaverFn('blendColor'), + clearColor: createStateArgsSaverFn('clearColor'), + colorMask: createStateArgsSaverFn('colorMask'), + depthRange: createStateArgsSaverFn('depthRange'), + bindBuffer(target, buffer) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.bindBuffer(target, buffer); + switch (gl.target) { + case gl.ARRAY_BUFFER: + this._state.arrayBuffer = buffer; + break; + case gl.COPY_READ_BUFFER: + this._state.copyReadBuffer = buffer; + break; + case gl.COPY_WRITE_BUFFER: + this._state.copyWriteBuffer = buffer; + break; + case gl.PIXEL_PACK_BUFFER: + this._state.pixelPackBuffer = buffer; + break; + case gl.PIXEL_UNPACK_BUFFER: + this._state.pixelUnpackBuffer = buffer; + break; + case gl.TRANSFORM_FEEDBACK_BUFFER: + this._state.transformFeedbackBuffer = buffer; + break; + case gl.UNIFORM_BUFFER: + this._state.uniformBuffer = buffer; + } + }, + bindBufferBase(target, index, buffer) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.bindBufferBase(target, index, buffer); + switch (target) { + case gl.UNIFORM_BUFFER: { + const ub = this._state.uniformBufferBindings[index]; + ub.buffer = buffer; + ub.size = 0; + ub.start = 0; + break; + } + default: + break; + } + }, + bindBufferRange(target, index, buffer, offset, size) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.bindBufferRange(target, index, buffer, offset, size); + switch (target) { + case gl.UNIFORM_BUFFER: { + const ub = this._state.uniformBufferBindings[index]; + ub.buffer = buffer; + ub.size = size; + ub.start = offset; + break; + } + default: + break; + } + }, + bindTexture(target, texture) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.bindTexture(target, texture); + const unit = this._state.textureUnits[this._state.activeTexture - gl.TEXTURE0]; + switch (target) { + case gl.TEXTURE_2D: + unit.texture2D = texture; + break; + case gl.TEXTURE_CUBE_MAP: + unit.textureCubemap = texture; + break; + case gl.TEXTURE_2D_ARRAY: + unit.texture2DArray = texture; + break; + case gl.TEXTURE_3D: + unit.texture3D = texture; + break; + } + }, + bindRenderbuffer(target, renderbuffer) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.bindRenderbuffer(target, renderbuffer); + this._state.renderbuffer = renderbuffer; + }, + bindSampler(unit, sampler) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.bindSampler(unit, sampler); + this._state.textureUnits[unit].sampler = sampler; + }, + bindVertexArray(va) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + if (va === null) { + va = this._defaultVertexArray; + } + gl.bindVertexArray(va); + this._state.vertexArray = va; + }, + getContextAttributes() { + return this._contextAttributes; + }, + readPixels(...args) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + clearIfNeeded(this); + const gl = sharedWebGLContext; + return gl.readPixels(...args); + }, + getParameter(pname) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + const value = gl.getParameter(pname); + switch (pname) { + case gl.FRAMEBUFFER_BINDING: + if (value === this._drawingbufferFramebuffer) { + return null; + } + break; + case gl.DRAW_BUFFER0: + if (isFramebufferBindingNull(this)) { + if (value === gl.COLOR_ATTACHMENT0) { + return gl.BACK; + } + } + break; + case gl.READ_BUFFER: + if (isFramebufferBindingNull(this)) { + if (value === gl.COLOR_ATTACHMENT0) { + return gl.BACK; + } + } + break; + case gl.VERTEX_ARRAY_BINDING: + if (value === this._defaultVertexArray) { + return null; + } + break; + } + return value; + }, + blendFunc(sfactor, dfactor) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.blendFunc(sfactor, dfactor); + this._state.blendSrcRgb = sfactor; + this._state.blendSrcAlpha = sfactor; + this._state.blendDstRgb = dfactor; + this._state.blendDstAlpha = dfactor; + }, + blendEquation(mode) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.blendEquation(mode); + this._state.blendEquationRgb = mode; + this._state.blendEquationAlpha = mode; + }, + blendFuncSeparate: createSaveStateNamedArgs('blendFuncSeparate', ['blendSrcRgb', 'blendDstRgb', 'blendSrcAlpha', 'blendDstAlpha']), + blendEquationSeparate: createSaveStateNamedArgs('blendEquationSeparate', ['blendEquationRgb', 'blendEquationAlpha']), + cullFace: createSaveStateNamedArgs('cullFace', ['cullFaceMode']), + clearDepth: createSaveStateNamedArgs('clearDepth', ['clearDepth']), + depthFunc: createSaveStateNamedArgs('depthFunc', ['depthFunc']), + depthMask: createSaveStateNamedArgs('depthMask', ['depthMask']), + frontFace: createSaveStateNamedArgs('frontFace', ['frontFace']), + lineWidth: createSaveStateNamedArgs('lineWidth', ['lineWidth']), + polygonOffset: createSaveStateNamedArgs('polygonOffset', ['polygonOffsetFactor', 'polygonOffsetUnits']), + sampleCoverage: createSaveStateNamedArgs('sampleCoverage', ['sampleCoverageValue', 'sampleCoverageUnits']), + clearStencil: createSaveStateNamedArgs('clearStencil', ['clearStencilValue']), + hint(pname, value) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.hint(pname, value); + this._state.generateMipmapHint = value; + }, + bindFramebuffer(bindPoint, framebuffer) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + if (framebuffer === null) { + // bind our drawingBuffer + framebuffer = this._drawingbufferFramebuffer; + } + gl.bindFramebuffer(bindPoint, framebuffer); + switch (bindPoint) { + case gl.FRAMEBUFFER: + this._state.readFramebuffer = framebuffer; + this._state.drawFramebuffer = framebuffer; + break; + case gl.DRAW_FRAMEBUFFER: + this._state.drawFramebuffer = framebuffer; + break; + case gl.READ_FRAMEBUFFER: + this._state.readFramebuffer = framebuffer; + break; + } + }, + drawBuffers: (function() { + const gl = sharedWebGLContext; + const backBuffer = [gl.COLOR_ATTACHMENT0]; + + return function(drawingBuffers) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + // if the virtual context is bound to canvas then fake it + if (isFramebufferBindingNull(this)) { + // this really isn't checking everything + // for example if the user passed in array.length != 1 + // then we are supposed to generate an error + if (drawingBuffers[0] === gl.BACK) { + drawingBuffers = backBuffer; + } + } + + gl.drawBuffers(drawingBuffers); + }; + }()), + clear: createDrawWrapper(WebGL2RenderingContext.prototype.clear), + drawArrays: createDrawWrapper(WebGL2RenderingContext.prototype.drawArrays), + drawElements: createDrawWrapper(WebGL2RenderingContext.prototype.drawElements), + drawArraysInstanced: createDrawWrapper(WebGL2RenderingContext.prototype.drawArraysInstanced), + drawElementsInstanced: createDrawWrapper(WebGL2RenderingContext.prototype.drawElementsInstanced), + drawRangeElements: createDrawWrapper(WebGL2RenderingContext.prototype.drawRangeElements), + useProgram(program) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.useProgram(program); + this._state.currentProgram = program; + }, + bindTransformFeedback(target, tb) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.bindTransformFeedback(target, tb); + this._state.transformFeedback = tb; + }, + readBuffer(src) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + if (src === gl.BACK) { + src = gl.COLOR_ATTACHMENT0; + } + gl.readBuffer(src); + this._state.readBuffer = src; + }, + stencilFunc(func, ref, mask) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.stencilFunc(func, ref, mask); + saveStencilFunc(this._state, gl.FRONT_AND_BACK, func, ref, mask); + }, + stencilFuncSeparate(face, func, ref, mask) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.stencilFuncSeparate(face, func, ref, mask); + saveStencilFunc(this._state, face, func, ref, mask); + }, + stencilMask(mask) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.stencilMask(mask); + saveStencilMask(this._state, gl.FRONT_AND_BACK, mask); + }, + stencilMaskSeparate(face, mask) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.stencilMaskSeparate(face, mask); + saveStencilMask(this._state, face, mask); + }, + stencilOp(fail, zfail, zpass) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.stencilOp(fail, zfail, zpass); + saveStencilOp(this._state, gl.FRONT_AND_BACK, fail, zfail, zpass); + }, + stencilOpSeparate(face, fail, zfail, zpass) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + gl.stencilOpSeparate(face, fail, zfail, zpass); + saveStencilOp(this._state, face, fail, zfail, zpass); + }, + vertexAttrib1f: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1f, ([x]) => [FLOAT, [x, 0, 0, 1]]), + vertexAttrib2f: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1f, ([x, y]) => [FLOAT, [x, y, 0, 1]]), + vertexAttrib3f: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1f, ([x, y, z]) => [FLOAT, [x, y, z, 1]]), + vertexAttrib4f: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1f, ([x, y, z, w]) => [FLOAT, [x, y, z, w]]), + vertexAttrib1fv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1fv, ([[x]]) => [FLOAT, [x, 0, 0, 1]]), + vertexAttrib2fv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1fv, ([[x, y]]) => [FLOAT, [x, y, 0, 1]]), + vertexAttrib3fv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1fv, ([[x, y, z]]) => [FLOAT, [x, y, z, 1]]), + vertexAttrib4fv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1fv, ([[x, y, z, w]]) => [FLOAT, [x, y, z, w]]), + vertexAttrib1i: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1i, ([x]) => [FLOAT, [x, 0, 0, 1]]), + vertexAttrib2i: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1i, ([x, y]) => [FLOAT, [x, y, 0, 1]]), + vertexAttrib3i: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1i, ([x, y, z]) => [FLOAT, [x, y, z, 1]]), + vertexAttrib4i: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1i, ([x, y, z, w]) => [FLOAT, [x, y, z, w]]), + vertexAttrib1iv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1iv, ([[x]]) => [FLOAT, [x, 0, 0, 1]]), + vertexAttrib2iv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1iv, ([[x, y]]) => [FLOAT, [x, y, 0, 1]]), + vertexAttrib3iv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1iv, ([[x, y, z]]) => [FLOAT, [x, y, z, 1]]), + vertexAttrib4iv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1iv, ([[x, y, z, w]]) => [FLOAT, [x, y, z, w]]), + vertexAttrib1ui: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1ui, ([x]) => [FLOAT, [x, 0, 0, 1]]), + vertexAttrib2ui: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1ui, ([x, y]) => [FLOAT, [x, y, 0, 1]]), + vertexAttrib3ui: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1ui, ([x, y, z]) => [FLOAT, [x, y, z, 1]]), + vertexAttrib4ui: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1ui, ([x, y, z, w]) => [FLOAT, [x, y, z, w]]), + vertexAttrib1uiv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1uiv, ([[x]]) => [FLOAT, [x, 0, 0, 1]]), + vertexAttrib2uiv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1uiv, ([[x, y]]) => [FLOAT, [x, y, 0, 1]]), + vertexAttrib3uiv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1uiv, ([[x, y, z]]) => [FLOAT, [x, y, z, 1]]), + vertexAttrib4uiv: createVertexAttribWrapper(WebGL2RenderingContext.prototype.vertexAttrib1uiv, ([[x, y, z, w]]) => [FLOAT, [x, y, z, w]]), + }; + + const webgl1Extensions = { + oes_texture_float: { + fn() { + return {}; + }, + }, + oes_vertex_array_object: { + fn(vCtx) { + return { + VERTEX_ARRAY_BINDING_OES: sharedWebGLContext.VERTEX_ARRAY_BINDING, + createVertexArrayOES() { + return sharedWebGLContext.createVertexArray(); + }, + deleteVertexArrayOES(va) { + sharedWebGLContext.deleteVertexArray(va); + }, + bindVertexArrayOES(va) { + virtualFns.bindVertexArray.call(vCtx, va); + }, + isVertexArrayOES(va) { + return sharedWebGLContext.isVertexArray(va); + }, + }; + }, + }, + angle_instanced_arrays: { + fn(vCtx) { + return { + VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE: 0x88FE, + drawArraysInstancedANGLE(...args) { + virtualFns.drawArraysInstanced.call(vCtx, ...args); + }, + drawElementsInstancedANGLE(...args) { + virtualFns.drawElementsInstanced.call(vCtx, ...args); + }, + vertexAttribDivisorANGLE(...args) { + sharedWebGLContext.vertexAttribDivisor(...args); + }, + }; + }, + }, + // We can't easily support WebGL_draw_buffers because WebGL2 does not + // support gl_FragData. Instead, it requires you to write your shaders + // in GLSL ES 3.0 if you want to use multiple color attachments. To support + // that correctly would require writing a GLSL parser. We need to change + // 'attribute' -> 'in', 'varying' -> 'out' in vertex shaders, 'varying' to 'in' + // in fragment shader, 'texture2D' to 'texture' and declare outputs. That sounds + // simple but it quickly becomes complicated. + // + // * 'texture' is a valid identifier in GLSL ES 1.0 but a reserved word in + // GLSL ES 3.0 so we'd have to rename identifiers + // + // * Changing the fragment shader means it's on longer compatible with + // GLSL ES 1.0 vertex shaders. But, back in WebGL1, we could easily use + // the same vertex shader with and without WEBGL_draw_buffers. That means + // we now need 2 versions of every vertex shader (ES 1.0 and ES 3.0), OR + // we need to translate ALL shaders to GLSL ES 3.0 + // + // * GLSL 1.0 shaders support dynamically indexing an array of samplers. + // GLSL 3.0 does not. So we'd have to emit an emulation function. + // + // The point is it's not a trivial amount of work. + /* + WEBGL_draw_buffers: { + fn(vCtx) { + return { + COLOR_ATTACHMENT0_WEBGL : 0x8CE0, + COLOR_ATTACHMENT1_WEBGL : 0x8CE1, + COLOR_ATTACHMENT2_WEBGL : 0x8CE2, + COLOR_ATTACHMENT3_WEBGL : 0x8CE3, + COLOR_ATTACHMENT4_WEBGL : 0x8CE4, + COLOR_ATTACHMENT5_WEBGL : 0x8CE5, + COLOR_ATTACHMENT6_WEBGL : 0x8CE6, + COLOR_ATTACHMENT7_WEBGL : 0x8CE7, + COLOR_ATTACHMENT8_WEBGL : 0x8CE8, + COLOR_ATTACHMENT9_WEBGL : 0x8CE9, + COLOR_ATTACHMENT10_WEBGL : 0x8CEA, + COLOR_ATTACHMENT11_WEBGL : 0x8CEB, + COLOR_ATTACHMENT12_WEBGL : 0x8CEC, + COLOR_ATTACHMENT13_WEBGL : 0x8CED, + COLOR_ATTACHMENT14_WEBGL : 0x8CEE, + COLOR_ATTACHMENT15_WEBGL : 0x8CEF, + + DRAW_BUFFER0_WEBGL : 0x8825, + DRAW_BUFFER1_WEBGL : 0x8826, + DRAW_BUFFER2_WEBGL : 0x8827, + DRAW_BUFFER3_WEBGL : 0x8828, + DRAW_BUFFER4_WEBGL : 0x8829, + DRAW_BUFFER5_WEBGL : 0x882A, + DRAW_BUFFER6_WEBGL : 0x882B, + DRAW_BUFFER7_WEBGL : 0x882C, + DRAW_BUFFER8_WEBGL : 0x882D, + DRAW_BUFFER9_WEBGL : 0x882E, + DRAW_BUFFER10_WEBGL : 0x882F, + DRAW_BUFFER11_WEBGL : 0x8830, + DRAW_BUFFER12_WEBGL : 0x8831, + DRAW_BUFFER13_WEBGL : 0x8832, + DRAW_BUFFER14_WEBGL : 0x8833, + DRAW_BUFFER15_WEBGL : 0x8834, + + MAX_COLOR_ATTACHMENTS_WEBGL : 0x8CDF, + MAX_DRAW_BUFFERS_WEBGL : 0x8824, + + drawBuffersWEBGL(buffers) { + virtualFns.drawBuffers.call(vCtx, buffers); + }, + }; + }, + }, + */ + }; + + const texImage2DArgParersMap = new Map([ + [9, function([target, level, internalFormat, width, height, , format, type]) { + return {target, level, internalFormat, width, height, format, type}; + }, ], + [6, function([target, level, internalFormat, format, type, texImageSource]) { + return {target, level, internalFormat, width: texImageSource.width, height: texImageSource.height, format, type}; + }, ], + [10, function([target, level, internalFormat, width, height, , format, type]) { + return {target, level, internalFormat, width, height, format, type}; + }, ], + ]); + + const webgl1Fns = { + getExtension(name) { + name = name.toLowerCase(); + const existingExt = this._extensions[name]; + if (existingExt) { + return existingExt; + } + + const info = webgl1Extensions[name]; + if (!info) { + return virtualFns.getExtension.call(this, name); + } + + return info.fn(this); + }, + texImage2D(...args) { + makeCurrentContext(this); + resizeCanvasIfChanged(this); + const gl = sharedWebGLContext; + const fn = texImage2DArgParersMap.get(args.length); + const {internalFormat, type} = fn(args); + if (type === sharedWebGLContext.FLOAT) { + switch (internalFormat) { + case gl.RGBA: args[2] = gl.RGBA32F; break; + case gl.RGB: args[2] = gl.RGB32F; break; + } + } + gl.texImage2D(...args); + }, + getSupportedExtensions: function() { + return [ + ...sharedWebGLContext.getSupportedExtensions(), + 'OES_texture_float', + 'WEBGL_depth_texture', + 'OES_vertex_array_object', + // "WEBGL_draw_buffers", // See other comment + ]; + }, + }; + + // copy all WebGL constants and functions to the prototype of + // VirtualWebGLContext + function copyProperties(keys, VirtualClass, overrideFns) { + for (const key of keys) { + const propDesc = Object.getOwnPropertyDescriptor(WebGL2RenderingContext.prototype, key); + if (propDesc.get) { + // it's a getter/setter ? + const virtualPropDesc = Object.getOwnPropertyDescriptor(VirtualClass.prototype, key); + if (!virtualPropDesc) { + console.warn(`WebGL2RenderingContext.${key} is not supported`); + } + continue; + } + switch (key) { + default: { + const value = WebGL2RenderingContext.prototype[key]; + let newValue = value; + const fn = overrideFns[key] || virtualFns[key]; + if (fn) { + newValue = fn; + } else { + if (typeof value === 'function') { + newValue = createWrapper(value, key); + } + } + VirtualClass.prototype[key] = newValue; + break; + } + } + } + } + copyProperties(Object.keys(WebGLRenderingContext.prototype), VirtualWebGLContext, webgl1Fns); + copyProperties(Object.keys(WebGL2RenderingContext.prototype), VirtualWebGL2Context, {}); + + function makeCurrentContext(vCtx) { + if (currentVirtualContext === vCtx) { + return; + } + + // save all current WebGL state on the previous current virtual context + if (currentVirtualContext) { + saveAllState(currentVirtualContext._state, currentVirtualContext); + } + + // restore all state for the new context + restoreAllState(vCtx._state, vCtx); + + // check if the current state is supposed to be rendering to the canvas. + // if so bind vCtx._drawingbuffer + + currentVirtualContext = vCtx; + } + + function resizeCanvasIfChanged(vCtx) { + const width = vCtx.canvas.width; + const height = vCtx.canvas.height; + + if (width !== vCtx._width || height !== vCtx._height) { + vCtx._width = width; + vCtx._height = height; + const gl = sharedWebGLContext; + const oldTexture = gl.getParameter(gl.TEXTURE_BINDING_2D); + const format = vCtx._contextAttributes.alpha ? gl.RGBA : gl.RGB; + gl.bindTexture(gl.TEXTURE_2D, vCtx._drawingbufferTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, gl.UNSIGNED_BYTE, null); + gl.bindTexture(gl.TEXTURE_2D, oldTexture); + + if (vCtx._depthRenderbuffer) { + const oldRenderbuffer = gl.getParameter(gl.RENDERBUFFER_BINDING); + const internalFormat = vCtx._contextAttributes.stencil ? gl.DEPTH_STENCIL : gl.DEPTH_COMPONENT16; + gl.bindRenderbuffer(gl.RENDERBUFFER, vCtx._depthRenderbuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height); + gl.bindRenderbuffer(gl.RENDERBUFFER, oldRenderbuffer); + } + } + } + + function createOrGetVirtualWebGLContext(canvas, type, contextAttributes) { + // check if this canvas already has a context + const existingVirtualCtx = canvasToVirtualContextMap.get(canvas); + if (existingVirtualCtx) { + return existingVirtualCtx; + } + + const compositor = settings.compositorCreator(canvas, type, contextAttributes) || new DefaultCompositor(canvas, type, contextAttributes); + const newVirtualCtx = type === 'webgl2' + ? new VirtualWebGL2Context(canvas, contextAttributes, compositor, () => { + canvasToVirtualContextMap.delete(canvas); + }) + : new VirtualWebGLContext(canvas, contextAttributes, compositor, () => { + canvasToVirtualContextMap.delete(canvas); + }); + + canvasToVirtualContextMap.set(canvas, newVirtualCtx); + + return newVirtualCtx; + } + + function createProgram(gl, shaderSources) { + const program = gl.createProgram(); + [gl.VERTEX_SHADER, gl.FRAGMENT_SHADER].forEach((type, ndx) => { + const shader = gl.createShader(type); + gl.shaderSource(shader, shaderSources[ndx]); + gl.compileShader(shader); + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + console.error(gl.getShaderInfoLog(shader)); // eslint-disable-line + } + gl.attachShader(program, shader); + }); + gl.bindAttribLocation(program, 0, 'position'); + gl.linkProgram(program); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + console.error(gl.getProgramInfoLog(program)); // eslint-disable-line + } + + return program; + } + + function saveAllState(state, vCtx) { + // save all WebGL state (current bindings, current texture units, + // current attributes and/or vertex shade object, current program, + // current blend, stencil, zBuffer, culling, viewport etc... state + for (const fns of extensionSaveRestoreHelpersArray) { + fns.save(state, vCtx); + } + } + + function setStencil(gl, face, st) { + gl.stencilFuncSeparate(face, st.func, st.ref, st.valueMask); + gl.stencilOpSeparate(face, st.fail, st.depthFail, st.depthPass); + gl.stencilMaskSeparate(face, st.writeMask); + } + + function restoreAllState(state, vCtx) { + // restore all WebGL state (current bindings, current texture units, + // current attributes and/or vertex shade object, current program, + // current blend, stencil, zBuffer, culling, viewport etc... state + // save all WebGL state (current bindings, current texture units, + // current attributes and/or vertex shade object, current program, + // current blend, stencil, zBuffer, culling, viewport etc... state + const gl = sharedWebGLContext; + + gl.bindRenderbuffer(gl.RENDERBUFFER, state.renderbuffer); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, state.readFramebuffer); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, state.drawFramebuffer); + + // restore attributes + gl.bindVertexArray(state.vertexArray); + for (let i = 0; i < numAttribs; ++i) { + const attr = state.attribValues[i]; + switch (attr.type) { + case gl.FLOAT: + gl.vertexAttrib4fv(i, attr.value); + break; + case gl.INT: + gl.vertexAttribI4iv(i, attr.value); + break; + case gl.UNSIGNED_INT: + gl.vertexAttribI4uiv(i, attr.value); + break; + } + } + + gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, state.transformFeedback); + + // restore texture units + for (let i = 0; i < numTextureUnits; ++i) { + gl.activeTexture(gl.TEXTURE0 + i); + const unit = state.textureUnits[i]; + gl.bindTexture(gl.TEXTURE_2D, unit.texture2D); + gl.bindTexture(gl.TEXTURE_CUBE_MAP, unit.textureCubemap); + gl.bindTexture(gl.TEXTURE_2D_ARRAY, unit.texture2DArray); + gl.bindTexture(gl.TEXTURE_3D, unit.texture3D); + gl.bindSampler(i, unit.sampler); + } + gl.activeTexture(state.activeTexture); + + // uniform buffer bindings (must be restored before UNIFORM_BUFFER restore) + for (let i = 0; i < numUniformBufferBindings; ++i) { + const ub = state.uniformBufferBindings[i]; + if (ub.size || ub.start) { + gl.bindBufferRange(gl.UNIFORM_BUFFER, i, ub.buffer, ub.start, ub.size); + } else { + gl.bindBufferBase(gl.UNIFORM_BUFFER, i, ub.buffer); + } + } + + // bindings + gl.bindBuffer(gl.ARRAY_BUFFER, state.arrayBuffer); + gl.bindBuffer(gl.COPY_READ_BUFFER, state.copyReadBuffer); + gl.bindBuffer(gl.COPY_WRITE_BUFFER, state.copyWriteBuffer); + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, state.pixelPackBuffer); + gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, state.pixelUnpackBuffer); + gl.bindBuffer(gl.TRANSFORM_FEEDBACK_BUFFER, state.transformFeedbackBuffer); + gl.bindBuffer(gl.UNIFORM_BUFFER, state.uniformBuffer); + + gl.readBuffer(state.readBuffer); + + state.enable.forEach((value, key) => { + enableDisable(gl, key, value); + }); + + state.pack.forEach((value, key) => { + gl.pixelStorei(key, value); + }); + + gl.useProgram(state.currentProgram); + + gl.viewport(...state.viewport); + gl.scissor(...state.scissor); + gl.blendFuncSeparate(state.blendSrcRgb, state.blendDstRgb, state.blendSrcAlpha, state.blendDstAlpha); + gl.blendEquationSeparate(state.blendEquationRgb, state.blendEquationAlpha); + gl.blendColor(...state.blendColor); + gl.clearColor(...state.clearColor); + gl.colorMask(...state.colorMask); + gl.cullFace(state.cullFaceMode); + gl.clearDepth(state.clearDepth); + gl.depthFunc(state.depthFunc); + gl.depthRange(...state.depthRange); + gl.depthMask(state.depthMask); + gl.frontFace(state.frontFace); + gl.hint(gl.GENERATE_MIPMAP_HINT, state.generateMipmapHint); + gl.lineWidth(state.lineWidth); + gl.polygonOffset(state.polygonOffsetFactor, state.polygonOffsetUnits); + gl.sampleCoverage(state.sampleCoverageValue, state.sampleCoverageUnits); + + setStencil(gl, gl.BACK, state.stencilBack); + setStencil(gl, gl.FRONT, state.stencilFront); + + gl.clearStencil(state.stencilClearValue); + + for (const fns of extensionSaveRestoreHelpersArray) { + fns.restore(state, vCtx); + } + } + + function enableDisable(gl, feature, enable) { + if (enable) { + gl.enable(feature); + } else { + gl.disable(feature); + } + } + + function renderAllDirtyVirtualCanvases() { + if (!someContextsNeedRendering) { + return; + } + someContextsNeedRendering = false; + + // save all current WebGL state on the previous current virtual context + if (currentVirtualContext) { + saveAllState(currentVirtualContext._state, currentVirtualContext); + currentVirtualContext = null; + } + + // set the state back to the one for drawing the canvas + restoreAllState(baseState); + + for (const vCtx of canvasToVirtualContextMap.values()) { + if (!vCtx._needComposite) { + continue; + } + + vCtx._needComposite = false; + vCtx.composite(sharedWebGLContext); + } + } + + window.requestAnimationFrame = (function(origFn) { + return function(callback) { + return origFn.call(window, (time) => { + const result = callback(time); + renderAllDirtyVirtualCanvases(); + return result; + }); + }; + + }(window.requestAnimationFrame)); + + function setup(options) { + Object.assign(settings, options); + } + + window.virtualWebGL = { + setup, + }; + +}()); diff --git a/src/__mocks__/ssh_session.ts b/src/__mocks__/ssh_session.ts index d8cadb67..32ca4d81 100644 --- a/src/__mocks__/ssh_session.ts +++ b/src/__mocks__/ssh_session.ts @@ -23,7 +23,7 @@ export class SSHSession implements Session { static wrongPassword = false onStateChange: (state: State) => void - onPayloadUpdate: (payload: string) => void + onCMD: (payload: string) => void constructor(address: string, username: string, password: string, port?=22) { console.log("New mocked SSH seesion", address, username, password, port) } @@ -52,7 +52,7 @@ export class SSHSession implements Session { export class HybridSession implements Session { onStateChange: (state: State) => void - onPayloadUpdate: (payload: string) => void + onCMD: (payload: string) => void static fail = false static wrongPassword = false constructor(address: string, username: string, password: string, port?=22) { diff --git a/src/__mocks__/webrtc_session.ts b/src/__mocks__/webrtc_session.ts index d547e710..9f378cbe 100644 --- a/src/__mocks__/webrtc_session.ts +++ b/src/__mocks__/webrtc_session.ts @@ -18,7 +18,7 @@ export class MockChannel implements Channel { export class HTTPWebRTCSession implements Session { onStateChange: (state: State) => void - onPayloadUpdate: (payload: string) => void + onCMD: (payload: string) => void static fail = false constructor(address: string, username: string, password: string, port?=22) { console.log("New seesion", address, username, password, port) diff --git a/src/bell.js b/src/bell.ts similarity index 100% rename from src/bell.js rename to src/bell.ts diff --git a/src/cell.js b/src/cell.js deleted file mode 100644 index d53691fe..00000000 --- a/src/cell.js +++ /dev/null @@ -1,191 +0,0 @@ -/*! Terminal 7 Cell - a class used as super for both Pane & Layout - * - * Copyright: (c) 2021 Benny A. Daon - benny@tuzig.com - * License: GPLv3 - */ -import * as Hammer from 'hammerjs' -const ABIT = 10, - FOCUSED_BORDER_COLOR = "#F4DB53", - UNFOCUSED_BORDER_COLOR = "#373702" - -export class Cell { - constructor(props) { - this.gate = props.gate || null - this.w = props.w - this.id = props.id || undefined - this.layout = props.layout || null - this.createElement(props.className) - this.sx = props.sx || 0.8 - this.sy = props.sy || 0.8 - this.xoff = props.xoff || 0 - this.yoff = props.yoff || 0 - this.zoomed = false - this.t7 = window.terminal7 - } - /* - * Creates the HTML elment that will store our dimensions and content - * get an optional className to be added to the element - */ - createElement(className) { - // creates the div element that will hold the term - this.e = document.createElement("div") - this.e.cell = this - this.e.classList = "cell" - if (typeof className == "string") - this.e.classList.add(className) - this.w.e.appendChild(this.e) - return this.e - } - - /* - * Set the focus on the cell - */ - focus() { - if (this.w.activeP !== null) { - this.w.activeP.e.style.borderColor = UNFOCUSED_BORDER_COLOR - } - this.w.activeP = this - this.e.style.borderColor = FOCUSED_BORDER_COLOR - this.w.updateDivideButtons() - setTimeout(() => window.location.href = `#pane-${this.id}`) - this.w.nameE.setAttribute("href", `#pane-${this.id}`) - } - - /* - * Catches gestures on an elment using hammerjs. - * If an element is not passed in, `this.e` is used - */ - catchFingers(elem) { - let e = (typeof elem == 'undefined')?this.e:elem, - h = new Hammer.Manager(e, {}), - // h.options.domEvents=true; // enable dom events - singleTap = new Hammer.Tap({event: "tap"}), - doubleTap = new Hammer.Tap({event: "doubletap", taps: 2}), - pinch = new Hammer.Pinch({event: "pinch"}) - - h.add([singleTap, - doubleTap, - pinch, - new Hammer.Tap({event: "twofingerstap", pointers: 2})]) - - h.on('tap', () => { - if (this.w.activeP != this) { - this.focus() - this.gate.sendState() - } - }) - h.on('twofingerstap', () => { - this.toggleZoom() - }) - h.on('doubletap', () => { - this.toggleZoom() - }) - - h.on('pinch', e => { - this.t7.log(e.additionalEvent, e.distance, e.velocityX, e.velocityY, e.direction, e.isFinal) - if (e.deltaTime < this.lastEventT) - this.lastEventT = 0 - if ((e.deltaTime - this.lastEventT < 200) || - (e.velocityY > this.t7.conf.ui.pinchMaxYVelocity)) - return - this.lastEventT = e.deltaTime - if (e.additionalEvent == "pinchout") - this.scale(1) - else - this.scale(-1) - }) - this.mc = h - } - get sx(){ - return parseFloat(this.e.style.width.slice(0,-1)) / 100.0 - } - set sx(val) { - if (val > 1.0) - val = 1.0 - this.e.style.width = String(val*100) + "%" - } - get sy() { - return parseFloat(this.e.style.height.slice(0,-1)) / 100.0 - } - set sy(val) { - this.e.style.height = String(val*100) + "%" - } - get xoff() { - return parseFloat(this.e.style.left.slice(0,-1)) / 100.0 - } - set xoff(val) { - this.e.style.left = String(val*100) + "%" - } - get yoff() { - return parseFloat(this.e.style.top.slice(0,-1)) / 100.0 - } - set yoff(val) { - this.e.style.top = String(val*100) + "%" - } - /* - * Cell.close removes a cell's elment and removes itself from the window - */ - close() { - // zero canvas dimension to free it - this.e.querySelectorAll("canvas").forEach(canvas => { - canvas.height = 0; - canvas.width = 0; - }) - if (this.zoomed) - this.unzoom() - this.e.remove() - if (this.layout) - this.layout.onClose(this) - this.gate.sendState() - } - styleZoomed(e) { - e = e || this.t7.zoomedE.querySelector(".pane") - const se = this.gate.e.querySelector(".search-box") - let style - if (se.classList.contains("show")) - style = `${document.querySelector('.windows-container').offsetHeight - 22}px` - else - style = `${document.body.offsetHeight - 36}px` - e.style.height = style - e.style.top = "0px" - e.style.width = "100%" - this.fit() - } - toggleZoom() { - if (this.zoomed) - this.unzoom() - else - this.zoom() - this.gate.sendState() - this.t7.run(() => this.focus(), ABIT) - } - zoom() { - let c = document.createElement('div'), - e = document.createElement('div'), - te = this.e.removeChild(this.e.children[0]) - c.classList.add("zoomed") - e.classList.add("pane", "focused") - e.style.borderColor = FOCUSED_BORDER_COLOR - e.appendChild(te) - c.appendChild(e) - this.catchFingers(e) - document.body.appendChild(c) - this.t7.zoomedE = c - this.w.e.classList.add("hidden") - this.resizeObserver = new window.ResizeObserver(() => this.styleZoomed(e)) - this.resizeObserver.observe(e) - this.zoomed = true - } - unzoom() { - if (this.resizeObserver != null) { - this.resizeObserver.disconnect() - this.resizeObserver = null - } - let te = this.t7.zoomedE.children[0].children[0] - this.e.appendChild(te) - document.body.removeChild(this.t7.zoomedE) - this.t7.zoomedE = null - this.w.e.classList.remove("hidden") - this.zoomed = false - } -} diff --git a/src/cell.ts b/src/cell.ts new file mode 100644 index 00000000..d3042b0c --- /dev/null +++ b/src/cell.ts @@ -0,0 +1,110 @@ +/*! Terminal 7 Cell - a class used as super for both Pane & Layout + * + * Copyright: (c) 2021 Benny A. Daon - benny@tuzig.com + * License: GPLv3 + */ +import { Gate } from "./gate" +import { Window } from "./window" +import { Layout } from "./layout" +import { Terminal7 } from "./terminal7" +import { Pane } from "./pane" + +const FOCUSED_BORDER_COLOR = "#F4DB53", + UNFOCUSED_BORDER_COLOR = "#373702" + +export abstract class Cell { + gate?: Gate + w: Window + id?: number + layout?: Layout + t7: Terminal7 + e: HTMLDivElement & {cell?: Cell} + lastEventT: number + protected constructor(props) { + this.gate = props.gate || null + this.w = props.w + this.id = props.id || undefined + this.layout = props.layout || null + this.createElement(props.className) + this.sx = props.sx || 0.8 + this.sy = props.sy || 0.8 + this.xoff = props.xoff || 0 + this.yoff = props.yoff || 0 + this.t7 = terminal7 + } + /* + * Creates the HTML elment that will store our dimensions and content + * get an optional className to be added to the element + */ + createElement(className) { + // creates the div element that will hold the term + this.e = document.createElement("div") as HTMLDivElement & {cell?: Cell} + this.e.cell = this + this.e.className = "cell" + if (typeof className == "string") + this.e.classList.add(className) + this.w.e.appendChild(this.e) + return this.e + } + + /* + * Set the focus on the cell + */ + focus() { + if (this.w.activeP !== null) { + this.w.activeP.e.style.borderColor = UNFOCUSED_BORDER_COLOR + } + this.w.activeP = this as unknown as Pane + this.e.style.borderColor = FOCUSED_BORDER_COLOR + this.w.updateDivideButtons() + setTimeout(() => window.location.href = `#pane-${this.id}`) + this.w.nameE.setAttribute("href", `#pane-${this.id}`) + } + + abstract dump() + + abstract refreshDividers() + + abstract fit() + + get sx(){ + return parseFloat(this.e.style.width.slice(0,-1)) / 100.0 + } + set sx(val) { + if (val > 1.0) + val = 1.0 + this.e.style.width = String(val*100) + "%" + } + get sy() { + return parseFloat(this.e.style.height.slice(0,-1)) / 100.0 + } + set sy(val) { + this.e.style.height = String(val*100) + "%" + } + get xoff() { + return parseFloat(this.e.style.left.slice(0,-1)) / 100.0 + } + set xoff(val) { + this.e.style.left = String(val*100) + "%" + } + get yoff() { + return parseFloat(this.e.style.top.slice(0,-1)) / 100.0 + } + set yoff(val) { + this.e.style.top = String(val*100) + "%" + } + /* + * Cell.close removes a cell's elment and removes itself from the window + */ + close() { + // zero canvas dimension to free it + this.e.querySelectorAll("canvas").forEach(canvas => { + canvas.height = 0 + canvas.width = 0 + }) + this.e.remove() + if (this.layout) + this.layout.onClose(this) + this.gate.sendState() + } +} diff --git a/src/commands.ts b/src/commands.ts index e9ad944a..c70ba586 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,17 +1,16 @@ -import { CapacitorPurchases } from '@capgo/capacitor-purchases' +import { Purchases } from '@revenuecat/purchases-capacitor' import { Clipboard } from '@capacitor/clipboard' import { Shell } from "./shell" import { Preferences } from "@capacitor/preferences" -import { DEFAULT_DOTFILE, Terminal7 } from "./terminal7" +import { DEFAULT_DOTFILE } from "./terminal7" import { Fields } from "./form" +//@ts-ignore import fortuneURL from "../resources/fortune.txt" import { Gate } from './gate' -import { Capacitor } from '@capacitor/core' import { SSHSession, SSHChannel } from './ssh_session' import { Failure } from './session' import { NativeBiometric } from 'capacitor-native-biometric' - -declare const terminal7 : Terminal7 +import { Capacitor } from "@capacitor/core" export type Command = { name: string @@ -55,7 +54,7 @@ export function loadCommands(shell: Shell): Map { name: "copykey", help: "Copy the public key", usage: "copy[key]", - execute: async args => copyKeyCMD(shell, args) + execute: async () => copyKeyCMD(shell) }, edit: { name: "edit", @@ -349,6 +348,8 @@ async function resetCMD(shell: Shell, args: string[]) { { prompt: "Reset connection & Layout" }, { prompt: "\x1B[31mFactory reset\x1B[0m" }, ] + if (gate.session && !gate.fitScreen) + fields.splice(0, 0, { prompt: "Fit my screen" }) if (!gate.onlySSH) // Add the connection reset option for webrtc fields.splice(0, 0, { prompt: "Reset connection" }) @@ -361,9 +362,15 @@ async function resetCMD(shell: Shell, args: string[]) { } switch (choice) { + case "Fit my screen": + gate.setFitScreen() + gate.map.showLog(false) + return + case "Reset connection": // TODO: simplify if (gate.session) { + gate.session.onStateChange = undefined gate.session.close() gate.session = null } @@ -414,7 +421,7 @@ async function resetCMD(shell: Shell, args: string[]) { shell.t.writeln("dotfile back to default") break case "Fingerprint": - await CapacitorPurchases.logOut() + await Purchases.logOut() await terminal7.deleteFingerprint() shell.t.writeln("Cleared fingerprint and disconnected from PeerBook") terminal7.pbClose() @@ -517,6 +524,7 @@ async function editCMD(shell:Shell, args: string[]) { shell.t.writeln("Failed to delete host") return } + shell.t.writeln("Gate deleted") } gate.delete() break @@ -569,7 +577,7 @@ async function copyKeyCMD(shell: Shell) { return shell.t.writeln(`${publicKey}\n☝️ copied to 📋`) } async function subscribeCMD(shell: Shell) { - const { customerInfo } = await CapacitorPurchases.getCustomerInfo() + const { customerInfo } = await Purchases.getCustomerInfo() if (Capacitor.isNativePlatform() && !customerInfo.entitlements.active.peerbook) { shell.t.writeln("Join PeerBook subscribers and enjoy:") shell.t.writeln("") @@ -587,11 +595,10 @@ async function subscribeCMD(shell: Shell) { "SIX_MONTH": "6 Months", "ANNUAL": "Year", } - const { offerings } = await CapacitorPurchases.getOfferings(), + const offerings = await Purchases.getOfferings(), offer = offerings.current - const products = offer.availablePackages.map(p => { - const identifier = p.identifier, - price = p.product.priceString, + const packages = offer.availablePackages.map(p => { + const price = p.product.priceString, period = TYPES[p.packageType], introPrice = p.product.introPrice let prompt = `${price} / ${period}` @@ -601,9 +608,9 @@ async function subscribeCMD(shell: Shell) { period = (introPrice.periodNumberOfUnits == 1)?unit:`${introPrice.periodNumberOfUnits} ${unit}s` prompt += ` 🎁 ${price} for the first ${period} 🎁` } - return { identifier, prompt } + return { prompt, p } }) - const fields: Fields = products.map(p => ({ prompt: p.prompt })) + const fields: Fields = packages.map(p => ({ prompt: p.prompt })) fields.push({ prompt: "Restore Purchases" }) fields.push({ prompt: "Cancel" }) let choice @@ -622,14 +629,14 @@ async function subscribeCMD(shell: Shell) { throw e }) try { - await CapacitorPurchases.restorePurchases() + await Purchases.restorePurchases() } catch(e) { shell.stopWatchdog() shell.t.writeln("Error restoring purchases, please try again or `support`") return } shell.stopWatchdog() - const { customerInfo } = await CapacitorPurchases.getCustomerInfo() + const { customerInfo } = await Purchases.getCustomerInfo() if (!customerInfo.entitlements.active.peerbook) { shell.t.writeln("Sorry, no active subscription found") return @@ -637,7 +644,7 @@ async function subscribeCMD(shell: Shell) { shell.t.writeln("Subscription restored") } } else { - const product = products.find(p => p.prompt == choice) + const p = packages.find(p => p.prompt == choice) shell.t.writeln("Thank you! directing you to the store") shell.startWatchdog(120000).catch(e => { shell.t.writeln("Sorry, subscribe command timed out") @@ -646,7 +653,7 @@ async function subscribeCMD(shell: Shell) { }) terminal7.ignoreAppEvents = true try { - await terminal7.pb.purchase(product.identifier, offer.identifier) + await terminal7.pb.purchase(p.p) shell.stopWatchdog() } catch(e) { shell.stopWatchdog() @@ -662,6 +669,7 @@ async function subscribeCMD(shell: Shell) { shell.t.writeln("If you are already subscribed, please `login`") return } + terminal7.pb.startSpinner() try { await terminal7.pb.connect(customerInfo.originalAppUserId) } catch(e) { @@ -679,9 +687,11 @@ async function subscribeCMD(shell: Shell) { shell.t.writeln("Please try again and if persists, `support`") return } + } finally { + terminal7.pb.stopSpinner() } - } else - shell.t.writeln("You are already subscribed and registered") + } + shell.t.writeln("You are subscribed and registered 🙏") const uid = await terminal7.pb.getUID() const answer = await shell.askValue(`Copy user id to the clipboard? (y/N)`, "n") if (answer.toLowerCase() == "y") { @@ -730,7 +740,7 @@ export async function installCMD(shell: Shell, args: string[]) { } } else { gate = terminal7.activeG - if (!gate) { + if (!gate && native) { if (terminal7.gates.length == 1) { gate = terminal7.gates[0] shell.t.writeln(`Installing on the only server: ${gate.name}`) @@ -771,9 +781,10 @@ export async function installCMD(shell: Shell, args: string[]) { ] if (native) fields.unshift({ prompt: "Connect & send command" }) - shell.t.writeln("To Enjoy WebRTC you need Terminal7 agent installed & running") + shell.t.writeln("To download and install the agent's binary run:") + shell.t.writeln("") + shell.t.writeln(`\t\x1B[1m${cmd}\x1B[0m`) shell.t.writeln("") - shell.t.writeln(`$ \x1B[1m${cmd}\x1B[0m`) const choice= await shell.runForm(fields, "menu") if (choice == "Cancel") return @@ -803,10 +814,6 @@ export async function installCMD(shell: Shell, args: string[]) { const session = new SSHSession(gate.addr, gate.username) - session.onClose = () => { - // TODO: handle close without installation - terminal7.log("Install SSH session closed") - } session.onStateChange = async (state, failure?: Failure) => { let channel: SSHChannel terminal7.log("Install SSH session got state", state, failure) @@ -901,7 +908,10 @@ export async function installCMD(shell: Shell, args: string[]) { .catch(() => timedOut = true) while (!gate.fp && ! timedOut) await (new Promise(r => setTimeout(r, 100))) - shell.t.writeln(`Gate is installed & verified, use \`connect ${gate.name}\``) + if (gate.fp) + shell.t.writeln(`Gate is installed & verified, use \`connect ${gate.name}\``) + else + shell.t.writeln("Install failed, please try again or `support`") shell.stopWatchdog() } else { shell.t.writeln("Install failed") @@ -917,15 +927,16 @@ async function configCMD(shell: Shell) { await shell.openConfig() } async function supportCMD(shell: Shell) { - shell.t.writeln("https://discord.gg/Puu2afdUtr") - shell.t.writeln("☝️ Please click to join and get help") + shell.t.write("Sorry things are not working well. Please ") + shell.t.writeln("\x1B]8;;https://github.com/tuzig/terminal7/issues/new?template=bug_report.md\x07report a bug\x1B]8;;\x07") + shell.t.writeln("or talk to us on our \x1B]8;;https://discord.gg/Puu2afdUtr\x07discord server\x1B]8;;\x07") } async function loginCMD(shell: Shell) { if (terminal7.pb.isOpen()) { shell.t.writeln("You are already logged in") return } - const { customerInfo } = await CapacitorPurchases.getCustomerInfo() + const { customerInfo } = await Purchases.getCustomerInfo() if (customerInfo.entitlements.active.peerbook) { shell.t.writeln("You are already subscribed, please `subscribe` to login") return diff --git a/src/cyclic.js b/src/cyclic.js deleted file mode 100644 index 782ba6e1..00000000 --- a/src/cyclic.js +++ /dev/null @@ -1,50 +0,0 @@ -export function CyclicArray(n) { - if (!(this instanceof CyclicArray)) return new CyclicArray(n) - - this.head = 0 - this.tail = 0 - this.capacity = n - this.list = new Array(n) - this.length = 0 -} - -CyclicArray.prototype.get = function (i) { - if (i >= this.length) return - var j = (this.head + i) % this.capacity - return this.list[j] -} - -CyclicArray.prototype.push = function () { - for (var i = 0; i < arguments.length; i++) { - this.list[this.tail] = arguments[i] - this.tail = (this.tail + 1) % this.capacity - if (this.length == this.capacity) - this.head = this.tail - else - this.length++ - } -} - -CyclicArray.prototype.pop = function () { - if (!this.length) return - var tail = (this.tail - 1 + this.capacity) % this.capacity - var item = this.list[tail] - this.tail = tail - this.length-- - return item -} - -CyclicArray.prototype.shift = function () { - if (!this.length) return - var item = this.list[this.head] - this.head = (this.head + 1) % this.capacity - this.length-- - return item -} - -CyclicArray.prototype.forEach = function (fn, context) { - for (var i = 0; i < this.length; i++) { - var j = (this.head + i) % this.capacity - fn.call(context, this.list[j], i, this) - } -} diff --git a/src/cyclic.ts b/src/cyclic.ts new file mode 100644 index 00000000..3b946ba8 --- /dev/null +++ b/src/cyclic.ts @@ -0,0 +1,47 @@ +export default class CyclicArray { + head = 0 + tail = 0 + list: unknown[] + length = 0 + + constructor(public capacity: number) { + this.list = new Array(capacity) + } + + get(i) { + if (i >= this.length) return + const j = (this.head + i) % this.capacity + return this.list[j] + } + push(...args) { + for (let i = 0; i < args.length; i++) { + this.list[this.tail] = args[i] + this.tail = (this.tail + 1) % this.capacity + if (this.length == this.capacity) + this.head = this.tail + else + this.length++ + } + } + pop() { + if (!this.length) return + const tail = (this.tail - 1 + this.capacity) % this.capacity + const item = this.list[tail] + this.tail = tail + this.length-- + return item + } + shift() { + if (!this.length) return + const item = this.list[this.head] + this.head = (this.head + 1) % this.capacity + this.length-- + return item + } + forEach(fn, context) { + for (let i = 0; i < this.length; i++) { + const j = (this.head + i) % this.capacity + fn.call(context, this.list[j], i, this) + } + } +} diff --git a/src/gate.ts b/src/gate.ts index 04b7a339..4bc05ffa 100644 --- a/src/gate.ts +++ b/src/gate.ts @@ -7,7 +7,7 @@ */ import { Clipboard } from '@capacitor/clipboard' -import { Pane } from './pane.js' +import { Pane } from './pane' import { T7Map } from './map' import { Failure, Session } from './session' import { PB } from './peerbook' @@ -15,12 +15,21 @@ import { SSHSession } from './ssh_session' import { Terminal7 } from './terminal7' import { Capacitor } from '@capacitor/core' -import { HTTPWebRTCSession, PeerbookSession } from './webrtc_session' -import { Window } from './window.js' +import { HTTPWebRTCSession, PeerbookSession, WebRTCSession } from './webrtc_session' +import { SerializedWindow, Window } from './window' import { Preferences } from '@capacitor/preferences' const FAILED_COLOR = "red"// ashort period of time, in milli +const TOOLBAR_HEIGHT = 135 + +export interface ServerPayload { + height: number + width: number + windows: SerializedWindow[] + active?: boolean +} + /* * The gate class abstracts a host connection */ @@ -28,12 +37,12 @@ export class Gate { activeW: Window addr: string boarding: boolean - e: Element + e: HTMLDivElement id: string marker: number name: string secret: string - session: Session | null + session: PeerbookSession | SSHSession | HTTPWebRTCSession | WebRTCSession | Session | null user: string username: string nameE: Element @@ -49,6 +58,14 @@ export class Gate { firstConnection: boolean keyRejected: boolean connectionFailed: boolean + layoutWidth: number + layoutHeight: number + fontScale: number + fitScreen: boolean + windows: Window[] + breadcrumbs: Window[] + sendStateTask?: number = null + lastDisconnect?: number constructor (props) { // given properties this.id = props.id @@ -64,19 +81,17 @@ export class Gate { // this.windows = [] this.boarding = false - this.lastMsgId = 0 - // a mapping of refrence number to function called on received ack this.breadcrumbs = [] - this.sendStateTask = null - this.timeoutID = null this.fp = props.fp // TODO: move t7 & map into props - this.t7 = window.terminal7 + this.t7 = terminal7 this.map = this.t7.map this.session = null this.onlySSH = props.onlySSH || false this.onFailure = Function.prototype() this.firstConnection = props.firstConnection || false + this.fontScale = props.fontScale || 1 + this.fitScreen = true } /* @@ -86,13 +101,13 @@ export class Gate { // create the gate element - holding the tabs, windows and tab bar this.e = document.createElement('div') this.e.className = "gate hidden" - this.e.style.zIndex = 2 + this.e.style.zIndex = "2" this.e.id = `gate-${this.id}` e.appendChild(this.e) // add the tab bar - let t = document.getElementById("gate-template") + let t = document.getElementById("gate-template") as HTMLTemplateElement if (t) { - t = t.content.cloneNode(true) + t = t.content.cloneNode(true) as HTMLTemplateElement t.querySelector(".reset").addEventListener('click', ev => { this.t7.map.shell.runCommand("reset", [this.name]) ev.preventDefault() @@ -119,6 +134,8 @@ export class Gate { */ this.e.appendChild(t) } + this.layoutWidth = document.body.clientWidth + this.layoutHeight = document.body.clientHeight - TOOLBAR_HEIGHT } // deletes removes the gate from terminal7 and the map delete() { @@ -147,8 +164,8 @@ export class Gate { this.updateNameE() } setIndicatorColor(color) { - this.e.querySelector(".tabbar-names").style.setProperty( - "--indicator-color", color) + const e = this.e.querySelector(".tabbar-names") as HTMLElement + e.style.setProperty("--indicator-color", color) } /* * onSessionState(state) is called when the connection @@ -173,10 +190,9 @@ export class Gate { this.lastDisconnect = Date.now() // TODO: start the rain this.setIndicatorColor(FAILED_COLOR) - if (terminal7.recovering) { - this.session.msgHandlers.forEach(v => { - v[1]("Disconnected") - }) + if (terminal7.recovering) { + const session = this.session as WebRTCSession + session.msgHandlers.forEach(v => v[1]("Disconnected")) setTimeout(() => this.reconnect(), 10) return } @@ -212,11 +228,12 @@ export class Gate { this.notify("Sorry, wrong password") try { password = await this.map.shell.askPass() - } catch (e) { + } catch (e) { this.onFailure(failure) return } - this.session.passConnect(this.marker, password) + const s = this.session as SSHSession + s.passConnect(this.marker, password) return case Failure.BadRemoteDescription: this.session.close() @@ -251,7 +268,7 @@ export class Gate { terminal7.log("Cleaned session as failure on recovering") return } - this.notify(failure?"Lost Data Channel":"Lost Connection") + this.notify(failure?"Lost Data Channel":"Lost Connection" + ", please try `reset`") break case Failure.KeyRejected: @@ -266,13 +283,12 @@ export class Gate { firstGate = (await Preferences.get({key: "first_gate"})).value if (firstGate) terminal7.ignoreAppEvents = true - this.session.passConnect(this.marker, password) + const session = this.session as SSHSession + session.passConnect(this.marker, password) return case Failure.FailedToConnect: this.notify("Failed to connect") - // SSH failed, don't offer install - await this.map.shell.onDisconnect(this) - return + break case Failure.TimedOut: this.connectionFailed = true @@ -295,7 +311,7 @@ export class Gate { return new Promise((resolve, reject) => { if (!isSSH && !isNative) { this.session.reconnect(this.marker).then(layout => { - this.setLayout(layout) + this.setLayout(layout as ServerPayload) resolve() }).catch(() => { if (this.session) { @@ -307,25 +323,24 @@ export class Gate { }) return } + const closeSessionAndDisconnect = () => { + if (this.session && !this.session.isSSH) { + this.session.close() + this.session = null + } + this.map.shell.onDisconnect(this, isSSH).then(resolve).catch(reject) + } this.t7.readId().then(({publicKey, privateKey}) => { this.session.reconnect(this.marker, publicKey, privateKey).then(layout => { - this.setLayout(layout) + this.setLayout(layout as ServerPayload) resolve() }).catch(e => { - if (this.session && !this.session.isSSH) { - this.session.close() - this.session = null - } - terminal7.log("reconnect failed, calling the shell to handle it", isSSH, e) - this.map.shell.onDisconnect(this, isSSH).then(resolve).catch(reject) + closeSessionAndDisconnect() + this.t7.log("reconnect failed, calling the shell to handle it", isSSH, e) }) }).catch((e) => { this.t7.log("failed to read id", e) - if (this.session && !this.session.isSSH) { - this.session.close() - this.session = null - } - this.map.shell.onDisconnect(this, isSSH).then(resolve).catch(reject) + closeSessionAndDisconnect() resolve() }) }) @@ -361,7 +376,7 @@ export class Gate { /* * returns an array of panes */ - panes() { + panes(): Pane[] { const r = [] this.t7.cells.forEach(c => { if (c instanceof Pane && (c.gate == this)) @@ -373,9 +388,11 @@ export class Gate { reset() { this.t7.map.shell.runCommand("reset", [this.name]) } - setLayout(state: object) { + setLayout(state: ServerPayload = null, fromPresenter = false) { console.log("in setLayout", state) const winLen = this.windows.length + if(this.fitScreen) + this.fontScale = 1 // got an empty state if ((state == null) || !(state.windows instanceof Array) || (state.windows.length == 0)) { // create the first window and pane @@ -383,17 +400,22 @@ export class Gate { this.clear() this.activeW = this.addWindow("", true) } else if (winLen > 0) { - // TODO: validate the current layout is like the state - this.t7.log("Restoring with marker, opening channel") - this.panes().forEach(p => { - if (p.d) - p.openChannel({id: p.d.id}) - }) + this.t7.log("Restoring to an existing layout") + if (this.activeW && this.activeW.activeP.zoomed) + this.activeW.activeP.unzoom() + this.syncLayout(state) + this.panes().forEach(p => p.openChannel({id: p.channelID})) } else { this.t7.log("Setting layout: ", state) this.clear() + if (this.layoutWidth != state.width || this.layoutHeight != state.height) { + this.layoutWidth = state.width + this.layoutHeight = state.height + if (fromPresenter) this.fitScreen = false + this.scaleContainer() + } state.windows.forEach(w => { - const win = this.addWindow(w.name) + const win = this.addWindow(w.name, false, w.id) if (w.active) this.activeW = win win.restoreLayout(w.layout, w.active) @@ -408,7 +430,7 @@ export class Gate { let foundNull = false this.panes().forEach((p, i) => { if (p.d) { - if (p.needsResize) { + if (p.needsResize && this.fitScreen) { // TODO: fix webexec so there's no need for this this.t7.run(() => p.d.resize(p.t.cols, p.t.rows), i*10) p.needsResize = false @@ -423,13 +445,80 @@ export class Gate { }, 400) this.focus() } + scaleContainer() { + const container = this.e.querySelector(".windows-container") as HTMLDivElement + let scale + + if (this.fitScreen) { + container.style.width = "100%" + container.style.removeProperty("height") + scale = 1 + container.style.left = "0" + container.style.top = "22px" + container.style.removeProperty("transform") + } else { + + const width = this.layoutWidth, + height = this.layoutHeight + if (!width || !height) + return + const maxWidth = document.body.clientWidth, + maxHeight = document.body.clientHeight - TOOLBAR_HEIGHT + const sx = maxWidth / width, + sy = maxHeight / height + scale = Math.min(sx, sy, 1) + const scaledWidth = width * scale, + scaledHeight = height * scale + container.style.width = `${scaledWidth}px` + container.style.height = `${scaledHeight}px` + container.style.left = "50%" + container.style.top = "calc(50% - 45px)" + container.style.transform = `translate(-50%, -50%)` + container.style.transformOrigin = "top left" + } + this.panes().forEach(p => { + // NOTE: the step of changing the font size is 0.5, there is no visual change when doing smaller steps + const fontSize = p.fontSize * scale + p.t.options.fontSize = Math.floor(fontSize) + (String(fontSize).includes('.') ? .5 : 0) + }) + this.fontScale = scale + } + syncLayout(state: ServerPayload) { + if (state.width != this.layoutWidth || state.height != this.layoutHeight) { + this.layoutWidth = state.width + this.layoutHeight = state.height + this.scaleContainer() + console.log("setting fitScreen to false") + this.fitScreen = false + } + state.windows.forEach(w => { + const win = this.windows.find(win => win.id == w.id) + if (!win) { + // Add window + this.t7.log(`Adding window ${w.name}`) + const newW = this.addWindow(w.name, false, w.id) + newW.restoreLayout(w.layout, w.active) + if (w.active) + newW.focus() + return + } + if (win.name != w.name) { + win.name = w.name + win.nameE.innerHTML = w.name + } + win.rootLayout = win.syncLayout(w.layout) + win.nameE?.setAttribute("href", `#pane-${win.activeP?.id}`) + if (w.active) + win.focus() + }) + } /* * Adds a window, opens it and returns it */ - addWindow(name, createPane) { + addWindow(name, createPane?, id?) { this.t7.log(`adding Window: ${name}`) - const id = this.windows.length, - w = new Window({name:name, gate: this, id: id}) + id = id || this.windows.length + const w = new Window({name:name, gate: this, id: id}) this.windows.push(w) if (this.windows.length >= this.t7.conf.ui.max_tabs) this.e.querySelector(".add-tab").classList.add("off") @@ -452,11 +541,13 @@ export class Gate { this.e.querySelector(".tabbar-names").innerHTML = "" this.e.querySelectorAll(".window").forEach(e => e.remove()) this.e.querySelectorAll(".modal").forEach(e => e.classList.add("hidden")) + this.e.querySelector(".windows-container").removeAttribute("style") if (this.activeW && this.activeW.activeP.zoomed) this.activeW.activeP.unzoom() this.windows = [] this.breadcrumbs = [] - this.msgs = {} + this.layoutWidth = 0 + this.layoutHeight = 0 this.t7.cells.forEach((c, i, cells) => { if (c instanceof Pane && (c.gate == this)) cells.splice(i, 1) @@ -465,18 +556,23 @@ export class Gate { /* * dump dumps the host to a state object * */ - dump() { - const wins = [] + dump(): ServerPayload { + const windows = [] this.windows.forEach(w => { - const win = { + const win: SerializedWindow = { name: w.name, - layout: w.dump() + id: w.id, + layout: w.dump(), } if (w == this.activeW) win.active = true - wins.push(win) + windows.push(win) }) - return { windows: wins } + if (!this.fitScreen) + return {windows, width: this.layoutWidth, height: this.layoutHeight} + const width = document.body.clientWidth, + height = document.body.clientHeight - TOOLBAR_HEIGHT + return { windows, width, height } } storeState() { /* TODO: restore the restore to last state @@ -494,23 +590,26 @@ export class Gate { } sendState() { - if (this.sendStateTask != null) + if ((this.sendStateTask != null) || !this.session) return + // @ts-ignore + this.sendStateTask = setTimeout(() => { - this.storeState() - // send the state only when all panes have a channel - if (this.session && (this.panes().every(p => p.d != null))) - this.sendStateTask = setTimeout(() => { - this.sendStateTask = null - if (!this.session) - return + this.sendStateTask = null + + if (!this.session) + return + + if (this.panes().every(p => p.channelID)) this.session.setPayload(this.dump()).then(() => { if ((this.windows.length == 0) && (this.session != null)) { this.t7.log("Closing gate after updating to empty state") this.close() } }) - }, 100) + else + this.sendState() + }, 100)// TODO: make it run when the update is done and all channels opened } async onPaneConnected() { // hide notifications @@ -537,7 +636,7 @@ export class Gate { * It first sends a mark request and on it's ack store the restore marker * and closes the peer connection. */ - disengage() { + disengage(): Promise { return new Promise((resolve, reject) => { this.t7.log(`disengaging. boarding is ${this.boarding}`) if (!this.session) { @@ -545,7 +644,7 @@ export class Gate { return } return this.session.disconnect().then(marker => { - this.marker = marker + this.marker = marker as number resolve() }).catch(() => { resolve() @@ -563,6 +662,7 @@ export class Gate { } } async copyFingerprint() { + const fp = await this.t7.getFingerprint() const cmd = `echo "${fp}" >> ~/.config/webexec/authorized_fingerprints` const fpForm = [{ prompt: `\n ${this.addr} refused our fingerprint. @@ -580,15 +680,15 @@ export class Gate { this.connect(this.onConnected) } } - async completeConnect(): void { + async completeConnect(): Promise { this.keyRejected = false const isNative = Capacitor.isNativePlatform() const overPB = this.fp && !this.onlySSH && this.online if (overPB) { this.notify("🎌 PeerBook") - if (!terminal7.pb.isOpen()) + if (!terminal7.pb.isOpen()) await terminal7.pbConnect() - this.session = new PeerbookSession(this.fp, this.t7.pb) + this.session = new PeerbookSession(this.fp) } else { if (isNative) { this.session = new SSHSession(this.addr, this.username) @@ -599,14 +699,15 @@ export class Gate { } } this.session.onStateChange = (state, failure?) => this.onSessionState(state, failure) - this.session.onPayloadUpdate = layout => { - this.notify("TBD: update new layout") - this.t7.log("TBD: update layout", layout) + this.session.onCMD = msg => { + if (msg.type == "set_payload") { + this.setLayout(msg.args.payload, true) + } } this.t7.log("opening session") if (overPB) { try { - this.session.connect(this.marker) + await this.session.connect(this.marker) } catch(e) { this.t7.log("error connecting", e) this.notify(`${PB} Connection failed: ${e}`) @@ -618,20 +719,22 @@ export class Gate { const firstGate = (await Preferences.get({key: "first_gate"})).value if (firstGate) terminal7.ignoreAppEvents = true - this.session.connect(this.marker, publicKey, privateKey) + + const session = this.session as SSHSession + await session.connect(this.marker, publicKey, privateKey) } catch(e) { terminal7.log("error connecting with keys", e) this.handleFailure(Failure.KeyRejected) } } else - this.session.connect(this.marker) + await this.session.connect(this.marker) } } load() { this.t7.log("loading gate") this.session.getPayload().then(layout => { console.log("got payload", layout) - this.setLayout(layout) + this.setLayout(layout as ServerPayload) }) document.getElementById("map").classList.add("hidden") } @@ -670,5 +773,12 @@ export class Gate { this.session = null } } - + setFitScreen() { + this.layoutWidth = document.body.clientWidth + this.layoutHeight = document.body.clientHeight - TOOLBAR_HEIGHT + this.fitScreen = true + this.scaleContainer() + this.fit() + this.sendState() + } } diff --git a/src/layout.js b/src/layout.ts similarity index 82% rename from src/layout.js rename to src/layout.ts index bd62e62d..4f807fe2 100644 --- a/src/layout.js +++ b/src/layout.ts @@ -5,14 +5,27 @@ * Copyright: (c) 2021 Benny A. Daon - benny@tuzig.com * License: GPLv3 */ -import { Cell } from './cell.js' -import { Pane } from './pane.js' +import { Cell } from './cell' +import { Pane } from './pane' const ABIT = 10 +export interface SerializedLayout { + dir: string, + sx: number, + sy: number, + xoff: number, + yoff: number, + cells: Cell[], + active?: boolean +} + export class Layout extends Cell { + cells?: Cell[] + dir: "TBD" | "topbottom" | "rightleft" + active?: boolean /* - * Layout contructor creates a `Layout` object based on a cell. + * Layout constructor creates a `Layout` object based on a cell. * The new object wraps the `basedOn` cell and makes it his first son */ constructor(dir, basedOn) { @@ -56,8 +69,8 @@ export class Layout extends Cell { /* * On a cell going away, resize the other elements */ - onClose(c) { - if (c.zoomed) + onClose(c: Cell) { + if (c instanceof Pane && c.zoomed) c.unzoom() this.t7.cells.splice(this.t7.cells.indexOf(c), 1) // if this is the only pane in the layout, close the layout @@ -70,7 +83,7 @@ export class Layout extends Cell { } this.e.remove() } else { - let i = this.cells.indexOf(c), + const i = this.cells.indexOf(c), p = (i > 0)?this.cells[i-1]:this.cells[1] // if no peer it means we're removing the last pane in the window if (p === undefined) { @@ -102,16 +115,17 @@ export class Layout extends Cell { /* * Adds a new pane. If the gate is connected the pane will open a * new data channel. + * If index is given, the pane is replacing the one at that index */ - addPane(props) { - // CONGRATS! a new pane is born. props must include at keast sx & sy - let p = props || {} + addPane(props, index = null) { + // CONGRATS! a new pane is born. props must include at least sx & sy + const p = props || {} p.w = this.w p.gate = this.gate p.layout = this - p.channel_id = props.channel_id + p.channelID = props.channelID p.id = this.t7.cells.length - let pane = new Pane(p) + const pane = new Pane(p) this.t7.cells.push(pane) if (props.parent instanceof Cell) { @@ -119,10 +133,13 @@ export class Layout extends Cell { this.cells.splice(this.cells.indexOf(props.parent)+1, 0, pane) if (props.parent && props.parent.d) parent = props.parent.d.id - pane.openTerminal(parent, props.channel_id) + pane.openTerminal(parent, props.channelID) } else { - this.cells.push(pane) - pane.openTerminal(null, props.channel_id) + if (typeof index == "number") + this.cells.splice(index, 1, pane) + else + this.cells.push(pane) + pane.openTerminal(null, props.channelID) } // opening the terminal and the datachannel are heavy so we wait @@ -155,19 +172,19 @@ export class Layout extends Cell { if (c == this) this.t7.log("ERROR: layout shouldn't have `this` in his cells") // TODO: remove this workaround - `c != this` - if ((c != this) && (typeof c.toText == "function")) + if ((c != this) && c instanceof Layout && (typeof c.toText == "function")) r += c.toText() else - r += `,${c.id || c.d.id}` + r += `,${c.id || (c as Pane).d.id}` }) r += (this.dir=="rightleft")?"]":"}" return r } // Layout.dump dumps the layout to an object - dump() { + dump(): SerializedLayout { // r is the text the function returns - let d = { + const d: SerializedLayout = { dir: this.dir, sx: this.sx, sy: this.sy, @@ -188,7 +205,7 @@ export class Layout extends Cell { * the layout's direction. */ set sx(val) { - let oldS = this.sx, + const oldS = this.sx, r = val/oldS this.e.style.width = String(val * 100) + "%" if (isNaN(r) || this.cells == undefined || this.cells.length == 0) @@ -196,7 +213,7 @@ export class Layout extends Cell { let off = this.cells[0].xoff this.cells.forEach((c) => { if (this.dir == "topbottom") { - let oldS = c.sx, + const oldS = c.sx, s = oldS * r c.xoff = off c.sx = s @@ -212,7 +229,7 @@ export class Layout extends Cell { * the layout's direction. */ set sy(val) { - let oldS = this.sy, + const oldS = this.sy, r = val/oldS this.e.style.height = String(val * 100) + "%" if (isNaN(r) || this.cells == undefined || this.cells.length == 0) @@ -220,7 +237,7 @@ export class Layout extends Cell { let off = this.cells[0].yoff this.cells.forEach((c) => { if (this.dir == "rightleft") { - let oldS = c.sy, + const oldS = c.sy, s = oldS * r c.yoff = off c.sy = s @@ -267,18 +284,18 @@ export class Layout extends Cell { }) } prevCell(c) { - var i = this.cells.indexOf(c) - 1 + const i = this.cells.indexOf(c) - 1 return (i >= 0)?this.cells[i]:null } nextCell(c) { - var i = this.cells.indexOf(c) + 1 + const i = this.cells.indexOf(c) + 1 return (i < this.cells.length)?this.cells[i]:null } /* * Layout.moveBorder moves a pane's border */ moveBorder(pane, border, dest) { - var s, off + let s, off let p0 = null, p1 = null // first, check if it's a horizontal or vertical border we're moving @@ -304,10 +321,10 @@ export class Layout extends Cell { this.layout && this.layout.moveBorder(this, border, dest) return } - let max = this.findNext(p1) + const max = this.findNext(p1) dest = Math.max(dest, p0[off] + 0.02) dest = Math.min(dest, (max?.[off] || 1) - 0.02) - let by = p1[off] - dest + const by = p1[off] - dest p0[s] -= by p1[s] += by p1[off] = dest @@ -318,9 +335,20 @@ export class Layout extends Cell { findNext(c) { if (this.nextCell(c)) return this.nextCell(c) - let root = this.layout?.layout + const root = this.layout?.layout if (root) return root.findNext(this.layout) return null } + // Layout.allCells returns all the cells in the layout + allCells() { + let cells = [] + this.cells.forEach((c) => { + if (c instanceof Layout) + cells = cells.concat(c.allCells()) + else + cells.push(c) + }) + return cells + } } diff --git a/src/map.ts b/src/map.ts index 2071b10a..bfce9634 100644 --- a/src/map.ts +++ b/src/map.ts @@ -12,18 +12,23 @@ import { Gate } from './gate' import { WebLinksAddon } from 'xterm-addon-web-links' import { FitAddon } from "xterm-addon-fit" import { WebglAddon } from 'xterm-addon-webgl' -import { ImageAddon } from 'xterm-addon-image'; +import { ImageAddon } from 'xterm-addon-image' import XtermWebfont from '@liveconfig/xterm-webfont' import { Shell } from './shell' import { Capacitor } from '@capacitor/core' +import { WebRTCSession } from "./webrtc_session" + +export declare interface TerminalWithAddons extends Terminal { + loadWebfontAndOpen(element): Promise +} export class T7Map { - t0: Terminal + t0: TerminalWithAddons ttyWait: number shell: Shell fitAddon: FitAddon - open() { + open(): Promise { return new Promise(resolve => { this.t0 = new Terminal({ cursorBlink: true, @@ -31,7 +36,6 @@ export class T7Map { theme: window.terminal7?.conf.theme, fontFamily: "FiraCode", fontSize: 14, - rendererType: "canvas", convertEol: true, rows: 20, cols: 55, @@ -40,7 +44,7 @@ export class T7Map { window.open(url, "_blank", "noopener") } } - }) + }) as TerminalWithAddons this.shell = new Shell(this) const e = document.getElementById("t0") this.fitAddon = new FitAddon() @@ -94,7 +98,7 @@ export class T7Map { this.t0.loadWebfontAndOpen(e).then(() => { if (Capacitor.getPlatform() === "android") { // hack for android spacebar & virtual keyboard - this.t0.element.addEventListener("input", ev => { + this.t0.element.addEventListener("input", (ev: Event & {data?}) => { if (ev.data) this.shell.keyHandler(ev.data) }) @@ -124,7 +128,7 @@ export class T7Map { }) } add(g: Gate): Element { - const d = document.createElement('div') + const d = (document.createElement('div') as HTMLDivElement & {gate: Gate}) const container = document.createElement('div') d.className = "gate-pad" if (g.fp) @@ -167,12 +171,11 @@ export class T7Map { const edit = b.children[1] edit.innerHTML = `pencil` if (peerbook) { + const extraClass = offline? "offline" : "" if (unverified) - nameE.innerHTML += `lock_shield` + nameE.innerHTML += `lock_shield` else - nameE.innerHTML += `peerbook` - if (offline) - nameE.classList.add("offline") + nameE.innerHTML += `peerbook` } // there's nothing more to update for static hosts if (boarding) @@ -201,8 +204,8 @@ export class T7Map { async updateStats() { terminal7.gates.forEach(async (g: Gate) => { let html = "" - if (g && g.session && g.session.getStats) { - const stats = await g.session.getStats() + if (g && g.session && (g.session as WebRTCSession).getStats) { + const stats = await (g.session as WebRTCSession).getStats() if (!stats) return @@ -226,9 +229,9 @@ export class T7Map { } /* * showLog display or hides the notifications. - * if the parameters in udefined the function toggles the displays + * if the parameters in undefined the function toggles the displays */ - showLog(show) { + showLog(show = undefined) { const log = document.getElementById("log") const minimized = document.getElementById("log-minimized") if (show === undefined) diff --git a/src/pane.js b/src/pane.ts similarity index 78% rename from src/pane.js rename to src/pane.ts index bdd920da..50e73fb5 100644 --- a/src/pane.js +++ b/src/pane.ts @@ -4,25 +4,29 @@ * Copyright: (c) 2021 Benny A. Daon - benny@tuzig.com * License: GPLv3 */ -import { Cell } from './cell.js' -import { Terminal } from 'xterm' +import { Cell } from './cell' +import { ITheme, Terminal } from 'xterm' import { Clipboard } from '@capacitor/clipboard' import { Preferences } from '@capacitor/preferences' import { FitAddon } from 'xterm-addon-fit' import { SearchAddon } from 'xterm-addon-search' import { WebglAddon } from 'xterm-addon-webgl' import { WebLinksAddon } from 'xterm-addon-web-links' -import { ImageAddon } from 'xterm-addon-image'; +import { ImageAddon } from 'xterm-addon-image' import { Camera } from '@capacitor/camera' /* restore the bell. commented as it silences all background audio -import { BELL_SOUND } from './bell.js' +import { BELL_SOUND } from './bell' */ -import { Failure } from './session' +import { Channel, Failure } from './session' import XtermWebfont from '@liveconfig/xterm-webfont' +import * as Hammer from "hammerjs" +import { Manager } from "hammerjs" +import { TerminalWithAddons } from "./map" -const REGEX_SEARCH = false, +const ABIT = 10, + REGEX_SEARCH = false, COPYMODE_BORDER_COLOR = "#F952F9", FOCUSED_BORDER_COLOR = "#F4DB53", SEARCH_OPTS = { @@ -32,30 +36,62 @@ const REGEX_SEARCH = false, caseSensitive: true, } +export interface SerializedPane { + sx: number, + sy: number, + xoff: number, + yoff: number, + fontSize: number, + channelID: number, + active: boolean, + zoomed: boolean, + rows: number, + cols: number +} export class Pane extends Cell { + active = false + aLeader = false + buffer = [] + channelID: number + cmAtEnd?: boolean + cmCursor?: {x: number, y:number} + cmDecorations = [] + cmMarking = false + cmSelection: { + startRow: number, + startColumn: number, + endRow: number, + endColumn: number + } + copyMode = false + d?: Channel = null + dividers = [] + flashTimer? = null + fitAddon: FitAddon + fontSize: number + imageAddon: ImageAddon + lastKey = '' + needsResize = false + searchAddon: SearchAddon + searchDown = false + searchTerm = '' + t: TerminalWithAddons + theme: ITheme + repetition = 0 + retries = 0 + WebLinksAddon: WebLinksAddon + resizeObserver: ResizeObserver + zoomed = false + constructor(props) { props.className = "pane" super(props) this.catchFingers() - this.d = null - this.active = false - this.fontSize = props.fontSize || 12 + this.fontSize = (props.fontSize || 12 ) * this.gate.fontScale this.theme = props.theme || this.t7.conf.theme - this.copyMode = false - this.cmAtEnd = null - this.cmCursor = null - this.cmMarking = false - this.cmSelection = null - this.cmDecorations = [] - this.dividers = [] - this.flashTimer = null - this.aLeader = false - this.retries = 0 - this.lastKey = '' - this.repetition = 0 this.resizeObserver = new window.ResizeObserver(() => this.fit()) - this.needsResize = false + this.channelID = props.channelID } /* @@ -69,13 +105,13 @@ export class Pane extends Cell { * Pane.openTerminal opens an xtermjs terminal on our element */ openTerminal(parentID, channelID) { - console.log("in OpenTerminal") - var con = document.createElement("div") + if (channelID) + this.channelID = channelID + const con = document.createElement("div") this.t = new Terminal({ convertEol: false, fontFamily: "FiraCode", - fontSize: this.fontSize, - rendererType: "canvas", + fontSize: this.fontSize * this.gate.fontScale, theme: this.theme, rows:24, cols:80, @@ -83,7 +119,7 @@ export class Pane extends Cell { /* TODO: restore this. commented because it silences spotify bellStyle: "sound", bellSound: BELL_SOUND, */ - }) + }) as TerminalWithAddons this.fitAddon = new FitAddon() this.searchAddon = new SearchAddon() this.WebLinksAddon = new WebLinksAddon((MouseEvent, url) => { @@ -113,7 +149,7 @@ export class Pane extends Cell { this.t.loadAddon(webGLAddon) this.t.textarea.tabIndex = -1 this.t.attachCustomKeyEventHandler(ev => { - var toDo = true + let toDo = true // ctrl c is a special case if (ev.ctrlKey && (ev.key == "c") && (this.d != null)) { this.d.send(String.fromCharCode(3)) @@ -158,7 +194,7 @@ export class Pane extends Cell { this.t.clearSelection() } }) - this.resizeObserver.observe(this.e); + this.resizeObserver.observe(this.e) this.fit(pane => { if (pane != null) pane.openChannel({parent: parentID, id: channelID}) @@ -178,23 +214,114 @@ export class Pane extends Cell { this.fontSize += by if (this.fontSize < 6) this.fontSize = 6 else if (this.fontSize > 30) this.fontSize = 30 - this.t.options.fontSize = this.fontSize + const fontSize = this.fontSize * this.gate.fontScale + this.t.options.fontSize = Math.floor(fontSize) + (String(fontSize).includes('.') ? .5 : 0) this.fit() + this.gate.sendState() } + /* + * Catches gestures on an element using hammerjs. + * If an element is not passed in, `this.e` is used + */ + catchFingers(elem = undefined) { + const e = (typeof elem == 'undefined')?this.e:elem, + h = new Manager(e, {}), + // h.options.domEvents=true; // enable dom events + singleTap = new Hammer.Tap({event: "tap"}), + doubleTap = new Hammer.Tap({event: "doubletap", taps: 2}), + pinch = new Hammer.Pinch({event: "pinch"}) + + h.add([singleTap, + doubleTap, + pinch, + new Hammer.Tap({event: "twofingerstap", pointers: 2})]) + + h.on('tap', () => { + if (this.w.activeP != this as unknown as Pane) { + this.focus() + this.gate.sendState() + } + }) + h.on('twofingerstap', () => { + this.toggleZoom() + }) + h.on('doubletap', () => { + this.toggleZoom() + }) + + h.on('pinch', (e: HammerInput) => { + // @ts-ignore additionalEvent is not in the .d.ts + this.t7.log(e.additionalEvent, e.distance, e.velocityX, e.velocityY, e.direction, e.isFinal) + if (e.deltaTime < this.lastEventT) + this.lastEventT = 0 + if ((e.deltaTime - this.lastEventT < 200) || + (e.velocityY > this.t7.conf.ui.pinchMaxYVelocity)) + return + this.lastEventT = e.deltaTime + + // @ts-ignore additionalEvent is not in the .d.ts + if (e.additionalEvent == "pinchout") + this.scale(1) + else + this.scale(-1) + }) + } + + zoom() { + const c = document.createElement('div'), + e = document.createElement('div'), + te = this.e.removeChild(this.e.children[0]) + c.classList.add("zoomed") + e.classList.add("pane", "focused") + e.style.borderColor = FOCUSED_BORDER_COLOR + e.appendChild(te) + c.appendChild(e) + this.catchFingers(e) + document.body.appendChild(c) + this.t7.zoomedE = c + this.w.e.classList.add("hidden") + this.resizeObserver = new window.ResizeObserver(() => this.styleZoomed(e)) + this.resizeObserver.observe(e) + this.zoomed = true + } + unzoom() { + if (this.resizeObserver != null) { + this.resizeObserver.disconnect() + this.resizeObserver = null + } + const te = this.t7.zoomedE.children[0].children[0] + this.e.appendChild(te) + document.body.removeChild(this.t7.zoomedE) + this.t7.zoomedE = null + this.w.e.classList.remove("hidden") + this.zoomed = false + } + + toggleZoom() { + if (this.zoomed) + this.unzoom() + else + this.zoom() + this.gate.sendState() + this.t7.run(() => this.focus(), ABIT) + + this.fit() + } // fit a pane to the display area. If it was resized, the server is updated. // returns true is size was changed // TODO: make it async - fit(cb) { - if (!this.t) { - if (cb instanceof Function) cb(this) + fit(cb = null) { + if (!this.t || !this.gate?.fitScreen) { + if (cb instanceof Function) + cb(this) return } - let oldr = this.t.rows - let oldc = this.t.cols + const oldr = this.t.rows + const oldc = this.t.cols // there's no point in fitting when in the middle of a restore - // it happens in the eend anyway + // it happens in the end anyway try { this.fitAddon.fit() } catch (e) { @@ -207,7 +334,7 @@ export class Pane extends Cell { } this.refreshDividers() if (this.t.rows != oldr || this.t.cols != oldc) { - if (this.d) + if (this.d && this.gate.fitScreen) this.d.resize(this.t.cols, this.t.rows) else this.needsResize = true @@ -229,12 +356,10 @@ export class Pane extends Cell { * and the relative size (0-1) of the area left for us. * Returns the new pane. */ - split(dir, s) { + split(dir, s = 0.5) { if (!this.isSplittable(dir)) return - var sx, sy, xoff, yoff, l + let sx, sy, xoff, yoff, l // if the current dir is `TBD` we can swing it our way - if (typeof s == "undefined") - s = 0.5 if ((this.layout.dir == "TBD") || (this.layout.cells.length == 1)) this.layout.dir = dir // if we need to create a new layout do it and add us and new pane as cells @@ -251,7 +376,7 @@ export class Pane extends Cell { this.sy -= sy yoff = this.yoff + this.sy } - else { + else { sy = this.sy sx = this.sx * (1 - s) yoff = this.yoff @@ -261,16 +386,22 @@ export class Pane extends Cell { this.fit() // add the new pane - let p = l.addPane({sx: sx, sy: sy, + const p = l.addPane({sx: sx, sy: sy, xoff: xoff, yoff: yoff, parent: this}) p.focus() - this.gate.sendState() return p } onChannelConnected(channel) { - const reconnect = this.d != null + const reconnect = typeof this.channelID == "number" + if (this.d) { + this.d.onMessage = undefined + this.d.onClose = undefined + this.d.close() + } + this.d = channel + this.channelID = channel.id this.d.onMessage = m => this.onChannelMessage(m) this.d.onClose = () => { this.d = null @@ -290,13 +421,13 @@ export class Pane extends Cell { this.buffer = [] if (opts.id) { this.gate.session.openChannel(opts.id) - .then((channel, id) =>this.onChannelConnected(channel, id)) + .then((channel) =>this.onChannelConnected(channel)) .then(resolve) .catch(m => console.log(m)) } else { this.gate.session.openChannel( this.t7.conf.exec.shell, opts.parent, this.t.cols, this.t.rows) - .then((channel, id) =>this.onChannelConnected(channel, id)) + .then((channel) =>this.onChannelConnected(channel)) .then(resolve) .catch(m => console.log(m)) } @@ -316,10 +447,6 @@ export class Pane extends Cell { this.flashIndicator() this.write(m) } - toggleZoom() { - super.toggleZoom() - this.fit() - } toggleSearch() { const se = this.gate.e.querySelector(".search-box") if (!se.classList.contains("show")) @@ -330,15 +457,15 @@ export class Pane extends Cell { } } - showSearch(searchDown) { + showSearch(searchDown = false) { // show the search field - this.searchDown = searchDown || false + this.searchDown = searchDown const se = this.gate.e.querySelector(".search-box") se.classList.add("show") se.classList.remove("hidden") document.getElementById("search-button").classList.add("on") // TODO: restore regex search - let i = se.querySelector("input[name='search-term']") + const i = se.querySelector("input[name='search-term']") as HTMLInputElement this.disableSearchButtons() i.setAttribute("placeholder", "search string here") if (this.searchTerm) @@ -363,7 +490,20 @@ export class Pane extends Cell { }) i.focus() } - enterCopyMode(marking) { + styleZoomed(e = null) { + e = e || this.t7.zoomedE.querySelector(".pane") + const se = this.gate.e.querySelector(".search-box") + let style + if (se.classList.contains("show")) + style = `${(document.querySelector('.windows-container') as HTMLDivElement).offsetHeight - 22}px` + else + style = `${document.body.offsetHeight - 36}px` + e.style.height = style + e.style.top = "0px" + e.style.width = "100%" + this.fit() + } + enterCopyMode(marking = false) { if (marking) this.cmMarking = true if (!this.copyMode) { @@ -371,7 +511,7 @@ export class Pane extends Cell { this.cmInitCursor() this.cmAtEnd = null if (this.zoomed) - this.t7.zoomedE.children[0].style.borderColor = COPYMODE_BORDER_COLOR + (this.t7.zoomedE.children[0] as HTMLElement).style.borderColor = COPYMODE_BORDER_COLOR else this.e.style.borderColor = COPYMODE_BORDER_COLOR Preferences.get({key: "first_copymode"}).then(v => { @@ -392,7 +532,7 @@ export class Pane extends Cell { this.t.clearSelection() this.t.scrollToBottom() if (this.zoomed) - this.t7.zoomedE.children[0].style.borderColor = FOCUSED_BORDER_COLOR + (this.t7.zoomedE.children[0] as HTMLElement).style.borderColor = FOCUSED_BORDER_COLOR else this.e.style.borderColor = FOCUSED_BORDER_COLOR this.focus() @@ -407,56 +547,55 @@ export class Pane extends Cell { this.styleZoomed() } exitSearch() { - this.hideSearch(); - this.exitCopyMode(); + this.hideSearch() + this.exitCopyMode() } handleMetaKey(ev) { - var f = null - this.t7.log(`Handling meta key ${ev.key}`) - switch (ev.key) { - case "c": + let f = null + this.t7.log(`Handling meta key ${ev.code}`) + switch (ev.code) { + case "KeyC": this.copySelection() break - case "z": + case "KeyZ": f = () => this.toggleZoom() break - case ",": + case "Comma": f = () => this.w.rename() break - case "d": + case "KeyD": f = () => this.close() break - case "0": + case "Digit0": f = () => this.scale(12 - this.fontSize) break - case "=": + case "Equal": f = () => this.scale(1) break - case "-": + case "Minus": f = () => this.scale(-1) break - case "\\": + case "Backslash": f = () => this.split("topbottom") break - case "'": + case "Quote": f = () => this.split("rightleft") break - case "[": - + case "BracketLeft": f = () => this.enterCopyMode() break - case "f": + case "KeyF": f = () => this.showSearch() break // next two keys are on the gate level - case "t": + case "KeyT": f = () => this.gate.newTab() break - case "r": + case "KeyR": f = () => this.gate.reset() break // this key is at terminal level - case "l": + case "KeyL": f = () => this.t7.map.showLog() break case "ArrowLeft": @@ -471,14 +610,17 @@ export class Pane extends Cell { case "ArrowDown": f = () => this.w.moveFocus("down") break - case "p": + case "KeyP": f = () => this.t7.dumpLog() break default: - if (ev.key >= "1" && ev.key <= "9") { + if (ev.code >= "Digit1" && ev.code <= "Digit9") { const win = this.gate.windows[ev.key - 1] + if (this.zoomed) + this.toggleZoom() if (win) win.focus() + ev.preventDefault() } break } @@ -489,25 +631,13 @@ export class Pane extends Cell { } return true } - findNext(searchTerm) { - const notFound = this.gate.e.querySelector(".not-found") - if (searchTerm) { - this.cmAtEnd = null - // this.t.options.selectionStyle = "plain" - this.searchTerm = searchTerm - } - - if (this.searchTerm) { - if (!this.searchAddon.findNext(this.searchTerm, SEARCH_OPTS)) - notFound.classList.remove("hidden") - else { - notFound.classList.add("hidden") - this.enterCopyMode(true) - this.markSelection() - } - } + findNext(searchTerm = '') { + this.find(searchTerm, (st) => this.searchAddon.findNext(st, SEARCH_OPTS)) } - findPrev(searchTerm) { + findPrev(searchTerm = '') { + this.find(searchTerm, (st) => this.searchAddon.findPrevious(st, SEARCH_OPTS)) + } + private find(searchTerm: string, findFunc: (string) => boolean): void { const notFound = this.gate.e.querySelector(".not-found") if (searchTerm) { this.cmAtEnd = null @@ -516,7 +646,7 @@ export class Pane extends Cell { } if (this.searchTerm) { - if (!this.searchAddon.findPrevious(this.searchTerm, SEARCH_OPTS)) + if (!findFunc(this.searchTerm)) notFound.classList.remove("hidden") else { notFound.classList.add("hidden") @@ -540,13 +670,13 @@ export class Pane extends Cell { * */ createDividers() { // create the dividers - var t = document.getElementById("divider-template") + const t = document.getElementById("divider-template") as HTMLTemplateElement if (t) { - var d = [t.content.cloneNode(true), + const d = [t.content.cloneNode(true), t.content.cloneNode(true)] - d.forEach((e, i) => { + d.forEach((e: HTMLElement & {pane?: Pane}, i) => { this.w.e.prepend(e) - e = this.w.e.children[0] + e = this.w.e.children[0] as HTMLElement e.classList.add((i==0)?"left-divider":"top-divider") e.pane = this this.dividers.push(e) @@ -558,10 +688,10 @@ export class Pane extends Cell { * moved or resized */ refreshDividers() { - var W = this.w.e.offsetWidth, - H = this.w.e.offsetHeight, - d = this.dividers[0] - if (this.xoff > 0.001 & this.sy * H > 50) { + const W = this.w.e.offsetWidth, + H = this.w.e.offsetHeight + let d = this.dividers[0] + if (this.xoff > 0.001 && this.sy * H > 50) { // refresh left divider position d.style.left = `${this.xoff * W - 4 - 20 }px` d.style.top = `${(this.yoff + this.sy/2)* H - 22 - 40}px` @@ -569,7 +699,7 @@ export class Pane extends Cell { } else d.classList.add("hidden") d = this.dividers[1] - if (this.yoff > 0.001 & this.sx * W > 50) { + if (this.yoff > 0.001 && this.sx * W > 50) { // refresh top divider position d.style.top = `${this.yoff * H - 25 - 20 }px` d.style.left = `${(this.xoff + this.sx/2)* W - 22 - 40}px` @@ -579,25 +709,31 @@ export class Pane extends Cell { } close() { try { - this.resizeObserver.unobserve(this.e); + this.resizeObserver.unobserve(this.e) } catch (e) {} if (this.d) this.d.close() this.dividers.forEach(d => d.classList.add("hidden")) document.querySelector('.add-tab').classList.remove("off") + if (this.zoomed) + this.unzoom() super.close() } - dump() { - var cell = { + dump(): SerializedPane { + const cell = { sx: this.sx, sy: this.sy, xoff: this.xoff, yoff: this.yoff, - fontSize: this.fontSize + fontSize: this.fontSize, + channelID: null, + active: false, + zoomed: false, + rows: this.t.rows, + cols: this.t.cols } - if (this.d) - cell.channel_id = this.d.id + cell.channelID = this.channelID if (this.w.activeP && this == this.w.activeP) cell.active = true if (this.zoomed) @@ -627,7 +763,7 @@ export class Pane extends Cell { return Clipboard.write({string: lines.join('\n')}) } handleCMKey(key) { - var x, y, newX, newY, + let x, y, newX, newY, selection = this.cmSelection, line // chose the x & y we're going to change @@ -636,7 +772,7 @@ export class Pane extends Cell { if (!this.cmCursor) this.cmInitCursor() x = this.cmCursor.x - y = this.cmCursor.y; + y = this.cmCursor.y selection = { startColumn: x, endColumn: x, @@ -646,11 +782,11 @@ export class Pane extends Cell { } else if (this.cmAtEnd) { x = selection.endColumn - y = selection.endRow; + y = selection.endRow } else { x = selection.startColumn - y = selection.startRow; + y = selection.startRow } newX = x newY = y @@ -658,7 +794,7 @@ export class Pane extends Cell { if (key.match(/\d/)) this.repetition = 10 * this.repetition + parseInt(key) else { - let temp = this.repetition + const temp = this.repetition this.repetition = 0 for (let i = 0; i < temp; i++) { this.handleCMKey(key) @@ -726,7 +862,7 @@ export class Pane extends Cell { break case "Enter": this.copySelection() - this.exitCopyMode(); + this.exitCopyMode() break case '/': this.showSearch(true) @@ -860,7 +996,7 @@ export class Pane extends Cell { if ((newY != y) || (newX != x)) { if (!this.cmMarking) { this.cmCursor.x = newX - this.cmCursor.y = newY; + this.cmCursor.y = newY } else if (this.cmAtEnd) { if ((newY < selection.startRow) || @@ -893,8 +1029,8 @@ export class Pane extends Cell { this.cmSelectionUpdate(selection) if ((newY >= this.t.buffer.active.viewportY + this.t.rows) || (newY < this.t.buffer.active.viewportY)) { - let scroll = newY - this.t.buffer.active.viewportY - this.t.scrollLines(scroll, true) + const scroll = newY - this.t.buffer.active.viewportY + this.t.scrollLines(scroll) } } } @@ -993,14 +1129,14 @@ export class Pane extends Cell { } enableSearchButtons() { const se = this.gate.e.querySelector(".search-box") - let up = se.querySelector(".search-up"), + const up = se.querySelector(".search-up"), down = se.querySelector(".search-down") up.classList.remove("off") down.classList.remove("off") } disableSearchButtons() { const se = this.gate.e.querySelector(".search-box") - let up = se.querySelector(".search-up"), + const up = se.querySelector(".search-up"), down = se.querySelector(".search-down") up.classList.add("off") down.classList.add("off") @@ -1009,7 +1145,7 @@ export class Pane extends Cell { startIndex = startIndex || 0 let match = -1 str.replace(regex, (...args) => { - let i = args.find(x => typeof(x) == "number") + const i = args.find(x => typeof(x) == "number") if (match == -1 && i > startIndex) match = i }) @@ -1017,7 +1153,7 @@ export class Pane extends Cell { } // showVideo replace the terminal with a video and vice versa // if `show` is undefined the video is toggled - showVideo(show) { + showVideo(show = undefined) { const video = document.querySelector("video") if (show === undefined) show = video === null @@ -1029,7 +1165,7 @@ export class Pane extends Cell { if (show) { // first remove all videos button.classList.add("on") - const v = document.createElement("video"); + const v = document.createElement("video") this.e.querySelector("div").classList.add("hidden") this.e.prepend(v) Camera.checkPermissions().then(result => { diff --git a/src/peerbook.ts b/src/peerbook.ts index 5b3ff503..b92f8306 100644 --- a/src/peerbook.ts +++ b/src/peerbook.ts @@ -7,15 +7,35 @@ * License: GPLv3 */ -export const PB = "\uD83D\uDCD6" +import { CustomerInfo } from "@revenuecat/purchases-typescript-internal-esm" -import { Device } from '@capacitor/device'; -import { CapacitorPurchases } from '@capgo/capacitor-purchases' -import { Failure } from './session' +export const PB = "\uD83D\uDCD6" +import { Device } from '@capacitor/device' +import { Purchases } from '@revenuecat/purchases-capacitor' import { HTTPWebRTCSession } from './webrtc_session' import { Gate } from './gate' import { Shell } from './shell' -import { Capacitor } from '@capacitor/core'; +import { Capacitor } from '@capacitor/core' +import {ERROR_HTML_SYMBOL, CLOSED_HTML_SYMBOL, OPEN_HTML_SYMBOL} from './terminal7' + +interface PeerbookProps { + fp: string, + host: string, + insecure: boolean, + shell: Shell +} + +interface Peer { + name: string + user: string + kind: string + verified: boolean + created_on: number + verified_on: number + last_connected: number + online: boolean + auth_token?: string +} export class PeerbookConnection { ws: WebSocket = null @@ -30,8 +50,10 @@ export class PeerbookConnection { shell: Shell uid: string updatingStore = false + spinnerInterval = null + headers: Map - constructor(props:Map) { + constructor(props:PeerbookProps) { // copy all props to this Object.assign(this, props) this.pending = [] @@ -40,7 +62,7 @@ export class PeerbookConnection { this.uid = "" } - async adminCommand(cmd: string, ...args: string[]) { + async adminCommand(cmd: string, ...args: string[]): Promise { const c = args?[cmd, ...args]:[cmd] if (!this.session) { console.log("Admin command with no session") @@ -48,7 +70,7 @@ export class PeerbookConnection { await this.connect() } catch (e) { console.log("Failed to connect to peerbook", e) - throw new Failure("Failed to connect") + throw new Error("Failed to connect") } } @@ -60,7 +82,7 @@ export class PeerbookConnection { terminal7.log(`cmd ${cmd} ${args} closed with: ${ret}`) resolve(ret) } - channel.onMessage = (data) => { + channel.onMessage = (data: Uint8Array) => { reply.push(...data) } }).catch(reject) @@ -109,10 +131,10 @@ export class PeerbookConnection { this.echo("and use it to generate a One Time Password") // verify ourselves - it's the first time and we were approved thanks // to the revenuecat's user id - this.shell.startWatchdog().catch(() => { + this.shell.startWatchdog(3000).catch(() => { this.shell.t.writeln("Timed out waiting for OTP") this.shell.printPrompt() - }, 3000) + }) try { fp = await terminal7.getFingerprint() } catch (e) { @@ -130,7 +152,7 @@ export class PeerbookConnection { } finally { this.shell.stopWatchdog() } - await CapacitorPurchases.logIn({ appUserID: uid }) + await Purchases.logIn({ appUserID: uid }) this.shell.t.writeln("Validated! Use `install` to install on a server") try { await this.wsConnect() @@ -142,6 +164,7 @@ export class PeerbookConnection { } async startPurchases() { console.log("Starting purchases") + await Purchases.setMockWebResults({ shouldMockWebResults: true }) const keys = { ios: 'appl_qKHwbgKuoVXokCTMuLRwvukoqkd', android: 'goog_ncGFZWWmIsdzdfkyMRtPqqyNlsx' @@ -151,8 +174,7 @@ export class PeerbookConnection { } try { - await CapacitorPurchases.setDebugLogsEnabled({ enabled: true }) - await CapacitorPurchases.setup(props) + await Purchases.configure(props) } catch (e) { terminal7.log("Failed to setup purchases", e) return @@ -163,9 +185,11 @@ export class PeerbookConnection { * gets customer info from revenuecat and act on it */ async updateCustomerInfo() { - let data: CapacitorPurchases.PurchasesUpdatedPurchaserInfo + let data: { + customerInfo: CustomerInfo; + } try { - data = await CapacitorPurchases.getCustomerInfo() + data = await Purchases.getCustomerInfo() } catch (e) { terminal7.log("Failed to get customer info", e) return @@ -183,14 +207,6 @@ export class PeerbookConnection { const active = data.customerInfo.entitlements.active this.close() if (!active.peerbook) { - // log out to clear the cache - /* - try { - CapacitorPurchases.logOut() - } catch (e) { - terminal7.log("Failed to log out", e) - } - */ this.updatingStore = false return } @@ -253,7 +269,7 @@ export class PeerbookConnection { terminal7.log("Got TBD as uid") reject("Unregistered") } else { - CapacitorPurchases.logIn({ appUserID: uid }) + Purchases.logIn({ appUserID: uid }) this.wsConnect().then(resolve).catch(reject) } }).catch(e => { @@ -264,6 +280,7 @@ export class PeerbookConnection { return } else if (state == 'failed') { + this.stopSpinner() this.session = null console.log("PB webrtc connection failed", failure) if (this.uid == "TBD") @@ -296,19 +313,22 @@ export class PeerbookConnection { } } const schema = this.insecure?"ws":"wss", - url = encodeURI(`${schema}://${this.host}/ws?fp=${this.fp}`) - const ws = new WebSocket(url) + url = encodeURI(`${schema}://${this.host}/ws?fp=${this.fp}`), + statusE = document.getElementById("peerbook-status"), + ws = new WebSocket(url) this.ws = ws ws.onmessage = ev => { const m = JSON.parse(ev.data) if (m.code >= 400) { console.log("peerbook connect got code", m.code) - reject(`PeerBook connection error ${m.code}`) + this.stopSpinner() + statusE.innerHTML = ERROR_HTML_SYMBOL + // reject(`PeerBook connection error ${m.code}`) return } if (firstMessage) { + this.stopSpinner() firstMessage = false - terminal7.notify(`Connected to ${PB} PeerBook ${PB}`) resolve() } if (this.onUpdate) @@ -317,13 +337,18 @@ export class PeerbookConnection { terminal7.log("got ws message but no onUpdate", m) } ws.onerror = ev => { - window.terminal7.log("peerbook ws error", ev.toString()) - reject(ev.toString()) + terminal7.log("peerbook ws error", ev.toString()) + this.ws = null + this.stopSpinner() + statusE.innerHTML = ERROR_HTML_SYMBOL + reject() } ws.onclose = (ev) => { - window.terminal7.log("peerbook ws closed", ev) - window.terminal7.notify(`${PB} Disconnected. Please \`sub\` to reconnect`) + terminal7.log("peerbook ws closed", ev) this.ws = null + this.stopSpinner() + if (statusE.innerHTML != ERROR_HTML_SYMBOL) + statusE.innerHTML = CLOSED_HTML_SYMBOL } ws.onopen = () => { console.log("peerbook ws open") @@ -394,7 +419,7 @@ export class PeerbookConnection { }) return ret } - async verifyFP(fp: string, prompt: string) { + async verifyFP(fp: string, prompt?: string) { let validated = false // TODO:gAdd biometrics verification while (!validated) { @@ -423,13 +448,10 @@ export class PeerbookConnection { this.echo("Invalid OTP, please try again") } } - purchase(id, offeringId): Promise { + purchase(aPackage): Promise { return new Promise((resolve, reject) => { // ensure there's only one listener - CapacitorPurchases.purchasePackage({ - identifier: id, - offeringIdentifier: offeringId, - }).then(customerInfo => { + Purchases.purchasePackage({ aPackage }).then(customerInfo => { this.onPurchasesUpdate(customerInfo).then(resolve).catch(reject) }).catch(e => { console.log("purchase failed", e) @@ -437,4 +459,28 @@ export class PeerbookConnection { }) }) } + stopSpinner() { + const statusE = document.getElementById("peerbook-status") as HTMLElement + statusE.style.opacity = "1" + if (this.spinnerInterval) { + clearInterval(this.spinnerInterval) + this.spinnerInterval = null + } + } + startSpinner() { + const statusE = document.getElementById("peerbook-status") + let i = 0.1, change = 0.1 + if (this.spinnerInterval) + return + this.spinnerInterval = setInterval(() => { + i = i + change + if (i > 1 || i < 0) { + change = -change + i = i + change + } + statusE.style.opacity = String(i) + }, 200) + statusE.innerHTML = OPEN_HTML_SYMBOL + statusE.style.opacity = "0" + } } diff --git a/src/session.ts b/src/session.ts index 88e258a1..72044f6b 100644 --- a/src/session.ts +++ b/src/session.ts @@ -2,7 +2,8 @@ import { Terminal7 } from "./terminal7" export type CallbackType = (e: unknown) => void export type ChannelID = number -export type State = "new" | "connecting" | "connected" | "reconnected" | "disconnected" | "failed" | "unauthorized" | "wrong password" +export type State = "new" | "connecting" | "connected" | "reconnected" | "disconnected" | "failed" | + "unauthorized" | "wrong password" | "closed" // possible reasons for a failure export enum Failure { @@ -19,6 +20,7 @@ export enum Failure { WrongAddress='Wrong Address', DataChannelLost="Data Channel Lost", FailedToConnect="Failed To Connect", + Overrun='Overrun', } export interface Event { @@ -31,43 +33,41 @@ export interface Channel { id?: ChannelID onClose : CallbackType onMessage : CallbackType - close(): Promise - send(data: string): void + close(): void + send(data: ArrayBuffer | string): void resize(sx: number, sy: number): Promise - get readyState(): string + readonly readyState: string } export interface Session { readonly isSSH: boolean onStateChange : (state: State, failure?: Failure) => void - onPayloadUpdate: (payload: string) => void + onCMD: (payload: unknown) => void // for reconnect - openChannel(id: ChannelID): Promise - // for new channel - openChannel(cmd: string | string[], parent?: ChannelID, sx?: number, sy?: number): - Promise + openChannel(id: ChannelID | string | string[], parent?: ChannelID, sx?: number, sy?: number): Promise close(): void - getPayload(): Promise - setPayload(payload: string): Promise - reconnect(marker?: number, publicKey?: string, privateKey?: string): Promise - disconnect(): Promise - connect(marker?:number | null, publicKey?: string, privateKey?: string): void + getPayload(): Promise + setPayload(payload: unknown): Promise + reconnect(marker?: number, publicKey?: string, privateKey?: string): Promise + disconnect(): Promise + connect(marker?: number, publicKey?: string, privateKey?: string): Promise + connect(marker?: number, noCDC?: boolean): Promise fail(failure?: Failure): void } export abstract class BaseChannel implements Channel { id?: ChannelID - t7: object + t7: Terminal7 onClose : CallbackType onMessage : CallbackType - abstract close(): Promise - abstract send(data: string): void + abstract close(): void + abstract send(data: ArrayBuffer): void abstract resize(sx: number, sy: number): Promise constructor() { this.onMessage = () => void 0 this.onClose = () => void 0 - this.t7 = window.terminal7 + this.t7 = terminal7 } get readyState(): string { @@ -78,25 +78,26 @@ export abstract class BaseSession implements Session { t7: Terminal7 watchdog: number onStateChange : (state: State, failure?: Failure) => void - onPayloadUpdate: (payload: string) => void - constructor() { - this.t7 = window.terminal7 + onCMD: (payload: string) => void + protected constructor() { + this.t7 = terminal7 } get isSSH(): boolean { throw new Error("Not implemented") } - async getPayload(): Promise { + async getPayload(): Promise { return null } // TODO: get it to throw "Not Implemented" async setPayload(payload) { console.log(`ignoring set payload: ${JSON.stringify(payload)}`) } - async reconnect(): Promise { + async reconnect(marker?: number, publicKey?: string, privateKey?: string): Promise { throw "Not Implemented" } + // base disconnect is rejected as it's not supported - disconnect(): Promise{ + disconnect(): Promise { return new Promise((resolve, reject) => { reject() }) @@ -107,9 +108,11 @@ export abstract class BaseSession implements Session { this.onStateChange("failed", failure) } abstract close(): void + // for reconnect - abstract openChannel(id: ChannelID): Promise - abstract openChannel(cmd: string | ChannelID, parent?: ChannelID, sx?: number, sy?: number): - Promise - abstract connect(): void + abstract openChannel(id: number | string | string[], parent?: number, sx?: number, sy?: number): Promise + + abstract connect(marker?: number, publicKey?: string, privateKey?: string): Promise + abstract connect(marker?: number, noCDC?: boolean): Promise + } diff --git a/src/shell.ts b/src/shell.ts index a5dd20fd..f47da96d 100644 --- a/src/shell.ts +++ b/src/shell.ts @@ -1,14 +1,13 @@ -import { Channel } from "./session" +import { Channel, Failure } from "./session" import { Clipboard } from "@capacitor/clipboard" import { Terminal } from 'xterm' import { Command, loadCommands } from './commands' import { Fields, Form } from './form' import { Gate } from "./gate" import { T7Map } from './map' -import { Failure } from "./session" import CodeMirror from '@tuzig/codemirror/src/codemirror.js' import { vimMode } from '@tuzig/codemirror/keymap/vim.js' -import { tomlMode} from '@tuzig/codemirror/mode/toml/toml.js' +import { tomlMode } from '@tuzig/codemirror/mode/toml/toml.js' import { dialogAddOn } from '@tuzig/codemirror/addon/dialog/dialog.js' export class Shell { @@ -31,7 +30,7 @@ export class Shell { historyIndex = 0 confEditor: CodeMirror.EditorFromTextArea exitConf: () => void - lineAboveForm: 0 + lineAboveForm = 0 constructor(map: T7Map) { this.map = map @@ -201,7 +200,7 @@ export class Shell { if (!this.watchdog) return this.stopWatchdog() if (terminal7.activeG) - terminal7.activeG.onFailure("Overrun") + terminal7.activeG.onFailure(Failure.Overrun) await new Promise(r => setTimeout(r, 100)) this.printPrompt() } @@ -339,7 +338,7 @@ export class Shell { async openConfig() { const modal = document.getElementById("settings"), button = document.getElementById("dotfile-button"), - area = document.getElementById("edit-conf"), + area = document.getElementById("edit-conf") as HTMLTextAreaElement, conf = await terminal7.getDotfile() area.value = conf @@ -371,7 +370,7 @@ export class Shell { } closeConfig(save = false) { - const area = document.getElementById("edit-conf") + const area = document.getElementById("edit-conf") as HTMLTextAreaElement document.getElementById("dotfile-button").classList.remove("on") if (save) { this.confEditor.save() diff --git a/src/ssh_session.ts b/src/ssh_session.ts index 07d3ca63..6b773b8f 100644 --- a/src/ssh_session.ts +++ b/src/ssh_session.ts @@ -1,5 +1,5 @@ import { SSH, SSHSessionID, StartByPasswd, StartByKey} from 'capacitor-ssh-plugin' -import { Channel, BaseChannel, BaseSession, Failure, Session, State, ChannelID } from './session' +import { BaseChannel, BaseSession, Failure, Session, ChannelID } from './session' import { WebRTCSession } from './webrtc_session' const ACCEPT_CMD = "/usr/local/bin/webexec accept" @@ -8,7 +8,9 @@ export class SSHChannel extends BaseChannel { async close(): Promise { return SSH.closeChannel({channel: this.id}) } - send(data: string): void { + send(data: string | ArrayBuffer): void { + //TODO: remove next line when fixed - https://github.com/tuzig/capacitor-ssh-plugin/issues/15 + //@ts-ignore the field is actually called "message" not "s" SSH.writeToChannel({channel: this.id, message: data}) .catch(e => console.log("error from writeToChannel", e)) } @@ -26,14 +28,12 @@ export class SSHChannel extends BaseChannel { } } } -// SSHSession is an implmentation of a real time session over ssh +// SSHSession is an implementation of a real time session over ssh export class SSHSession extends BaseSession { id: SSHSessionID address: string username: string port: number - onStateChange : (state: State, failure?: Failure) => void - onPayloadUpdate: (payload: string) => void constructor(address: string, username: string, port=22) { super() this.username = username @@ -45,12 +45,13 @@ export class SSHSession extends BaseSession { this.id = session this.onStateChange("connected") } - async connect(marker?:number, publicKey: string, privateKey:string) { + async connect(marker?:number, publicKey?: string | boolean, privateKey?: string): Promise { terminal7.log("Connecting using SSH", this.address, this.username, this.port) SSH.startSessionByKey({ address: this.address, port: this.port, username: this.username, + // @ts-ignore publicKey: publicKey, privateKey: privateKey }).then(args => { @@ -75,6 +76,7 @@ export class SSHSession extends BaseSession { } SSH.startSessionByPasswd(args) .then(args => { + //@ts-ignore bug in .d.ts? this.onSSHSession(args.session) }).catch(e => { const msg = e.toString() @@ -90,21 +92,21 @@ export class SSHSession extends BaseSession { } openChannel(cmd: unknown, parent?: ChannelID, sx?: number, sy?: number): - Promise { + Promise { return new Promise((resolve, reject) => { const channel = new SSHChannel() SSH.newChannel({session: this.id}) .then(({ id }) => { console.log("got new channel with id ", id) channel.id = id - SSH.startShell({channel: id, command: cmd}, m => channel.handleData(m)) + SSH.startShell({channel: id, command: cmd as string}, m => channel.handleData(m)) .then(callbackID => { console.log("got from startShell: ", callbackID) - resolve(channel, id) + resolve(channel) SSH.setPtySize({channel: id, width: sx, height: sy}) .then(() => { console.log("after setting size") - resolve(channel, id) + resolve(channel) }) }).catch(e => { @@ -138,10 +140,8 @@ export class SSHSession extends BaseSession { // over SSH export class HybridSession extends SSHSession { candidate: string - webrtcSession: Session + webrtcSession: WebRTCSession sentMessages: Array - onStateChange : (state: State, failure?: Failure) => void - onPayloadUpdate: (payload: string) => void gotREADY: boolean constructor(address: string, username: string, port=22) { super(address, username, port) @@ -153,11 +153,13 @@ export class HybridSession extends SSHSession { * connect must recieve either password or tag to choose * whether to use password or identity key based authentication */ - async connect(marker?:number, publicKey: string, privateKey:string) { + async connect(marker?:number, publicKey?: string, privateKey?:string) { + const args: StartByKey = { address: this.address, port: this.port, username: this.username, + // @ts-ignore publicKey: publicKey, privateKey: privateKey } @@ -180,6 +182,7 @@ export class HybridSession extends SSHSession { password: password, } SSH.startSessionByPasswd(args) + //@ts-ignore bug in .d.ts? .then(async ({ session }) => { this.t7.log("Got ssh session", session) this.id = session @@ -213,7 +216,6 @@ export class HybridSession extends SSHSession { .split("\r\n") .filter(line => line.length > 0) .forEach(async line => { - let c = {} this.t7.log("line webexec accept: ", line) if (line.startsWith("READY")) { try { @@ -228,6 +230,7 @@ export class HybridSession extends SSHSession { } this.candidate += line // ignore echo + let c: unknown = {} if (this.sentMessages.indexOf(this.candidate) != -1) { this.t7.log("igonring message: "+this.candidate) this.candidate = "" @@ -240,7 +243,7 @@ export class HybridSession extends SSHSession { this.candidate = "" if (c == null) return - if (c.candidate) + if ((c as {candidate?}).candidate) try { await this.webrtcSession.pc.addIceCandidate(c) } catch(e) { @@ -249,7 +252,7 @@ export class HybridSession extends SSHSession { } else try { - await this.webrtcSession.pc.setRemoteDescription(c) + await this.webrtcSession.pc.setRemoteDescription(c as RTCSessionDescriptionInit) } catch(e) { this.t7.log("got error setting remote desc:", e.message, c) } }) } @@ -273,27 +276,29 @@ export class HybridSession extends SSHSession { this.webrtcSession.onIceCandidate = e => { const candidate = JSON.stringify(e.candidate) this.sentMessages.push(candidate) + //@ts-ignore bug in the .d.ts SSH.writeToChannel({channel: channelId, message: candidate + "\n"}) } this.webrtcSession.onNegotiationNeeded = () => { - this.t7.log("on negotiation needed") + this.t7.log("on negotiation needed"); this.webrtcSession.pc.createOffer().then(d => { - const offer = JSON.stringify(d) + const offer = JSON.stringify(d); this.webrtcSession.pc.setLocalDescription(d) this.sentMessages.push(offer) + //@ts-ignore a .d.ts bug SSH.writeToChannel({channel: channelId, message: offer + "\n"}) }) } this.webrtcSession.connect(marker) }) } - async openChannel(cmd: unknown, parent?: ChannelID, sx?: number, sy?: number) { + async openChannel(cmd: number | string | string[], parent?: number, sx?: number, sy?: number): Promise { if (!this.webrtcSession) { return super.openChannel(cmd, parent, sx, sy) } else // start webrtc data channel - return this.webrtcSession.openChannel(cmd, parent, sx, sy) + return this.webrtcSession.openChannel(cmd, parent, sx, sy) as unknown as SSHChannel } async reconnect(marker?: number, publicKey?: string, privateKey?: string) { @@ -307,7 +312,7 @@ export class HybridSession extends SSHSession { return this.webrtcSession.close() } - getPayload(): Promise { + getPayload(): Promise { if (this.webrtcSession) return this.webrtcSession.getPayload() else @@ -319,7 +324,7 @@ export class HybridSession extends SSHSession { else return super.setPayload(payload) } - disconnect(): Promise { + disconnect(): Promise { if (this.webrtcSession) return this.webrtcSession.disconnect() else diff --git a/src/terminal7.js b/src/terminal7.ts similarity index 80% rename from src/terminal7.js rename to src/terminal7.ts index d09b1ac3..1cef7e38 100644 --- a/src/terminal7.js +++ b/src/terminal7.ts @@ -6,16 +6,17 @@ * Copyright: (c) 2020 Benny A. Daon - benny@tuzig.com * License: GPLv3 */ -import { Gate } from './gate.ts' -import { T7Map } from './map.ts' -import { CyclicArray } from './cyclic.js' +import { Gate } from './gate' +import { T7Map } from './map' +import CyclicArray from './cyclic' import * as TOML from '@tuzig/toml' -import { formatDate } from './utils.js' +import { formatDate } from './utils' import { openDB } from 'idb' import { marked } from 'marked' +// @ts-ignore import changelogURL from '../CHANGELOG.md?url' -import ssh from 'ed25519-keygen/ssh'; -import { randomBytes } from 'ed25519-keygen/utils'; +import ssh from 'ed25519-keygen/ssh' +import { randomBytes } from 'ed25519-keygen/utils' import { Capacitor } from '@capacitor/core' import { App } from '@capacitor/app' @@ -28,26 +29,51 @@ import { RateApp } from 'capacitor-rate-app' import { PeerbookConnection, PB } from './peerbook' -import { Failure } from './session'; +import { Failure } from './session' +import { Cell } from "./cell" +import { Pane } from "./pane" +import { PeerbookSession } from "./webrtc_session" +declare type NavType = { + standalone?: boolean + getInstalledRelatedApps(): Promise<{ + id?: string, + platform: "chrome_web_store" | "play" | "chromeos_play" | "webapp" | "windows" | "f-droid" | "amazon", + url?: string, + version?: string, + }[]> +} & Navigator + +declare let window: { + navigator: NavType +} & Window + +declare let navigator: NavType + +export const OPEN_HTML_SYMBOL = "📡" +export const ERROR_HTML_SYMBOL = "🤕" +export const CLOSED_HTML_SYMBOL = "🙁" +export const LOCK_HTML_SYMBOL = "🔒" const WELCOME=` 🖖 Greetings & Salutations 🖖 Thanks for choosing Terminal7. This is TWR, a local -terminal used to control the terminal and log messages.` -const WELCOME_NATIVE=WELCOME+` -Type \`help\`, \`add\` or \`hide\` if you're ready to board. -For WebRTC 🍯 please \`subscribe\` to our online service. +terminal used to control the terminal and log messages. +Most buttons launch a TWR command so you don't need to +use \`help\`, just \`hide\`. +If some characters looks off try CTRL-l.` +const WELCOME_FOOTER=` Enjoy! - +PS - Found a bug? Missing a feature? Please use \`support\` ` +const WELCOME_NATIVE=WELCOME+` +For WebRTC 🍯 please \`subscribe\` to our PeerBook service. +` + WELCOME_FOOTER const WELCOME_OTHER=WELCOME+` -Type \`help\`, \`install\` or \`hide\` if you're ready to board. -If you are one of our PeerBook subscribers, please \`login\`. - -Enjoy! - -` +Type \`install\` for instruction on how to install the agent. +If you are a PeerBook subscriber, please \`login\`. +(Sorry, no way to subscribe from here yet) +` + WELCOME_FOOTER export const DEFAULT_DOTFILE = `# Terminal7's configurations file [theme] @@ -87,13 +113,75 @@ function compactCert(cert) { const ret = cert.getFingerprints()[0].value.toUpperCase().replaceAll(":", "") return ret } + +declare global { + let terminal7: Terminal7 + interface Window { + terminal7: Terminal7 + } +} + +export interface IceServers { + credential: string, + credentialType: "password" | string, + urls: string[], + username?: string +} + export class Terminal7 { + gates: Gate[] + cells: Cell[] + timeouts: number[] + activeG?: Gate + scrollLingers4: number + shortestLongPress: number + borderHotSpotSize: number + certificates?: RTCCertificate[] = null + netConnected = true + logBuffer: CyclicArray + zoomedE?: HTMLDivElement + pendingPanes + pb?: PeerbookConnection = null + ignoreAppEvents = false + purchasesStarted = false + iceServers?: IceServers[] + recovering?: boolean + metaPressStart: number + map: T7Map + lastActiveState: boolean + e: HTMLDivElement + conf:{ + theme + exec + net + ui + peerbook? + retries?: number + } + longPressGate: number + gesture?: { + where: "left" | "top", + pane: Pane + } + pointer0: number + firstPointer: { + pageX: number, + pageY: number + } + lastIdVerify: number + keys: {publicKey: string, privateKey: string} + DEFAULT_KEY_TAG = "dev.terminal7.keys.default" /* * Terminal7 constructor, all properties should be initiated here */ - constructor(settings) { - settings = settings || {} + constructor(settings: { + scrollLingers4?: number, + shortestLongPress?: number, + borderHotSpotSize?: number, + logLines?: number, + iceServers?: IceServers[] + } = {}) { this.gates = [] this.cells = [] this.timeouts = [] @@ -102,16 +190,11 @@ export class Terminal7 { this.scrollLingers4 = settings.scrollLingers4 || 2000 this.shortestLongPress = settings.shortestLongPress || 1000 this.borderHotSpotSize = settings.borderHotSpotSize || 30 - this.certificates = null - this.confEditor = null - this.flashTimer = null - this.netConnected = true - this.logBuffer = CyclicArray(settings.logLines || 101) + + this.logBuffer = new CyclicArray(settings.logLines || 101) this.zoomedE = null this.pendingPanes = {} - this.pb = null - this.ignoreAppEvents = false - this.purchasesStarted = false + this.iceServers = settings.iceServers || null } showKeyHelp () { @@ -122,15 +205,40 @@ export class Terminal7 { document.getElementById('keys-help').classList.remove('hidden') } } + onAppStateChange(state) { + const active = state.isActive + if (this.lastActiveState == active) { + this.log("app state event on unchanged state ignored") + return + } + this.lastActiveState = active + this.log("app state changed", this.ignoreAppEvents) + if (!active) { + if (this.ignoreAppEvents) { + terminal7.log("ignoring benched app event") + return + } + this.updateNetworkStatus({connected: false}, false) + } else { + // We're back! puts us in recovery mode so that it'll + // quietly reconnect to the active gate on failure + if (this.ignoreAppEvents) { + this.ignoreAppEvents = false + return + } + this.clearTimeouts() + Network.getStatus().then(s => this.updateNetworkStatus(s)) + } + } /* * Terminal7.open opens terminal on the given DOM element, * loads the gates from local storage and redirects to home */ async open() { - let e = document.getElementById('terminal7') + const e = document.getElementById('terminal7') this.log("in open") this.lastActiveState = true - this.e = e + this.e = e as HTMLDivElement await Preferences.migrate() // reading conf let d = {}, @@ -146,7 +254,7 @@ export class Terminal7 { this.run(() => this.notify( `Using default conf as parsing the dotfile failed:\n ${err}`, - 10)) + true), 10) } this.loadConf(d) @@ -192,6 +300,11 @@ export class Terminal7 { ev.stopPropagation() ev.preventDefault() }) + document.getElementById('peerbook-legend').addEventListener( + 'click', async (ev) => { + setTimeout(() => this.map.shell.runCommand('subscribe', []), 50) + ev.stopPropagation() + }) // hide the modal on xmark click // Handle network events for the indicator Network.addListener('networkStatusChange', s => @@ -222,31 +335,8 @@ export class Terminal7 { if (Capacitor.isNativePlatform()) { // this is a hack as some operation, like bio verification // fire two events - App.addListener('appStateChange', state => { - const active = state.isActive - if (this.lastActiveState == active) { - this.log("app state event on unchanged state ignored") - return - } - this.lastActiveState = active - console.log("app state changed", this.ignoreAppEvents) - if (!active) { - if (this.ignoreAppEvents) { - terminal7.log("ignoring benched app event") - return - } - this.updateNetworkStatus({connected: false}, false) - } else { - // We're back! puts us in recovery mode so that it'll - // quietly reconnect to the active gate on failure - if (this.ignoreAppEvents) { - this.ignoreAppEvents = false - return - } - this.clearTimeouts() - Network.getStatus().then(s => this.updateNetworkStatus(s)) - } - }) + App.addListener('appStateChange', state => this.onAppStateChange(state)) + } e.addEventListener("click", e => { @@ -282,12 +372,17 @@ export class Terminal7 { } }) ) + const resizeObserver = new ResizeObserver(() => { + if (this.activeG) + this.activeG.setFitScreen() + }) + resizeObserver.observe(document.body) } /* * restoreState is a future feature that uses local storage to restore * terminal7 to it's last state */ - restoreState() { + restoreState(): Promise { return new Promise((resolve, reject) => { if (!this.conf.ui.autoRestore) { reject() @@ -298,7 +393,7 @@ export class Terminal7 { reject() else { const state = JSON.parse(value) - let gate = this.gates[state.gateId] + const gate = this.gates[state.gateId] if (!gate) { console.log("Invalid restore state. Starting fresh", state) this.notify("Invalid restore state. Starting fresh") @@ -319,33 +414,57 @@ export class Terminal7 { this.pb.close() } } - async pbConnect() { + async pbConnect(): Promise { + const statusE = document.getElementById("peerbook-status") as HTMLSpanElement return new Promise((resolve, reject) => { + function callResolve() { + if (terminal7.pb) + terminal7.pb.stopSpinner() + statusE.style.opacity = "1" + resolve() + } + function callReject(e, symbol = "") { + if (terminal7.pb) + terminal7.pb.stopSpinner() + statusE.style.opacity = "1" + statusE.innerHTML = symbol + reject(e) + } const catchConnect = e => { + let symbol = "⛔︎" if (e =="Unregistered") - this.notify(`${PB} You are unregistered, please \`subscribe\``) + this.notify(Capacitor.isNativePlatform()? + `${PB} You need to register, please \`subscribe\``: + `${PB} You need to regisrer, please \`subscribe\` on your tablet`) + else if (e == Failure.NotSupported) // TODO: this should be changed to a notification // after we upgrade peerbook console.log("PB not supported") else if (e != "Unauthorized") { terminal7.log("PB connect failed", e) - this.notify(`${PB} Failed to connect, please try \`sub\``) + this.notify(Capacitor.isNativePlatform()? + `${PB} Failed to connect, please try \`subscribe\``: + `${PB} Failed to connect, please try \`login\``) this.notify("If the problem persists, `support`") - } - reject(e) + symbol = ERROR_HTML_SYMBOL + } else + symbol = LOCK_HTML_SYMBOL + + callReject(e, symbol) } // do nothing when no subscription or already connected if (this.pb) { + this.pb.startSpinner() if ((this.pb.uid != "TBD") && (this.pb.uid != "")) { - this.pb.wsConnect().then(resolve).catch(reject) + this.pb.wsConnect().then(callResolve).catch(callReject) return } if (this.pb.isOpen()) - resolve() + callResolve() else - this.pb.connect().then(resolve).catch(catchConnect) + this.pb.connect().then(callResolve).catch(catchConnect) return } this.getFingerprint().then(fp => { @@ -355,21 +474,22 @@ export class Terminal7 { insecure: this.conf.peerbook && this.conf.peerbook.insecure, shell: this.map.shell }) + this.pb.startSpinner() this.pb.onUpdate = (m) => this.onPBMessage(m) if (!this.purchasesStarted) { this.pb.startPurchases().then(() => - this.pb.connect().then(resolve).catch(catchConnect) - // this.pb.updateCustomerInfo().then(resolve).catch(reject) - ).catch(reject).finally(() => this.purchasesStarted = true) + this.pb.connect().then(callResolve).catch(catchConnect) + // this.pb.updateCustomerInfo().then(callResolve).catch(callReject) + ).catch(callReject).finally(() => this.purchasesStarted = true) } else - this.pb.connect().then(resolve).catch(catchConnect) + this.pb.connect().then(callResolve).catch(catchConnect) }) }) } catchFingers() { this.e.addEventListener("pointerdown", ev => this.onPointerDown(ev)) this.e.addEventListener("pointerup", ev => this.onPointerUp(ev)) - this.e.addEventListener("pointercancel", ev => this.onPointerCancel(ev)) + this.e.addEventListener("pointercancel", () => this.onPointerCancel()) this.e.addEventListener("pointermove", ev => this.onPointerMove(ev)) } /* @@ -379,10 +499,10 @@ export class Terminal7 { */ // TOFO: add onMap to props addGate(props, onMap = true) { - let p = props || {} + const p = props || {} // add the id p.id = p.fp || p.name - let g = new Gate(p) + const g = new Gate(p) g.onlySSH = p.onlySSH this.gates.push(g) g.open(this.e) @@ -393,10 +513,10 @@ export class Terminal7 { return g } async storeGates() { - let out = [] + const out = [] this.gates.forEach(g => { if (g.store) { - let ws = [] + const ws = [] g.windows.forEach((w) => ws.push(w.id)) out.push({id: g.id, addr: g.addr, user: g.user, secret: g.secret, name:g.name, windows: ws, store: true, verified: g.verified, @@ -419,7 +539,7 @@ export class Terminal7 { await this.map.shell.escapeActiveForm() } async goHome() { - Preferences.remove({key: "last_state"}) + await Preferences.remove({key: "last_state"}) const s = document.getElementById('map-button') s.classList.add('off') if (this.activeG) { @@ -461,7 +581,7 @@ export class Terminal7 { this.map.showLog(true) } run(cb, delay) { - var i = this.timeouts.length, + const i = this.timeouts.length, r = window.setTimeout(ev => { this.timeouts.splice(i, 1) cb(ev) @@ -476,10 +596,10 @@ export class Terminal7 { /* * disengage gets each active gate to disengae */ - disengage() { + disengage(): Promise { return new Promise(resolve => { this.pbClose() - var count = 0 + let count = 0 if (this.activeG && this.activeG.boarding) this.notify("🌜 Benched", true) if (this.gates.length > 0) { @@ -492,7 +612,7 @@ export class Terminal7 { } }) } - let callCB = () => terminal7.run(() => { + const callCB = () => terminal7.run(() => { if (count == 0) resolve() else @@ -502,7 +622,7 @@ export class Terminal7 { }) } async updateNetworkStatus (status, updateNetPopup = true) { - let off = document.getElementById("offline").classList + const off = document.getElementById("offline").classList if (this.netConnected == status.connected) { if (updateNetPopup) { if (this.netConnected) @@ -527,7 +647,9 @@ export class Terminal7 { if (this.pb.isOpen()) gate.notify("Timed out") else - this.notify(`${PB} timed out, please try \`subscribe\``) + this.notify(Capacitor.isNativePlatform()? + `${PB} timed out, please retry with \`subscribe\``: + `${PB} timed out, please retry with \`login\``) gate.stopBoarding() }) } else @@ -598,7 +720,7 @@ export class Terminal7 { } // gets the will formatted fingerprint from the current certificate - getFingerprint() { + getFingerprint(): Promise { // gets the certificate from indexDB. If they are not there, create them return new Promise((resolve, reject) => { if (this.certificates) { @@ -611,7 +733,7 @@ export class Terminal7 { autoIncrement: true}) }, }).then(db => { - let tx = db.transaction("certificates"), + const tx = db.transaction("certificates"), store = tx.objectStore("certificates") store.getAll().then(certificates => { if (certificates.length == 0) { @@ -643,6 +765,7 @@ export class Terminal7 { this.log('generating the certificate') RTCPeerConnection.generateCertificate({ name: "ECDSA", + // @ts-ignore namedCurve: "P-256", expires: 31536000000 }).then(cert => { @@ -657,7 +780,7 @@ export class Terminal7 { }) }) } - storeCertificate() { + storeCertificate(): Promise { return new Promise((resolve, reject) => { openDB("t7", 1, { upgrade(db) { @@ -665,9 +788,9 @@ export class Terminal7 { autoIncrement: true}) }, }).then(db => { - let tx = db.transaction("certificates", "readwrite"), + const tx = db.transaction("certificates", "readwrite"), store = tx.objectStore("certificates"), - c = this.certificates[0] + c = this.certificates[0] as RTCCertificate & {id:number} c.id = 1 store.add(c).then(() => { db.close() @@ -685,7 +808,7 @@ export class Terminal7 { // var helpId = (this.activeG)? "help-gate":"help-home", // var helpId = (this.activeG && this.activeG.activeW.activeP.copyMode)? // "help-copymode":"help-gate", - var helpId = "help-gate", + const helpId = "help-gate", ecl = document.getElementById(helpId).classList, bcl = document.getElementById("help-button").classList @@ -697,12 +820,14 @@ export class Terminal7 { } // handle incomming peerbook messages (coming over sebsocket) async onPBMessage(m) { + const statusE = document.getElementById("peerbook-status") this.log("got pb message", m) if (m["code"] !== undefined) { if (m["code"] == 200) { - this.notify("\uD83D\uDCD6 Logged in") + statusE.innerHTML = OPEN_HTML_SYMBOL this.pb.uid = m["text"] } else + // TODO: update statusE this.notify(`\uD83D\uDCD6 ${m["text"]}`) return } @@ -739,35 +864,36 @@ export class Terminal7 { g.online = m.peer_update.online g.verified = m.peer_update.verified g.fp = m.source_fp - g.updateNameE() + await g.updateNameE() return } if (!g.session) { console.log("session is close ignoring message", m) return } + const session = g.session as PeerbookSession if (m.candidate !== undefined) { - g.session.peerCandidate(m.candidate) + session.peerCandidate(m.candidate) return } if (m.answer !== undefined ) { - var answer = JSON.parse(atob(m.answer)) - g.session.peerAnswer(answer) + const answer = JSON.parse(atob(m.answer)) + session.peerAnswer(answer) return } } log (...args) { - var line = "" + let line = "" args.forEach(a => line += JSON.stringify(a) + " ") console.log(line) this.logBuffer.push(line) } async dumpLog() { - var data = "" + let data = "" while (this.logBuffer.length > 0) { data += this.logBuffer.shift() + "\n" } - Clipboard.write({string: data}) + await Clipboard.write({string: data}) this.notify("Log copied to clipboard") /* TODO: wwould be nice to store log to file, problme is * Preferences pluging failes @@ -785,7 +911,6 @@ export class Terminal7 { onPointerCancel() { this.pointer0 = null this.firstPointer = null - this.lastT = null this.gesture = null if (this.longPressGate) { clearTimeout(this.longPressGate) @@ -827,7 +952,7 @@ export class Terminal7 { this.log(`identified: ${this.gesture}`) } onPointerMove(ev) { - let x = ev.pageX, + const x = ev.pageX, y = ev.pageY /* @@ -836,9 +961,9 @@ export class Terminal7 { */ if (this.gesture) { - let where = this.gesture.where, + const where = this.gesture.where, dest = Math.min(1.0, (where == "top") - ? y / document.querySelector('.windows-container').offsetHeight + ? y / (document.querySelector('.windows-container') as HTMLDivElement).offsetHeight : x / document.body.offsetWidth) this.gesture.pane.layout.moveBorder(this.gesture.pane, where, dest) ev.stopPropagation() @@ -846,7 +971,7 @@ export class Terminal7 { } } async onPointerUp(ev) { - let e = ev.target, + const e = ev.target, gatePad = e.closest(".gate-pad") if (!this.pointer0) @@ -857,7 +982,7 @@ export class Terminal7 { if (!gate) return else { - let deltaT = Date.now() - this.pointer0 + const deltaT = Date.now() - this.pointer0 clearTimeout(this.longPressGate) this.longPressGate = null if (deltaT < this.conf.ui.quickest_press) { @@ -872,9 +997,10 @@ export class Terminal7 { ev.stopPropagation() ev.preventDefault() } else if (this.gesture) { - this.activeG.sendState() + if (this.activeG && this.activeG.fitScreen) + this.activeG.sendState() } else if (this.firstPointer) { - let deltaT = Date.now() - this.pointer0, + const deltaT = Date.now() - this.pointer0, x = ev.pageX, y = ev.pageY, dx = this.firstPointer.pageX - x, @@ -887,7 +1013,7 @@ export class Terminal7 { const minS = (dx > dy)?this.conf.ui.cutMinSpeedY:this.conf.ui.cutMinSpeedX if (s > minS) { // it's a cut!! - let cell = ev.target.closest(".cell"), + const cell = ev.target.closest(".cell"), pane = (cell != null)?cell.cell:undefined if (pane && !pane.zoomed) { if (r < 1.0) @@ -910,7 +1036,7 @@ export class Terminal7 { async showGreetings() { const greeted = (await Preferences.get({key: 'greeted'})).value if (!greeted) { - Preferences.set({key: "greeted", value: "yep"}) + await Preferences.set({key: "greeted", value: "yep"}) if (Capacitor.isNativePlatform()) this.map.tty(WELCOME_NATIVE) else @@ -922,7 +1048,7 @@ export class Terminal7 { || window.navigator.standalone || (Capacitor.getPlatform() != "web") || document.referrer.includes('android-app://'))) - if (navigator.getInstalledRelatedApps) + if (navigator.getInstalledRelatedApps) navigator.getInstalledRelatedApps().then(relatedApps => { if (relatedApps.length > 0) this.map.tty("PWA installed, better use it\n") @@ -957,7 +1083,7 @@ export class Terminal7 { } async deleteFingerprint() { const db = await openDB("t7", 1) - let tx = db.transaction("certificates", "readwrite"), + const tx = db.transaction("certificates", "readwrite"), store = tx.objectStore("certificates") await store.clear() } @@ -981,7 +1107,7 @@ export class Terminal7 { e.innerHTML = marked.parse(changelog) // add prefix to all ids to avoid conflicts e.querySelectorAll("[id]").forEach(e => e.id = "changelog-" + e.id) - document.querySelectorAll("a[href]").forEach(e => { + document.querySelectorAll("a[href]").forEach((e: HTMLAnchorElement) => { e.addEventListener("click", ev => { ev.stopPropagation() ev.preventDefault() @@ -990,7 +1116,7 @@ export class Terminal7 { }) } // if show is undefined the change log view state is toggled - showChangelog(show) { + showChangelog(show = undefined) { const e = document.getElementById("changelog") if (show === undefined) // if show is undefined toggle current state @@ -1004,7 +1130,7 @@ export class Terminal7 { /* * collects the default id and returns a { publicKet, privateKey */ - async readId() { + async readId(): Promise<{publicKey: string, privateKey: string}> { const now = Date.now() if (this.keys && (now - this.lastIdVerify < this.conf.ui.verificationTTL)) return this.keys @@ -1055,14 +1181,17 @@ export class Terminal7 { } saveDotfile(text) { this.cells.forEach(c => { - if (typeof(c.setTheme) == "function") + if (c instanceof Pane) c.setTheme(this.conf.theme) }) terminal7.loadConf(TOML.parse(text)) if (this.pb && - ((this.pb.host != this.conf.net.peerbook) + ((this.pb.host != this.conf.net.peerbook) + // TODO: is bug? + // @ts-ignore || (this.pb.peerName != this.conf.peerbook.peer_name) || (this.pb.insecure != this.conf.peerbook.insecure) + // @ts-ignore || (this.pb.email != this.conf.peerbook.email))) { this.pbClose() this.pb = null @@ -1072,7 +1201,7 @@ export class Terminal7 { } async pbVerify() { const fp = await this.getFingerprint() - const schema = this.insecure?"http":"https" + const schema = this.pb.insecure?"http":"https" let response try { response = await fetch(`${schema}://${this.conf.net.peerbook}/verify`, { diff --git a/src/utils.js b/src/utils.ts similarity index 66% rename from src/utils.js rename to src/utils.ts index e8acd631..a03d0b34 100644 --- a/src/utils.js +++ b/src/utils.ts @@ -3,78 +3,77 @@ * copied from : https://stackoverflow.com/a/14638191/66595 * used as in: `x.innerHTML = formatDate(d, "dddd h:mmtt d MMM yyyy")` */ -export function formatDate(date, format, utc) { - var MMMM = ["\x00", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] - var MMM = ["\x01", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] - var dddd = ["\x02", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] - var ddd = ["\x03", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] - - function ii(i, len) { - var s = i + "" - len = len || 2 +export function formatDate(date, format, utc = false) { + const MMMM = ["\x00", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] + const MMM = ["\x01", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + const dddd = ["\x02", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + const ddd = ["\x03", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] + + function ii(i, len = 2) { + let s = i + "" while (s.length < len) s = "0" + s return s } - var y = utc ? date.getUTCFullYear() : date.getFullYear() + const y = utc ? date.getUTCFullYear() : date.getFullYear() format = format.replace(/(^|[^\\])yyyy+/g, "$1" + y) format = format.replace(/(^|[^\\])yy/g, "$1" + y.toString().substr(2, 2)) format = format.replace(/(^|[^\\])y/g, "$1" + y) - var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1 + const M = (utc ? date.getUTCMonth() : date.getMonth()) + 1 format = format.replace(/(^|[^\\])MMMM+/g, "$1" + MMMM[0]) format = format.replace(/(^|[^\\])MMM/g, "$1" + MMM[0]) format = format.replace(/(^|[^\\])MM/g, "$1" + ii(M)) format = format.replace(/(^|[^\\])M/g, "$1" + M) - var d = utc ? date.getUTCDate() : date.getDate() + const d = utc ? date.getUTCDate() : date.getDate() format = format.replace(/(^|[^\\])dddd+/g, "$1" + dddd[0]) format = format.replace(/(^|[^\\])ddd/g, "$1" + ddd[0]) format = format.replace(/(^|[^\\])dd/g, "$1" + ii(d)) format = format.replace(/(^|[^\\])d/g, "$1" + d) - var H = utc ? date.getUTCHours() : date.getHours() + const H = utc ? date.getUTCHours() : date.getHours() format = format.replace(/(^|[^\\])HH+/g, "$1" + ii(H)) format = format.replace(/(^|[^\\])H/g, "$1" + H) - var h = H > 12 ? H - 12 : H == 0 ? 12 : H + const h = H > 12 ? H - 12 : H == 0 ? 12 : H format = format.replace(/(^|[^\\])hh+/g, "$1" + ii(h)) format = format.replace(/(^|[^\\])h/g, "$1" + h) - var m = utc ? date.getUTCMinutes() : date.getMinutes() + const m = utc ? date.getUTCMinutes() : date.getMinutes() format = format.replace(/(^|[^\\])mm+/g, "$1" + ii(m)) format = format.replace(/(^|[^\\])m/g, "$1" + m) - var s = utc ? date.getUTCSeconds() : date.getSeconds() + const s = utc ? date.getUTCSeconds() : date.getSeconds() format = format.replace(/(^|[^\\])ss+/g, "$1" + ii(s)) format = format.replace(/(^|[^\\])s/g, "$1" + s) - var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds() + let f = utc ? date.getUTCMilliseconds() : date.getMilliseconds() format = format.replace(/(^|[^\\])fff+/g, "$1" + ii(f, 3)) f = Math.round(f / 10) format = format.replace(/(^|[^\\])ff/g, "$1" + ii(f)) f = Math.round(f / 10) format = format.replace(/(^|[^\\])f/g, "$1" + f) - var T = H < 12 ? "AM" : "PM" + const T = H < 12 ? "AM" : "PM" format = format.replace(/(^|[^\\])TT+/g, "$1" + T) format = format.replace(/(^|[^\\])T/g, "$1" + T.charAt(0)) - var t = T.toLowerCase() + const t = T.toLowerCase() format = format.replace(/(^|[^\\])tt+/g, "$1" + t) format = format.replace(/(^|[^\\])t/g, "$1" + t.charAt(0)) - var tz = -date.getTimezoneOffset() - var K = utc || !tz ? "Z" : tz > 0 ? "+" : "-" + let tz = -date.getTimezoneOffset() + let K = utc || !tz ? "Z" : tz > 0 ? "+" : "-" if (!utc) { tz = Math.abs(tz) - var tzHrs = Math.floor(tz / 60) - var tzMin = tz % 60 + const tzHrs = Math.floor(tz / 60) + const tzMin = tz % 60 K += ii(tzHrs) + ":" + ii(tzMin) } format = format.replace(/(^|[^\\])K/g, "$1" + K) - var day = (utc ? date.getUTCDay() : date.getDay()) + 1 + const day = (utc ? date.getUTCDay() : date.getDay()) + 1 format = format.replace(new RegExp(dddd[0], "g"), dddd[day]) format = format.replace(new RegExp(ddd[0], "g"), ddd[day]) diff --git a/src/webrtc_session.ts b/src/webrtc_session.ts index 11310514..50b3703a 100644 --- a/src/webrtc_session.ts +++ b/src/webrtc_session.ts @@ -1,5 +1,7 @@ -import { CapacitorHttp } from '@capacitor/core'; -import { BaseChannel, BaseSession, CallbackType, Channel, ChannelID, Failure } from './session'; +import { CapacitorHttp, HttpHeaders } from '@capacitor/core'; +import { BaseChannel, BaseSession, Channel, ChannelID, Failure } from './session'; +import { IceServers } from "./terminal7" +import { ServerPayload } from "./gate" type ChannelOpenedCB = (channel: Channel, id: ChannelID) => void type RTCStats = { @@ -12,17 +14,13 @@ type RTCStats = { export class WebRTCChannel extends BaseChannel { dataChannel: RTCDataChannel session: WebRTCSession - id: number - createdOn: number - onMessage : CallbackType - constructor(session: BaseSession, + constructor(session: WebRTCSession, id: number, dc: RTCDataChannel) { super() this.id = id this.session = session this.dataChannel = dc - this.createdOn = session.lastMarker } // readyState can be one of the four underlying states: // "connecting", "open", "closing", closed" @@ -72,7 +70,7 @@ export class WebRTCSession extends BaseSession { channels: Map pendingCDCMsgs: Array pendingChannels: Map - msgHandlers: Mapvoid>> + msgHandlers: Mapvoid>> cdc: RTCDataChannel pc: RTCPeerConnection lastMsgId: number @@ -84,12 +82,13 @@ export class WebRTCSession extends BaseSession { this.msgHandlers = new Map() this.lastMsgId = 0 } - onIceCandidate(e: RTCPeerConnectionIceEvent): void - onNegotiationNeeded(ev: Event): void + onIceCandidate(e: RTCPeerConnectionIceEvent): void { throw new Error("Unimplemented method onIceCandidate()") } + onNegotiationNeeded(ev: Event): void { throw new Error("Unimplemented method onNegotiationNeeded()") } public get isSSH() { return false } - async connect(marker=null, noCDC?: boolean): Promise { + + async connect(marker?: number, noCDC?: boolean | string, privateKey?: string): Promise { console.log("in connect", marker, noCDC) if (this.t7.iceServers == null) { @@ -114,7 +113,7 @@ export class WebRTCSession extends BaseSession { this.pc.onconnectionstatechange = () => { const state = this.pc.connectionState console.log("new connection state", state, marker) - if ((state == "connected") && (marker != null)) { + if ((state === "connected") && (marker != null)) { this.sendCTRLMsg({ type: "restore", args: { marker }}, @@ -125,7 +124,7 @@ export class WebRTCSession extends BaseSession { this.onStateChange("failed", Failure.BadMarker) }) } else { - if (state == 'failed') + if (state === 'failed') this.closeChannels() if (this.onStateChange) this.onStateChange(state) @@ -177,8 +176,10 @@ export class WebRTCSession extends BaseSession { // callbacks are set after the resolve as that's // where caller's onMessage & onClose are set dc.onmessage = m => { - const data = new Uint8Array(m.data) - channel.onMessage(data) + if (typeof channel.onMessage == "function") { + const data = new Uint8Array(m.data) + channel.onMessage(data) + } } dc.onclose = m => { this.channels.delete(id) @@ -186,12 +187,12 @@ export class WebRTCSession extends BaseSession { } return channel } - openChannel(cmdorid: string | string[], parent?: ChannelID, sx?: number, sy?: number): + openChannel(cmdorid: number | string | string[], parent?: ChannelID, sx?: number, sy?: number): Promise { return new Promise((resolve, reject) => { let msgID: number if (sx !== undefined) { - if (typeof cmdorid == "string") + if (typeof cmdorid === "string") cmdorid = [cmdorid] msgID = this.sendCTRLMsg({ type: "add_pane", @@ -213,7 +214,7 @@ export class WebRTCSession extends BaseSession { this.pendingChannels[msgID] = (dc: RTCDataChannel, id: ChannelID) => { clearTimeout(watchdog) const channel = this.onDCOpened(dc, id) - resolve(channel, id) + resolve(channel) } }) } @@ -236,7 +237,7 @@ export class WebRTCSession extends BaseSession { // stop listening for messages if (this.cdc) this.cdc.onmessage = undefined - // TODO: improve error handling and add areject + // TODO: improve error handling and add a reject return new Promise((resolve) => { console.log(">>> opening cdc") const cdc = this.pc.createDataChannel('%') @@ -244,7 +245,7 @@ export class WebRTCSession extends BaseSession { cdc.onopen = () => { this.t7.log(">>> cdc opened") if (this.pendingCDCMsgs.length > 0) - // TODO: why the time out? why 100mili? + // TODO: why the time out? why 100milli? this.t7.run(() => { this.t7.log("sending pending messages") this.pendingCDCMsgs.forEach((m) => this.sendCTRLMsg(m[0], m[1], m[2])) @@ -265,7 +266,7 @@ export class WebRTCSession extends BaseSession { this.t7.log("got cdc message:", msg) if (msg.type == "nack") { if (handlers && (typeof handlers[1] == "function")) - handlers[1](msg.args.body) + handlers[1](msg.desc) else console.log("A nack is unhandled", msg) } else { @@ -274,7 +275,8 @@ export class WebRTCSession extends BaseSession { else console.log("an ack is unhandled", msg) } - } + } else if (this.onCMD) + this.onCMD(msg) } }) } @@ -287,7 +289,7 @@ export class WebRTCSession extends BaseSession { msg.time = Date.now() this.msgHandlers.set(msg.message_id, [resolve, reject]) if (!this.cdc || this.cdc.readyState != "open") - // message stays frozen when restrting + // message stays frozen when restarting this.pendingCDCMsgs.push([msg, resolve, reject]) else { const s = msg.payload || JSON.stringify(msg) @@ -302,7 +304,7 @@ export class WebRTCSession extends BaseSession { } return msg.message_id } - getPayload(): Promise{ + getPayload(): Promise{ return new Promise((resolve, reject) => this.sendCTRLMsg({ type: "get_payload", @@ -310,7 +312,7 @@ export class WebRTCSession extends BaseSession { }, resolve, reject) ) } - setPayload(payload: string): Promise{ + setPayload(payload: string | ServerPayload): Promise{ return new Promise((resolve, reject) => this.sendCTRLMsg({ type: "set_payload", @@ -351,7 +353,7 @@ export class WebRTCSession extends BaseSession { this.pc = null } } - getIceServers() { + getIceServers(): Promise { return new Promise((resolve, reject) => { const ctrl = new AbortController(), tId = setTimeout(() => ctrl.abort(), 1000), @@ -436,13 +438,13 @@ export class PeerbookSession extends WebRTCSession { } this.pc.setRemoteDescription(sd) .catch (e => { - this.t7.log(`Failed to set remote description: ${e}`) + terminal7.log(`Failed to set remote description: ${e}`) this.onStateChange("failed", Failure.BadRemoteDescription) }) } peerCandidate(candidate) { this.pc.addIceCandidate(candidate).catch(e => - this.t7.log(`ICE candidate error: ${e}`)) + terminal7.log(`ICE candidate error: ${e}`)) return } } @@ -451,8 +453,7 @@ export class PeerbookSession extends WebRTCSession { // SSHSession is an implmentation of a real time session over ssh export class HTTPWebRTCSession extends WebRTCSession { address: string - fetchTimeout: number - headers: CapacitorHttp.HttpHeaders + headers: HttpHeaders constructor(address: string, headers?: Map) { super() this.address = address @@ -466,7 +467,6 @@ export class HTTPWebRTCSession extends WebRTCSession { this.t7.log("over HTTP on negotiation needed", e) this.pc.createOffer().then(offer => { this.pc.setLocalDescription(offer) - }) } onIceCandidate(ev: RTCPeerConnectionIceEvent) { diff --git a/src/window.js b/src/window.ts similarity index 60% rename from src/window.js rename to src/window.ts index 56287271..65bf53f2 100644 --- a/src/window.js +++ b/src/window.ts @@ -5,13 +5,31 @@ * Copyright: (c) 2020 Benny A. Daon - benny@tuzig.com * License: GPLv3 */ -import { Layout } from './layout.js' -import { Pane } from './pane.js' +import { Layout, SerializedLayout } from './layout' +import { Pane } from './pane' import * as Hammer from 'hammerjs' +import { Gate } from "./gate" +import { Terminal7 } from "./terminal7" const ABIT = 10 +export interface SerializedWindow { + name: string + id: number + layout: SerializedLayout + active?: boolean +} + export class Window { + gate: Gate + id: number + name: string + rootLayout: Layout + e?: HTMLElement + activeP?: Pane + t7: Terminal7 + nameE: HTMLAnchorElement + active: boolean constructor(props) { this.gate = props.gate this.id = props.id @@ -19,7 +37,7 @@ export class Window { this.rootLayout = null this.e = null this.activeP = null - this.t7 = window.terminal7 + this.t7 = terminal7 } /* * Window.open opens creates the window's element and the first layout and @@ -32,13 +50,12 @@ export class Window { e.appendChild(this.e) // Add the name with link to tab bar - let a = document.createElement('a') + const a = document.createElement('a') as HTMLAnchorElement & {w: Window} a.id = this.e.id+'-name' a.w = this a.innerHTML = this.name // Add gestures on the window name for rename and drag to trash - let h = new Hammer.Manager(a, {}) - h.options.domEvents=true; // enable dom events + const h = new Hammer.Manager(a, {domEvents:true}) // enable dom events h.add(new Hammer.Press({event: "rename", pointers: 1})) h.add(new Hammer.Tap({event: "switch", pointers: 1})) h.on("rename", () => this.rename()) @@ -52,7 +69,7 @@ export class Window { */ focus() { // turn off the current active - let a = this.gate.activeW + const a = this.gate.activeW if (a) { a.nameE.classList.remove("on") a.e.classList.add("hidden") @@ -69,7 +86,7 @@ export class Window { this.activeP.focus() } addLayout(dir, basedOn) { - let l = new Layout(dir, basedOn) + const l = new Layout(dir, basedOn) l.id = this.t7.cells.length this.t7.cells.push(l) if (this.rootLayout == null) @@ -80,7 +97,7 @@ export class Window { * restoreLayout restores a layout, creating the panes and layouts as needed */ restoreLayout(layout, activeWindow) { - var l = this.addLayout(layout.dir, { + const l = this.addLayout(layout.dir, { w: this, gate: this.gate, sx: layout.sx || null, @@ -106,7 +123,7 @@ export class Window { return l } dump() { - let r = this.rootLayout.dump() + const r = this.rootLayout.dump() if (this.active) r.active = true return r @@ -118,7 +135,7 @@ export class Window { rename() { const e = this.nameE, se = this.gate.e.querySelector(".rename-box"), - textbox = this.gate.e.querySelector("#name-input") + textbox = this.gate.e.querySelector("#name-input") as HTMLInputElement se.classList.remove("hidden") textbox.value = e.innerHTML @@ -162,11 +179,11 @@ export class Window { this.rootLayout.fit() } moveFocus(where) { - var a = this.activeP, + const a = this.activeP, b = a.t.buffer.active, x = a.xoff + b.cursorX * a.sx / a.t.cols, - y = a.yoff + b.cursorY * a.sy / a.t.rows, - match = null, + y = a.yoff + b.cursorY * a.sy / a.t.rows + let match = null, nextPane = null switch(where) { case "left": @@ -198,8 +215,8 @@ export class Window { } } updateDivideButtons() { - let bV = document.getElementById("divide-v") - let bH = document.getElementById("divide-h") + const bV = document.getElementById("divide-v") + const bH = document.getElementById("divide-h") if (this.activeP.isSplittable("topbottom")) bV.classList.remove("off") else @@ -209,4 +226,71 @@ export class Window { else bH.classList.add("off") } + syncLayout(thatLayout, theseCells = null) { + let zoomed + if (!theseCells) + theseCells = this.rootLayout.allCells() + thatLayout.w = this + thatLayout.gate = this.gate + const newLayout = new Layout(thatLayout.dir, thatLayout) + thatLayout.cells.forEach(thatCell => { + let thisCell = null + if (thatCell.dir) { + console.log("syncing layout", thatCell) + thatCell.w = this + thatCell.gate = this.gate + thisCell = new Layout(thatCell.dir, thatCell) + thisCell = this.syncLayout(thatCell, theseCells) + thisCell.layout = newLayout + newLayout.cells.push(thisCell) + } else { + const thisI = theseCells.findIndex(c => c.channelID == thatCell.channelID) + if (thisI >= 0) { + console.log("found pane in ", thisI) + // found it, sync it + thisCell = theseCells.splice(thisI, 1)[0] + thisCell.layout = newLayout + newLayout.cells?.push(thisCell) + } else { + console.log("didn't find pane ", thatCell.channelID) + thisCell = newLayout.addPane(thatCell) + } + } + thisCell.sx = thatCell.sx + thisCell.sy = thatCell.sy + thisCell.xoff = thatCell.xoff + thisCell.yoff = thatCell.yoff + thisCell.fontSize = thatCell.fontSize + if (thisCell.t) { + // NOTE: the step of changing the font size is 0.5, there is no visual change when doing smaller steps + const fontSize = thatCell.fontSize * this.gate.fontScale + thisCell.t.options.fontSize = Math.floor(fontSize) + (String(fontSize).includes('.') ? .5 : 0) + + const availableHeight = thisCell.t.element.parentElement.clientHeight + const availableWidth = thisCell.t.element.parentElement.clientWidth + + const adjustFontSize = (availableWidth: number, availableHeight: number) => { + const charDims: { width: number, height: number } = thisCell.t._core._renderService.dimensions.css.cell + if (charDims.width * thatCell.cols > availableWidth || charDims.height * thatCell.rows > availableHeight) { + thisCell.t.options.fontSize -= .5 + adjustFontSize(availableWidth, availableHeight) + } + } + adjustFontSize(availableWidth, availableHeight) + thisCell.t.resize(thatCell.cols, thatCell.rows) + } + if (thatCell.active) + thisCell.focus() + if (thatCell.zoomed) // && this.activeP == thisCell) + zoomed = thisCell + + }) + if (zoomed) { + setTimeout(() => zoomed.zoom(), 100) + console.log("will zoom in 100ms") + } + newLayout.refreshDividers() + return newLayout + + } } diff --git a/tests/cert.test.js b/tests/cert.test.ts similarity index 95% rename from tests/cert.test.js rename to tests/cert.test.ts index a1ee5acf..345a61fd 100644 --- a/tests/cert.test.js +++ b/tests/cert.test.ts @@ -3,7 +3,7 @@ * Copyright: (c) 2020 Benny A. Daon - benny@tuzig.com * License: GPLv3 */ -import { Terminal7Mock } from './infra.ts' +import { Terminal7Mock } from './infra' import { assert } from "chai" import { deleteDB } from 'idb' diff --git a/tests/commands.test.ts b/tests/commands.test.ts index 69128728..307abee5 100644 --- a/tests/commands.test.ts +++ b/tests/commands.test.ts @@ -13,6 +13,7 @@ import { T7Map } from '../src/map' let out = "" vi.mock('xterm') +vi.mock('@revenuecat/purchases-capacitor') vi.mock('../src/ssh_session.ts') vi.mock('../src/webrtc_session.ts') @@ -24,7 +25,7 @@ describe("TWR commands", function() { beforeEach(async () => { t = new Terminal7Mock() e = document.getElementById("t7") - window.terminal7=t + terminal7=t t.open(e) }) afterEach(() => t && t.clearTimeouts()) diff --git a/tests/form.test.ts b/tests/form.test.ts index c1965732..986faef7 100644 --- a/tests/form.test.ts +++ b/tests/form.test.ts @@ -12,6 +12,7 @@ import { T7Map } from '../src/map' import { IDisposable } from 'xterm' vi.mock('xterm') +vi.mock('@revenuecat/purchases-capacitor') describe("form", () => { let word diff --git a/tests/gate.test.js b/tests/gate.test.ts similarity index 95% rename from tests/gate.test.js rename to tests/gate.test.ts index 1d6d664b..9f3eadb8 100644 --- a/tests/gate.test.js +++ b/tests/gate.test.ts @@ -6,16 +6,13 @@ * License: GPLv3 */ import { vi, describe, beforeAll, afterEach, it, expect } from 'vitest' -import { Layout } from '../src/layout.js' -import { Cell } from '../src/cell.js' import { T7Map } from '../src/map' -import { Terminal7Mock, sleep } from './infra.ts' +import { Terminal7Mock, sleep } from './infra' import { Preferences } from '@capacitor/preferences' -import { Gate } from '../src/gate.ts' import { HTTPWebRTCSession } from '../src/webrtc_session' -import { Terminal } from 'xterm' vi.mock('xterm') +vi.mock('@revenuecat/purchases-capacitor') vi.mock('../src/webrtc_session.ts') vi.mock('../src/ssh_session.ts') @@ -25,7 +22,7 @@ describe("gate", () => { await Preferences.clear() t = new Terminal7Mock() e = document.getElementById("t7") - window.terminal7 = t + terminal7 = t t.open(e) }) afterEach(() => { diff --git a/tests/infra.ts b/tests/infra.ts index ba4a6594..7470cc82 100644 --- a/tests/infra.ts +++ b/tests/infra.ts @@ -1,11 +1,12 @@ import { RTSession, RTChannel } from "../src//rtsession.ts" -import { Terminal7 } from "../src/terminal7.js" +import { Terminal7 } from "../src/terminal7" import { Gate } from "../src/gate" import { T7Map } from "../src/map" import { vi } from "vitest"; import { Terminal } from "xterm" vi.mock("xterm"); +vi.mock('@revenuecat/purchases-capacitor') export class resizeObs { constructor(cb) { diff --git a/tests/pane.test.js b/tests/pane.test.ts similarity index 93% rename from tests/pane.test.js rename to tests/pane.test.ts index 4f837e97..8d4dc5ce 100644 --- a/tests/pane.test.js +++ b/tests/pane.test.ts @@ -5,15 +5,13 @@ * Copyright: (c) 2020 Benny A. Daon - benny@tuzig.com * License: GPLv3 */ -import { Layout } from '../src/layout.js' -import { Cell } from '../src/cell.js' import { Gate } from '../src/gate' -import { Terminal7Mock } from './infra.ts' -import { assert } from "chai" +import { Terminal7Mock } from './infra' import { Preferences } from '@capacitor/preferences' import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest' vi.mock('xterm') +vi.mock('@revenuecat/purchases-capacitor') describe("pane", () => { var t, e, h, w, p0 @@ -25,7 +23,7 @@ describe("pane", () => { } t = new Terminal7Mock() e = document.getElementById("t7") - window.terminal7=t + terminal7=t t.open(e) h = t.addGate() h.open(e) @@ -36,7 +34,7 @@ describe("pane", () => { w.activeP.yoff = 0.2 p0 = w.activeP await p0.fit() - Preferences.set({ key: 'first_copymode', value: 1 }) + Preferences.set({ key: 'first_copymode', value: "1" }) }) afterEach(() => t && t.clearTimeouts()) it("can forward jump words in copy mode", () => { diff --git a/tests/peerbook.test.js b/tests/peerbook.test.ts similarity index 98% rename from tests/peerbook.test.js rename to tests/peerbook.test.ts index ff72a770..c6256aad 100644 --- a/tests/peerbook.test.js +++ b/tests/peerbook.test.ts @@ -11,6 +11,7 @@ import { PeerbookConnection } from '../src/peerbook.ts' import { sleep } from './infra' vi.mock('xterm') +vi.mock('@revenuecat/purchases-capacitor') vi.mock('../src/ssh_session.ts') vi.mock('../src/webrtc_session.ts') diff --git a/tests/termina7.test.js b/tests/termina7.test.ts similarity index 72% rename from tests/termina7.test.js rename to tests/termina7.test.ts index c38434d7..0301be32 100644 --- a/tests/termina7.test.js +++ b/tests/termina7.test.ts @@ -6,8 +6,8 @@ * License: GPLv3 */ import * as TOML from '@tuzig/toml' -import { Layout } from '../src/layout.js' -import { Cell } from '../src/cell.js' +import { Layout } from '../src/layout' +import { Cell } from '../src/cell' import { Gate } from '../src/gate' import { T7Map } from '../src/map' import { Terminal7Mock, sleep } from './infra' @@ -17,6 +17,7 @@ import { expect, vi } from 'vitest' import { HTTPWebRTCSession } from '../src/webrtc_session' vi.mock('xterm') +vi.mock('@revenuecat/purchases-capacitor') vi.mock('../src/ssh_session.ts') vi.mock('../src/webrtc_session.ts') @@ -30,7 +31,7 @@ describe("terminal7", function() { console.log("before each") t = new Terminal7Mock() e = document.getElementById("t7") - window.terminal7=t + terminal7=t t.open(e) }) afterEach(() => t && t.clearTimeouts()) @@ -300,7 +301,7 @@ describe("terminal7", function() { expect(d.cells[0].yoff).to.equal(0.2) expect(d.cells[1].yoff).to.equal(0.5) }) - it("can be restored from a -| layout", () => { + it("can be restored and dumped from a -| layout", () => { h = t.addGate() h.open(e) w = h.addWindow("restored") @@ -312,7 +313,7 @@ describe("terminal7", function() { sy: 0.3, xoff: 0.1, yoff: 0.2, - pane_id: 12 + channelID: 12 }, { dir: "rightleft", cells: [ @@ -321,13 +322,13 @@ describe("terminal7", function() { sy: 0.3, xoff: 0.1, yoff: 0.5, - pane_id: -1 + channelID: -1 }, { sx: 0.4, sy: 0.3, xoff: 0.5, yoff: 0.5, - pane_id: -1 + channelID: -1 } ] } @@ -336,7 +337,191 @@ describe("terminal7", function() { expect(w.rootLayout.cells.length).to.equal(2) expect(w.rootLayout.cells[1].dir).to.equal("rightleft") expect(w.rootLayout.cells[1].cells.length).to.equal(2) + const dump = w.rootLayout.dump() + expect(dump.dir).to.equal("topbottom") + expect(dump.cells.length).to.equal(2) + expect(dump.cells[1].dir).to.equal("rightleft") + expect(dump.cells[1].cells.length).to.equal(2) }) + it("can be synced from a single pane to - layout", () => { + h = t.addGate() + h.open(e) + w = h.addWindow("restored") + w.restoreLayout({ + "dir": "topbottom", + "cells": [ + { + sx: 0.8, + sy: 0.3, + xoff: 0.1, + yoff: 0.2, + channelID: 12 + }]}, false) + const newLayout = { + dir: "rightleft", + cells: [ + { + sx: 0.4, + sy: 0.3, + xoff: 0.1, + yoff: 0.5, + channelID: 1, + zoomed: false + }, { + sx: 0.4, + sy: 0.3, + xoff: 0.5, + yoff: 0.5, + channelID: 2, + zoomed: false + } + ] + } + const newL = w.syncLayout(newLayout) + expect(newL.dir).to.equal("rightleft") + expect(newL.cells.length).to.equal(2) + expect(newL.cells[0].sx).to.equal(0.4) + expect(newL.cells[0].channelID).to.equal(1) + expect(newL.cells[1].channelID).to.equal(2) + }) + it("can be sync from a double pane to single one", () => { + h = t.addGate() + h.open(e) + w = h.addWindow("restored") + w.restoreLayout({ + "dir": "rightleft", + "cells": [ + { + sx: 0.4, + sy: 0.3, + xoff: 0.1, + yoff: 0.5, + channelID: 1, + zoomed: false + }, { + sx: 0.4, + sy: 0.3, + xoff: 0.5, + yoff: 0.5, + channelID: 2, + zoomed: false + } + ]}, false) + const newLayout = { + "dir": "rightleft", + "cells": [ + { + sx: 0.8, + sy: 0.3, + xoff: 0.1, + yoff: 0.5, + channelID: 1, + zoomed: false + } + ]} + const newL = w.syncLayout(newLayout) + expect(newL.dir).to.equal("rightleft") + expect(newL.cells.length).to.equal(1) + expect(newL.cells[0].sx).to.equal(0.8) + expect(newL.cells[0].channelID).to.equal(1) + }) + it("can be sync from a double pane to triple one", () => { + h = t.addGate() + h.open(e) + w = h.addWindow("restored") + w.restoreLayout({ + "dir": "topbottom", + "cells": [ + { + sx: 0.8, + sy: 0.3, + xoff: 0.1, + yoff: 0.1, + channelID: 1, + zoomed: false + }, { + sx: 0.8, + sy: 0.3, + xoff: 0.1, + yoff: 0.4, + channelID: 2, + zoomed: false + } + ]}, false) + const newLayout = { + "dir": "topbottom", + "cells": [ + { + sx: 0.8, + sy: 0.3, + xoff: 0.1, + yoff: 0.1, + channelID: 1 + }, { + dir: "rightleft", + cells: [ + { + sx: 0.4, + sy: 0.3, + xoff: 0.1, + yoff: 0.5, + channelID: 2 + }, { + sx: 0.4, + sy: 0.3, + xoff: 0.5, + yoff: 0.5, + channelID: 3 + } + ] + } + ]} + const newL = w.syncLayout(newLayout) + expect(newL.dir).to.equal("topbottom") + expect(newL.cells.length).to.equal(2) + expect(newL.cells[0].sx).to.equal(0.8) + expect(newL.cells[0].channelID).to.equal(1) + const l2 = newL.cells[1] + expect(l2.dir).to.equal("rightleft") + expect(l2.cells.length).to.equal(2) + expect(l2.cells[0].sx).to.equal(0.4) + expect(l2.cells[0].channelID).to.equal(2) + expect(l2.cells[1].channelID).to.equal(3) + }) + it("can be sync from a double pane to the same layout", () => { + h = t.addGate() + h.open(e) + w = h.addWindow("restored") + w.restoreLayout({ + "dir": "rightleft", + "cells": [ + { + sx: 0.4, + sy: 0.3, + xoff: 0.1, + yoff: 0.5, + channelID: 1, + zoomed: false + }, { + sx: 0.4, + sy: 0.3, + xoff: 0.5, + yoff: 0.5, + channelID: 2, + zoomed: false + } + ]}, false) + const dump = w.rootLayout.dump() + console.log("dump", dump) + const newL = w.syncLayout(dump) + expect(newL.dir).to.equal("rightleft") + expect(newL.cells.length).to.equal(2) + expect(newL.cells[0].sx).to.equal(0.4) + expect(newL.cells[0].sy).to.equal(0.3) + expect(newL.cells[0].channelID).to.equal(1) + }) + + it("can move a border between panes", function () { h = t.addGate() @@ -447,13 +632,13 @@ describe("terminal7", function() { sy: 0.6, xoff: 0.1, yoff: 0.2, - pane_id: 0 + channelID: 0 }, { sx: 0.8, sy: 0.6, xoff: 0.1, yoff: 0.2, - pane_id: 1, + channelID: 1, zoomed: true, active: true } diff --git a/vite.config.js b/vite.config.js index 66356f5b..9bbb9500 100644 --- a/vite.config.js +++ b/vite.config.js @@ -16,7 +16,7 @@ export default defineConfig({ plugins: [ VitePWA({ strategies: 'injectManifest', - includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png'], + includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png', 'virtual-webgl2.js'], manifest: { name: 'Terminal7', short_name: 'The web-age terminal', diff --git a/vite.dev.config.js b/vite.dev.config.js index 11ab38bb..f0cea1ae 100644 --- a/vite.dev.config.js +++ b/vite.dev.config.js @@ -16,7 +16,7 @@ export default defineConfig({ plugins: [ VitePWA({ strategies: 'injectManifest', - includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png'], + includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png', 'virtual-webgl2.js'], manifest: { name: 'Terminal7', short_name: 'The web-age terminal', diff --git a/yarn.lock b/yarn.lock index 4798eb23..5cbb7d52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,53 +24,56 @@ jsonpointer "^5.0.0" leven "^3.1.0" -"@babel/code-frame@^7.0.0": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3" - integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== dependencies: - "@babel/highlight" "^7.22.10" + "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" - integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== - dependencies: - "@babel/highlight" "^7.22.5" - -"@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== "@babel/core@^7.11.1": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" - integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.15.tgz#15d4fd03f478a459015a4b94cfbb3bd42c48d2f4" + integrity sha512-PtZqMmgRrvj8ruoEOIwVA3yoF91O+Hgw9o7DAUTNBA6Mo2jpu31clx9a7Nz/9JznqetTR6zwfC4L3LAjKQXUwA== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.9" - "@babel/helper-module-transforms" "^7.22.9" - "@babel/helpers" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.8" - "@babel/types" "^7.22.5" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.22.15" + "@babel/helpers" "^7.22.15" + "@babel/parser" "^7.22.15" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.15" + "@babel/types" "^7.22.15" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.2" + json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.22.7", "@babel/generator@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" - integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== +"@babel/generator@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" + integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.22.15" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/generator@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" + integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== + dependencies: + "@babel/types" "^7.23.0" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" @@ -83,32 +86,32 @@ "@babel/types" "^7.22.5" "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz#a3f4758efdd0190d8927fcffd261755937c71878" - integrity sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.22.15" -"@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" - integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== +"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== dependencies: "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" browserslist "^4.21.9" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.22.5": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz#c36ea240bb3348f942f08b0fbe28d6d979fab236" - integrity sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ== +"@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4" + integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-replace-supers" "^7.22.9" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" @@ -116,9 +119,9 @@ semver "^6.3.1" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6" - integrity sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" regexpu-core "^5.3.1" @@ -135,6 +138,11 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + "@babel/helper-environment-visitor@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" @@ -148,6 +156,14 @@ "@babel/template" "^7.22.5" "@babel/types" "^7.22.5" +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" @@ -155,30 +171,30 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-member-expression-to-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" - integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== +"@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz#b95a144896f6d491ca7863576f820f3628818621" + integrity sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.22.15" -"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" - integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== +"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.22.15" -"@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" - integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== +"@babel/helper-module-transforms@^7.22.15", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.15.tgz#40ad2f6950f143900e9c1c72363c0b431a606082" + integrity sha512-l1UiX4UyHSFsYt17iQ3Se5pQQZZHa22zyIXURmvkmLCD4t/aU+dvNWHatKac/D9Vm9UES7nvIqHs4jZqKviUmQ== dependencies: "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" "@babel/helper-simple-access" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.15" "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" @@ -192,7 +208,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== -"@babel/helper-remap-async-to-generator@^7.22.5": +"@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz#53a25b7484e722d7efb9c350c75c032d4628de82" integrity sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ== @@ -236,86 +252,79 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" - integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== +"@babel/helper-validator-identifier@^7.22.15", "@babel/helper-validator-identifier@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044" + integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ== -"@babel/helper-validator-option@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" - integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== "@babel/helper-wrap-function@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz#189937248c45b0182c1dcf32f3444ca153944cb9" - integrity sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q== + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz#d845e043880ed0b8c18bd194a12005cb16d2f614" + integrity sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ== dependencies: "@babel/helper-function-name" "^7.22.5" "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/types" "^7.22.10" -"@babel/helpers@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd" - integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA== +"@babel/helpers@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1" + integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw== dependencies: - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.6" - "@babel/types" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.15" + "@babel/types" "^7.22.15" -"@babel/highlight@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7" - integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ== +"@babel/highlight@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" + integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== dependencies: "@babel/helper-validator-identifier" "^7.22.5" chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/highlight@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" - integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== - dependencies: - "@babel/helper-validator-identifier" "^7.22.5" - chalk "^2.0.0" - js-tokens "^4.0.0" +"@babel/parser@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.15.tgz#d34592bfe288a32e741aa0663dbc4829fcd55160" + integrity sha512-RWmQ/sklUN9BvGGpCDgSubhHWfAx24XDTDObup4ffvxaYsptOg2P3KG0j+1eWKLxpkX0j0uHxmpq2Z1SP/VhxA== -"@babel/parser@^7.22.5", "@babel/parser@^7.22.7": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" - integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== +"@babel/parser@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" + integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" - integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962" + integrity sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca" - integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz#2aeb91d337d4e1a1e7ce85b76a37f5301781200f" + integrity sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.22.15" "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== -"@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" - integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" @@ -450,14 +459,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-async-generator-functions@^7.22.7": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz#053e76c0a903b72b573cb1ab7d6882174d460a1b" - integrity sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg== +"@babel/plugin-transform-async-generator-functions@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz#3b153af4a6b779f340d5b80d3f634f55820aefa3" + integrity sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-remap-async-to-generator" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.9" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-transform-async-to-generator@^7.22.5": @@ -476,10 +485,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-block-scoping@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz#8bfc793b3a4b2742c0983fadc1480d843ecea31b" - integrity sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg== +"@babel/plugin-transform-block-scoping@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz#494eb82b87b5f8b1d8f6f28ea74078ec0a10a841" + integrity sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -491,27 +500,27 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-class-static-block@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba" - integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA== +"@babel/plugin-transform-class-static-block@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz#dc8cc6e498f55692ac6b4b89e56d87cec766c974" + integrity sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g== dependencies: - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.11" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363" - integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ== +"@babel/plugin-transform-classes@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz#aaf4753aee262a232bbc95451b4bdf9599c65a0b" + integrity sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-compilation-targets" "^7.22.15" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" @@ -523,14 +532,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/template" "^7.22.5" -"@babel/plugin-transform-destructuring@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz#d3aca7438f6c26c78cdd0b0ba920a336001b27cc" - integrity sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ== +"@babel/plugin-transform-destructuring@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz#e7404ea5bb3387073b9754be654eecb578324694" + integrity sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-dotall-regex@^7.22.5", "@babel/plugin-transform-dotall-regex@^7.4.4": +"@babel/plugin-transform-dotall-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== @@ -545,10 +554,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-dynamic-import@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e" - integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ== +"@babel/plugin-transform-dynamic-import@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz#2c7722d2a5c01839eaf31518c6ff96d408e447aa" + integrity sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" @@ -561,18 +570,18 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-export-namespace-from@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b" - integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg== +"@babel/plugin-transform-export-namespace-from@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz#b3c84c8f19880b6c7440108f8929caf6056db26c" + integrity sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-transform-for-of@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f" - integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A== +"@babel/plugin-transform-for-of@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz#f64b4ccc3a4f131a996388fae7680b472b306b29" + integrity sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -585,10 +594,10 @@ "@babel/helper-function-name" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-json-strings@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0" - integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A== +"@babel/plugin-transform-json-strings@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz#689a34e1eed1928a40954e37f74509f48af67835" + integrity sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" @@ -600,10 +609,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-logical-assignment-operators@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c" - integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA== +"@babel/plugin-transform-logical-assignment-operators@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz#24c522a61688bde045b7d9bc3c2597a4d948fc9c" + integrity sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" @@ -623,22 +632,22 @@ "@babel/helper-module-transforms" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-modules-commonjs@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa" - integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA== +"@babel/plugin-transform-modules-commonjs@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz#b11810117ed4ee7691b29bd29fd9f3f98276034f" + integrity sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg== dependencies: - "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" -"@babel/plugin-transform-modules-systemjs@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496" - integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ== +"@babel/plugin-transform-modules-systemjs@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz#3386be5875d316493b517207e8f1931d93154bb1" + integrity sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA== dependencies: "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.9" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.5" @@ -665,32 +674,32 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-nullish-coalescing-operator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381" - integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA== +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz#debef6c8ba795f5ac67cd861a81b744c5d38d9fc" + integrity sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-numeric-separator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58" - integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g== +"@babel/plugin-transform-numeric-separator@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz#498d77dc45a6c6db74bb829c02a01c1d719cbfbd" + integrity sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-object-rest-spread@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1" - integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ== +"@babel/plugin-transform-object-rest-spread@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz#21a95db166be59b91cde48775310c0df6e1da56f" + integrity sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q== dependencies: - "@babel/compat-data" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.5" + "@babel/compat-data" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.22.5" + "@babel/plugin-transform-parameters" "^7.22.15" "@babel/plugin-transform-object-super@^7.22.5": version "7.22.5" @@ -700,27 +709,27 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.5" -"@babel/plugin-transform-optional-catch-binding@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333" - integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg== +"@babel/plugin-transform-optional-catch-binding@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz#461cc4f578a127bb055527b3e77404cad38c08e0" + integrity sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.22.5", "@babel/plugin-transform-optional-chaining@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz#4bacfe37001fe1901117672875e931d439811564" - integrity sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg== +"@babel/plugin-transform-optional-chaining@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz#d7a5996c2f7ca4ad2ad16dbb74444e5c4385b1ba" + integrity sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-parameters@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18" - integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg== +"@babel/plugin-transform-parameters@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz#719ca82a01d177af358df64a514d64c2e3edb114" + integrity sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -732,13 +741,13 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-private-property-in-object@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32" - integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ== +"@babel/plugin-transform-private-property-in-object@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz#ad45c4fc440e9cb84c718ed0906d96cf40f9a4e1" + integrity sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.11" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" @@ -749,13 +758,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-regenerator@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz#cd8a68b228a5f75fa01420e8cc2fc400f0fc32aa" - integrity sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw== +"@babel/plugin-transform-regenerator@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz#8ceef3bd7375c4db7652878b0241b2be5d0c3cca" + integrity sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" - regenerator-transform "^0.15.1" + regenerator-transform "^0.15.2" "@babel/plugin-transform-reserved-words@^7.22.5": version "7.22.5" @@ -800,10 +809,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-escapes@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz#ce0c248522b1cb22c7c992d88301a5ead70e806c" - integrity sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg== +"@babel/plugin-transform-unicode-escapes@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz#c723f380f40a2b2f57a62df24c9005834c8616d9" + integrity sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" @@ -832,16 +841,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@^7.11.0": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.9.tgz#57f17108eb5dfd4c5c25a44c1977eba1df310ac7" - integrity sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g== + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.15.tgz#142716f8e00bc030dae5b2ac6a46fbd8b3e18ff8" + integrity sha512-tZFHr54GBkHk6hQuVA8w4Fmq+MSPsfvMG0vPnOYyTnJpyfMqybL8/MbNCPRT9zc2KBO2pe4tq15g6Uno4Jpoag== dependencies: "@babel/compat-data" "^7.22.9" - "@babel/helper-compilation-targets" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.15" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.15" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" @@ -862,69 +871,67 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.22.5" - "@babel/plugin-transform-async-generator-functions" "^7.22.7" + "@babel/plugin-transform-async-generator-functions" "^7.22.15" "@babel/plugin-transform-async-to-generator" "^7.22.5" "@babel/plugin-transform-block-scoped-functions" "^7.22.5" - "@babel/plugin-transform-block-scoping" "^7.22.5" + "@babel/plugin-transform-block-scoping" "^7.22.15" "@babel/plugin-transform-class-properties" "^7.22.5" - "@babel/plugin-transform-class-static-block" "^7.22.5" - "@babel/plugin-transform-classes" "^7.22.6" + "@babel/plugin-transform-class-static-block" "^7.22.11" + "@babel/plugin-transform-classes" "^7.22.15" "@babel/plugin-transform-computed-properties" "^7.22.5" - "@babel/plugin-transform-destructuring" "^7.22.5" + "@babel/plugin-transform-destructuring" "^7.22.15" "@babel/plugin-transform-dotall-regex" "^7.22.5" "@babel/plugin-transform-duplicate-keys" "^7.22.5" - "@babel/plugin-transform-dynamic-import" "^7.22.5" + "@babel/plugin-transform-dynamic-import" "^7.22.11" "@babel/plugin-transform-exponentiation-operator" "^7.22.5" - "@babel/plugin-transform-export-namespace-from" "^7.22.5" - "@babel/plugin-transform-for-of" "^7.22.5" + "@babel/plugin-transform-export-namespace-from" "^7.22.11" + "@babel/plugin-transform-for-of" "^7.22.15" "@babel/plugin-transform-function-name" "^7.22.5" - "@babel/plugin-transform-json-strings" "^7.22.5" + "@babel/plugin-transform-json-strings" "^7.22.11" "@babel/plugin-transform-literals" "^7.22.5" - "@babel/plugin-transform-logical-assignment-operators" "^7.22.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.22.11" "@babel/plugin-transform-member-expression-literals" "^7.22.5" "@babel/plugin-transform-modules-amd" "^7.22.5" - "@babel/plugin-transform-modules-commonjs" "^7.22.5" - "@babel/plugin-transform-modules-systemjs" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.15" + "@babel/plugin-transform-modules-systemjs" "^7.22.11" "@babel/plugin-transform-modules-umd" "^7.22.5" "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" "@babel/plugin-transform-new-target" "^7.22.5" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5" - "@babel/plugin-transform-numeric-separator" "^7.22.5" - "@babel/plugin-transform-object-rest-spread" "^7.22.5" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" + "@babel/plugin-transform-numeric-separator" "^7.22.11" + "@babel/plugin-transform-object-rest-spread" "^7.22.15" "@babel/plugin-transform-object-super" "^7.22.5" - "@babel/plugin-transform-optional-catch-binding" "^7.22.5" - "@babel/plugin-transform-optional-chaining" "^7.22.6" - "@babel/plugin-transform-parameters" "^7.22.5" + "@babel/plugin-transform-optional-catch-binding" "^7.22.11" + "@babel/plugin-transform-optional-chaining" "^7.22.15" + "@babel/plugin-transform-parameters" "^7.22.15" "@babel/plugin-transform-private-methods" "^7.22.5" - "@babel/plugin-transform-private-property-in-object" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.11" "@babel/plugin-transform-property-literals" "^7.22.5" - "@babel/plugin-transform-regenerator" "^7.22.5" + "@babel/plugin-transform-regenerator" "^7.22.10" "@babel/plugin-transform-reserved-words" "^7.22.5" "@babel/plugin-transform-shorthand-properties" "^7.22.5" "@babel/plugin-transform-spread" "^7.22.5" "@babel/plugin-transform-sticky-regex" "^7.22.5" "@babel/plugin-transform-template-literals" "^7.22.5" "@babel/plugin-transform-typeof-symbol" "^7.22.5" - "@babel/plugin-transform-unicode-escapes" "^7.22.5" + "@babel/plugin-transform-unicode-escapes" "^7.22.10" "@babel/plugin-transform-unicode-property-regex" "^7.22.5" "@babel/plugin-transform-unicode-regex" "^7.22.5" "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.22.5" - babel-plugin-polyfill-corejs2 "^0.4.4" - babel-plugin-polyfill-corejs3 "^0.8.2" - babel-plugin-polyfill-regenerator "^0.5.1" + "@babel/preset-modules" "0.1.6-no-external-plugins" + "@babel/types" "^7.22.15" + babel-plugin-polyfill-corejs2 "^0.4.5" + babel-plugin-polyfill-corejs3 "^0.8.3" + babel-plugin-polyfill-regenerator "^0.5.2" core-js-compat "^3.31.0" semver "^6.3.1" -"@babel/preset-modules@^0.1.5": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6.tgz#31bcdd8f19538437339d17af00d177d854d9d458" - integrity sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg== +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" "@babel/types" "^7.4.4" esutils "^2.0.2" @@ -934,50 +941,59 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.11.2", "@babel/runtime@^7.8.4": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" - integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== - dependencies: - regenerator-runtime "^0.13.11" - -"@babel/template@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" - integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== - dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/parser" "^7.22.5" - "@babel/types" "^7.22.5" - -"@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": - version "7.22.8" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" - integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== - dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.7" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" + integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.22.15", "@babel/template@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/traverse@^7.22.15": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.23.0" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.7" - "@babel/types" "^7.22.5" + "@babel/parser" "^7.23.0" + "@babel/types" "^7.23.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.22.5", "@babel/types@^7.4.4": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" - integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== +"@babel/types@^7.22.10", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.4.4": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.15.tgz#266cb21d2c5fd0b3931e7a91b6dd72d2f617d282" + integrity sha512-X+NLXr0N8XXmN5ZsaQdm9U2SSC3UbIYq/doL++sueHOTisgZHoKaQtZxGuV2cUPQHMfjKEfg/g6oy7Hm6SKFtA== dependencies: "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.15" + to-fast-properties "^2.0.0" + +"@babel/types@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" + integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" "@capacitor/android@^5.0.0": - version "5.2.2" - resolved "https://registry.yarnpkg.com/@capacitor/android/-/android-5.2.2.tgz#9f2cfd5b81f96deecc22d77aee9a3161c14c70b4" - integrity sha512-2kx5C2lJ/DSwPWS7fKvextWhg4AK3SQvQnHgp8+WX6SDYgphBNlGUh45N/qBoMeVM7lqjANJFGQDCk0FjimlWQ== + version "5.3.0" + resolved "https://registry.yarnpkg.com/@capacitor/android/-/android-5.3.0.tgz#d6bf2db5f385ac9ec6bc9ccf080c253f3c7b3db8" + integrity sha512-5rEwD8u0whGXP+dZ0qXRFP3DWHi4WhZdzcxxZ/D4mLh5eVaQCxPpoHBcrS4FpKtl7PAWNDD2n32D9YrEcfERMA== "@capacitor/app@^5.0.0": version "5.0.6" @@ -1008,9 +1024,9 @@ integrity sha512-wEI7Na6PVzSP/00ud7pjbBwXwVG7HywCdy2fJT/hzF6yuHn4tDirbOvbr1JKd9LZqKs2Xn+TapV38JhBRhX6YA== "@capacitor/camera@^5.0.6": - version "5.0.6" - resolved "https://registry.yarnpkg.com/@capacitor/camera/-/camera-5.0.6.tgz#08d7290825f19dd698b2d400c03daa0952a12868" - integrity sha512-X9WhbFz3dGv58JyfoAFGk/KN0oacEA+ReZkRc+JMRef0GUDfzlqEhqY4kP0i9nKH2koOFNdq4Pmya/Wu3mQXMg== + version "5.0.7" + resolved "https://registry.yarnpkg.com/@capacitor/camera/-/camera-5.0.7.tgz#42f60168d731fc521df797aacc7cda703b7ac2b1" + integrity sha512-1Wk3Dk0UhhNHdBB07UrPvUOSL7Wi5gFZRyLY1LZL2awt34iqy2cnajtfJplFmEZHk8lD0i7NAl3HbkWm4td4OQ== "@capacitor/cli@^3.2.5": version "3.9.0" @@ -1035,9 +1051,9 @@ xml2js "^0.4.23" "@capacitor/cli@^5.0.0": - version "5.2.2" - resolved "https://registry.yarnpkg.com/@capacitor/cli/-/cli-5.2.2.tgz#7799e4bfccc8a5bd7f51df1c6ea74d9505a5aae9" - integrity sha512-h/cefX3sefLcmd4VI2h+fNIYKWH1QCUACWAH8bkdJ+aEL+x2xYbGlqeW6Belb3E2Uvu9OA+cz7YVDnNRcjyQyw== + version "5.3.0" + resolved "https://registry.yarnpkg.com/@capacitor/cli/-/cli-5.3.0.tgz#6ee30148a8a6f9317056c8d9d1591ac04783f088" + integrity sha512-ku23HPqUHUnSgo/SyEWxVviEAxb4ieWvAVMI3KfrrBoinAhTOvNSZwT346rIpxZ9Xj3Qp41UjdIz0ME+DYwhfA== dependencies: "@ionic/cli-framework-output" "^2.2.5" "@ionic/utils-fs" "^3.1.6" @@ -1070,9 +1086,9 @@ tslib "^2.1.0" "@capacitor/core@^5.0.0": - version "5.2.2" - resolved "https://registry.yarnpkg.com/@capacitor/core/-/core-5.2.2.tgz#46fb818ac866b3f8dfe9fbe0de2722e2a008f40c" - integrity sha512-3jKECZC5+YD2rljMZm1e/K3AYyoxUmLDZCyofTPbRYPBSI0wJh5ZCkX+XIGzNM0o/Wokl3Voa1JB8xsLC0MPxA== + version "5.3.0" + resolved "https://registry.yarnpkg.com/@capacitor/core/-/core-5.3.0.tgz#07ae34746f1e62ebe6a1078509d29c2465efa445" + integrity sha512-mvhh1yJtcUTZ0hUUriBKKpxq47hn75bjxH3tYPRgAFu1z3gowCg+OtG4Rce3W5gr5fSfCjQgOSL0Vp7k9hPUWw== dependencies: tslib "^2.1.0" @@ -1082,9 +1098,9 @@ integrity sha512-tmjK0H8IKbDLMcmzZzJPbV+9yLkKJ76QOdz4A7fZAOYx2GnFHsFngxldq/wKotGAJuDX/ih3ZzHNrzVguzlv2g== "@capacitor/ios@^5.0.0": - version "5.2.2" - resolved "https://registry.yarnpkg.com/@capacitor/ios/-/ios-5.2.2.tgz#209cc3b15199325f3f5515f7a208ff9830482f52" - integrity sha512-6nNhB+G9tamJFMdvPOIATEPIXlh3GNXMq8eeY3xrFO72bBTmQpXdbDQmMJeBbpVlkEnFoVUXiSxAu6O0E9x9Lw== + version "5.3.0" + resolved "https://registry.yarnpkg.com/@capacitor/ios/-/ios-5.3.0.tgz#d2d090fae999d2c0161e72ee55efd8f61e28586d" + integrity sha512-AxcW6kp2dOhJ7D6Jz7FAiVi+tq8rv3K3VrfZ0rcGJGlqZfrOJ1m8ImcQ7cO9Fec7wf7a/XSwMOqWemYZFLOO8w== "@capacitor/keyboard@^5.0.0": version "5.0.6" @@ -1106,11 +1122,6 @@ resolved "https://registry.yarnpkg.com/@capacitor/status-bar/-/status-bar-5.0.6.tgz#281568a7f7aeacf80777702cb9947c29dc32685c" integrity sha512-7od8CxsBnot1XMK3IeOkproFL4hgoKoWAc3pwUvmDOkQsXoxwQm4SR9mLwQavv1XfxtHbFV9Ukd7FwMxOPSViw== -"@capgo/capacitor-purchases@^5.0.0": - version "5.0.8" - resolved "https://registry.yarnpkg.com/@capgo/capacitor-purchases/-/capacitor-purchases-5.0.8.tgz#b95efec3e0ebbd9a4a398224dd0c95529e13adc8" - integrity sha512-/2XA7UNakOekNm9j6e9wSh77qKrUoXtOW0Oz6pe8QWa7YWqhBeoxWEB227lCguooyZ6iFcL9X4BTJhcywZwpzA== - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -1118,115 +1129,115 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@esbuild/android-arm64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.18.tgz#a52e0a1276065b1bf6b2de45b482cf36b6b945bd" - integrity sha512-dkAPYzRHq3dNXIzOyAknYOzsx8o3KWaNiuu56B2rP9IFPmFWMS58WQcTlUQi6iloku8ZyHHMluCe5sTWhKq/Yw== - -"@esbuild/android-arm@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.18.tgz#ffd591b956ced1c96e1224edfbed1001adadf2ae" - integrity sha512-oBymf7ZwplAawSxmiSlBCf+FMcY0f4bs5QP2jn43JKUf0M9DnrUTjqa5RvFPl1elw+sMfcpfBRPK+rb+E1q7zg== - -"@esbuild/android-x64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.18.tgz#6e8a7b41fc80265849e0a1de928fe162b27990c7" - integrity sha512-r7/pVcrUQMYkjvtE/1/n6BxhWM+/9tvLxDG1ev1ce4z3YsqoxMK9bbOM6bFcj0BowMeGQvOZWcBV182lFFKmrw== - -"@esbuild/darwin-arm64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.18.tgz#36755dc44cefac6c56e82ed3b67f9d457ca27156" - integrity sha512-MSe2iV9MAH3wfP0g+vzN9bp36rtPPuCSk+bT5E2vv/d8krvW5uB/Pi/Q5+txUZuxsG3GcO8dhygjnFq0wJU9hQ== - -"@esbuild/darwin-x64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.18.tgz#8aa691d0cbd3fb67f9f9083375c0c72e0463b8b2" - integrity sha512-ARFYISOWkaifjcr48YtO70gcDNeOf1H2RnmOj6ip3xHIj66f3dAbhcd5Nph5np6oHI7DhHIcr9MWO18RvUL1bw== - -"@esbuild/freebsd-arm64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.18.tgz#0aafde382df508d7863360950d5f491c07024806" - integrity sha512-BHnXmexzEWRU2ZySJosU0Ts0NRnJnNrMB6t4EiIaOSel73I8iLsNiTPLH0rJulAh19cYZutsB5XHK6N8fi5eMg== - -"@esbuild/freebsd-x64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.18.tgz#f00e54a3b65824ac3c749173bec9cd56d95fe73b" - integrity sha512-n823w35wm0ZOobbuE//0sJjuz1Qj619+AwjgOcAJMN2pomZhH9BONCtn+KlfrmM/NWZ+27yB/eGVFzUIWLeh3w== - -"@esbuild/linux-arm64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.18.tgz#e04203429670257126a1bfee79bbd56448b24f5e" - integrity sha512-zANxnwF0sCinDcAqoMohGoWBK9QaFJ65Vgh0ZE+RXtURaMwx+RfmfLElqtnn7X8OYNckMoIXSg7u+tZ3tqTlrA== - -"@esbuild/linux-arm@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.18.tgz#863236dc47df2269f860001ca5c5ff50931e9933" - integrity sha512-Kck3jxPLQU4VeAGwe8Q4NU+IWIx+suULYOFUI9T0C2J1+UQlOHJ08ITN+MaJJ+2youzJOmKmcphH/t3SJxQ1Tw== - -"@esbuild/linux-ia32@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.18.tgz#9ef6c7eeb8c86c5c1b7234a9684c6f45cbc2ed57" - integrity sha512-+VHz2sIRlY5u8IlaLJpdf5TL2kM76yx186pW7bpTB+vLWpzcFQVP04L842ZB2Ty13A1VXUvy3DbU1jV65P2skg== - -"@esbuild/linux-loong64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.18.tgz#dca8624674924ac92c9e56399af160479283f130" - integrity sha512-fXPEPdeGBvguo/1+Na8OIWz3667BN1cwbGtTEZWTd0qdyTsk5gGf9jVX8MblElbDb/Cpw6y5JiaQuL96YmvBwQ== - -"@esbuild/linux-mips64el@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.18.tgz#e6525b60ae9d8c3bdc652a773e6ebf66caa3fdd3" - integrity sha512-dLvRB87pIBIRnEIC32LIcgwK1JzlIuADIRjLKdUIpxauKwMuS/xMpN+cFl+0nN4RHNYOZ57DmXFFmQAcdlFOmw== - -"@esbuild/linux-ppc64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.18.tgz#2ea6a4e0c6b0db21770d2c3c1525623dceadfe46" - integrity sha512-fRChqIJZ7hLkXSKfBLYgsX9Ssb5OGCjk3dzCETF5QSS1qjTgayLv0ALUdJDB9QOh/nbWwp+qfLZU6md4XcjL7w== - -"@esbuild/linux-riscv64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.18.tgz#296c25d5bdeb3bab9ca79ad5279a8cc0a42fbeea" - integrity sha512-ALK/BT3u7Hoa/vHjow6W6+MKF0ohYcVcVA1EpskI4bkBPVuDLrUDqt2YFifg5UcZc8qup0CwQqWmFUd6VMNgaA== - -"@esbuild/linux-s390x@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.18.tgz#bec4e9c982e778c51deaa754e1ed3f0546705647" - integrity sha512-crT7jtOXd9iirY65B+mJQ6W0HWdNy8dtkZqKGWNcBnunpLcTCfne5y5bKic9bhyYzKpQEsO+C/VBPD8iF0RhRw== - -"@esbuild/linux-x64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.18.tgz#22c9666920d3b7ef453289516ccff1c3ecbfdddd" - integrity sha512-/NSgghjBOW9ELqjXDYxOCCIsvQUZpvua1/6NdnA9Vnrp9UzEydyDdFXljUjMMS9p5KxMzbMO9frjHYGVHBfCHg== - -"@esbuild/netbsd-x64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.18.tgz#99b6125868c5ba8f0131bacc3f2bd05918245f45" - integrity sha512-8Otf05Vx5sZjLLDulgr5QS5lsWXMplKZEyHMArH9/S4olLlhzmdhQBPhzhJTNwaL2FJNdWcUPNGAcoD5zDTfUA== - -"@esbuild/openbsd-x64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.18.tgz#c2685bdd1e5aa11be1e212db371f474812a9b158" - integrity sha512-tFiFF4kT5L5qhVrWJUNxEXWvvX8nK/UX9ZrB7apuTwY3f6+Xy4aFMBPwAVrBYtBd5MOUuyOVHK6HBZCAHkwUlw== - -"@esbuild/sunos-x64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.18.tgz#277b2f5727119fe3004e673eb9f6ead0b4ff0738" - integrity sha512-MPogVV8Bzh8os4OM+YDGGsSzCzmNRiyKGtHoJyZLtI4BMmd6EcxmGlcEGK1uM46h1BiOyi7Z7teUtzzQhvkC+w== - -"@esbuild/win32-arm64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.18.tgz#e94d9e6d058e0ccb92d858badd4a6aa74772150e" - integrity sha512-YKD6LF/XXY9REu+ZL5RAsusiG48n602qxsMVh/E8FFD9hp4OyTQaL9fpE1ovxwQXqFio+tT0ITUGjDSSSPN13w== - -"@esbuild/win32-ia32@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.18.tgz#454916b1d0b85d2f82252192ae7bd5ea65c98ea1" - integrity sha512-NjSBmBsyZBTsZB6ga6rA6PfG/RHnwruUz/9YEVXcm4STGauFWvhYhOMhEyw1yU5NVgYYm8CH5AltCm77TS21/Q== - -"@esbuild/win32-x64@0.18.18": - version "0.18.18" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.18.tgz#914c007ab1dbd28ca84e79ee666adeee6ccf92b4" - integrity sha512-eTSg/gC3p3tdjj4roDhe5xu94l1s2jMazP8u2FsYO8SEKvSpPOO71EucprDn/IuErDPvTFUhV9lTw5z5WJCRKQ== +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" @@ -1236,14 +1247,14 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8" - integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw== + version "4.8.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.0.tgz#11195513186f68d42fbf449f9a7136b2c0c92005" + integrity sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg== -"@eslint/eslintrc@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.1.tgz#18d635e24ad35f7276e8a49d135c7d3ca6a46f93" - integrity sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA== +"@eslint/eslintrc@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -1255,15 +1266,15 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@^8.46.0": - version "8.46.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.46.0.tgz#3f7802972e8b6fe3f88ed1aabc74ec596c456db6" - integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA== +"@eslint/js@8.48.0": + version "8.48.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.48.0.tgz#642633964e217905436033a2bd08bf322849b7fb" + integrity sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw== "@humanwhocodes/config-array@^0.11.10": - version "0.11.10" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" - integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== + version "0.11.11" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" + integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -1368,10 +1379,10 @@ untildify "^4.0.0" wrap-ansi "^7.0.0" -"@jest/schemas@^29.6.0": - version "29.6.0" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" - integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: "@sinclair/typebox" "^0.27.8" @@ -1384,12 +1395,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/resolve-uri@^3.0.3": +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== @@ -1407,12 +1413,7 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@1.4.14": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.15": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -1426,12 +1427,12 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" - integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + version "0.3.19" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@liveconfig/xterm-webfont@^2.1.0": version "2.1.0" @@ -1472,12 +1473,12 @@ fastq "^1.6.0" "@playwright/test@^1.20.1": - version "1.36.2" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.36.2.tgz#9edd68a02b0929c5d78d9479a654ceb981dfb592" - integrity sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g== + version "1.37.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.37.1.tgz#e7f44ae0faf1be52d6360c6bbf689fd0057d9b6f" + integrity sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg== dependencies: "@types/node" "*" - playwright-core "1.36.2" + playwright-core "1.37.1" optionalDependencies: fsevents "2.3.2" @@ -1489,6 +1490,18 @@ "@xml-tools/parser" "^1.0.11" prettier ">=2.4.0" +"@revenuecat/purchases-capacitor@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@revenuecat/purchases-capacitor/-/purchases-capacitor-7.0.0.tgz#1828b34e3d354f0b94eb937815a1cacde2a3358d" + integrity sha512-rVOyugeZl7SyvluJU2M8xcSWCRDlF/V/1yzGh0W5rN7bNLJIefNHu3I5u28bNp47DeNW1wYH658x0SEBRWcvig== + dependencies: + "@revenuecat/purchases-typescript-internal-esm" "7.0.0" + +"@revenuecat/purchases-typescript-internal-esm@7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@revenuecat/purchases-typescript-internal-esm/-/purchases-typescript-internal-esm-7.0.0.tgz#f751b72eaaf08b8bb4296a59891174c1e14bbf03" + integrity sha512-f4Py3SrXsgFAPDU+33+ceq5zg3zjSfyHpxkpMDlLFXtb1eE3XK64UCzAO5nHV5n0hLswMPvzJfVJxWLOz2S/TQ== + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -1527,9 +1540,9 @@ picomatch "^2.2.2" "@scure/base@~1.1.0", "@scure/base@~1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" - integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + version "1.1.3" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" + integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== "@sinclair/typebox@^0.27.8": version "0.27.8" @@ -1634,14 +1647,14 @@ "@types/chai" "*" "@types/chai@*", "@types/chai@^4.3.5": - version "4.3.5" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b" - integrity sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng== + version "4.3.6" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.6.tgz#7b489e8baf393d5dd1266fb203ddd4ea941259e6" + integrity sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw== "@types/cross-spawn@^6.0.2": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.2.tgz#168309de311cd30a2b8ae720de6475c2fbf33ac7" - integrity sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw== + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.3.tgz#c743cb2608f55860ee9776d8c99135d6032c763c" + integrity sha512-BDAkU7WHHRHnvBf5z89lcvACsvkz/n7Tv+HyD/uW76O29HoH1Tk/W6iQrepaZVbisvlEek4ygwT8IW7ow9XLAA== dependencies: "@types/node" "*" @@ -1669,6 +1682,11 @@ dependencies: "@types/node" "*" +"@types/hammerjs@^2.0.42": + version "2.0.42" + resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.42.tgz#d7a53edbc51b2c13a9a759c45d7b5e61243d7dba" + integrity sha512-Xxk14BrwHnGi0xlURPRb+Y0UNn2w3cTkeFm7pKMsYOaNgH/kabbJLhcBoNIodwsbTz7Z8KcWjtDvlGH0nc0U9w== + "@types/ini@^1.3.31": version "1.3.31" resolved "https://registry.yarnpkg.com/@types/ini/-/ini-1.3.31.tgz#c78541a187bd88d5c73e990711c9d85214800d1b" @@ -1688,9 +1706,9 @@ integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== "@types/lodash@^4.14.175": - version "4.14.196" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.196.tgz#a7c3d6fc52d8d71328b764e28e080b4169ec7a95" - integrity sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ== + version "4.14.197" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.197.tgz#e95c5ddcc814ec3e84c891910a01e0c8a378c54b" + integrity sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g== "@types/marked@^4.0.7": version "4.3.1" @@ -1703,9 +1721,9 @@ integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node@*": - version "20.4.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.8.tgz#b5dda19adaa473a9bf0ab5cbd8f30ec7d43f5c85" - integrity sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg== + version "20.5.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.9.tgz#a70ec9d8fa0180a314c3ede0e20ea56ff71aed9a" + integrity sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -1728,9 +1746,9 @@ "@types/node" "*" "@types/semver@^7.3.12": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" - integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== + version "7.5.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.1.tgz#0480eeb7221eb9bc398ad7432c9d7e14b1a5a367" + integrity sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg== "@types/slice-ansi@^4.0.0": version "4.0.0" @@ -1747,6 +1765,13 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.3.tgz#a136f83b0758698df454e328759dbd3d44555311" integrity sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g== +"@types/xterm@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/xterm/-/xterm-3.0.0.tgz#5b177010903ee1dc26fdc25d6e4e1f2490d44ce6" + integrity sha512-+VaAJQmE7E1d1ebkIh/Zdc2mbXBVwxZGGSgqwzDPpk/HKo0mNT+iX5ZrnswztHSV+CDV+bURl7Yg7PWF7IZfXQ== + dependencies: + xterm "*" + "@typescript-eslint/eslint-plugin@<6": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" @@ -1831,44 +1856,44 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@vitest/expect@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.34.1.tgz#2ba6cb96695f4b4388c6d955423a81afc79b8da0" - integrity sha512-q2CD8+XIsQ+tHwypnoCk8Mnv5e6afLFvinVGCq3/BOT4kQdVQmY6rRfyKkwcg635lbliLPqbunXZr+L1ssUWiQ== +"@vitest/expect@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.34.3.tgz#576e1fd6a3a8b8b7a79a06477f3d450a77d67852" + integrity sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg== dependencies: - "@vitest/spy" "0.34.1" - "@vitest/utils" "0.34.1" + "@vitest/spy" "0.34.3" + "@vitest/utils" "0.34.3" chai "^4.3.7" -"@vitest/runner@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.34.1.tgz#23c21ba1db8bff610988c72744db590d0fb6c4ba" - integrity sha512-YfQMpYzDsYB7yqgmlxZ06NI4LurHWfrH7Wy3Pvf/z/vwUSgq1zLAb1lWcItCzQG+NVox+VvzlKQrYEXb47645g== +"@vitest/runner@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.34.3.tgz#ce09b777d133bbcf843e1a67f4a743365764e097" + integrity sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA== dependencies: - "@vitest/utils" "0.34.1" + "@vitest/utils" "0.34.3" p-limit "^4.0.0" pathe "^1.1.1" -"@vitest/snapshot@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.34.1.tgz#814c65f8e714eaf255f47838541004b2a2ba28e6" - integrity sha512-0O9LfLU0114OqdF8lENlrLsnn024Tb1CsS9UwG0YMWY2oGTQfPtkW+B/7ieyv0X9R2Oijhi3caB1xgGgEgclSQ== +"@vitest/snapshot@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-0.34.3.tgz#cb4767aa44711a1072bd2e06204b659275c4f0f2" + integrity sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ== dependencies: magic-string "^0.30.1" pathe "^1.1.1" pretty-format "^29.5.0" -"@vitest/spy@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.34.1.tgz#2f77234a3d554c5dea664943f2caaab92d304f3c" - integrity sha512-UT4WcI3EAPUNO8n6y9QoEqynGGEPmmRxC+cLzneFFXpmacivjHZsNbiKD88KUScv5DCHVDgdBsLD7O7s1enFcQ== +"@vitest/spy@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.34.3.tgz#d4cf25e6ca9230991a0223ecd4ec2df30f0784ff" + integrity sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ== dependencies: tinyspy "^2.1.1" -"@vitest/utils@0.34.1": - version "0.34.1" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.34.1.tgz#e5545c6618775fb9a2dae2a80d94fc2f35222233" - integrity sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q== +"@vitest/utils@0.34.3": + version "0.34.3" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.34.3.tgz#6e243189a358b736b9fc0216e6b6979bc857e897" + integrity sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA== dependencies: diff-sequences "^29.4.3" loupe "^2.3.6" @@ -2094,7 +2119,7 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -babel-plugin-polyfill-corejs2@^0.4.4: +babel-plugin-polyfill-corejs2@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c" integrity sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg== @@ -2103,7 +2128,7 @@ babel-plugin-polyfill-corejs2@^0.4.4: "@babel/helper-define-polyfill-provider" "^0.4.2" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.8.2: +babel-plugin-polyfill-corejs3@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz#b4f719d0ad9bb8e0c23e3e630c0c8ec6dd7a1c52" integrity sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA== @@ -2111,7 +2136,7 @@ babel-plugin-polyfill-corejs3@^0.8.2: "@babel/helper-define-polyfill-provider" "^0.4.2" core-js-compat "^3.31.0" -babel-plugin-polyfill-regenerator@^0.5.1: +babel-plugin-polyfill-regenerator@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz#80d0f3e1098c080c8b5a65f41e9427af692dc326" integrity sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA== @@ -2200,7 +2225,7 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.21.9: +browserslist@^4.21.10, browserslist@^4.21.9: version "4.21.10" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== @@ -2266,9 +2291,9 @@ camelcase@^5.0.0, camelcase@^5.3.1: integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== caniuse-lite@^1.0.30001517: - version "1.0.30001519" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz#3e7b8b8a7077e78b0eb054d69e6edf5c7df35601" - integrity sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg== + version "1.0.30001527" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001527.tgz#813826554828245ccee776c850566dce12bdeaba" + integrity sha512-YkJi7RwPgWtXVSgK4lG9AHH57nSzvvOp9MesgXmw4Q7n0C3H04L0foHqfxcmSAm5AcWb8dW9AYj2tR7/5GnddQ== capacitor-native-biometric@^4.1.3: version "4.2.2" @@ -2290,9 +2315,9 @@ capacitor-ssh-plugin@^0.5.0: wd "^0.0.26" chai@^4.3.7: - version "4.3.7" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51" - integrity sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A== + version "4.3.8" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.8.tgz#40c59718ad6928da6629c70496fe990b2bb5b17c" + integrity sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ== dependencies: assertion-error "^1.1.0" check-error "^1.0.2" @@ -2302,7 +2327,7 @@ chai@^4.3.7: pathval "^1.1.1" type-detect "^4.0.5" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2623,11 +2648,11 @@ convert-source-map@^1.7.0: integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== core-js-compat@^3.31.0: - version "3.32.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.0.tgz#f41574b6893ab15ddb0ac1693681bd56c8550a90" - integrity sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw== + version "3.32.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.1.tgz#55f9a7d297c0761a8eb1d31b593e0f5b6ffae964" + integrity sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA== dependencies: - browserslist "^4.21.9" + browserslist "^4.21.10" core-util-is@~1.0.0: version "1.0.3" @@ -2821,9 +2846,9 @@ diff-sequences@^27.5.1: integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== diff-sequences@^29.4.3: - version "29.4.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" - integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diff@^4.0.1: version "4.0.2" @@ -2911,9 +2936,9 @@ ejs@^3.1.6: jake "^10.8.5" electron-to-chromium@^1.4.477: - version "1.4.485" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.485.tgz#fde3ee9ee8112a3414c0dfa545385ad08ec43408" - integrity sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w== + version "1.4.508" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.508.tgz#5641ff2f5ba11df4bd960fe6a2f9f70aa8b9af96" + integrity sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg== elementtree@^0.1.7: version "0.1.7" @@ -2956,7 +2981,7 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.19.0, es-abstract@^1.20.4: +es-abstract@^1.20.4, es-abstract@^1.22.1: version "1.22.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== @@ -3020,32 +3045,32 @@ es-to-primitive@^1.2.1: is-symbol "^1.0.2" esbuild@^0.18.10: - version "0.18.18" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.18.tgz#196838a905c7068d1c1653b04f2c5dfdb68ef927" - integrity sha512-UckDPWvdVJLNT0npk5AMTpVwGRQhS76rWFLmHwEtgNvWlR9sgVV1eyc/oeBtM86q9s8ABBLMmm0CwNxhVemOiw== + version "0.18.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== optionalDependencies: - "@esbuild/android-arm" "0.18.18" - "@esbuild/android-arm64" "0.18.18" - "@esbuild/android-x64" "0.18.18" - "@esbuild/darwin-arm64" "0.18.18" - "@esbuild/darwin-x64" "0.18.18" - "@esbuild/freebsd-arm64" "0.18.18" - "@esbuild/freebsd-x64" "0.18.18" - "@esbuild/linux-arm" "0.18.18" - "@esbuild/linux-arm64" "0.18.18" - "@esbuild/linux-ia32" "0.18.18" - "@esbuild/linux-loong64" "0.18.18" - "@esbuild/linux-mips64el" "0.18.18" - "@esbuild/linux-ppc64" "0.18.18" - "@esbuild/linux-riscv64" "0.18.18" - "@esbuild/linux-s390x" "0.18.18" - "@esbuild/linux-x64" "0.18.18" - "@esbuild/netbsd-x64" "0.18.18" - "@esbuild/openbsd-x64" "0.18.18" - "@esbuild/sunos-x64" "0.18.18" - "@esbuild/win32-arm64" "0.18.18" - "@esbuild/win32-ia32" "0.18.18" - "@esbuild/win32-x64" "0.18.18" + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" escalade@^3.1.1: version "3.1.1" @@ -3089,20 +3114,20 @@ eslint-scope@^7.2.2: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.2: - version "3.4.2" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f" - integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@<9: - version "8.46.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.46.0.tgz#a06a0ff6974e53e643acc42d1dcf2e7f797b3552" - integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg== + version "8.48.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.48.0.tgz#bf9998ba520063907ba7bfe4c480dc8be03c2155" + integrity sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.1" - "@eslint/js" "^8.46.0" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "8.48.0" "@humanwhocodes/config-array" "^0.11.10" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -3113,7 +3138,7 @@ eslint@<9: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.2" + eslint-visitor-keys "^3.4.3" espree "^9.6.1" esquery "^1.4.2" esutils "^2.0.2" @@ -3275,14 +3300,15 @@ find-up@^5.0.0: path-exists "^4.0.0" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.1.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.0.tgz#0e54ab4a1a60fe87e2946b6b00657f1c99e1af3f" + integrity sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew== dependencies: - flatted "^3.1.0" + flatted "^3.2.7" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0: +flatted@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== @@ -3349,27 +3375,32 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@2.3.2, fsevents@~2.3.2: +fsevents@2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" -functions-have-names@^1.2.2, functions-have-names@^1.2.3: +functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -3385,9 +3416,9 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" + integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" @@ -3503,9 +3534,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + version "13.21.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" + integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== dependencies: type-fest "^0.20.2" @@ -3734,7 +3765,7 @@ ini@^3.0.1: resolved "https://registry.yarnpkg.com/ini/-/ini-3.0.1.tgz#c76ec81007875bc44d544ff7a11a55d12294102d" integrity sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ== -internal-slot@^1.0.3, internal-slot@^1.0.5: +internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== @@ -4058,6 +4089,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -4093,7 +4129,7 @@ json-stringify-safe@^5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@^2.2.0, json5@^2.2.2: +json5@^2.2.0, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -4122,6 +4158,13 @@ jsonpointer@^5.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== +keyv@^4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" + integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== + dependencies: + json-buffer "3.0.1" + kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -4239,9 +4282,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" "lru-cache@^9.1.1 || ^10.0.0": - version "10.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" - integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw== + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" @@ -4251,9 +4294,9 @@ magic-string@^0.25.0, magic-string@^0.25.7: sourcemap-codec "^1.4.8" magic-string@^0.30.1: - version "0.30.2" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.2.tgz#dcf04aad3d0d1314bc743d076c50feb29b3c7aca" - integrity sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug== + version "0.30.3" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.3.tgz#403755dfd9d6b398dfa40635d52e96c5ac095b85" + integrity sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" @@ -4410,9 +4453,9 @@ minipass@^5.0.0: integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== "minipass@^5.0.0 || ^6.0.2 || ^7.0.0": - version "7.0.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.2.tgz#58a82b7d81c7010da5bd4b2c0c85ac4b4ec5131e" - integrity sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA== + version "7.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" + integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== minizlib@^2.1.1: version "2.1.2" @@ -4433,14 +4476,14 @@ mkdirp@^1.0.3: integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mlly@^1.2.0, mlly@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.0.tgz#830c10d63f1f97bd8785377b24dc2a15d972832b" - integrity sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg== + version "1.4.2" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.2.tgz#7cf406aa319ff6563d25da6b36610a93f2a8007e" + integrity sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg== dependencies: - acorn "^8.9.0" + acorn "^8.10.0" pathe "^1.1.1" pkg-types "^1.0.3" - ufo "^1.1.2" + ufo "^1.3.0" modify-values@^1.0.0: version "1.0.1" @@ -4507,9 +4550,9 @@ neo-async@^2.6.2: integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== node-abi@^3.3.0: - version "3.45.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.45.0.tgz#f568f163a3bfca5aacfce1fbeee1fa2cc98441f5" - integrity sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ== + version "3.47.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.47.0.tgz#6cbfa2916805ae25c2b7156ca640131632eb05e8" + integrity sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A== dependencies: semver "^7.3.5" @@ -4519,9 +4562,9 @@ node-addon-api@^5.0.0: integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== node-fetch@^2.6.12, node-fetch@^2.6.7: - version "2.6.12" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" - integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" @@ -4847,10 +4890,10 @@ pkg-types@^1.0.3: mlly "^1.2.0" pathe "^1.1.0" -playwright-core@1.36.2: - version "1.36.2" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.36.2.tgz#32382f2d96764c24c65a86ea336cf79721c2e50e" - integrity sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ== +playwright-core@1.37.1: + version "1.37.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.37.1.tgz#cb517d52e2e8cb4fa71957639f1cd105d1683126" + integrity sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA== plist@^3.0.2, plist@^3.0.4, plist@^3.0.5, plist@^3.0.6: version "3.1.0" @@ -4861,10 +4904,10 @@ plist@^3.0.2, plist@^3.0.4, plist@^3.0.5, plist@^3.0.6: base64-js "^1.5.1" xmlbuilder "^15.1.1" -postcss@^8.4.26: - version "8.4.27" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057" - integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ== +postcss@^8.4.27: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== dependencies: nanoid "^3.3.6" picocolors "^1.0.0" @@ -4894,9 +4937,9 @@ prelude-ls@^1.2.1: integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier@>=2.4.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.1.tgz#65271fc9320ce4913c57747a70ce635b30beaa40" - integrity sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ== + version "3.0.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" + integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== prettier@^2.7.1: version "2.8.8" @@ -4923,11 +4966,11 @@ pretty-format@^27.0.0, pretty-format@^27.5.1: react-is "^17.0.1" pretty-format@^29.5.0: - version "29.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.2.tgz#3d5829261a8a4d89d8b9769064b29c50ed486a47" - integrity sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg== + version "29.6.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7" + integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw== dependencies: - "@jest/schemas" "^29.6.0" + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" react-is "^18.0.0" @@ -5099,15 +5142,15 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== -regenerator-transform@^0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" - integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== dependencies: "@babel/runtime" "^7.8.4" @@ -5116,7 +5159,7 @@ regexp-to-ast@0.5.0: resolved "https://registry.yarnpkg.com/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz#56c73856bee5e1fef7f73a00f1473452ab712a24" integrity sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw== -regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.0: +regexp.prototype.flags@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== @@ -5223,10 +5266,10 @@ rollup@^2.43.1: optionalDependencies: fsevents "~2.3.2" -rollup@^3.25.2: - version "3.27.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.27.2.tgz#59adc973504408289be89e5978e938ce852c9520" - integrity sha512-YGwmHf7h2oUHkVBT248x0yt6vZkYQ3/rvE5iQuVBh3WO8GcJ6BNeOkpoX1yMHIiBm18EMLjBPIoUDkhgnyxGOQ== +rollup@^3.27.1: + version "3.28.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.28.1.tgz#fb44aa6d5e65c7e13fd5bcfff266d0c4ea9ba433" + integrity sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw== optionalDependencies: fsevents "~2.3.2" @@ -5504,9 +5547,9 @@ stackback@0.0.2: integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== std-env@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.3.tgz#a54f06eb245fdcfef53d56f3c0251f1d5c3d01fe" - integrity sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg== + version "3.4.3" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.4.3.tgz#326f11db518db751c83fd58574f449b7c3060910" + integrity sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q== stream-buffers@2.2.x: version "2.2.0" @@ -5523,17 +5566,17 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: strip-ansi "^6.0.1" string.prototype.matchall@^4.0.6: - version "4.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" - integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg== + version "4.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.9.tgz#148779de0f75d36b13b15885fec5cadde994520d" + integrity sha512-6i5hL3MqG/K2G43mWXWgP+qizFW/QH/7kCNN13JrJS5q48FN5IKksLDscexKP3dnmB6cdm9jlNgAsWNLpSykmA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" - get-intrinsic "^1.1.3" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" - regexp.prototype.flags "^1.4.3" + internal-slot "^1.0.5" + regexp.prototype.flags "^1.5.0" side-channel "^1.0.4" string.prototype.trim@^1.2.7: @@ -5555,13 +5598,13 @@ string.prototype.trimend@^1.0.6: es-abstract "^1.20.4" string.prototype.trimstart@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" - integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.20.4" + define-properties "^1.2.0" + es-abstract "^1.22.1" string_decoder@^1.1.1: version "1.3.0" @@ -5673,9 +5716,9 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^6.1.11: - version "6.1.15" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" - integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -5711,9 +5754,9 @@ tempy@^1.0.1: unique-string "^2.0.0" terser@^5.0.0: - version "5.19.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.2.tgz#bdb8017a9a4a8de4663a7983f45c506534f9234e" - integrity sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA== + version "5.19.4" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.4.tgz#941426fa482bf9b40a0308ab2b3cd0cf7c775ebd" + integrity sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -5855,9 +5898,9 @@ tslib@^1.8.1: integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" - integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== tsutils@^3.21.0: version "3.21.0" @@ -5954,10 +5997,10 @@ typescript@<5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -ufo@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.2.0.tgz#28d127a087a46729133fdc89cb1358508b3f80ba" - integrity sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg== +ufo@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.0.tgz#c92f8ac209daff607c57bbd75029e190930a0019" + integrity sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw== uglify-js@^3.1.4: version "3.17.4" @@ -6088,10 +6131,10 @@ vi-canvas-mock@^1.0.0: cssfontparser "^1.2.1" moo-color "^1.0.2" -vite-node@0.34.1: - version "0.34.1" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.34.1.tgz#144900ca4bd54cc419c501d671350bcbc07eb1ee" - integrity sha512-odAZAL9xFMuAg8aWd7nSPT+hU8u2r9gU3LRm9QKjxBEF2rRdWpMuqkrkjvyVQEdNFiBctqr2Gg4uJYizm5Le6w== +vite-node@0.34.3: + version "0.34.3" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.34.3.tgz#de134fe38bc1555ac8ab5e489d7df6159a3e1a4c" + integrity sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig== dependencies: cac "^6.7.14" debug "^4.3.4" @@ -6112,29 +6155,29 @@ vite-plugin-pwa@^0.16.4: workbox-window "^7.0.0" "vite@^3.0.0 || ^4.0.0", vite@^4.0.0: - version "4.4.8" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.8.tgz#31e4a438f8748695c68bd57ffd262ba93540fdf7" - integrity sha512-LONawOUUjxQridNWGQlNizfKH89qPigK36XhMI7COMGztz8KNY0JHim7/xDd71CZwGT4HtSRgI7Hy+RlhG0Gvg== + version "4.4.9" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.9.tgz#1402423f1a2f8d66fd8d15e351127c7236d29d3d" + integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA== dependencies: esbuild "^0.18.10" - postcss "^8.4.26" - rollup "^3.25.2" + postcss "^8.4.27" + rollup "^3.27.1" optionalDependencies: fsevents "~2.3.2" vitest@<1.0.0: - version "0.34.1" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.34.1.tgz#3ad7f845e7a9fb0d72ab703cae832a54b8469e1e" - integrity sha512-G1PzuBEq9A75XSU88yO5G4vPT20UovbC/2osB2KEuV/FisSIIsw7m5y2xMdB7RsAGHAfg2lPmp2qKr3KWliVlQ== + version "0.34.3" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.34.3.tgz#863d61c133d01b16e49fd52d380c09fa5ac03188" + integrity sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ== dependencies: "@types/chai" "^4.3.5" "@types/chai-subset" "^1.3.3" "@types/node" "*" - "@vitest/expect" "0.34.1" - "@vitest/runner" "0.34.1" - "@vitest/snapshot" "0.34.1" - "@vitest/spy" "0.34.1" - "@vitest/utils" "0.34.1" + "@vitest/expect" "0.34.3" + "@vitest/runner" "0.34.3" + "@vitest/snapshot" "0.34.3" + "@vitest/spy" "0.34.3" + "@vitest/utils" "0.34.3" acorn "^8.9.0" acorn-walk "^8.2.0" cac "^6.7.14" @@ -6149,7 +6192,7 @@ vitest@<1.0.0: tinybench "^2.5.0" tinypool "^0.7.0" vite "^3.0.0 || ^4.0.0" - vite-node "0.34.1" + vite-node "0.34.3" why-is-node-running "^2.2.2" w3c-hr-time@^1.0.2: @@ -6557,6 +6600,11 @@ xterm-addon-webgl@^0.14.0: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.14.0.tgz#bd9136710bd5d130a0a51fd42e6d14ea39348236" integrity sha512-zcxL4RVVjeS7NNFeKe5HHQI8OUEx3wZpE4EqLoTVipa2UrTR+qLsigo16UEp/yVcYBMhK7tsJ/AJokoEe/f0mw== +xterm@*: + version "5.3.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.3.0.tgz#867daf9cc826f3d45b5377320aabd996cb0fce46" + integrity sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg== + xterm@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.1.tgz#b3fea7bdb55b9be1d4b31f4cd1091f26ac42afb8"