From b91e72bf40253fc53ee943dc482152d40559178a Mon Sep 17 00:00:00 2001 From: Priyanka Patel Date: Thu, 14 Jul 2022 09:17:17 +0000 Subject: [PATCH 1/5] trace collection UI Created new UI components for trace collection. Since the proxy is not yet migrated, click through the buttons to automatically cycle through the components. Bug: b/238981126 Test: npm run start Click Web ADB tab to see basic component. Click ADB proxy tab to see proxy component. Click Retry to get to authorisation options. Click Connect to get to trace configuration component. Change-Id: I1e7a9895ece06856c095d8c85c28d22bbfa94b31 --- tools/winscope-ng/package-lock.json | 69 +++++ tools/winscope-ng/package.json | 1 + tools/winscope-ng/src/app/app.component.ts | 22 +- tools/winscope-ng/src/app/app.module.ts | 40 ++- tools/winscope-ng/src/styles.css | 45 +++- .../adb_proxy.component.spec.ts | 41 +++ .../trace_collection/adb_proxy.component.ts | 99 ++++++++ .../collect_traces.component.spec.ts | 40 +++ .../collect_traces.component.ts | 236 ++++++++++++++++++ .../src/trace_collection/proxy_client.ts | 26 ++ .../trace_config.component.spec.ts | 40 +++ .../trace_config.component.ts | 88 +++++++ .../web_adb.component.spec.ts | 45 ++++ .../src/trace_collection/web_adb.component.ts | 35 +++ 14 files changed, 818 insertions(+), 9 deletions(-) create mode 100644 tools/winscope-ng/src/trace_collection/adb_proxy.component.spec.ts create mode 100644 tools/winscope-ng/src/trace_collection/adb_proxy.component.ts create mode 100644 tools/winscope-ng/src/trace_collection/collect_traces.component.spec.ts create mode 100644 tools/winscope-ng/src/trace_collection/collect_traces.component.ts create mode 100644 tools/winscope-ng/src/trace_collection/proxy_client.ts create mode 100644 tools/winscope-ng/src/trace_collection/trace_config.component.spec.ts create mode 100644 tools/winscope-ng/src/trace_collection/trace_config.component.ts create mode 100644 tools/winscope-ng/src/trace_collection/web_adb.component.spec.ts create mode 100644 tools/winscope-ng/src/trace_collection/web_adb.component.ts diff --git a/tools/winscope-ng/package-lock.json b/tools/winscope-ng/package-lock.json index 71bad18b8..abf723a2e 100644 --- a/tools/winscope-ng/package-lock.json +++ b/tools/winscope-ng/package-lock.json @@ -14,6 +14,7 @@ "@angular/core": "^14.0.1", "@angular/elements": "^14.0.1", "@angular/forms": "^14.0.0", + "@angular/material": "^14.0.4", "@angular/platform-browser": "^14.0.0", "@angular/platform-browser-dynamic": "^14.0.0", "@angular/router": "^14.0.0", @@ -460,6 +461,30 @@ "@angular/core": "14.0.6" } }, + "node_modules/@angular/cdk": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-14.0.4.tgz", + "integrity": "sha512-zPM4VZadoKzTF9TZ7Yx5gJ7GtQpt62f8ofdH/BF2atG+TaNzOEFqtzogP4WuJDFAxJXOPMePobhth4YjUk0Wbw==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^5.0.0" + }, + "peerDependencies": { + "@angular/common": "^14.0.0 || ^15.0.0", + "@angular/core": "^14.0.0 || ^15.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cdk/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true, + "peer": true + }, "node_modules/@angular/cli": { "version": "14.0.6", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.0.6.tgz", @@ -607,6 +632,23 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-14.0.4.tgz", + "integrity": "sha512-Ysz6oPbpLH7CvRR6oxQwpUImSbFqxL4+eiH0LPc7vkaOSrvGdZ/7cWhAfT6hVnw3bEY+eq5qBSMgyVUB44z4eg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^14.0.0 || ^15.0.0", + "@angular/cdk": "14.0.4", + "@angular/common": "^14.0.0 || ^15.0.0", + "@angular/core": "^14.0.0 || ^15.0.0", + "@angular/forms": "^14.0.0 || ^15.0.0", + "@angular/platform-browser": "^14.0.0 || ^15.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/platform-browser": { "version": "14.0.6", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.0.6.tgz", @@ -15269,6 +15311,25 @@ "tslib": "^2.3.0" } }, + "@angular/cdk": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-14.0.4.tgz", + "integrity": "sha512-zPM4VZadoKzTF9TZ7Yx5gJ7GtQpt62f8ofdH/BF2atG+TaNzOEFqtzogP4WuJDFAxJXOPMePobhth4YjUk0Wbw==", + "peer": true, + "requires": { + "parse5": "^5.0.0", + "tslib": "^2.3.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true, + "peer": true + } + } + }, "@angular/cli": { "version": "14.0.6", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-14.0.6.tgz", @@ -15355,6 +15416,14 @@ "tslib": "^2.3.0" } }, + "@angular/material": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-14.0.4.tgz", + "integrity": "sha512-Ysz6oPbpLH7CvRR6oxQwpUImSbFqxL4+eiH0LPc7vkaOSrvGdZ/7cWhAfT6hVnw3bEY+eq5qBSMgyVUB44z4eg==", + "requires": { + "tslib": "^2.3.0" + } + }, "@angular/platform-browser": { "version": "14.0.6", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.0.6.tgz", diff --git a/tools/winscope-ng/package.json b/tools/winscope-ng/package.json index eee118a98..1872e570b 100644 --- a/tools/winscope-ng/package.json +++ b/tools/winscope-ng/package.json @@ -22,6 +22,7 @@ "@angular/core": "^14.0.1", "@angular/elements": "^14.0.1", "@angular/forms": "^14.0.0", + "@angular/material": "^14.0.4", "@angular/platform-browser": "^14.0.0", "@angular/platform-browser-dynamic": "^14.0.0", "@angular/router": "^14.0.0", diff --git a/tools/winscope-ng/src/app/app.component.ts b/tools/winscope-ng/src/app/app.component.ts index 0e66ed4f2..717009ea1 100644 --- a/tools/winscope-ng/src/app/app.component.ts +++ b/tools/winscope-ng/src/app/app.component.ts @@ -24,21 +24,33 @@ import {Core} from "./core";
Winscope Viewer 2.0
+
+ + + + + Upload Traces +
+ +
+
+
- -
- -
+
- ` + +
+
+ `, + styles: [".card-container{width: 100%; display:flex; flex-direction: row; overflow: auto;}"] }) export class AppComponent { title = "winscope-ng"; diff --git a/tools/winscope-ng/src/app/app.module.ts b/tools/winscope-ng/src/app/app.module.ts index 9f6140299..74952603e 100644 --- a/tools/winscope-ng/src/app/app.module.ts +++ b/tools/winscope-ng/src/app/app.module.ts @@ -1,16 +1,50 @@ import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; +import { CommonModule } from "@angular/common"; +import { MatCardModule } from "@angular/material/card"; +import { MatButtonModule } from "@angular/material/button"; +import { MatGridListModule } from "@angular/material/grid-list"; +import { MatListModule } from "@angular/material/list"; +import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import { FormsModule } from "@angular/forms"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatIconModule } from "@angular/material/icon"; +import { MatInputModule } from "@angular/material/input"; +import { MatSelectModule } from "@angular/material/select"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { AppComponent } from "./app.component"; -import { ViewerWindowManagerComponent} from "viewers/viewer_window_manager/viewer_window_manager.component"; +import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component"; +import { CollectTracesComponent } from "trace_collection/collect_traces.component"; +import { AdbProxyComponent } from "trace_collection/adb_proxy.component"; +import { WebAdbComponent } from "trace_collection/web_adb.component"; +import { TraceConfigComponent } from "trace_collection/trace_config.component"; @NgModule({ declarations: [ AppComponent, - ViewerWindowManagerComponent + ViewerWindowManagerComponent, + CollectTracesComponent, + AdbProxyComponent, + WebAdbComponent, + TraceConfigComponent, ], imports: [ - BrowserModule + BrowserModule, + CommonModule, + MatCardModule, + MatButtonModule, + MatGridListModule, + FormsModule, + MatListModule, + MatCheckboxModule, + MatIconModule, + MatProgressSpinnerModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + BrowserAnimationsModule ], providers: [], bootstrap: [AppComponent] diff --git a/tools/winscope-ng/src/styles.css b/tools/winscope-ng/src/styles.css index 50e45553c..db918bf1a 100644 --- a/tools/winscope-ng/src/styles.css +++ b/tools/winscope-ng/src/styles.css @@ -13,6 +13,49 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@import "~@angular/material/prebuilt-themes/indigo-pink.css"; +@import 'https://fonts.googleapis.com/icon?family=Material+Icons'; + #title { - color: aqua; + font-weight: bold; + font-family: Arial, Helvetica, sans-serif; + color:rgb(194, 65, 108); + font-size: 20; } + +button { + cursor: pointer; +} + +.homepage-card { + border: 1px solid rgb(129, 129, 129); + width: 45rem; + height: 30rem; + overflow: auto; + display: flex; + margin: 10px; +} + +mat-checkbox { + margin-left: 10px; +} + +mat-form-field { + margin: 10px; + height: 5px; +} + +.card-block { + margin: 15px; +} + +button.mat-raised-button { + background-color:rgb(194, 65, 108); + color: white; + margin: 10px; +} + +.tab.inactive { + background-color:white; + color: black; +} \ No newline at end of file diff --git a/tools/winscope-ng/src/trace_collection/adb_proxy.component.spec.ts b/tools/winscope-ng/src/trace_collection/adb_proxy.component.spec.ts new file mode 100644 index 000000000..20ddb46cb --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/adb_proxy.component.spec.ts @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {ComponentFixture, TestBed} from "@angular/core/testing"; +import {AdbProxyComponent} from "./adb_proxy.component"; + +describe("AdbProxyComponent", () => { + let fixture: ComponentFixture; + let component: AdbProxyComponent; + let htmlElement: HTMLElement; + + beforeAll(async () => { + await TestBed.configureTestingModule({ + declarations: [AdbProxyComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdbProxyComponent); + component = fixture.componentInstance; + htmlElement = fixture.nativeElement; + }); + + it("can be created", () => { + expect(component).toBeTruthy(); + }); + + +}); diff --git a/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts b/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts new file mode 100644 index 000000000..35bcc455c --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input, Output, EventEmitter } from "@angular/core"; +import { ProxyState } from "./proxy_client"; + +@Component({ + selector: "adb-proxy", + template: ` +
+
Unable to connect to Winscope ADB proxy
+
+

Launch the Winscope ADB Connect proxy to capture traces directly from your browser.

+

Python 3.5+ and ADB are required.

+

Run:

+
python3
+
$ANDROID_BUILD_TOP/development/tools/winscope/adb_proxy/winscope_proxy.py
+

Or get it from the AOSP repository.

+
+
+ + +
+
+ +
+
Your local version of the ADB Connect proxy is incompatible with Winscope.
+
+

Please update the proxy to version {{ proxyVersion }}.

+

Run:

+
python3
+
$ANDROID_BUILD_TOP/development/tools/winscope/adb_proxy/winscope_proxy.py
+

Or get it from the AOSP repository.

+
+
+ + +
+
+ +
+
Proxy authorisation required
+
+

Enter Winscope proxy token:

+ + + +

The proxy token is printed to console on proxy launch, copy and paste it above.

+
+
+ +
+
+ `, + styles: [".proxy-key-field {width: 30rem}"] +}) +export class AdbProxyComponent { + readonly proxyVersion = "0.8"; + states = ProxyState; + + @Input() + status = this.states.NO_PROXY; + + @Output() + statusChange = new EventEmitter(); + + proxyKey = ""; + readonly downloadProxyUrl: string = "https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py"; + + public onEnter() { + console.log("this is the key,", this.proxyKey); + } + + public restart() { + this.status = ProxyState.START_TRACE; + this.statusChange.emit(this.status); + } + + public triggerUnauthComponent() { + this.status = ProxyState.UNAUTH; + this.statusChange.emit(this.status); + } +} diff --git a/tools/winscope-ng/src/trace_collection/collect_traces.component.spec.ts b/tools/winscope-ng/src/trace_collection/collect_traces.component.spec.ts new file mode 100644 index 000000000..381d01ff3 --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/collect_traces.component.spec.ts @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {ComponentFixture, TestBed} from "@angular/core/testing"; +import {CollectTracesComponent} from "./collect_traces.component"; + +describe("CollectTracesComponent", () => { + let fixture: ComponentFixture; + let component: CollectTracesComponent; + let htmlElement: HTMLElement; + + beforeAll(async () => { + await TestBed.configureTestingModule({ + declarations: [CollectTracesComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectTracesComponent); + component = fixture.componentInstance; + htmlElement = fixture.nativeElement; + }); + + it("can be created", () => { + expect(component).toBeTruthy(); + }); + +}); diff --git a/tools/winscope-ng/src/trace_collection/collect_traces.component.ts b/tools/winscope-ng/src/trace_collection/collect_traces.component.ts new file mode 100644 index 000000000..78702e8e3 --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/collect_traces.component.ts @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Component} from "@angular/core"; +import { ProxyState } from "./proxy_client"; + +interface TraceConfiguration { + name: string, + defaultCheck?: boolean, + config?: ConfigurationOptions +} + +export interface ConfigurationOptions { + enableConfigs: Array, + selectionConfigs: Array +} + +export interface SelectionConfiguration { + name: string, + options: Array, + value: string +} + +export type configMap = { +[key: string]: Array | string; +} + +interface Device { + authorised: boolean; + id: string; +} + + +@Component({ + selector: "collect-traces", + template: ` + Collect Traces + +
+ + + + +
+ +
+
+ + + {{ device.authorised ? "smartphone" : "screen_lock_portrait" }} + selected device name + + +
+ +
+
+ + + +
+

Trace targets:

+ +
+ +
+

Dump targets:

+
+ {{DUMPS[dumpKey]}} +
+
+
+
+ `, +}) +export class CollectTracesComponent { + objectKeys = Object.keys; + isAdbProxy = true; + startTrace = false; + startDump = false; + downloadProxyUrl = "https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py"; + + DUMPS: configMap = { + "window_dump": "Window Manager", + "layers_dump": "Surface Flinger" + }; + + wmTraceSelectionConfigs = [ + { + name: "wmbuffersize (KB)", + options: [ + "4000", + "8000", + "16000", + "32000", + ], + value: "4000" + }, + { + name: "tracingtype", + options: [ + "frame", + "transaction", + ], + value: "frame" + }, + { + name: "tracinglevel", + options: [ + "verbose", + "debug", + "critical", + ], + value: "verbose" + }, + ]; + + traceConfigurations: Array = [ + { + name: "Surface Flinger", + defaultCheck: true, + config: { + enableConfigs: ["composition","metadata","hwc","tracebuffers"], + selectionConfigs: [ + { + name: "sfbuffersize (KB)", + options: ["4000","8000","16000","32000",], + value: "4000" + } + ] + } + }, + { + name: "Window Manager", + defaultCheck: true, + config: { + enableConfigs: [], + selectionConfigs: this.wmTraceSelectionConfigs, + } + }, + { + name: "Screen Recording", + }, + { + name: "Accessibility", + }, + { + name: "Transaction", + }, + { + name: "Input Method Clients", + defaultCheck: true, + }, + { + name: "Input Method Service", + defaultCheck: true, + }, + { + name: "Input Method Manager Service", + defaultCheck: true, + }, + ]; + + states = ProxyState; + status = this.states.NO_PROXY; + + public devices(): Array { + return [ + {authorised: true, id: "1"}, + ]; + } + + public restart() { + this.status === this.states.START_TRACE; + } + + public proxySuccess() { + return this.status === this.states.START_TRACE; + } + + public resetLastDevice() { + this.restart(); + } + + public selectDevice(id: string) { + console.log("selected", id); + } + + public displayAdbProxyTab() { + this.isAdbProxy = true; + console.log("Adb Proxy options?", this.isAdbProxy); + } + + public displayWebAdbTab() { + this.isAdbProxy = false; + console.log("Web ADB options?", !this.isAdbProxy); + } + + public startTracing() { + this.startTrace = true; + console.log("begin tracing"); + } + + public dumpState() { + this.startDump = true; + console.log("begin dump"); + } + + public tabClass(adbTab: boolean) { + let isActive: string; + if (adbTab) { + isActive = this.isAdbProxy ? "active" : "inactive"; + } else { + isActive = !this.isAdbProxy ? "active" : "inactive"; + } + return ["tab", isActive]; + } +} diff --git a/tools/winscope-ng/src/trace_collection/proxy_client.ts b/tools/winscope-ng/src/trace_collection/proxy_client.ts new file mode 100644 index 000000000..93cdc7c12 --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/proxy_client.ts @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export enum ProxyState { + ERROR = 0, + CONNECTING = 1, + NO_PROXY = 2, + INVALID_VERSION = 3, + UNAUTH = 4, + DEVICES = 5, + START_TRACE = 6, + END_TRACE = 7, + LOAD_DATA = 8, +} diff --git a/tools/winscope-ng/src/trace_collection/trace_config.component.spec.ts b/tools/winscope-ng/src/trace_collection/trace_config.component.spec.ts new file mode 100644 index 000000000..c2110fa4c --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/trace_config.component.spec.ts @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {ComponentFixture, TestBed} from "@angular/core/testing"; +import {TraceConfigComponent} from "./trace_config.component"; + +describe("TraceConfigComponent", () => { + let fixture: ComponentFixture; + let component: TraceConfigComponent; + let htmlElement: HTMLElement; + + beforeAll(async () => { + await TestBed.configureTestingModule({ + declarations: [TraceConfigComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TraceConfigComponent); + component = fixture.componentInstance; + htmlElement = fixture.nativeElement; + }); + + it("can be created", () => { + expect(component).toBeTruthy(); + }); + +}); diff --git a/tools/winscope-ng/src/trace_collection/trace_config.component.ts b/tools/winscope-ng/src/trace_collection/trace_config.component.ts new file mode 100644 index 000000000..8bdac72a9 --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/trace_config.component.ts @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input, Output, EventEmitter } from "@angular/core"; +import { ProxyState } from "./proxy_client"; +import { ConfigurationOptions, SelectionConfiguration } from "./collect_traces.component"; + + +@Component({ + selector: "trace-config", + template: ` +
+ {{name}} +
+ {{enableConfig}} +
+ + {{con.name}} + + {{ option }} + + +
+
+
+ `, + styles: [".adv-config {margin-left: 5rem;}"], +}) + +export class TraceConfigComponent { + states = ProxyState; + objectKeys = Object.keys; + + @Input() + name = ""; + + @Input() + configs: ConfigurationOptions | null = null; + + @Input() + defaultCheck: boolean | undefined = false; + + @Input() + status = this.states.UNAUTH; + + @Output() + statusChange = new EventEmitter(); + + public traceEnableConfigs(): Array { + if (this.configs && this.configs.enableConfigs) { + return this.configs.enableConfigs; + } else { + return []; + } + } + + public traceSelectionConfigs(): Array { + if (this.configs) { + return this.configs.selectionConfigs; + } else { + return []; + } + } + + public restart() { + this.status = this.states.CONNECTING; + this.statusChange.emit(this.status); + } + + public resetLastDevice() { + this.restart(); + } +} diff --git a/tools/winscope-ng/src/trace_collection/web_adb.component.spec.ts b/tools/winscope-ng/src/trace_collection/web_adb.component.spec.ts new file mode 100644 index 000000000..0021a9e26 --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/web_adb.component.spec.ts @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {ComponentFixture, TestBed} from "@angular/core/testing"; +import {WebAdbComponent} from "./web_adb.component"; + +describe("WebAdbComponent", () => { + let fixture: ComponentFixture; + let component: WebAdbComponent; + let htmlElement: HTMLElement; + + beforeAll(async () => { + await TestBed.configureTestingModule({ + declarations: [WebAdbComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(WebAdbComponent); + component = fixture.componentInstance; + htmlElement = fixture.nativeElement; + }); + + it("can be created", () => { + expect(component).toBeTruthy(); + }); + + it("renders the title", () => { + const divTitle = htmlElement.querySelector(".web-adb div.title"); + expect(divTitle?.innerHTML).toContain("Unable to connect to Web ADB"); + }); + +}); diff --git a/tools/winscope-ng/src/trace_collection/web_adb.component.ts b/tools/winscope-ng/src/trace_collection/web_adb.component.ts new file mode 100644 index 000000000..9dd26ab9e --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/web_adb.component.ts @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Component} from "@angular/core"; + + +@Component({ + selector: "web-adb", + template: ` +
Unable to connect to Web ADB
+
+

Instructions for connecting via Web ADB.

+
+
+ +
+ `, +}) +export class WebAdbComponent { + public restart() { + console.log("Try connecting again"); + } +} From 88ea98f6baf301b73a7f6fa60efb128712911d30 Mon Sep 17 00:00:00 2001 From: Priyanka Patel Date: Fri, 15 Jul 2022 10:45:52 +0000 Subject: [PATCH 2/5] Migration of proxy. Migrating the proxy to the new trace collection UI. Should be able to enter the proxy token, select your connected device and then make changes to trace configrurations. Test: try running the proxy and test that functionality is same as old winscope. Bug: b/238113543 Change-Id: I195f65df5d09e22db54cb9f5a7ba0396948f43d3 --- tools/winscope-ng/adb_proxy/winscope_proxy.py | 867 +++++++++++ tools/winscope-ng/package-lock.json | 1318 +++++++++++++++-- tools/winscope-ng/package.json | 7 + tools/winscope-ng/src/app/app.component.ts | 18 +- tools/winscope-ng/src/app/app.module.ts | 9 +- .../src/common/persistent_store.ts | 23 + .../trace_collection/adb_proxy.component.ts | 57 +- .../collect_traces.component.ts | 313 ++-- .../src/trace_collection/proxy_client.ts | 141 +- .../trace_config.component.ts | 49 +- .../src/trace_collection/web_adb.component.ts | 4 +- 11 files changed, 2572 insertions(+), 234 deletions(-) create mode 100644 tools/winscope-ng/adb_proxy/winscope_proxy.py create mode 100644 tools/winscope-ng/src/common/persistent_store.ts diff --git a/tools/winscope-ng/adb_proxy/winscope_proxy.py b/tools/winscope-ng/adb_proxy/winscope_proxy.py new file mode 100644 index 000000000..b032599ab --- /dev/null +++ b/tools/winscope-ng/adb_proxy/winscope_proxy.py @@ -0,0 +1,867 @@ +#!/usr/bin/python3 + +# Copyright (C) 2019 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# This is an ADB proxy for Winscope. +# +# Requirements: python3.5 and ADB installed and in system PATH. +# +# Usage: +# run: python3 winscope_proxy.py +# + +import json +import logging +import os +import re +import secrets +import signal +import subprocess +import sys +import threading +import time +from abc import abstractmethod +from enum import Enum +from http import HTTPStatus +from http.server import HTTPServer, BaseHTTPRequestHandler +from tempfile import NamedTemporaryFile +import base64 + +# CONFIG # + +LOG_LEVEL = logging.DEBUG + +PORT = 5544 + +# Keep in sync with WINSCOPE_PROXY_VERSION in Winscope DataAdb.vue +VERSION = '0.8' + +WINSCOPE_VERSION_HEADER = "Winscope-Proxy-Version" +WINSCOPE_TOKEN_HEADER = "Winscope-Token" + +# Location to save the proxy security token +WINSCOPE_TOKEN_LOCATION = os.path.expanduser('~/.config/winscope/.token') + +# Winscope traces extensions +WINSCOPE_EXT = ".winscope" +WINSCOPE_EXT_LEGACY = ".pb" +WINSCOPE_EXTS = [WINSCOPE_EXT, WINSCOPE_EXT_LEGACY] + +# Winscope traces directory +WINSCOPE_DIR = "/data/misc/wmtrace/" + +# Max interval between the client keep-alive requests in seconds +KEEP_ALIVE_INTERVAL_S = 5 + +logging.basicConfig(stream=sys.stderr, level=LOG_LEVEL, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +log = logging.getLogger("ADBProxy") + + +class File: + def __init__(self, file, filetype) -> None: + self.file = file + self.type = filetype + + def get_filepaths(self, device_id): + return [self.file] + + def get_filetype(self): + return self.type + + +class FileMatcher: + def __init__(self, path, matcher, filetype) -> None: + self.path = path + self.matcher = matcher + self.type = filetype + + def get_filepaths(self, device_id): + matchingFiles = call_adb( + f"shell su root find {self.path} -name {self.matcher}", device_id) + + log.debug("Found file %s", matchingFiles.split('\n')[:-1]) + return matchingFiles.split('\n')[:-1] + + def get_filetype(self): + return self.type + + +class WinscopeFileMatcher(FileMatcher): + def __init__(self, path, matcher, filetype) -> None: + self.path = path + self.internal_matchers = list(map(lambda ext: FileMatcher(path, f'{matcher}{ext}', filetype), + WINSCOPE_EXTS)) + self.type = filetype + + def get_filepaths(self, device_id): + for matcher in self.internal_matchers: + files = matcher.get_filepaths(device_id) + if len(files) > 0: + return files + log.debug("No files found") + return [] + + +class TraceTarget: + """Defines a single parameter to trace. + + Attributes: + file_matchers: the matchers used to identify the paths on the device the trace results are saved to. + trace_start: command to start the trace from adb shell, must not block. + trace_stop: command to stop the trace, should block until the trace is stopped. + """ + + def __init__(self, files, trace_start: str, trace_stop: str) -> None: + if type(files) is not list: + files = [files] + self.files = files + self.trace_start = trace_start + self.trace_stop = trace_stop + +# Order of files matters as they will be expected in that order and decoded in that order +TRACE_TARGETS = { + "window_trace": TraceTarget( + WinscopeFileMatcher(WINSCOPE_DIR, "wm_trace", "window_trace"), + 'su root cmd window tracing start\necho "WM trace started."', + 'su root cmd window tracing stop >/dev/null 2>&1' + ), + "accessibility_trace": TraceTarget( + WinscopeFileMatcher("/data/misc/a11ytrace", "a11y_trace", "accessibility_trace"), + 'su root cmd accessibility start-trace\necho "Accessibility trace started."', + 'su root cmd accessibility stop-trace >/dev/null 2>&1' + ), + "layers_trace": TraceTarget( + WinscopeFileMatcher(WINSCOPE_DIR, "layers_trace", "layers_trace"), + 'su root service call SurfaceFlinger 1025 i32 1\necho "SF trace started."', + 'su root service call SurfaceFlinger 1025 i32 0 >/dev/null 2>&1' + ), + "screen_recording": TraceTarget( + File(f'/data/local/tmp/screen.mp4', "screen_recording"), + f'screenrecord --bit-rate 8M /data/local/tmp/screen.mp4 >/dev/null 2>&1 &\necho "ScreenRecorder started."', + 'pkill -l SIGINT screenrecord >/dev/null 2>&1' + ), + "transactions": TraceTarget( + WinscopeFileMatcher(WINSCOPE_DIR, "transactions_trace", "transactions"), + 'su root service call SurfaceFlinger 1041 i32 1\necho "SF transactions recording started."', + 'su root service call SurfaceFlinger 1041 i32 0 >/dev/null 2>&1' + ), + "transactions_legacy": TraceTarget( + [ + WinscopeFileMatcher(WINSCOPE_DIR, "transaction_trace", "transactions_legacy"), + FileMatcher(WINSCOPE_DIR, f'transaction_merges_*', "transaction_merges"), + ], + 'su root service call SurfaceFlinger 1020 i32 1\necho "SF transactions recording started."', + 'su root service call SurfaceFlinger 1020 i32 0 >/dev/null 2>&1' + ), + "proto_log": TraceTarget( + WinscopeFileMatcher(WINSCOPE_DIR, "wm_log", "proto_log"), + 'su root cmd window logging start\necho "WM logging started."', + 'su root cmd window logging stop >/dev/null 2>&1' + ), + "ime_trace_clients": TraceTarget( + WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_clients", "ime_trace_clients"), + 'su root ime tracing start\necho "Clients IME trace started."', + 'su root ime tracing stop >/dev/null 2>&1' + ), + "ime_trace_service": TraceTarget( + WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_service", "ime_trace_service"), + 'su root ime tracing start\necho "Service IME trace started."', + 'su root ime tracing stop >/dev/null 2>&1' + ), + "ime_trace_managerservice": TraceTarget( + WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_managerservice", "ime_trace_managerservice"), + 'su root ime tracing start\necho "ManagerService IME trace started."', + 'su root ime tracing stop >/dev/null 2>&1' + ), + "wayland_trace": TraceTarget( + WinscopeFileMatcher("/data/misc/wltrace", "wl_trace", "wl_trace"), + 'su root service call Wayland 26 i32 1 >/dev/null\necho "Wayland trace started."', + 'su root service call Wayland 26 i32 0 >/dev/null' + ), +} + + +class SurfaceFlingerTraceConfig: + """Handles optional configuration for surfaceflinger traces. + """ + + def __init__(self) -> None: + # default config flags CRITICAL | INPUT | SYNC + self.flags = 1 << 0 | 1 << 1 | 1 << 6 + + def add(self, config: str) -> None: + self.flags |= CONFIG_FLAG[config] + + def is_valid(self, config: str) -> bool: + return config in CONFIG_FLAG + + def command(self) -> str: + return f'su root service call SurfaceFlinger 1033 i32 {self.flags}' + +class SurfaceFlingerTraceSelectedConfig: + """Handles optional selected configuration for surfaceflinger traces. + """ + + def __init__(self) -> None: + # defaults set for all configs + self.selectedConfigs = { + "sfbuffersize": "16000" + } + + def add(self, configType, configValue) -> None: + self.selectedConfigs[configType] = configValue + + def is_valid(self, configType) -> bool: + return configType in CONFIG_SF_SELECTION + + def setBufferSize(self) -> str: + return f'su root service call SurfaceFlinger 1029 i32 {self.selectedConfigs["sfbuffersize"]}' + +class WindowManagerTraceSelectedConfig: + """Handles optional selected configuration for windowmanager traces. + """ + + def __init__(self) -> None: + # defaults set for all configs + self.selectedConfigs = { + "wmbuffersize": "16000", + "tracinglevel": "debug", + "tracingtype": "frame", + } + + def add(self, configType, configValue) -> None: + self.selectedConfigs[configType] = configValue + + def is_valid(self, configType) -> bool: + return configType in CONFIG_WM_SELECTION + + def setBufferSize(self) -> str: + return f'su root cmd window tracing size {self.selectedConfigs["wmbuffersize"]}' + + def setTracingLevel(self) -> str: + return f'su root cmd window tracing level {self.selectedConfigs["tracinglevel"]}' + + def setTracingType(self) -> str: + return f'su root cmd window tracing {self.selectedConfigs["tracingtype"]}' + + +CONFIG_FLAG = { + "composition": 1 << 2, + "metadata": 1 << 3, + "hwc": 1 << 4, + "tracebuffers": 1 << 5 +} + +#Keep up to date with options in DataAdb.vue +CONFIG_SF_SELECTION = [ + "sfbuffersize", +] + +#Keep up to date with options in DataAdb.vue +CONFIG_WM_SELECTION = [ + "wmbuffersize", + "tracingtype", + "tracinglevel", +] + +class DumpTarget: + """Defines a single parameter to trace. + + Attributes: + file: the path on the device the dump results are saved to. + dump_command: command to dump state to file. + """ + + def __init__(self, files, dump_command: str) -> None: + if type(files) is not list: + files = [files] + self.files = files + self.dump_command = dump_command + + +DUMP_TARGETS = { + "window_dump": DumpTarget( + File(f'/data/local/tmp/wm_dump{WINSCOPE_EXT}', "window_dump"), + f'su root dumpsys window --proto > /data/local/tmp/wm_dump{WINSCOPE_EXT}' + ), + "layers_dump": DumpTarget( + File(f'/data/local/tmp/sf_dump{WINSCOPE_EXT}', "layers_dump"), + f'su root dumpsys SurfaceFlinger --proto > /data/local/tmp/sf_dump{WINSCOPE_EXT}' + ) +} + + +# END OF CONFIG # + + +def get_token() -> str: + """Returns saved proxy security token or creates new one""" + try: + with open(WINSCOPE_TOKEN_LOCATION, 'r') as token_file: + token = token_file.readline() + log.debug("Loaded token {} from {}".format( + token, WINSCOPE_TOKEN_LOCATION)) + return token + except IOError: + token = secrets.token_hex(32) + os.makedirs(os.path.dirname(WINSCOPE_TOKEN_LOCATION), exist_ok=True) + try: + with open(WINSCOPE_TOKEN_LOCATION, 'w') as token_file: + log.debug("Created and saved token {} to {}".format( + token, WINSCOPE_TOKEN_LOCATION)) + token_file.write(token) + os.chmod(WINSCOPE_TOKEN_LOCATION, 0o600) + except IOError: + log.error("Unable to save persistent token {} to {}".format( + token, WINSCOPE_TOKEN_LOCATION)) + return token + + +secret_token = get_token() + + +class RequestType(Enum): + GET = 1 + POST = 2 + HEAD = 3 + + +def add_standard_headers(server): + server.send_header('Cache-Control', 'no-cache, no-store, must-revalidate') + server.send_header('Access-Control-Allow-Origin', '*') + server.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS') + server.send_header('Access-Control-Allow-Headers', + WINSCOPE_TOKEN_HEADER + ', Content-Type, Content-Length') + server.send_header('Access-Control-Expose-Headers', + 'Winscope-Proxy-Version') + server.send_header(WINSCOPE_VERSION_HEADER, VERSION) + server.end_headers() + + +class RequestEndpoint: + """Request endpoint to use with the RequestRouter.""" + + @abstractmethod + def process(self, server, path): + pass + + +class AdbError(Exception): + """Unsuccessful ADB operation""" + pass + + +class BadRequest(Exception): + """Invalid client request""" + pass + + +class RequestRouter: + """Handles HTTP request authentication and routing""" + + def __init__(self, handler): + self.request = handler + self.endpoints = {} + + def register_endpoint(self, method: RequestType, name: str, endpoint: RequestEndpoint): + self.endpoints[(method, name)] = endpoint + + def __bad_request(self, error: str): + log.warning("Bad request: " + error) + self.request.respond(HTTPStatus.BAD_REQUEST, b"Bad request!\nThis is Winscope ADB proxy.\n\n" + + error.encode("utf-8"), 'text/txt') + + def __internal_error(self, error: str): + log.error("Internal error: " + error) + self.request.respond(HTTPStatus.INTERNAL_SERVER_ERROR, + error.encode("utf-8"), 'text/txt') + + def __bad_token(self): + log.info("Bad token") + self.request.respond(HTTPStatus.FORBIDDEN, b"Bad Winscope authorisation token!\nThis is Winscope ADB proxy.\n", + 'text/txt') + + def process(self, method: RequestType): + token = self.request.headers[WINSCOPE_TOKEN_HEADER] + if not token or token != secret_token: + return self.__bad_token() + path = self.request.path.strip('/').split('/') + if path and len(path) > 0: + endpoint_name = path[0] + try: + return self.endpoints[(method, endpoint_name)].process(self.request, path[1:]) + except KeyError: + return self.__bad_request("Unknown endpoint /{}/".format(endpoint_name)) + except AdbError as ex: + return self.__internal_error(str(ex)) + except BadRequest as ex: + return self.__bad_request(str(ex)) + except Exception as ex: + return self.__internal_error(repr(ex)) + self.__bad_request("No endpoint specified") + + +def call_adb(params: str, device: str = None, stdin: bytes = None): + command = ['adb'] + (['-s', device] if device else []) + params.split(' ') + try: + log.debug("Call: " + ' '.join(command)) + return subprocess.check_output(command, stderr=subprocess.STDOUT, input=stdin).decode('utf-8') + except OSError as ex: + log.debug('Error executing adb command: {}\n{}'.format( + ' '.join(command), repr(ex))) + raise AdbError('Error executing adb command: {}\n{}'.format( + ' '.join(command), repr(ex))) + except subprocess.CalledProcessError as ex: + log.debug('Error executing adb command: {}\n{}'.format( + ' '.join(command), ex.output.decode("utf-8"))) + raise AdbError('Error executing adb command: adb {}\n{}'.format( + params, ex.output.decode("utf-8"))) + + +def call_adb_outfile(params: str, outfile, device: str = None, stdin: bytes = None): + try: + process = subprocess.Popen(['adb'] + (['-s', device] if device else []) + params.split(' '), stdout=outfile, + stderr=subprocess.PIPE) + _, err = process.communicate(stdin) + outfile.seek(0) + if process.returncode != 0: + log.debug('Error executing adb command: adb {}\n'.format(params) + err.decode( + 'utf-8') + '\n' + outfile.read().decode('utf-8')) + raise AdbError('Error executing adb command: adb {}\n'.format(params) + err.decode( + 'utf-8') + '\n' + outfile.read().decode('utf-8')) + except OSError as ex: + log.debug('Error executing adb command: adb {}\n{}'.format( + params, repr(ex))) + raise AdbError( + 'Error executing adb command: adb {}\n{}'.format(params, repr(ex))) + + +class CheckWaylandServiceEndpoint(RequestEndpoint): + _listDevicesEndpoint = None + + def __init__(self, listDevicesEndpoint): + self._listDevicesEndpoint = listDevicesEndpoint + + def process(self, server, path): + self._listDevicesEndpoint.process(server, path) + foundDevices = self._listDevicesEndpoint._foundDevices + + if len(foundDevices) > 1: + res = 'false' + else: + raw_res = call_adb('shell service check Wayland') + res = 'false' if 'not found' in raw_res else 'true' + server.respond(HTTPStatus.OK, res.encode("utf-8"), "text/json") + + +class ListDevicesEndpoint(RequestEndpoint): + ADB_INFO_RE = re.compile("^([A-Za-z0-9.:\\-]+)\\s+(\\w+)(.*model:(\\w+))?") + _foundDevices = None + + def process(self, server, path): + lines = list(filter(None, call_adb('devices -l').split('\n'))) + devices = {m.group(1): { + 'authorised': str(m.group(2)) != 'unauthorized', + 'model': m.group(4).replace('_', ' ') if m.group(4) else '' + } for m in [ListDevicesEndpoint.ADB_INFO_RE.match(d) for d in lines[1:]] if m} + self._foundDevices = devices + j = json.dumps(devices) + log.debug("Detected devices: " + j) + server.respond(HTTPStatus.OK, j.encode("utf-8"), "text/json") + + +class DeviceRequestEndpoint(RequestEndpoint): + def process(self, server, path): + if len(path) > 0 and re.fullmatch("[A-Za-z0-9.:\\-]+", path[0]): + self.process_with_device(server, path[1:], path[0]) + else: + raise BadRequest("Device id not specified") + + @abstractmethod + def process_with_device(self, server, path, device_id): + pass + + def get_request(self, server) -> str: + try: + length = int(server.headers["Content-Length"]) + except KeyError as err: + raise BadRequest("Missing Content-Length header\n" + str(err)) + except ValueError as err: + raise BadRequest("Content length unreadable\n" + str(err)) + return json.loads(server.rfile.read(length).decode("utf-8")) + + +class FetchFilesEndpoint(DeviceRequestEndpoint): + def process_with_device(self, server, path, device_id): + if len(path) != 1: + raise BadRequest("File not specified") + if path[0] in TRACE_TARGETS: + files = TRACE_TARGETS[path[0]].files + elif path[0] in DUMP_TARGETS: + files = DUMP_TARGETS[path[0]].files + else: + raise BadRequest("Unknown file specified") + + file_buffers = dict() + + for f in files: + file_type = f.get_filetype() + file_paths = f.get_filepaths(device_id) + + for file_path in file_paths: + with NamedTemporaryFile() as tmp: + log.debug( + f"Fetching file {file_path} from device to {tmp.name}") + call_adb_outfile('exec-out su root cat ' + + file_path, tmp, device_id) + log.debug(f"Deleting file {file_path} from device") + call_adb('shell su root rm ' + file_path, device_id) + log.debug(f"Uploading file {tmp.name}") + if file_type not in file_buffers: + file_buffers[file_type] = [] + buf = base64.encodebytes(tmp.read()).decode("utf-8") + file_buffers[file_type].append(buf) + + if (len(file_buffers) == 0): + log.error("Proxy didn't find any file to fetch") + + # server.send_header('X-Content-Type-Options', 'nosniff') + # add_standard_headers(server) + j = json.dumps(file_buffers) + server.respond(HTTPStatus.OK, j.encode("utf-8"), "text/json") + + +def check_root(device_id): + log.debug("Checking root access on {}".format(device_id)) + return int(call_adb('shell su root id -u', device_id)) == 0 + + +TRACE_THREADS = {} + + +class TraceThread(threading.Thread): + def __init__(self, device_id, command): + self._keep_alive_timer = None + self.trace_command = command + self._device_id = device_id + self.out = None, + self.err = None, + self._success = False + try: + shell = ['adb', '-s', self._device_id, 'shell'] + log.debug("Starting trace shell {}".format(' '.join(shell))) + self.process = subprocess.Popen(shell, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, stdin=subprocess.PIPE, start_new_session=True) + except OSError as ex: + raise AdbError( + 'Error executing adb command: adb shell\n{}'.format(repr(ex))) + + super().__init__() + + def timeout(self): + if self.is_alive(): + log.warning( + "Keep-alive timeout for trace on {}".format(self._device_id)) + self.end_trace() + if self._device_id in TRACE_THREADS: + TRACE_THREADS.pop(self._device_id) + + def reset_timer(self): + log.debug( + "Resetting keep-alive clock for trace on {}".format(self._device_id)) + if self._keep_alive_timer: + self._keep_alive_timer.cancel() + self._keep_alive_timer = threading.Timer( + KEEP_ALIVE_INTERVAL_S, self.timeout) + self._keep_alive_timer.start() + + def end_trace(self): + if self._keep_alive_timer: + self._keep_alive_timer.cancel() + log.debug("Sending SIGINT to the trace process on {}".format( + self._device_id)) + self.process.send_signal(signal.SIGINT) + try: + log.debug("Waiting for trace shell to exit for {}".format( + self._device_id)) + self.process.wait(timeout=5) + except TimeoutError: + log.debug( + "TIMEOUT - sending SIGKILL to the trace process on {}".format(self._device_id)) + self.process.kill() + self.join() + + def run(self): + log.debug("Trace started on {}".format(self._device_id)) + self.reset_timer() + self.out, self.err = self.process.communicate(self.trace_command) + log.debug("Trace ended on {}, waiting for cleanup".format(self._device_id)) + time.sleep(0.2) + for i in range(50): + if call_adb("shell su root cat /data/local/tmp/winscope_status", device=self._device_id) == 'TRACE_OK\n': + call_adb( + "shell su root rm /data/local/tmp/winscope_status", device=self._device_id) + log.debug("Trace finished successfully on {}".format( + self._device_id)) + self._success = True + break + log.debug("Still waiting for cleanup on {}".format(self._device_id)) + time.sleep(0.1) + + def success(self): + return self._success + + +class StartTrace(DeviceRequestEndpoint): + TRACE_COMMAND = """ +set -e + +echo "Starting trace..." +echo "TRACE_START" > /data/local/tmp/winscope_status + +# Do not print anything to stdout/stderr in the handler +function stop_trace() {{ + trap - EXIT HUP INT + +{} + + echo "TRACE_OK" > /data/local/tmp/winscope_status +}} + +trap stop_trace EXIT HUP INT +echo "Signal handler registered." + +{} + +# ADB shell does not handle hung up well and does not call HUP handler when a child is active in foreground, +# as a workaround we sleep for short intervals in a loop so the handler is called after a sleep interval. +while true; do sleep 0.1; done +""" + + def process_with_device(self, server, path, device_id): + try: + requested_types = self.get_request(server) + requested_traces = [TRACE_TARGETS[t] for t in requested_types] + except KeyError as err: + raise BadRequest("Unsupported trace target\n" + str(err)) + if device_id in TRACE_THREADS: + log.warning("Trace already in progress for {}", device_id) + server.respond(HTTPStatus.OK, b'', "text/plain") + if not check_root(device_id): + raise AdbError( + "Unable to acquire root privileges on the device - check the output of 'adb -s {} shell su root id'".format( + device_id)) + command = StartTrace.TRACE_COMMAND.format( + '\n'.join([t.trace_stop for t in requested_traces]), + '\n'.join([t.trace_start for t in requested_traces])) + log.debug("Trace requested for {} with targets {}".format( + device_id, ','.join(requested_types))) + TRACE_THREADS[device_id] = TraceThread( + device_id, command.encode('utf-8')) + TRACE_THREADS[device_id].start() + server.respond(HTTPStatus.OK, b'', "text/plain") + + +class EndTrace(DeviceRequestEndpoint): + def process_with_device(self, server, path, device_id): + if device_id not in TRACE_THREADS: + raise BadRequest("No trace in progress for {}".format(device_id)) + if TRACE_THREADS[device_id].is_alive(): + TRACE_THREADS[device_id].end_trace() + + success = TRACE_THREADS[device_id].success() + out = TRACE_THREADS[device_id].out + \ + b"\n" + TRACE_THREADS[device_id].err + command = TRACE_THREADS[device_id].trace_command + TRACE_THREADS.pop(device_id) + if success: + server.respond(HTTPStatus.OK, out, "text/plain") + else: + raise AdbError( + "Error tracing the device\n### Output ###\n" + out.decode( + "utf-8") + "\n### Command: adb -s {} shell ###\n### Input ###\n".format(device_id) + command.decode( + "utf-8")) + + +def execute_command(server, device_id, shell, configType, configValue): + process = subprocess.Popen(shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdin=subprocess.PIPE, start_new_session=True) + log.debug(f"Changing trace config on device {device_id} {configType}:{configValue}") + out, err = process.communicate(configValue.encode('utf-8')) + if process.returncode != 0: + raise AdbError( + f"Error executing command:\n {configValue}\n\n### OUTPUT ###{out.decode('utf-8')}\n{err.decode('utf-8')}") + log.debug(f"Changing trace config finished on device {device_id}") + server.respond(HTTPStatus.OK, b'', "text/plain") + + +class ConfigTrace(DeviceRequestEndpoint): + def process_with_device(self, server, path, device_id): + try: + requested_configs = self.get_request(server) + config = SurfaceFlingerTraceConfig() + for requested_config in requested_configs: + if not config.is_valid(requested_config): + raise BadRequest( + f"Unsupported config {requested_config}\n") + config.add(requested_config) + except KeyError as err: + raise BadRequest("Unsupported trace target\n" + str(err)) + if device_id in TRACE_THREADS: + BadRequest(f"Trace in progress for {device_id}") + if not check_root(device_id): + raise AdbError( + f"Unable to acquire root privileges on the device - check the output of 'adb -s {device_id} shell su root id'") + command = config.command() + shell = ['adb', '-s', device_id, 'shell'] + log.debug(f"Starting shell {' '.join(shell)}") + execute_command(server, device_id, shell, "sf buffer size", command) + + +def add_selected_request_to_config(self, server, device_id, config): + try: + requested_configs = self.get_request(server) + for requested_config in requested_configs: + if config.is_valid(requested_config): + config.add(requested_config, requested_configs[requested_config]) + else: + raise BadRequest( + f"Unsupported config {requested_config}\n") + except KeyError as err: + raise BadRequest("Unsupported trace target\n" + str(err)) + if device_id in TRACE_THREADS: + BadRequest(f"Trace in progress for {device_id}") + if not check_root(device_id): + raise AdbError( + f"Unable to acquire root privileges on the device - check the output of 'adb -s {device_id} shell su root id'") + return config + + +class SurfaceFlingerSelectedConfigTrace(DeviceRequestEndpoint): + def process_with_device(self, server, path, device_id): + config = SurfaceFlingerTraceSelectedConfig() + config = add_selected_request_to_config(self, server, device_id, config) + setBufferSize = config.setBufferSize() + shell = ['adb', '-s', device_id, 'shell'] + log.debug(f"Starting shell {' '.join(shell)}") + execute_command(server, device_id, shell, "sf buffer size", setBufferSize) + + +class WindowManagerSelectedConfigTrace(DeviceRequestEndpoint): + def process_with_device(self, server, path, device_id): + config = WindowManagerTraceSelectedConfig() + config = add_selected_request_to_config(self, server, device_id, config) + setBufferSize = config.setBufferSize() + setTracingType = config.setTracingType() + setTracingLevel = config.setTracingLevel() + shell = ['adb', '-s', device_id, 'shell'] + log.debug(f"Starting shell {' '.join(shell)}") + execute_command(server, device_id, shell, "wm buffer size", setBufferSize) + execute_command(server, device_id, shell, "tracing type", setTracingType) + execute_command(server, device_id, shell, "tracing level", setTracingLevel) + + +class StatusEndpoint(DeviceRequestEndpoint): + def process_with_device(self, server, path, device_id): + if device_id not in TRACE_THREADS: + raise BadRequest("No trace in progress for {}".format(device_id)) + TRACE_THREADS[device_id].reset_timer() + server.respond(HTTPStatus.OK, str( + TRACE_THREADS[device_id].is_alive()).encode("utf-8"), "text/plain") + + +class DumpEndpoint(DeviceRequestEndpoint): + def process_with_device(self, server, path, device_id): + try: + requested_types = self.get_request(server) + requested_traces = [DUMP_TARGETS[t] for t in requested_types] + except KeyError as err: + raise BadRequest("Unsupported trace target\n" + str(err)) + if device_id in TRACE_THREADS: + BadRequest("Trace in progress for {}".format(device_id)) + if not check_root(device_id): + raise AdbError( + "Unable to acquire root privileges on the device - check the output of 'adb -s {} shell su root id'" + .format(device_id)) + command = '\n'.join(t.dump_command for t in requested_traces) + shell = ['adb', '-s', device_id, 'shell'] + log.debug("Starting dump shell {}".format(' '.join(shell))) + process = subprocess.Popen(shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdin=subprocess.PIPE, start_new_session=True) + log.debug("Starting dump on device {}".format(device_id)) + out, err = process.communicate(command.encode('utf-8')) + if process.returncode != 0: + raise AdbError("Error executing command:\n" + command + "\n\n### OUTPUT ###" + out.decode('utf-8') + "\n" + + err.decode('utf-8')) + log.debug("Dump finished on device {}".format(device_id)) + server.respond(HTTPStatus.OK, b'', "text/plain") + + +class ADBWinscopeProxy(BaseHTTPRequestHandler): + def __init__(self, request, client_address, server): + self.router = RequestRouter(self) + listDevicesEndpoint = ListDevicesEndpoint() + self.router.register_endpoint( + RequestType.GET, "devices", listDevicesEndpoint) + self.router.register_endpoint( + RequestType.GET, "status", StatusEndpoint()) + self.router.register_endpoint( + RequestType.GET, "fetch", FetchFilesEndpoint()) + self.router.register_endpoint(RequestType.POST, "start", StartTrace()) + self.router.register_endpoint(RequestType.POST, "end", EndTrace()) + self.router.register_endpoint(RequestType.POST, "dump", DumpEndpoint()) + self.router.register_endpoint( + RequestType.POST, "configtrace", ConfigTrace()) + self.router.register_endpoint( + RequestType.POST, "selectedsfconfigtrace", SurfaceFlingerSelectedConfigTrace()) + self.router.register_endpoint( + RequestType.POST, "selectedwmconfigtrace", WindowManagerSelectedConfigTrace()) + self.router.register_endpoint( + RequestType.GET, "checkwayland", CheckWaylandServiceEndpoint(listDevicesEndpoint)) + super().__init__(request, client_address, server) + + def respond(self, code: int, data: bytes, mime: str) -> None: + self.send_response(code) + self.send_header('Content-type', mime) + add_standard_headers(self) + self.wfile.write(data) + + def do_GET(self): + self.router.process(RequestType.GET) + + def do_POST(self): + self.router.process(RequestType.POST) + + def do_OPTIONS(self): + self.send_response(HTTPStatus.OK) + self.send_header('Allow', 'GET,POST') + add_standard_headers(self) + self.end_headers() + self.wfile.write(b'GET,POST') + + def log_request(self, code='-', size='-'): + log.info('{} {} {}'.format(self.requestline, str(code), str(size))) + + +if __name__ == '__main__': + print("Winscope ADB Connect proxy version: " + VERSION) + print('Winscope token: ' + secret_token) + httpd = HTTPServer(('localhost', PORT), ADBWinscopeProxy) + try: + httpd.serve_forever() + except KeyboardInterrupt: + log.info("Shutting down") \ No newline at end of file diff --git a/tools/winscope-ng/package-lock.json b/tools/winscope-ng/package-lock.json index abf723a2e..605e492c4 100644 --- a/tools/winscope-ng/package-lock.json +++ b/tools/winscope-ng/package-lock.json @@ -18,7 +18,12 @@ "@angular/platform-browser": "^14.0.0", "@angular/platform-browser-dynamic": "^14.0.0", "@angular/router": "^14.0.0", + "@auth0/auth0-angular": "^1.10.0", + "@ngrx/effects": "^14.0.2", + "@ngrx/store": "^14.0.2", + "@ngxs/store": "^3.7.4", "angular2-template-loader": "^0.6.2", + "auth0": "^2.42.0", "html-loader": "^3.1.0", "html-webpack-inline-source-plugin": "^1.0.0-beta.2", "html-webpack-plugin": "^5.5.0", @@ -39,7 +44,9 @@ "@angular-devkit/build-angular": "^14.0.0", "@angular/cli": "~14.0.0", "@angular/compiler-cli": "^14.0.0", + "@ngxs/devtools-plugin": "^3.7.4", "@types/jasmine": "~4.0.0", + "@types/node": "^18.0.4", "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", "eslint": "^8.19.0", @@ -710,6 +717,34 @@ "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", "dev": true }, + "node_modules/@auth0/auth0-angular": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-angular/-/auth0-angular-1.10.0.tgz", + "integrity": "sha512-ypM7PIHkWBB+DY5CqLD38VIQpjkbSfmncJxmQurqsHcpsJNIXoL3LGvv6hX+Wo9tp8kxKHHjV7RZ07JOplV90w==", + "dependencies": { + "@auth0/auth0-spa-js": "^1.22.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=9 <=14", + "@angular/core": ">=9 <=14", + "@angular/router": ">=9 <=14" + } + }, + "node_modules/@auth0/auth0-spa-js": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.22.1.tgz", + "integrity": "sha512-l0FCmiN3XubpgCtB3U0ds4h+5WQNTnIF4eLT/fudHEtcyrT65QF/03LybGVdLyuvqdIF/D6OQsfjwYw0Ms605Q==", + "dependencies": { + "abortcontroller-polyfill": "^1.7.3", + "browser-tabs-lock": "^1.2.15", + "core-js": "^3.22.6", + "es-cookie": "^1.3.2", + "fast-text-encoding": "^1.0.3", + "promise-polyfill": "^8.2.3", + "unfetch": "^4.2.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -2918,6 +2953,31 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@ngrx/effects": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-14.0.2.tgz", + "integrity": "sha512-1VBJ9eBoeCKSBDkn+wllV1gFGLBrRwq5So0F0QUX2rPcjpE5N1pdpuLAmmwQ84f8nttVlMOx4P9kcEM8qW9jLg==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/core": "^14.0.0", + "@ngrx/store": "14.0.2", + "rxjs": "^6.5.3 || ^7.5.0" + } + }, + "node_modules/@ngrx/store": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-14.0.2.tgz", + "integrity": "sha512-ogxcK7fj4zXCxCVJXzX2+jbVCfvC7ehbvbD5oWXT7GFajUIvA8FSXQzapUPAQEqFbjxgFzhjMFBApMaOcfleIQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/core": "^14.0.0", + "rxjs": "^6.5.3 || ^7.5.0" + } + }, "node_modules/@ngtools/webpack": { "version": "14.0.6", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.0.6.tgz", @@ -2934,6 +2994,51 @@ "webpack": "^5.54.0" } }, + "node_modules/@ngxs/devtools-plugin": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@ngxs/devtools-plugin/-/devtools-plugin-3.7.4.tgz", + "integrity": "sha512-aUKBMak80t606983SvBf5hnwfkWU+Wf9s3zqQD2pr5UFc2D1VIo+hmqI3oITJaKNmv3ODLwpN3af83rZq4+7zA==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ngxs" + }, + "peerDependencies": { + "@angular/core": ">=6.1.0 <15.0.0", + "@ngxs/store": "^3.7.4 || ^3.7.4-dev", + "rxjs": ">=6.5.5" + } + }, + "node_modules/@ngxs/devtools-plugin/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@ngxs/store": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-3.7.4.tgz", + "integrity": "sha512-/W+sSz4jOkS0ZlxozdQj2+fPhl2rv6Yhbuj3WDqRw++jJ+ZU29blOpfd5lgjc24EH/Dbi0ACZl5IyX1CUbJlNQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ngxs" + }, + "peerDependencies": { + "@angular/core": ">=6.1.0 <15.0.0", + "rxjs": ">=6.5.5" + } + }, + "node_modules/@ngxs/store/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3141,7 +3246,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, "engines": { "node": ">= 6" } @@ -3150,7 +3254,6 @@ "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -3175,7 +3278,6 @@ "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -3229,7 +3331,6 @@ "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.18", @@ -3237,17 +3338,33 @@ "@types/serve-static": "*" } }, + "node_modules/@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "dependencies": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, "node_modules/@types/express-serve-static-core": { "version": "4.17.29", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz", "integrity": "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==", - "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*" } }, + "node_modules/@types/express-unless": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.3.tgz", + "integrity": "sha512-TyPLQaF6w8UlWdv4gj8i46B+INBVzURBNRahCozCSXfsK2VTlL1wNyTlMKw817VHygBtlcl5jfnPadlydr06Yw==", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -3281,8 +3398,7 @@ "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { "version": "18.0.4", @@ -3304,14 +3420,12 @@ "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, "node_modules/@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/retry": { "version": "0.12.0", @@ -3338,7 +3452,6 @@ "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -3738,6 +3851,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abortcontroller-polyfill": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3805,7 +3923,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "dependencies": { "debug": "4" }, @@ -4088,8 +4205,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/atob": { "version": "2.1.2", @@ -4103,6 +4219,44 @@ "node": ">= 4.5.0" } }, + "node_modules/auth0": { + "version": "2.42.0", + "resolved": "https://registry.npmjs.org/auth0/-/auth0-2.42.0.tgz", + "integrity": "sha512-IiA224CoqjPF9Pnx0R80wQZBGlXMia69s7lQHg328Oe2mngdp6Qi32DaIKkkVtJb8FuMleWMgI+zmbwW0znhYw==", + "dependencies": { + "axios": "^0.26.1", + "form-data": "^3.0.1", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^1.12.1", + "lru-memoizer": "^2.1.4", + "rest-facade": "^1.16.3", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/auth0/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/auth0/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/autoprefixer": { "version": "10.4.7", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", @@ -4151,6 +4305,14 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, "node_modules/babel-loader": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", @@ -4412,6 +4574,15 @@ "node": ">=8" } }, + "node_modules/browser-tabs-lock": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz", + "integrity": "sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==", + "hasInstallScript": true, + "dependencies": { + "lodash": ">=4.17.21" + } + }, "node_modules/browserslist": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.2.tgz", @@ -4506,6 +4677,11 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4562,7 +4738,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -4633,6 +4808,68 @@ "node": ">=4" } }, + "node_modules/change-case": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-2.3.1.tgz", + "integrity": "sha512-3HE5jrTqqn9jeKzD0+yWi7FU4OMicLbwB57ph4bpwEn5jGi3hZug5WjZjnBD2RY7YyTKAAck86ACfShXUWJKLg==", + "dependencies": { + "camel-case": "^1.1.1", + "constant-case": "^1.1.0", + "dot-case": "^1.1.0", + "is-lower-case": "^1.1.0", + "is-upper-case": "^1.1.0", + "lower-case": "^1.1.1", + "lower-case-first": "^1.0.0", + "param-case": "^1.1.0", + "pascal-case": "^1.1.0", + "path-case": "^1.1.0", + "sentence-case": "^1.1.1", + "snake-case": "^1.1.0", + "swap-case": "^1.1.0", + "title-case": "^1.1.0", + "upper-case": "^1.1.1", + "upper-case-first": "^1.1.0" + } + }, + "node_modules/change-case/node_modules/camel-case": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-1.2.2.tgz", + "integrity": "sha512-rUug78lL8mqStaLehmH2F0LxMJ2TM9fnPFxb+gFkgyUjUM/1o2wKTQtalypHnkb2cFwH/DENBw7YEAOYLgSMxQ==", + "dependencies": { + "sentence-case": "^1.1.1", + "upper-case": "^1.1.1" + } + }, + "node_modules/change-case/node_modules/dot-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-1.1.2.tgz", + "integrity": "sha512-NzEIt12UjECXi6JZ/R/nBey6EE1qCN0yUTEFaPIaKW0AcOEwlKqujtcJVbtSfLNnj3CDoXLQyli79vAaqohyvw==", + "dependencies": { + "sentence-case": "^1.1.2" + } + }, + "node_modules/change-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, + "node_modules/change-case/node_modules/param-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-1.1.2.tgz", + "integrity": "sha512-gksk6zeZQxwBm1AHsKh+XDFsTGf1LvdZSkkpSIkfDtzW+EQj/P2PBgNb3Cs0Y9Xxqmbciv2JZe3fWU6Xbher+Q==", + "dependencies": { + "sentence-case": "^1.1.2" + } + }, + "node_modules/change-case/node_modules/pascal-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-1.1.2.tgz", + "integrity": "sha512-QWlbdQHdKWlcyTEuv/M0noJtlCa7qTmg5QFAqhx5X9xjAfCU1kXucL+rcOmd2HliESuRLIOz8521RAW/yhuQog==", + "dependencies": { + "camel-case": "^1.1.1", + "upper-case-first": "^1.1.0" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -4810,7 +5047,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4835,8 +5071,7 @@ "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "node_modules/compressible": { "version": "2.0.18", @@ -4943,6 +5178,15 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, + "node_modules/constant-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-1.1.2.tgz", + "integrity": "sha512-FQ/HuOuSnX6nIF8OnofRWj+KnOpGAHXQpOKHmsL1sAnuLwu6r5mHGK+mJc0SkHkbmNfcU/SauqXLTEOL1JQfJA==", + "dependencies": { + "snake-case": "^1.1.0", + "upper-case": "^1.1.1" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -5008,6 +5252,11 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + }, "node_modules/copy-anything": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", @@ -5119,6 +5368,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/core-js": { + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.23.5.tgz", + "integrity": "sha512-7Vh11tujtAZy82da4duVreQysIoO2EvVrur7y6IzZkH1IHPSekuDi8Vuw1+YKjkbfWLRD7Nc9ICQ/sIUDutcyg==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-js-compat": { "version": "3.23.4", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.23.4.tgz", @@ -5450,7 +5709,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -5487,6 +5745,14 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -5638,7 +5904,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -5821,6 +6086,14 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5982,6 +6255,11 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==" + }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -7022,6 +7300,16 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/fast-text-encoding": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", + "integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ==" + }, "node_modules/fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -7183,7 +7471,6 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "dev": true, "funding": [ { "type": "individual", @@ -7222,6 +7509,15 @@ "node": ">= 0.12" } }, + "node_modules/formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7357,7 +7653,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -7576,7 +7871,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -7827,7 +8121,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, "dependencies": { "@tootallnate/once": "1", "agent-base": "6", @@ -7880,7 +8173,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -8077,8 +8369,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "3.0.0", @@ -8297,6 +8588,19 @@ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true }, + "node_modules/is-lower-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", + "integrity": "sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==", + "dependencies": { + "lower-case": "^1.1.0" + } + }, + "node_modules/is-lower-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8391,6 +8695,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-upper-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", + "integrity": "sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==", + "dependencies": { + "upper-case": "^1.1.0" + } + }, "node_modules/is-what": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", @@ -8695,6 +9007,35 @@ "node >= 0.2.0" ] }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -8746,6 +9087,50 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.12.3.tgz", + "integrity": "sha512-cFipFDeYYaO9FhhYJcZWX/IyZgc0+g316rcHnDpT2dNRNIE/lMOmWKKqp09TkJoYlNFzrEVODsR4GgXJMgWhnA==", + "dependencies": { + "@types/express-jwt": "0.0.42", + "axios": "^0.21.1", + "debug": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "jsonwebtoken": "^8.5.1", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.2", + "ms": "^2.1.2", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/jwks-rsa/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/karma": { "version": "6.3.20", "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.20.tgz", @@ -9180,6 +9565,11 @@ "immediate": "~3.0.5" } }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9223,18 +9613,63 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -9355,6 +9790,19 @@ "tslib": "^2.0.3" } }, + "node_modules/lower-case-first": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", + "integrity": "sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==", + "dependencies": { + "lower-case": "^1.1.2" + } + }, + "node_modules/lower-case-first/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, "node_modules/lru-cache": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.0.tgz", @@ -9364,6 +9812,29 @@ "node": ">=12" } }, + "node_modules/lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, "node_modules/magic-string": { "version": "0.26.1", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.1.tgz", @@ -9590,7 +10061,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -9611,7 +10081,6 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, "bin": { "mime": "cli.js" }, @@ -9824,8 +10293,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -10338,7 +10806,6 @@ "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10767,6 +11234,14 @@ "tslib": "^2.0.3" } }, + "node_modules/path-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-1.1.2.tgz", + "integrity": "sha512-2snAGA6xVRqTuTPa40bn0iEpYtVK6gEqeyS/63dqpm5pGlesOv6EmRcnB9Rr6eAnAC2Wqlbz0tqgJZryttxhxg==", + "dependencies": { + "sentence-case": "^1.1.2" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -11605,6 +12080,11 @@ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true }, + "node_modules/promise-polyfill": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz", + "integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg==" + }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -11971,6 +12451,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -11978,6 +12463,11 @@ "dev": true, "optional": true }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -12015,7 +12505,6 @@ "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, "dependencies": { "side-channel": "^1.0.4" }, @@ -12119,7 +12608,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -12414,6 +12902,25 @@ "node": ">=0.10.0" } }, + "node_modules/rest-facade": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/rest-facade/-/rest-facade-1.16.3.tgz", + "integrity": "sha512-9BQTPLiwg23XZwcWi0ys1wTizfc//0b2G3U6vBWcgqh56ozs2K6CD+Jw4DYcw3AqdPQN7jj8nzRUcUXFVGzb0Q==", + "dependencies": { + "change-case": "^2.3.0", + "deepmerge": "^3.2.0", + "lodash.get": "^4.4.2", + "superagent": "^5.1.1" + }, + "peerDependencies": { + "superagent-proxy": "^3.0.0" + }, + "peerDependenciesMeta": { + "superagent-proxy": { + "optional": true + } + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -12906,6 +13413,19 @@ "node": ">= 0.8" } }, + "node_modules/sentence-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-1.1.3.tgz", + "integrity": "sha512-laa/UDTPXsrQnoN/Kc8ZO7gTeEjMsuPiDgUCk9N0iINRZvqAMCTXjGl8+tD27op1eF/JHbdUlEUmovDh6AX7sA==", + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/sentence-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -13050,7 +13570,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -13085,6 +13604,14 @@ "npm": ">= 3.0.0" } }, + "node_modules/snake-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-1.1.2.tgz", + "integrity": "sha512-oapUKC+qulnUIN+/O7Tbl2msi9PQvJeivGN9RNbygxzI2EOY0gA96i8BJLYnGUWSLGcYtyW4YYqnGTZEySU/gg==", + "dependencies": { + "sentence-case": "^1.1.2" + } + }, "node_modules/socket.io": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", @@ -13384,7 +13911,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -13393,7 +13919,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -13554,6 +14079,41 @@ "node": "*" } }, + "node_modules/superagent": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.3.1.tgz", + "integrity": "sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==", + "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at .", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + }, + "engines": { + "node": ">= 7.0.0" + } + }, + "node_modules/superagent/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -13577,6 +14137,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swap-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", + "integrity": "sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==", + "dependencies": { + "lower-case": "^1.1.1", + "upper-case": "^1.1.1" + } + }, + "node_modules/swap-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -13796,6 +14370,15 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/title-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-1.1.2.tgz", + "integrity": "sha512-xYbo5Um5MBgn24xJSK+x5hZ8ehuGXTVhgx32KJCThHRHwpyIb1lmABi1DH5VvN9E7rNEquPjz//rF/tZQd7mjQ==", + "dependencies": { + "sentence-case": "^1.1.1", + "upper-case": "^1.0.3" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -14067,6 +14650,11 @@ "node": "*" } }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -14168,6 +14756,19 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==" + }, + "node_modules/upper-case-first": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", + "integrity": "sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==", + "dependencies": { + "upper-case": "^1.1.1" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -14179,8 +14780,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utila": { "version": "0.4.0", @@ -15454,6 +16054,29 @@ "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", "dev": true }, + "@auth0/auth0-angular": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-angular/-/auth0-angular-1.10.0.tgz", + "integrity": "sha512-ypM7PIHkWBB+DY5CqLD38VIQpjkbSfmncJxmQurqsHcpsJNIXoL3LGvv6hX+Wo9tp8kxKHHjV7RZ07JOplV90w==", + "requires": { + "@auth0/auth0-spa-js": "^1.22.0", + "tslib": "^2.0.0" + } + }, + "@auth0/auth0-spa-js": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.22.1.tgz", + "integrity": "sha512-l0FCmiN3XubpgCtB3U0ds4h+5WQNTnIF4eLT/fudHEtcyrT65QF/03LybGVdLyuvqdIF/D6OQsfjwYw0Ms605Q==", + "requires": { + "abortcontroller-polyfill": "^1.7.3", + "browser-tabs-lock": "^1.2.15", + "core-js": "^3.22.6", + "es-cookie": "^1.3.2", + "fast-text-encoding": "^1.0.3", + "promise-polyfill": "^8.2.3", + "unfetch": "^4.2.0" + } + }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -17010,6 +17633,22 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "@ngrx/effects": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-14.0.2.tgz", + "integrity": "sha512-1VBJ9eBoeCKSBDkn+wllV1gFGLBrRwq5So0F0QUX2rPcjpE5N1pdpuLAmmwQ84f8nttVlMOx4P9kcEM8qW9jLg==", + "requires": { + "tslib": "^2.0.0" + } + }, + "@ngrx/store": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-14.0.2.tgz", + "integrity": "sha512-ogxcK7fj4zXCxCVJXzX2+jbVCfvC7ehbvbD5oWXT7GFajUIvA8FSXQzapUPAQEqFbjxgFzhjMFBApMaOcfleIQ==", + "requires": { + "tslib": "^2.0.0" + } + }, "@ngtools/webpack": { "version": "14.0.6", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-14.0.6.tgz", @@ -17017,6 +17656,38 @@ "dev": true, "requires": {} }, + "@ngxs/devtools-plugin": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@ngxs/devtools-plugin/-/devtools-plugin-3.7.4.tgz", + "integrity": "sha512-aUKBMak80t606983SvBf5hnwfkWU+Wf9s3zqQD2pr5UFc2D1VIo+hmqI3oITJaKNmv3ODLwpN3af83rZq4+7zA==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@ngxs/store": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@ngxs/store/-/store-3.7.4.tgz", + "integrity": "sha512-/W+sSz4jOkS0ZlxozdQj2+fPhl2rv6Yhbuj3WDqRw++jJ+ZU29blOpfd5lgjc24EH/Dbi0ACZl5IyX1CUbJlNQ==", + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -17185,14 +17856,12 @@ "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -17217,7 +17886,6 @@ "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, "requires": { "@types/node": "*" } @@ -17271,7 +17939,6 @@ "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.18", @@ -17279,17 +17946,33 @@ "@types/serve-static": "*" } }, + "@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "requires": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, "@types/express-serve-static-core": { "version": "4.17.29", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz", "integrity": "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==", - "dev": true, "requires": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*" } }, + "@types/express-unless": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.3.tgz", + "integrity": "sha512-TyPLQaF6w8UlWdv4gj8i46B+INBVzURBNRahCozCSXfsK2VTlL1wNyTlMKw817VHygBtlcl5jfnPadlydr06Yw==", + "requires": { + "@types/express": "*" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -17323,8 +18006,7 @@ "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/node": { "version": "18.0.4", @@ -17346,14 +18028,12 @@ "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, "@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "@types/retry": { "version": "0.12.0", @@ -17380,7 +18060,6 @@ "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, "requires": { "@types/mime": "^1", "@types/node": "*" @@ -17678,6 +18357,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abortcontroller-polyfill": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz", + "integrity": "sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q==" + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -17726,7 +18410,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "requires": { "debug": "4" } @@ -17938,8 +18621,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "atob": { "version": "2.1.2", @@ -17947,6 +18629,37 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "auth0": { + "version": "2.42.0", + "resolved": "https://registry.npmjs.org/auth0/-/auth0-2.42.0.tgz", + "integrity": "sha512-IiA224CoqjPF9Pnx0R80wQZBGlXMia69s7lQHg328Oe2mngdp6Qi32DaIKkkVtJb8FuMleWMgI+zmbwW0znhYw==", + "requires": { + "axios": "^0.26.1", + "form-data": "^3.0.1", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^1.12.1", + "lru-memoizer": "^2.1.4", + "rest-facade": "^1.16.3", + "retry": "^0.13.1" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + } + } + }, "autoprefixer": { "version": "10.4.7", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.7.tgz", @@ -17973,6 +18686,14 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, + "axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "requires": { + "follow-redirects": "^1.14.8" + } + }, "babel-loader": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", @@ -18180,6 +18901,14 @@ "fill-range": "^7.0.1" } }, + "browser-tabs-lock": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz", + "integrity": "sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==", + "requires": { + "lodash": ">=4.17.21" + } + }, "browserslist": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.2.tgz", @@ -18240,6 +18969,11 @@ "ieee754": "^1.1.13" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -18290,7 +19024,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -18339,6 +19072,70 @@ "supports-color": "^5.3.0" } }, + "change-case": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-2.3.1.tgz", + "integrity": "sha512-3HE5jrTqqn9jeKzD0+yWi7FU4OMicLbwB57ph4bpwEn5jGi3hZug5WjZjnBD2RY7YyTKAAck86ACfShXUWJKLg==", + "requires": { + "camel-case": "^1.1.1", + "constant-case": "^1.1.0", + "dot-case": "^1.1.0", + "is-lower-case": "^1.1.0", + "is-upper-case": "^1.1.0", + "lower-case": "^1.1.1", + "lower-case-first": "^1.0.0", + "param-case": "^1.1.0", + "pascal-case": "^1.1.0", + "path-case": "^1.1.0", + "sentence-case": "^1.1.1", + "snake-case": "^1.1.0", + "swap-case": "^1.1.0", + "title-case": "^1.1.0", + "upper-case": "^1.1.1", + "upper-case-first": "^1.1.0" + }, + "dependencies": { + "camel-case": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-1.2.2.tgz", + "integrity": "sha512-rUug78lL8mqStaLehmH2F0LxMJ2TM9fnPFxb+gFkgyUjUM/1o2wKTQtalypHnkb2cFwH/DENBw7YEAOYLgSMxQ==", + "requires": { + "sentence-case": "^1.1.1", + "upper-case": "^1.1.1" + } + }, + "dot-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-1.1.2.tgz", + "integrity": "sha512-NzEIt12UjECXi6JZ/R/nBey6EE1qCN0yUTEFaPIaKW0AcOEwlKqujtcJVbtSfLNnj3CDoXLQyli79vAaqohyvw==", + "requires": { + "sentence-case": "^1.1.2" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, + "param-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-1.1.2.tgz", + "integrity": "sha512-gksk6zeZQxwBm1AHsKh+XDFsTGf1LvdZSkkpSIkfDtzW+EQj/P2PBgNb3Cs0Y9Xxqmbciv2JZe3fWU6Xbher+Q==", + "requires": { + "sentence-case": "^1.1.2" + } + }, + "pascal-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-1.1.2.tgz", + "integrity": "sha512-QWlbdQHdKWlcyTEuv/M0noJtlCa7qTmg5QFAqhx5X9xjAfCU1kXucL+rcOmd2HliESuRLIOz8521RAW/yhuQog==", + "requires": { + "camel-case": "^1.1.1", + "upper-case-first": "^1.1.0" + } + } + } + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -18471,7 +19268,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -18490,8 +19286,7 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "compressible": { "version": "2.0.18", @@ -18587,6 +19382,15 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true }, + "constant-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-1.1.2.tgz", + "integrity": "sha512-FQ/HuOuSnX6nIF8OnofRWj+KnOpGAHXQpOKHmsL1sAnuLwu6r5mHGK+mJc0SkHkbmNfcU/SauqXLTEOL1JQfJA==", + "requires": { + "snake-case": "^1.1.0", + "upper-case": "^1.1.1" + } + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -18631,6 +19435,11 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + }, "copy-anything": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", @@ -18703,6 +19512,11 @@ } } }, + "core-js": { + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.23.5.tgz", + "integrity": "sha512-7Vh11tujtAZy82da4duVreQysIoO2EvVrur7y6IzZkH1IHPSekuDi8Vuw1+YKjkbfWLRD7Nc9ICQ/sIUDutcyg==" + }, "core-js-compat": { "version": "3.23.4", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.23.4.tgz", @@ -18939,7 +19753,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -18962,6 +19775,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" + }, "default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", @@ -19081,8 +19899,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "delegates": { "version": "1.0.0", @@ -19225,6 +20042,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -19355,6 +20180,11 @@ "is-arrayish": "^0.2.1" } }, + "es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==" + }, "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -20043,6 +20873,16 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "fast-text-encoding": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.4.tgz", + "integrity": "sha512-x6lDDm/tBAzX9kmsPcZsNbvDs3Zey3+scsxaZElS8xWLgUMAg/oFLeewfUz0mu1CblHhhsu15jGkraldkFh8KQ==" + }, "fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", @@ -20172,8 +21012,7 @@ "follow-redirects": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "dev": true + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" }, "forever-agent": { "version": "0.6.1", @@ -20192,6 +21031,11 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==" + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -20292,7 +21136,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -20458,8 +21301,7 @@ "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-unicode": { "version": "2.0.1", @@ -20659,7 +21501,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, "requires": { "@tootallnate/once": "1", "agent-base": "6", @@ -20694,7 +21535,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "requires": { "agent-base": "6", "debug": "4" @@ -20829,8 +21669,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "3.0.0", @@ -20991,6 +21830,21 @@ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true }, + "is-lower-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", + "integrity": "sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==", + "requires": { + "lower-case": "^1.1.0" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + } + } + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -21052,6 +21906,14 @@ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, + "is-upper-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", + "integrity": "sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==", + "requires": { + "upper-case": "^1.1.0" + } + }, "is-what": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", @@ -21294,6 +22156,30 @@ "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "dev": true }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -21344,6 +22230,52 @@ } } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jwks-rsa": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.12.3.tgz", + "integrity": "sha512-cFipFDeYYaO9FhhYJcZWX/IyZgc0+g316rcHnDpT2dNRNIE/lMOmWKKqp09TkJoYlNFzrEVODsR4GgXJMgWhnA==", + "requires": { + "@types/express-jwt": "0.0.42", + "axios": "^0.21.1", + "debug": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "jsonwebtoken": "^8.5.1", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.2", + "ms": "^2.1.2", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + } + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "karma": { "version": "6.3.20", "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.20.tgz", @@ -21675,6 +22607,11 @@ "immediate": "~3.0.5" } }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -21709,18 +22646,63 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -21813,12 +22795,52 @@ "tslib": "^2.0.3" } }, + "lower-case-first": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", + "integrity": "sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==", + "requires": { + "lower-case": "^1.1.2" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + } + } + }, "lru-cache": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.0.tgz", "integrity": "sha512-SNFKDOORR41fkWP3DXiIUvXvfzDRPg3bxD1+29iRyP2ZW+Njp2o6zhx9YkEpq1tbP0AEDNW2VBUedzDIxmNhdg==", "dev": true }, + "lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + } + } + }, "magic-string": { "version": "0.26.1", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.1.tgz", @@ -22003,8 +23025,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "micromatch": { "version": "4.0.5", @@ -22018,8 +23039,7 @@ "mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" }, "mime-db": { "version": "1.52.0", @@ -22170,8 +23190,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multicast-dns": { "version": "7.2.5", @@ -22576,8 +23595,7 @@ "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "object-keys": { "version": "1.1.1", @@ -22902,6 +23920,14 @@ "tslib": "^2.0.3" } }, + "path-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-1.1.2.tgz", + "integrity": "sha512-2snAGA6xVRqTuTPa40bn0iEpYtVK6gEqeyS/63dqpm5pGlesOv6EmRcnB9Rr6eAnAC2Wqlbz0tqgJZryttxhxg==", + "requires": { + "sentence-case": "^1.1.2" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -23413,6 +24439,11 @@ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true }, + "promise-polyfill": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz", + "integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg==" + }, "promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -23706,6 +24737,11 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -23713,6 +24749,11 @@ "dev": true, "optional": true }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -23740,7 +24781,6 @@ "version": "6.10.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, "requires": { "side-channel": "^1.0.4" } @@ -23812,7 +24852,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -24048,6 +25087,17 @@ } } }, + "rest-facade": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/rest-facade/-/rest-facade-1.16.3.tgz", + "integrity": "sha512-9BQTPLiwg23XZwcWi0ys1wTizfc//0b2G3U6vBWcgqh56ozs2K6CD+Jw4DYcw3AqdPQN7jj8nzRUcUXFVGzb0Q==", + "requires": { + "change-case": "^2.3.0", + "deepmerge": "^3.2.0", + "lodash.get": "^4.4.2", + "superagent": "^5.1.1" + } + }, "restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -24414,6 +25464,21 @@ } } }, + "sentence-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-1.1.3.tgz", + "integrity": "sha512-laa/UDTPXsrQnoN/Kc8ZO7gTeEjMsuPiDgUCk9N0iINRZvqAMCTXjGl8+tD27op1eF/JHbdUlEUmovDh6AX7sA==", + "requires": { + "lower-case": "^1.1.1" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + } + } + }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -24539,7 +25604,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -24564,6 +25628,14 @@ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true }, + "snake-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-1.1.2.tgz", + "integrity": "sha512-oapUKC+qulnUIN+/O7Tbl2msi9PQvJeivGN9RNbygxzI2EOY0gA96i8BJLYnGUWSLGcYtyW4YYqnGTZEySU/gg==", + "requires": { + "sentence-case": "^1.1.2" + } + }, "socket.io": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", @@ -24810,7 +25882,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, "requires": { "safe-buffer": "~5.2.0" }, @@ -24818,8 +25889,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -24920,6 +25990,36 @@ "normalize-path": "^3.0.0" } }, + "superagent": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.3.1.tgz", + "integrity": "sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==", + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -24934,6 +26034,22 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "swap-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", + "integrity": "sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==", + "requires": { + "lower-case": "^1.1.1", + "upper-case": "^1.1.1" + }, + "dependencies": { + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + } + } + }, "symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -25095,6 +26211,15 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "title-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-1.1.2.tgz", + "integrity": "sha512-xYbo5Um5MBgn24xJSK+x5hZ8ehuGXTVhgx32KJCThHRHwpyIb1lmABi1DH5VvN9E7rNEquPjz//rF/tZQd7mjQ==", + "requires": { + "sentence-case": "^1.1.1", + "upper-case": "^1.0.3" + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -25283,6 +26408,11 @@ "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", "dev": true }, + "unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -25350,6 +26480,19 @@ "picocolors": "^1.0.0" } }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==" + }, + "upper-case-first": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", + "integrity": "sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==", + "requires": { + "upper-case": "^1.1.1" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -25361,8 +26504,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "utila": { "version": "0.4.0", diff --git a/tools/winscope-ng/package.json b/tools/winscope-ng/package.json index 1872e570b..3abcb708d 100644 --- a/tools/winscope-ng/package.json +++ b/tools/winscope-ng/package.json @@ -26,7 +26,12 @@ "@angular/platform-browser": "^14.0.0", "@angular/platform-browser-dynamic": "^14.0.0", "@angular/router": "^14.0.0", + "@auth0/auth0-angular": "^1.10.0", + "@ngrx/effects": "^14.0.2", + "@ngrx/store": "^14.0.2", + "@ngxs/store": "^3.7.4", "angular2-template-loader": "^0.6.2", + "auth0": "^2.42.0", "html-loader": "^3.1.0", "html-webpack-inline-source-plugin": "^1.0.0-beta.2", "html-webpack-plugin": "^5.5.0", @@ -47,7 +52,9 @@ "@angular-devkit/build-angular": "^14.0.0", "@angular/cli": "~14.0.0", "@angular/compiler-cli": "^14.0.0", + "@ngxs/devtools-plugin": "^3.7.4", "@types/jasmine": "~4.0.0", + "@types/node": "^18.0.4", "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", "eslint": "^8.19.0", diff --git a/tools/winscope-ng/src/app/app.component.ts b/tools/winscope-ng/src/app/app.component.ts index 717009ea1..e3afb622a 100644 --- a/tools/winscope-ng/src/app/app.component.ts +++ b/tools/winscope-ng/src/app/app.component.ts @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, Injector, Inject} from "@angular/core"; -import {createCustomElement} from "@angular/elements"; -import {ViewerWindowManagerComponent} from "viewers/viewer_window_manager/viewer_window_manager.component"; -import {Core} from "./core"; +import { Component, Injector, Inject } from "@angular/core"; +import { createCustomElement } from "@angular/elements"; +import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component"; +import { Core } from "./core"; +import { ProxyState } from "trace_collection/proxy_client"; +import { PersistentStore } from "../common/persistent_store"; @Component({ selector: "app-root", @@ -26,7 +28,7 @@ import {Core} from "./core";
- + Upload Traces @@ -56,8 +58,12 @@ export class AppComponent { title = "winscope-ng"; private core!: Core; + states = ProxyState; + store: PersistentStore = new PersistentStore(); - constructor(@Inject(Injector) injector: Injector) { + constructor( + @Inject(Injector) injector: Injector + ) { customElements.define("viewer-window-manager", createCustomElement(ViewerWindowManagerComponent, {injector})); } diff --git a/tools/winscope-ng/src/app/app.module.ts b/tools/winscope-ng/src/app/app.module.ts index 74952603e..e367e6ee8 100644 --- a/tools/winscope-ng/src/app/app.module.ts +++ b/tools/winscope-ng/src/app/app.module.ts @@ -5,7 +5,8 @@ import { MatCardModule } from "@angular/material/card"; import { MatButtonModule } from "@angular/material/button"; import { MatGridListModule } from "@angular/material/grid-list"; import { MatListModule } from "@angular/material/list"; -import {MatProgressSpinnerModule} from "@angular/material/progress-spinner"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; import { FormsModule } from "@angular/forms"; import { MatCheckboxModule } from "@angular/material/checkbox"; import { MatFormFieldModule } from "@angular/material/form-field"; @@ -13,6 +14,7 @@ import { MatIconModule } from "@angular/material/icon"; import { MatInputModule } from "@angular/material/input"; import { MatSelectModule } from "@angular/material/select"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { HttpClientModule } from "@angular/common/http"; import { AppComponent } from "./app.component"; import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component"; @@ -32,6 +34,7 @@ import { TraceConfigComponent } from "trace_collection/trace_config.component"; ], imports: [ BrowserModule, + HttpClientModule, CommonModule, MatCardModule, MatButtonModule, @@ -41,10 +44,12 @@ import { TraceConfigComponent } from "trace_collection/trace_config.component"; MatCheckboxModule, MatIconModule, MatProgressSpinnerModule, + MatProgressBarModule, MatFormFieldModule, MatInputModule, MatSelectModule, - BrowserAnimationsModule + BrowserAnimationsModule, + HttpClientModule ], providers: [], bootstrap: [AppComponent] diff --git a/tools/winscope-ng/src/common/persistent_store.ts b/tools/winscope-ng/src/common/persistent_store.ts new file mode 100644 index 000000000..4ca5aaeb0 --- /dev/null +++ b/tools/winscope-ng/src/common/persistent_store.ts @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export class PersistentStore { + public addToStore(key: string, value: string) { + localStorage.setItem(key, value); + } + public getFromStore(key: string) { + return localStorage.getItem(key); + } +} \ No newline at end of file diff --git a/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts b/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts index 35bcc455c..1009b89bd 100644 --- a/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts +++ b/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts @@ -14,13 +14,17 @@ * limitations under the License. */ import { Component, Input, Output, EventEmitter } from "@angular/core"; -import { ProxyState } from "./proxy_client"; +import { ProxyClient, ProxyState } from "./proxy_client"; + @Component({ selector: "adb-proxy", template: ` -
-
Unable to connect to Winscope ADB proxy
+
+
+ error + Unable to connect to Winscope ADB proxy +

Launch the Winscope ADB Connect proxy to capture traces directly from your browser.

Python 3.5+ and ADB are required.

@@ -33,12 +37,15 @@ import { ProxyState } from "./proxy_client"; - +
-
-
Your local version of the ADB Connect proxy is incompatible with Winscope.
+
+
+ update + Your local proxy version is incompatible with Winscope. +

Please update the proxy to version {{ proxyVersion }}.

Run:

@@ -54,12 +61,15 @@ import { ProxyState } from "./proxy_client";
-
-
Proxy authorisation required
+
+
+ lock + Proxy authorisation required +

Enter Winscope proxy token:

- +

The proxy token is printed to console on proxy launch, copy and paste it above.

@@ -68,32 +78,31 @@ import { ProxyState } from "./proxy_client";
`, - styles: [".proxy-key-field {width: 30rem}"] + styles: [".proxy-key-field {width: 30rem}", ".icon-message {vertical-align: middle;}"] }) export class AdbProxyComponent { - readonly proxyVersion = "0.8"; - states = ProxyState; - @Input() - status = this.states.NO_PROXY; + proxy: any = {VERSION: 0.8}; @Output() - statusChange = new EventEmitter(); + proxyChange = new EventEmitter(); - proxyKey = ""; + @Output() + addKey = new EventEmitter(); + + states = ProxyState; + proxyKeyItem = ""; + readonly proxyVersion = this.proxy.VERSION; readonly downloadProxyUrl: string = "https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py"; - public onEnter() { - console.log("this is the key,", this.proxyKey); - } - public restart() { - this.status = ProxyState.START_TRACE; - this.statusChange.emit(this.status); + this.addKey.emit(this.proxyKeyItem); + this.proxy.setState(this.states.CONNECTING); + this.proxyChange.emit(this.proxy); } public triggerUnauthComponent() { - this.status = ProxyState.UNAUTH; - this.statusChange.emit(this.status); + this.proxy.setState(this.states.UNAUTH); + this.proxyChange.emit(this.proxy); } } diff --git a/tools/winscope-ng/src/trace_collection/collect_traces.component.ts b/tools/winscope-ng/src/trace_collection/collect_traces.component.ts index 78702e8e3..12a1fd52a 100644 --- a/tools/winscope-ng/src/trace_collection/collect_traces.component.ts +++ b/tools/winscope-ng/src/trace_collection/collect_traces.component.ts @@ -1,20 +1,6 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Component} from "@angular/core"; -import { ProxyState } from "./proxy_client"; +import {Component, Input, OnInit } from "@angular/core"; +import { ProxyState, proxyClient, ProxyEndpoint } from "./proxy_client"; +import { PersistentStore } from "../common/persistent_store"; interface TraceConfiguration { name: string, @@ -37,32 +23,48 @@ export type configMap = { [key: string]: Array | string; } + interface Device { authorised: boolean; - id: string; + model: string; } - @Component({ selector: "collect-traces", template: ` Collect Traces -
+
Connecting...
+ +
- +
-
-
- - - {{ device.authorised ? "smartphone" : "screen_lock_portrait" }} - selected device name +
+
{{ devices().length > 0 ? "Connected devices:" : "No devices detected" }}
+ + + + {{ proxy.devices[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }} + + + {{ proxy.devices[deviceId].authorised ? proxy.devices[deviceId].model : "unauthorised" }} ({{ deviceId }}) + +
+ +
+
+ + + smartphone + {{ selectedDevice().model }} ({{ proxy.selectedDevice }}) + +
@@ -77,26 +79,220 @@ interface Device { [name]="trace.name" [defaultCheck]="trace.defaultCheck" [configs]="trace.config ? trace.config : null" - [(status)]="status" + [(proxy)]="proxy" >

Dump targets:

- {{DUMPS[dumpKey]}} + {{DUMPS[dumpKey]}} +
+ +
+ error + Error: +
+          {{ errorText }}
+      
+ +
+ +
+ Tracing... + +
+          {{ errorText }}
+      
+ +
+ +
+ Loading data... + +
+ `, + styles: [".device-choice {cursor: pointer}"] }) -export class CollectTracesComponent { +export class CollectTracesComponent implements OnInit { objectKeys = Object.keys; isAdbProxy = true; startTrace = false; startDump = false; + errorText = ""; + proxy: any = null; downloadProxyUrl = "https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py"; + states = ProxyState; + notConnected = [ + this.states.NO_PROXY, + this.states.UNAUTH, + this.states.INVALID_VERSION, + ]; + + constructor() { + this.proxy = proxyClient; + } + + @Input() + store: PersistentStore = new PersistentStore(); + + ngOnInit(): void { + this.proxy.setState(ProxyState.CONNECTING); + this.proxy.onProxyChange(this.onProxyChange); + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.has("token")) { + this.proxy.proxyKey = urlParams.get("token")!; + } + this.proxy.getDevices(); + } + + ngOnDestroy(): void { + this.proxy.removeOnProxyChange(this.onProxyChange); + } + + public onAddKey(key: string) { + this.store.addToStore("adb.proxyKey", key); + proxyClient.proxyKey = key; + this.restart(); + } + + public adbSuccess() { + return !this.notConnected.includes(this.proxy.state); + } + + public onProxyChange(newState: ProxyState, errorText: string) { + if (newState === ProxyState.CONNECTING) { + proxyClient.getDevices(); + } + if (newState == ProxyState.START_TRACE) { + proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request: any,view:any) { + try { + if(request.responseText == "true") { + //TODO: add this function + //view.appendOptionalTraces('arc', view.TRACES); + } + } catch(err) { + console.error(err); + proxyClient.setState(ProxyState.ERROR, request.responseText); + } + }); + } + } + + public devices(): Array { + return Object.keys(this.proxy.devices); + } + + public selectedDevice(): Device { + return this.proxy.devices[this.proxy.selectedDevice]; + } + + public restart() { + this.proxy.setState(this.states.CONNECTING); + } + + public resetLastDevice() { + this.proxy.resetLastDevice(); + this.restart(); + } + + public selectDevice(id: string) { + this.proxy.selectDevice(id); + } + + public displayAdbProxyTab() { + this.isAdbProxy = true; + } + + public displayWebAdbTab() { + this.isAdbProxy = false; + } + + public startTracing() { + this.startTrace = true; + console.log("begin tracing"); + } + + public dumpState() { + this.startDump = true; + console.log("begin dump"); + } + + public endTrace() { + console.log("end trace"); + } + + public setAvailableTraces() { + this.DYNAMIC_TRACES = this.TRACES["default"]; + proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request:any, view:any) { + try { + if(request.responseText == "true") { + //TODO: add this function + //view.appendOptionalTraces('arc'); + } + } catch(err) { + console.error(err); + proxyClient.setState(ProxyState.ERROR, request.responseText); + } + }); + } + + public tabClass(adbTab: boolean) { + let isActive: string; + if (adbTab) { + isActive = this.isAdbProxy ? "active" : "inactive"; + } else { + isActive = !this.isAdbProxy ? "active" : "inactive"; + } + return ["tab", isActive]; + } + + TRACES = { + "default": { + "window_trace": { + name: "Window Manager", + }, + "accessibility_trace": { + name: "Accessibility", + }, + "layers_trace": { + name: "Surface Flinger", + }, + "transactions": { + name: "Transaction", + }, + "proto_log": { + name: "ProtoLog", + }, + "screen_recording": { + name: "Screen Recording", + }, + "ime_trace_clients": { + name: "Input Method Clients", + }, + "ime_trace_service": { + name: "Input Method Service", + }, + "ime_trace_managerservice": { + name: "Input Method Manager Service", + }, + }, + "arc": { + "wayland_trace": { + name: "Wayland", + }, + }, + }; + + DYNAMIC_TRACES: any = null; DUMPS: configMap = { "window_dump": "Window Manager", @@ -178,59 +374,4 @@ export class CollectTracesComponent { defaultCheck: true, }, ]; - - states = ProxyState; - status = this.states.NO_PROXY; - - public devices(): Array { - return [ - {authorised: true, id: "1"}, - ]; - } - - public restart() { - this.status === this.states.START_TRACE; - } - - public proxySuccess() { - return this.status === this.states.START_TRACE; - } - - public resetLastDevice() { - this.restart(); - } - - public selectDevice(id: string) { - console.log("selected", id); - } - - public displayAdbProxyTab() { - this.isAdbProxy = true; - console.log("Adb Proxy options?", this.isAdbProxy); - } - - public displayWebAdbTab() { - this.isAdbProxy = false; - console.log("Web ADB options?", !this.isAdbProxy); - } - - public startTracing() { - this.startTrace = true; - console.log("begin tracing"); - } - - public dumpState() { - this.startDump = true; - console.log("begin dump"); - } - - public tabClass(adbTab: boolean) { - let isActive: string; - if (adbTab) { - isActive = this.isAdbProxy ? "active" : "inactive"; - } else { - isActive = !this.isAdbProxy ? "active" : "inactive"; - } - return ["tab", isActive]; - } -} +} \ No newline at end of file diff --git a/tools/winscope-ng/src/trace_collection/proxy_client.ts b/tools/winscope-ng/src/trace_collection/proxy_client.ts index 93cdc7c12..366241561 100644 --- a/tools/winscope-ng/src/trace_collection/proxy_client.ts +++ b/tools/winscope-ng/src/trace_collection/proxy_client.ts @@ -1,11 +1,11 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright 2022, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { PersistentStore } from "common/persistent_store"; + export enum ProxyState { ERROR = 0, CONNECTING = 1, @@ -24,3 +26,138 @@ export enum ProxyState { END_TRACE = 7, LOAD_DATA = 8, } + +export enum ProxyEndpoint { + DEVICES = "/devices/", + START_TRACE = "/start/", + END_TRACE = "/end/", + CONFIG_TRACE = "/configtrace/", + SELECTED_WM_CONFIG_TRACE = "/selectedwmconfigtrace/", + SELECTED_SF_CONFIG_TRACE = "/selectedsfconfigtrace/", + DUMP = "/dump/", + FETCH = "/fetch/", + STATUS = "/status/", + CHECK_WAYLAND = "/checkwayland/", +} + +export class ProxyClient { + readonly WINSCOPE_PROXY_URL = "http://localhost:5544"; + readonly VERSION = "0.8"; + + state: ProxyState = ProxyState.CONNECTING; + stateChangeListeners: {(param:ProxyState, errorText:string): void;}[] = []; + refresh_worker: NodeJS.Timeout | undefined = undefined; + devices: any = {}; + selectedDevice = ""; + errorText = ""; + + proxyKey = ""; + lastDevice = ""; + + store = new PersistentStore(); + + call(method: string, path: string, view: any, onSuccess: any, type = null, jsonRequest = null) { + const request = new XMLHttpRequest(); + const client: ProxyClient = this; + request.onreadystatechange = function() { + if (this.readyState !== 4) { + return; + } + if (this.status === 0) { + client.setState(ProxyState.NO_PROXY); + } else if (this.status === 200) { + if (this.getResponseHeader("Winscope-Proxy-Version") !== client.VERSION) { + client.setState(ProxyState.INVALID_VERSION); + } else if (onSuccess) { + onSuccess(this, view); + } + } else if (this.status === 403) { + client.setState(ProxyState.UNAUTH); + } else { + if (this.responseType === "text" || !this.responseType) { + client.errorText = this.responseText; + } else if (this.responseType === "arraybuffer") { + client.errorText = String.fromCharCode.apply(null, new Array(this.response)); + } + client.setState(ProxyState.ERROR); + } + }; + request.responseType = type || ""; + request.open(method, client.WINSCOPE_PROXY_URL + path); + const lastKey = client.store.getFromStore("adb.proxyKey"); + if (lastKey !== null) { + client.proxyKey = lastKey; + } + + request.setRequestHeader("Winscope-Token", client.proxyKey); + if (jsonRequest) { + const json = JSON.stringify(jsonRequest); + request.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + request.send(json); + } else { + request.send(); + } + } + + setState(state:ProxyState, errorText = "") { + this.state = state; + this.errorText = errorText; + for (const listener of this.stateChangeListeners) { + listener(state, errorText); + } + } + + onProxyChange(fn: (state:ProxyState, errorText:string) => void) { + this.removeOnProxyChange(fn); + this.stateChangeListeners.push(fn); + } + + removeOnProxyChange(removeFn: (state:ProxyState, errorText:string) => void) { + this.stateChangeListeners = this.stateChangeListeners.filter(fn => fn !== removeFn); + } + + getDevices() { + if (this.state !== ProxyState.DEVICES && this.state !== ProxyState.CONNECTING) { + clearInterval(this.refresh_worker); + this.refresh_worker = undefined; + return; + } + const client = this; + this.call("GET", ProxyEndpoint.DEVICES, this, function(request: any, view: any) { + try { + client.devices = JSON.parse(request.responseText); + const last = client.store.getFromStore("adb.lastDevice"); + if (last && client.devices[last] && + client.devices[last].authorised) { + client.selectDevice(last); + } else { + if (client.refresh_worker === undefined) { + client.refresh_worker = setInterval(client.getDevices, 1000); + } + client.setState(ProxyState.DEVICES); + } + } catch (err) { + console.error(err); + client.errorText = request.responseText; + client.setState(ProxyState.ERROR); + } + }); + } + + selectDevice(device_id: string) { + this.selectedDevice = device_id; + this.store.addToStore("adb.lastDevice", device_id); + this.setState(ProxyState.START_TRACE); + } + + deviceId() { + return this.selectedDevice; + } + + resetLastDevice() { + this.lastDevice = ""; + this.store.addToStore("adb.lastDevice", ""); + } +} + +export const proxyClient = new ProxyClient(); diff --git a/tools/winscope-ng/src/trace_collection/trace_config.component.ts b/tools/winscope-ng/src/trace_collection/trace_config.component.ts index 8bdac72a9..170c77fe7 100644 --- a/tools/winscope-ng/src/trace_collection/trace_config.component.ts +++ b/tools/winscope-ng/src/trace_collection/trace_config.component.ts @@ -13,30 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, Input, Output, EventEmitter } from "@angular/core"; -import { ProxyState } from "./proxy_client"; + import { ConfigurationOptions, SelectionConfiguration } from "./collect_traces.component"; +import { Component, Input, Output, EventEmitter } from "@angular/core"; +import { ProxyState, ProxyClient } from "./proxy_client"; @Component({ selector: "trace-config", template: ` -
- {{name}} -
- {{enableConfig}} -
- - {{con.name}} - - {{ option }} - - -
+
+ {{name}} +
+ {{enableConfig}} +
+ + {{con.name}} + + {{ option }} + +
+
`, styles: [".adv-config {margin-left: 5rem;}"], @@ -47,19 +48,19 @@ export class TraceConfigComponent { objectKeys = Object.keys; @Input() - name = ""; + name = ""; @Input() - configs: ConfigurationOptions | null = null; + configs: ConfigurationOptions | null = null; @Input() - defaultCheck: boolean | undefined = false; + defaultCheck: boolean | undefined = false; @Input() - status = this.states.UNAUTH; + proxy: any = null; @Output() - statusChange = new EventEmitter(); + proxyChange = new EventEmitter(); public traceEnableConfigs(): Array { if (this.configs && this.configs.enableConfigs) { @@ -78,8 +79,8 @@ export class TraceConfigComponent { } public restart() { - this.status = this.states.CONNECTING; - this.statusChange.emit(this.status); + this.proxy.setState(this.states.CONNECTING); + this.proxyChange.emit(this.proxy); } public resetLastDevice() { diff --git a/tools/winscope-ng/src/trace_collection/web_adb.component.ts b/tools/winscope-ng/src/trace_collection/web_adb.component.ts index 9dd26ab9e..574bbdce9 100644 --- a/tools/winscope-ng/src/trace_collection/web_adb.component.ts +++ b/tools/winscope-ng/src/trace_collection/web_adb.component.ts @@ -19,9 +19,9 @@ import {Component} from "@angular/core"; @Component({ selector: "web-adb", template: ` -
Unable to connect to Web ADB
+
Connect a new device
-

Instructions for connecting via Web ADB.

+

Follow instructions in the Chrome pop-up.

From b6ee5d47948393bedfe5c67101f63b8d3108acac Mon Sep 17 00:00:00 2001 From: Priyanka Patel Date: Tue, 19 Jul 2022 10:32:09 +0000 Subject: [PATCH 3/5] Refactor code to use common connection. None of the components should directly interact with the proxy because non-android development workflow (using web adb) will also be possible in the future. Instead the ui components interact with a common connection interface which handles the proxy or web adb respectively. Test: check that proxy workflow still works as expected. Bug: b/234103636 Change-Id: I0e7bbaf4b5f342ca99dd67f890c135dbf3ef61ec --- tools/winscope-ng/package-lock.json | 109 ++++- tools/winscope-ng/package.json | 5 + tools/winscope-ng/src/app/app.component.ts | 2 + tools/winscope-ng/src/app/app.module.ts | 2 +- tools/winscope-ng/src/styles.css | 4 + .../trace_collection/adb_proxy.component.ts | 37 +- .../collect_traces.component.ts | 381 ++++++------------ .../src/trace_collection/connection.ts | 107 +++++ .../src/trace_collection/proxy_client.ts | 4 +- .../trace_collection_utils.ts | 145 +++++++ .../trace_config.component.ts | 58 +-- .../{ => web_adb}/web_adb.component.spec.ts | 0 .../{ => web_adb}/web_adb.component.ts | 24 +- .../web_adb}/winscope_proxy.py | 0 14 files changed, 531 insertions(+), 347 deletions(-) create mode 100644 tools/winscope-ng/src/trace_collection/connection.ts create mode 100644 tools/winscope-ng/src/trace_collection/trace_collection_utils.ts rename tools/winscope-ng/src/trace_collection/{ => web_adb}/web_adb.component.spec.ts (100%) rename tools/winscope-ng/src/trace_collection/{ => web_adb}/web_adb.component.ts (58%) rename tools/winscope-ng/{adb_proxy => src/trace_collection/web_adb}/winscope_proxy.py (100%) diff --git a/tools/winscope-ng/package-lock.json b/tools/winscope-ng/package-lock.json index 605e492c4..5b94d1ca0 100644 --- a/tools/winscope-ng/package-lock.json +++ b/tools/winscope-ng/package-lock.json @@ -22,11 +22,14 @@ "@ngrx/effects": "^14.0.2", "@ngrx/store": "^14.0.2", "@ngxs/store": "^3.7.4", + "@types/jsbn": "^1.2.30", "angular2-template-loader": "^0.6.2", "auth0": "^2.42.0", "html-loader": "^3.1.0", "html-webpack-inline-source-plugin": "^1.0.0-beta.2", "html-webpack-plugin": "^5.5.0", + "jsbn": "^1.1.0", + "jsbn-rsa": "^1.0.4", "kotlin": "^1.7.0", "kotlin-compiler": "^1.7.0", "loader-utils": "^2.0.0", @@ -46,7 +49,9 @@ "@angular/compiler-cli": "^14.0.0", "@ngxs/devtools-plugin": "^3.7.4", "@types/jasmine": "~4.0.0", + "@types/jquery": "^3.5.14", "@types/node": "^18.0.4", + "@types/w3c-web-usb": "^1.0.6", "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", "eslint": "^8.19.0", @@ -3385,6 +3390,20 @@ "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", "dev": true }, + "node_modules/@types/jquery": { + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", + "integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==", + "dev": true, + "dependencies": { + "@types/sizzle": "*" + } + }, + "node_modules/@types/jsbn": { + "version": "1.2.30", + "resolved": "https://registry.npmjs.org/@types/jsbn/-/jsbn-1.2.30.tgz", + "integrity": "sha512-VZouplBofjq3YOIHLNRBDxILs/nAArdTZ2QP1ooflyhS+yPExWsFE+i2paBIBb7OI3NJShfcde/nogqk4SPB/Q==" + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -3457,6 +3476,12 @@ "@types/node": "*" } }, + "node_modules/@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", + "dev": true + }, "node_modules/@types/sockjs": { "version": "0.3.33", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", @@ -3466,6 +3491,12 @@ "@types/node": "*" } }, + "node_modules/@types/w3c-web-usb": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz", + "integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -6086,6 +6117,12 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ecc-jsbn/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -8923,10 +8960,14 @@ } }, "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/jsbn-rsa": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/jsbn-rsa/-/jsbn-rsa-1.0.4.tgz", + "integrity": "sha512-unHyEPFGjr6WCzrcMiwdNhYMlq4gXt6Hg5JuKOyE7OXJ7GbVMpottnqsUkPeZCAYqByAkn4N8gJwCpnacduOew==" }, "node_modules/jsesc": { "version": "2.5.2", @@ -13872,6 +13913,12 @@ "node": ">=0.10.0" } }, + "node_modules/sshpk/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, "node_modules/ssri": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", @@ -17993,6 +18040,20 @@ "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", "dev": true }, + "@types/jquery": { + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz", + "integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==", + "dev": true, + "requires": { + "@types/sizzle": "*" + } + }, + "@types/jsbn": { + "version": "1.2.30", + "resolved": "https://registry.npmjs.org/@types/jsbn/-/jsbn-1.2.30.tgz", + "integrity": "sha512-VZouplBofjq3YOIHLNRBDxILs/nAArdTZ2QP1ooflyhS+yPExWsFE+i2paBIBb7OI3NJShfcde/nogqk4SPB/Q==" + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -18065,6 +18126,12 @@ "@types/node": "*" } }, + "@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", + "dev": true + }, "@types/sockjs": { "version": "0.3.33", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", @@ -18074,6 +18141,12 @@ "@types/node": "*" } }, + "@types/w3c-web-usb": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz", + "integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==", + "dev": true + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -20040,6 +20113,14 @@ "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" + }, + "dependencies": { + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + } } }, "ecdsa-sig-formatter": { @@ -22089,10 +22170,14 @@ } }, "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "jsbn-rsa": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/jsbn-rsa/-/jsbn-rsa-1.0.4.tgz", + "integrity": "sha512-unHyEPFGjr6WCzrcMiwdNhYMlq4gXt6Hg5JuKOyE7OXJ7GbVMpottnqsUkPeZCAYqByAkn4N8gJwCpnacduOew==" }, "jsesc": { "version": "2.5.2", @@ -25850,6 +25935,14 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" + }, + "dependencies": { + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + } } }, "ssri": { diff --git a/tools/winscope-ng/package.json b/tools/winscope-ng/package.json index 3abcb708d..8e0dd14af 100644 --- a/tools/winscope-ng/package.json +++ b/tools/winscope-ng/package.json @@ -30,11 +30,14 @@ "@ngrx/effects": "^14.0.2", "@ngrx/store": "^14.0.2", "@ngxs/store": "^3.7.4", + "@types/jsbn": "^1.2.30", "angular2-template-loader": "^0.6.2", "auth0": "^2.42.0", "html-loader": "^3.1.0", "html-webpack-inline-source-plugin": "^1.0.0-beta.2", "html-webpack-plugin": "^5.5.0", + "jsbn": "^1.1.0", + "jsbn-rsa": "^1.0.4", "kotlin": "^1.7.0", "kotlin-compiler": "^1.7.0", "loader-utils": "^2.0.0", @@ -54,7 +57,9 @@ "@angular/compiler-cli": "^14.0.0", "@ngxs/devtools-plugin": "^3.7.4", "@types/jasmine": "~4.0.0", + "@types/jquery": "^3.5.14", "@types/node": "^18.0.4", + "@types/w3c-web-usb": "^1.0.6", "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", "eslint": "^8.19.0", diff --git a/tools/winscope-ng/src/app/app.component.ts b/tools/winscope-ng/src/app/app.component.ts index e3afb622a..febcb1d82 100644 --- a/tools/winscope-ng/src/app/app.component.ts +++ b/tools/winscope-ng/src/app/app.component.ts @@ -39,6 +39,8 @@ import { PersistentStore } from "../common/persistent_store";
+ +
diff --git a/tools/winscope-ng/src/app/app.module.ts b/tools/winscope-ng/src/app/app.module.ts index e367e6ee8..444abe182 100644 --- a/tools/winscope-ng/src/app/app.module.ts +++ b/tools/winscope-ng/src/app/app.module.ts @@ -20,7 +20,7 @@ import { AppComponent } from "./app.component"; import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component"; import { CollectTracesComponent } from "trace_collection/collect_traces.component"; import { AdbProxyComponent } from "trace_collection/adb_proxy.component"; -import { WebAdbComponent } from "trace_collection/web_adb.component"; +import { WebAdbComponent } from "trace_collection/web_adb/web_adb.component"; import { TraceConfigComponent } from "trace_collection/trace_config.component"; @NgModule({ diff --git a/tools/winscope-ng/src/styles.css b/tools/winscope-ng/src/styles.css index db918bf1a..8c859d63e 100644 --- a/tools/winscope-ng/src/styles.css +++ b/tools/winscope-ng/src/styles.css @@ -45,6 +45,10 @@ mat-form-field { height: 5px; } +mat-icon { + margin: 5px; +} + .card-block { margin: 15px; } diff --git a/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts b/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts index 1009b89bd..c40de9821 100644 --- a/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts +++ b/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts @@ -16,13 +16,12 @@ import { Component, Input, Output, EventEmitter } from "@angular/core"; import { ProxyClient, ProxyState } from "./proxy_client"; - @Component({ selector: "adb-proxy", template: `
- error + error Unable to connect to Winscope ADB proxy
@@ -30,7 +29,7 @@ import { ProxyClient, ProxyState } from "./proxy_client";

Python 3.5+ and ADB are required.

Run:

python3
-
$ANDROID_BUILD_TOP/development/tools/winscope/adb_proxy/winscope_proxy.py
+
$ANDROID_BUILD_TOP/development/tools/winscope-ng/adb/winscope_proxy.py

Or get it from the AOSP repository.

@@ -50,7 +49,7 @@ import { ProxyClient, ProxyState } from "./proxy_client";

Please update the proxy to version {{ proxyVersion }}.

Run:

python3
-
$ANDROID_BUILD_TOP/development/tools/winscope/adb_proxy/winscope_proxy.py
+
$ANDROID_BUILD_TOP/development/tools/winscope-ng/adb/winscope_proxy.py

Or get it from the AOSP repository.

@@ -77,32 +76,30 @@ import { ProxyClient, ProxyState } from "./proxy_client";
+ `, styles: [".proxy-key-field {width: 30rem}", ".icon-message {vertical-align: middle;}"] }) export class AdbProxyComponent { - @Input() - proxy: any = {VERSION: 0.8}; - - @Output() - proxyChange = new EventEmitter(); - - @Output() - addKey = new EventEmitter(); - states = ProxyState; + proxyKeyItem = ""; + + @Input() + proxy: any = {}; + readonly proxyVersion = this.proxy.VERSION; - readonly downloadProxyUrl: string = "https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py"; + + @Output() + proxyChange = new EventEmitter(); + + @Output() addKey = new EventEmitter(); + + readonly downloadProxyUrl: string = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py"; public restart() { this.addKey.emit(this.proxyKeyItem); this.proxy.setState(this.states.CONNECTING); this.proxyChange.emit(this.proxy); } - - public triggerUnauthComponent() { - this.proxy.setState(this.states.UNAUTH); - this.proxyChange.emit(this.proxy); - } -} +} \ No newline at end of file diff --git a/tools/winscope-ng/src/trace_collection/collect_traces.component.ts b/tools/winscope-ng/src/trace_collection/collect_traces.component.ts index 12a1fd52a..aeb96ff55 100644 --- a/tools/winscope-ng/src/trace_collection/collect_traces.component.ts +++ b/tools/winscope-ng/src/trace_collection/collect_traces.component.ts @@ -1,124 +1,106 @@ -import {Component, Input, OnInit } from "@angular/core"; -import { ProxyState, proxyClient, ProxyEndpoint } from "./proxy_client"; +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Component, Input, OnInit} from "@angular/core"; +import { Connection, ProxyConnection, Device } from "./connection"; +import { ProxyState } from "./proxy_client"; +import { traceConfigurations, configMap } from "./trace_collection_utils"; import { PersistentStore } from "../common/persistent_store"; -interface TraceConfiguration { - name: string, - defaultCheck?: boolean, - config?: ConfigurationOptions -} - -export interface ConfigurationOptions { - enableConfigs: Array, - selectionConfigs: Array -} - -export interface SelectionConfiguration { - name: string, - options: Array, - value: string -} - -export type configMap = { -[key: string]: Array | string; -} - - -interface Device { - authorised: boolean; - model: string; -} - @Component({ selector: "collect-traces", template: ` - Collect Traces - -
Connecting...
+ Collect Traces + -
- - - - -
+
Connecting...
-
-
{{ devices().length > 0 ? "Connected devices:" : "No devices detected" }}
- - - - {{ proxy.devices[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }} - - - {{ proxy.devices[deviceId].authorised ? proxy.devices[deviceId].model : "unauthorised" }} ({{ deviceId }}) - - - -
+
+ + + + +
-
-
+
+
{{ devices().length > 0 ? "Connected devices:" : "No devices detected" }}
- - smartphone - {{ selectedDevice().model }} ({{ proxy.selectedDevice }}) - + + {{ connect.proxy.devices[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }} + {{ connect.proxy.devices[deviceId].authorised ? connect.proxy.devices[deviceId].model : "unauthorised" }} ({{ deviceId }}) +
-
-
- - - +
+
+ + + smartphone + {{ selectedDevice().model }} ({{ connect.proxy.selectedDevice }}) + +
-

Trace targets:

- -
-
-

Dump targets:

-
- {{DUMPS[dumpKey]}} - +
+
+ + + +
+

Trace targets:

+ +
+ +
+

Dump targets:

+
+ {{DUMPS[dumpKey]}} +
-
-
- error - Error: -
-          {{ errorText }}
-      
- -
+
+ error + Error: +
+            {{ errorText }}
+        
+ +
-
- Tracing... - -
-          {{ errorText }}
-      
- -
+
+ Tracing... + +
+            {{ errorText }}
+        
+ +
-
- Loading data... - -
+
+ Loading data... + +
- + `, styles: [".device-choice {cursor: pointer}"] }) @@ -128,92 +110,78 @@ export class CollectTracesComponent implements OnInit { startTrace = false; startDump = false; errorText = ""; - proxy: any = null; - downloadProxyUrl = "https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py"; - states = ProxyState; - notConnected = [ - this.states.NO_PROXY, - this.states.UNAUTH, - this.states.INVALID_VERSION, - ]; + traceConfigurations = traceConfigurations; - constructor() { - this.proxy = proxyClient; - } + connect: any = new ProxyConnection(); + + downloadProxyUrl = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py"; + + states = ProxyState; @Input() store: PersistentStore = new PersistentStore(); ngOnInit(): void { - this.proxy.setState(ProxyState.CONNECTING); - this.proxy.onProxyChange(this.onProxyChange); - const urlParams = new URLSearchParams(window.location.search); - if (urlParams.has("token")) { - this.proxy.proxyKey = urlParams.get("token")!; + if (this.isAdbProxy) { + this.connect = new ProxyConnection(); + } else { + //TODO: change to WebAdbConnection + this.connect = new ProxyConnection(); } - this.proxy.getDevices(); } ngOnDestroy(): void { - this.proxy.removeOnProxyChange(this.onProxyChange); + this.connect.proxy.removeOnProxyChange(this.onProxyChange); } public onAddKey(key: string) { this.store.addToStore("adb.proxyKey", key); - proxyClient.proxyKey = key; + this.connect.setProxyKey(key); this.restart(); } - public adbSuccess() { - return !this.notConnected.includes(this.proxy.state); + public onConnectChange(newState: Connection) { + this.connect.onConnectChange(newState); } public onProxyChange(newState: ProxyState, errorText: string) { - if (newState === ProxyState.CONNECTING) { - proxyClient.getDevices(); - } - if (newState == ProxyState.START_TRACE) { - proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request: any,view:any) { - try { - if(request.responseText == "true") { - //TODO: add this function - //view.appendOptionalTraces('arc', view.TRACES); - } - } catch(err) { - console.error(err); - proxyClient.setState(ProxyState.ERROR, request.responseText); - } - }); - } + this.connect.onConnectChange(newState); + } + + + public adbSuccess() { + return this.connect.adbSuccess(); } public devices(): Array { - return Object.keys(this.proxy.devices); + return this.connect.devices(); } public selectedDevice(): Device { - return this.proxy.devices[this.proxy.selectedDevice]; + return this.connect.selectedDevice(); } public restart() { - this.proxy.setState(this.states.CONNECTING); + this.connect.restart(); } public resetLastDevice() { - this.proxy.resetLastDevice(); - this.restart(); + this.connect.resetLastDevice(); } public selectDevice(id: string) { - this.proxy.selectDevice(id); + this.connect.selectDevice(id); } public displayAdbProxyTab() { this.isAdbProxy = true; + this.connect = new ProxyConnection(); } public displayWebAdbTab() { this.isAdbProxy = false; + //TODO: change to WebAdbConnection + this.connect = new ProxyConnection(); } public startTracing() { @@ -231,18 +199,7 @@ export class CollectTracesComponent implements OnInit { } public setAvailableTraces() { - this.DYNAMIC_TRACES = this.TRACES["default"]; - proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request:any, view:any) { - try { - if(request.responseText == "true") { - //TODO: add this function - //view.appendOptionalTraces('arc'); - } - } catch(err) { - console.error(err); - proxyClient.setState(ProxyState.ERROR, request.responseText); - } - }); + this.connect.setAvailableTraces(); } public tabClass(adbTab: boolean) { @@ -255,43 +212,6 @@ export class CollectTracesComponent implements OnInit { return ["tab", isActive]; } - TRACES = { - "default": { - "window_trace": { - name: "Window Manager", - }, - "accessibility_trace": { - name: "Accessibility", - }, - "layers_trace": { - name: "Surface Flinger", - }, - "transactions": { - name: "Transaction", - }, - "proto_log": { - name: "ProtoLog", - }, - "screen_recording": { - name: "Screen Recording", - }, - "ime_trace_clients": { - name: "Input Method Clients", - }, - "ime_trace_service": { - name: "Input Method Service", - }, - "ime_trace_managerservice": { - name: "Input Method Manager Service", - }, - }, - "arc": { - "wayland_trace": { - name: "Wayland", - }, - }, - }; - DYNAMIC_TRACES: any = null; DUMPS: configMap = { @@ -299,79 +219,4 @@ export class CollectTracesComponent implements OnInit { "layers_dump": "Surface Flinger" }; - wmTraceSelectionConfigs = [ - { - name: "wmbuffersize (KB)", - options: [ - "4000", - "8000", - "16000", - "32000", - ], - value: "4000" - }, - { - name: "tracingtype", - options: [ - "frame", - "transaction", - ], - value: "frame" - }, - { - name: "tracinglevel", - options: [ - "verbose", - "debug", - "critical", - ], - value: "verbose" - }, - ]; - - traceConfigurations: Array = [ - { - name: "Surface Flinger", - defaultCheck: true, - config: { - enableConfigs: ["composition","metadata","hwc","tracebuffers"], - selectionConfigs: [ - { - name: "sfbuffersize (KB)", - options: ["4000","8000","16000","32000",], - value: "4000" - } - ] - } - }, - { - name: "Window Manager", - defaultCheck: true, - config: { - enableConfigs: [], - selectionConfigs: this.wmTraceSelectionConfigs, - } - }, - { - name: "Screen Recording", - }, - { - name: "Accessibility", - }, - { - name: "Transaction", - }, - { - name: "Input Method Clients", - defaultCheck: true, - }, - { - name: "Input Method Service", - defaultCheck: true, - }, - { - name: "Input Method Manager Service", - defaultCheck: true, - }, - ]; } \ No newline at end of file diff --git a/tools/winscope-ng/src/trace_collection/connection.ts b/tools/winscope-ng/src/trace_collection/connection.ts new file mode 100644 index 000000000..658d46c93 --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/connection.ts @@ -0,0 +1,107 @@ +import { proxyClient, ProxyState, ProxyEndpoint, ProxyClient } from "trace_collection/proxy_client"; +import {TRACES, traceConfigurations} from "./trace_collection_utils"; + +export interface Device { + authorised: boolean; + model: string; +} + +export interface Connection { + adbSuccess: () => boolean; + setProxyKey(key:string): any; + devices(): Array; + selectedDevice(): Device; + restart(): any; + selectDevice(id:string): any; + DYNAMIC_TRACES: any; + state(): ProxyState; + onConnectChange(newState: any): any; + setAvailableTraces(): any; + resetLastDevice(): any; +} + +export class ProxyConnection implements Connection { + DYNAMIC_TRACES: any; + proxy = proxyClient; + + public state() { + return this.proxy.state; + } + + public devices() { + return Object.keys(this.proxy.devices); + } + notConnected = [ + ProxyState.NO_PROXY, + ProxyState.UNAUTH, + ProxyState.INVALID_VERSION, + ]; + + constructor() { + this.proxy.setState(ProxyState.CONNECTING); + this.proxy.onProxyChange(this.onConnectChange); + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.has("token")) { + this.proxy.proxyKey = urlParams.get("token")!; + } + this.proxy.getDevices(); + } + + public onConnectChange(newState: ProxyState) { + if (newState === ProxyState.CONNECTING) { + proxyClient.getDevices(); + } + if (newState == ProxyState.START_TRACE) { + proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request: any,view:any) { + try { + if(request.responseText == "true") { + //view.appendOptionalTraces('arc', view.TRACES); + } + } catch(err) { + console.error(err); + proxyClient.setState(ProxyState.ERROR, request.responseText); + } + }); + } + } + + public setProxyKey(key: string) { + proxyClient.proxyKey = key; + this.restart(); + } + + public adbSuccess() { + return !this.notConnected.includes(this.proxy.state); + } + + public selectedDevice(): Device { + return this.proxy.devices[this.proxy.selectedDevice]; + } + + public restart() { + this.proxy.setState(ProxyState.CONNECTING); + } + + public resetLastDevice() { + this.proxy.resetLastDevice(); + this.restart(); + } + + public selectDevice(id: string) { + this.proxy.selectDevice(id); + } + + public setAvailableTraces() { + this.DYNAMIC_TRACES = TRACES["default"]; + proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request:any, view:any) { + try { + if(request.responseText == "true") { + //view.appendOptionalTraces('arc'); + } + } catch(err) { + console.error(err); + proxyClient.setState(ProxyState.ERROR, request.responseText); + } + }); + } +} diff --git a/tools/winscope-ng/src/trace_collection/proxy_client.ts b/tools/winscope-ng/src/trace_collection/proxy_client.ts index 366241561..de906035a 100644 --- a/tools/winscope-ng/src/trace_collection/proxy_client.ts +++ b/tools/winscope-ng/src/trace_collection/proxy_client.ts @@ -58,7 +58,7 @@ export class ProxyClient { call(method: string, path: string, view: any, onSuccess: any, type = null, jsonRequest = null) { const request = new XMLHttpRequest(); - const client: ProxyClient = this; + const client = this; request.onreadystatechange = function() { if (this.readyState !== 4) { return; @@ -88,7 +88,6 @@ export class ProxyClient { if (lastKey !== null) { client.proxyKey = lastKey; } - request.setRequestHeader("Winscope-Token", client.proxyKey); if (jsonRequest) { const json = JSON.stringify(jsonRequest); @@ -147,6 +146,7 @@ export class ProxyClient { selectDevice(device_id: string) { this.selectedDevice = device_id; this.store.addToStore("adb.lastDevice", device_id); + this.lastDevice = device_id; this.setState(ProxyState.START_TRACE); } diff --git a/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts b/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts new file mode 100644 index 000000000..99a78472a --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts @@ -0,0 +1,145 @@ + +interface TraceConfiguration { + name: string, + defaultCheck?: boolean, + config?: ConfigurationOptions +} + +export interface ConfigurationOptions { + enableConfigs: Array, + selectionConfigs: Array +} + +export interface EnableConfiguration { + name: string, + defaultCheck: boolean, +} + +export interface SelectionConfiguration { + name: string, + options: Array, + value: string +} + +export type configMap = { +[key: string]: Array | string; +} + +export const TRACES = { + "default": { + "window_trace": { + name: "Window Manager", + }, + "accessibility_trace": { + name: "Accessibility", + }, + "layers_trace": { + name: "Surface Flinger", + }, + "transactions": { + name: "Transaction", + }, + "proto_log": { + name: "ProtoLog", + }, + "screen_recording": { + name: "Screen Recording", + }, + "ime_trace_clients": { + name: "Input Method Clients", + }, + "ime_trace_service": { + name: "Input Method Service", + }, + "ime_trace_managerservice": { + name: "Input Method Manager Service", + }, + }, + "arc": { + "wayland_trace": { + name: "Wayland", + }, + }, +}; + +const wmTraceSelectionConfigs = [ + { + name: "wmbuffersize (KB)", + options: [ + "4000", + "8000", + "16000", + "32000", + ], + value: "4000" + }, + { + name: "tracingtype", + options: [ + "frame", + "transaction", + ], + value: "frame" + }, + { + name: "tracinglevel", + options: [ + "verbose", + "debug", + "critical", + ], + value: "verbose" + }, +]; + + +export const traceConfigurations: Array = [ + { + name: "Surface Flinger", + defaultCheck: true, + config: { + enableConfigs: [ + {name: "composition", defaultCheck: false}, + {name: "metadata", defaultCheck: false}, + {name: "hwc", defaultCheck: false}, + {name: "tracebuffers", defaultCheck: false} + ], + selectionConfigs: [ + { + name: "sfbuffersize (KB)", + options: ["4000","8000","16000","32000",], + value: "4000" + } + ] + } + }, + { + name: "Window Manager", + defaultCheck: true, + config: { + enableConfigs: [], + selectionConfigs: wmTraceSelectionConfigs, + } + }, + { + name: "Screen Recording", + }, + { + name: "Accessibility", + }, + { + name: "Transaction", + }, + { + name: "IME Tracing", + defaultCheck: true, + config: { + enableConfigs: [ + {name: "Input Method Clients", defaultCheck: true}, + {name: "Input Method Service", defaultCheck: true}, + {name: "Input Method Manager Service", defaultCheck: true}, + ], + selectionConfigs: [] + } + }, +]; diff --git a/tools/winscope-ng/src/trace_collection/trace_config.component.ts b/tools/winscope-ng/src/trace_collection/trace_config.component.ts index 170c77fe7..8b2ce9606 100644 --- a/tools/winscope-ng/src/trace_collection/trace_config.component.ts +++ b/tools/winscope-ng/src/trace_collection/trace_config.component.ts @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { ConfigurationOptions, SelectionConfiguration } from "./collect_traces.component"; +import { ConfigurationOptions, SelectionConfiguration } from "./trace_collection_utils"; import { Component, Input, Output, EventEmitter } from "@angular/core"; import { ProxyState, ProxyClient } from "./proxy_client"; @@ -22,22 +21,22 @@ import { ProxyState, ProxyClient } from "./proxy_client"; @Component({ selector: "trace-config", template: ` -
- {{name}} -
- {{enableConfig}} -
- - {{con.name}} - - {{ option }} - - +
+ {{name}} +
+ {{enableConfig.name}} +
+ + {{con.name}} + + {{ option }} + + +
-
`, styles: [".adv-config {margin-left: 5rem;}"], @@ -48,21 +47,15 @@ export class TraceConfigComponent { objectKeys = Object.keys; @Input() - name = ""; + name = ""; @Input() - configs: ConfigurationOptions | null = null; + configs: ConfigurationOptions | null = null; @Input() - defaultCheck: boolean | undefined = false; + defaultCheck: boolean | undefined = false; - @Input() - proxy: any = null; - - @Output() - proxyChange = new EventEmitter(); - - public traceEnableConfigs(): Array { + public traceEnableConfigs(): Array { if (this.configs && this.configs.enableConfigs) { return this.configs.enableConfigs; } else { @@ -77,13 +70,4 @@ export class TraceConfigComponent { return []; } } - - public restart() { - this.proxy.setState(this.states.CONNECTING); - this.proxyChange.emit(this.proxy); - } - - public resetLastDevice() { - this.restart(); - } -} +} \ No newline at end of file diff --git a/tools/winscope-ng/src/trace_collection/web_adb.component.spec.ts b/tools/winscope-ng/src/trace_collection/web_adb/web_adb.component.spec.ts similarity index 100% rename from tools/winscope-ng/src/trace_collection/web_adb.component.spec.ts rename to tools/winscope-ng/src/trace_collection/web_adb/web_adb.component.spec.ts diff --git a/tools/winscope-ng/src/trace_collection/web_adb.component.ts b/tools/winscope-ng/src/trace_collection/web_adb/web_adb.component.ts similarity index 58% rename from tools/winscope-ng/src/trace_collection/web_adb.component.ts rename to tools/winscope-ng/src/trace_collection/web_adb/web_adb.component.ts index 574bbdce9..0c18dd77f 100644 --- a/tools/winscope-ng/src/trace_collection/web_adb.component.ts +++ b/tools/winscope-ng/src/trace_collection/web_adb/web_adb.component.ts @@ -15,21 +15,23 @@ */ import {Component} from "@angular/core"; - @Component({ selector: "web-adb", template: ` -
Connect a new device
-
-

Follow instructions in the Chrome pop-up.

-
-
- -
+
+ info + Add new device +
+
+

Click the button below to follow instructions in the Chrome pop-up.

+

Selecting a device will kill all existing ADB connections.

+
+
+ +
`, + styles: [".icon-message {vertical-align: middle;}"] }) export class WebAdbComponent { - public restart() { - console.log("Try connecting again"); - } + adbDevice: any = null; } diff --git a/tools/winscope-ng/adb_proxy/winscope_proxy.py b/tools/winscope-ng/src/trace_collection/web_adb/winscope_proxy.py similarity index 100% rename from tools/winscope-ng/adb_proxy/winscope_proxy.py rename to tools/winscope-ng/src/trace_collection/web_adb/winscope_proxy.py From 64b08efa130d8374467ea7abf336bec3f77b4d40 Mon Sep 17 00:00:00 2001 From: Priyanka Patel Date: Wed, 20 Jul 2022 16:38:49 +0000 Subject: [PATCH 4/5] Adding ability to run and end traces via proxy. Finishing proxy migration by creating functionality to run and end traces and dumps after selecting trace config. Test: Connect a device via remote device proxy and follow the proxy workflow for a trace or dump. Should see a placeholder message saying data loaded (trace views not yet created). Bug: b/238113543 Change-Id: Ic7e0948341511f6ec0bf1021d2ffbb7b198c9410 --- .../web_adb => adb}/winscope_proxy.py | 0 .../adb_proxy.component.spec.ts | 0 .../adb_proxy.component.ts | 27 +- tools/winscope-ng/src/app/app.component.ts | 33 +- tools/winscope-ng/src/app/app.module.ts | 8 +- .../collect_traces.component.spec.ts | 0 .../src/app/collect_traces.component.ts | 316 ++++++++++++++++++ tools/winscope-ng/src/app/core.ts | 5 + .../trace_config.component.spec.ts | 0 .../src/app/trace_config.component.ts | 94 ++++++ .../web_adb => app}/web_adb.component.spec.ts | 0 .../web_adb => app}/web_adb.component.ts | 0 tools/winscope-ng/src/styles.css | 4 + .../collect_traces.component.ts | 222 ------------ .../src/trace_collection/connection.ts | 209 +++++++++--- .../src/trace_collection/proxy_client.ts | 148 +++++--- .../trace_collection_utils.ts | 202 ++++++----- .../trace_config.component.ts | 73 ---- 18 files changed, 847 insertions(+), 494 deletions(-) rename tools/winscope-ng/src/{trace_collection/web_adb => adb}/winscope_proxy.py (100%) rename tools/winscope-ng/src/{trace_collection => app}/adb_proxy.component.spec.ts (100%) rename tools/winscope-ng/src/{trace_collection => app}/adb_proxy.component.ts (90%) rename tools/winscope-ng/src/{trace_collection => app}/collect_traces.component.spec.ts (100%) create mode 100644 tools/winscope-ng/src/app/collect_traces.component.ts rename tools/winscope-ng/src/{trace_collection => app}/trace_config.component.spec.ts (100%) create mode 100644 tools/winscope-ng/src/app/trace_config.component.ts rename tools/winscope-ng/src/{trace_collection/web_adb => app}/web_adb.component.spec.ts (100%) rename tools/winscope-ng/src/{trace_collection/web_adb => app}/web_adb.component.ts (100%) delete mode 100644 tools/winscope-ng/src/trace_collection/collect_traces.component.ts delete mode 100644 tools/winscope-ng/src/trace_collection/trace_config.component.ts diff --git a/tools/winscope-ng/src/trace_collection/web_adb/winscope_proxy.py b/tools/winscope-ng/src/adb/winscope_proxy.py similarity index 100% rename from tools/winscope-ng/src/trace_collection/web_adb/winscope_proxy.py rename to tools/winscope-ng/src/adb/winscope_proxy.py diff --git a/tools/winscope-ng/src/trace_collection/adb_proxy.component.spec.ts b/tools/winscope-ng/src/app/adb_proxy.component.spec.ts similarity index 100% rename from tools/winscope-ng/src/trace_collection/adb_proxy.component.spec.ts rename to tools/winscope-ng/src/app/adb_proxy.component.spec.ts diff --git a/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts b/tools/winscope-ng/src/app/adb_proxy.component.ts similarity index 90% rename from tools/winscope-ng/src/trace_collection/adb_proxy.component.ts rename to tools/winscope-ng/src/app/adb_proxy.component.ts index c40de9821..c04923c22 100644 --- a/tools/winscope-ng/src/trace_collection/adb_proxy.component.ts +++ b/tools/winscope-ng/src/app/adb_proxy.component.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { Component, Input, Output, EventEmitter } from "@angular/core"; -import { ProxyClient, ProxyState } from "./proxy_client"; +import { ProxyClient, ProxyState } from "../trace_collection/proxy_client"; @Component({ selector: "adb-proxy", @@ -29,7 +29,7 @@ import { ProxyClient, ProxyState } from "./proxy_client";

Python 3.5+ and ADB are required.

Run:

python3
-
$ANDROID_BUILD_TOP/development/tools/winscope-ng/adb/winscope_proxy.py
+
$ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py

Or get it from the AOSP repository.

@@ -42,14 +42,14 @@ import { ProxyClient, ProxyState } from "./proxy_client";
- update + update Your local proxy version is incompatible with Winscope.

Please update the proxy to version {{ proxyVersion }}.

Run:

python3
-
$ANDROID_BUILD_TOP/development/tools/winscope-ng/adb/winscope_proxy.py
+
$ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py

Or get it from the AOSP repository.

@@ -62,7 +62,7 @@ import { ProxyClient, ProxyState } from "./proxy_client";
- lock + lock Proxy authorisation required
@@ -78,23 +78,20 @@ import { ProxyClient, ProxyState } from "./proxy_client";
`, - styles: [".proxy-key-field {width: 30rem}", ".icon-message {vertical-align: middle;}"] + styles: [".proxy-key-field {width: 30rem}"] }) export class AdbProxyComponent { - states = ProxyState; - - proxyKeyItem = ""; - @Input() - proxy: any = {}; - - readonly proxyVersion = this.proxy.VERSION; + proxy: any = {}; @Output() - proxyChange = new EventEmitter(); + proxyChange = new EventEmitter(); @Output() addKey = new EventEmitter(); + states = ProxyState; + proxyKeyItem = ""; + readonly proxyVersion = this.proxy.VERSION; readonly downloadProxyUrl: string = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py"; public restart() { @@ -102,4 +99,4 @@ export class AdbProxyComponent { this.proxy.setState(this.states.CONNECTING); this.proxyChange.emit(this.proxy); } -} \ No newline at end of file +} diff --git a/tools/winscope-ng/src/app/app.component.ts b/tools/winscope-ng/src/app/app.component.ts index febcb1d82..e159f08c9 100644 --- a/tools/winscope-ng/src/app/app.component.ts +++ b/tools/winscope-ng/src/app/app.component.ts @@ -26,9 +26,9 @@ import { PersistentStore } from "../common/persistent_store";
Winscope Viewer 2.0
-
+
- + Upload Traces @@ -38,10 +38,11 @@ import { PersistentStore } from "../common/persistent_store";
-
- - - +
+ + Loaded data + +
@@ -59,9 +60,10 @@ import { PersistentStore } from "../common/persistent_store"; export class AppComponent { title = "winscope-ng"; - private core!: Core; + core: Core = new Core(); states = ProxyState; store: PersistentStore = new PersistentStore(); + dataLoaded: boolean = false; constructor( @Inject(Injector) injector: Injector @@ -70,10 +72,16 @@ export class AppComponent { createCustomElement(ViewerWindowManagerComponent, {injector})); } - public async onInputFile(event: Event) { - const files = await this.getInputFiles(event); + onCoreChange(newCore: Core) { + this.core = newCore; + } - this.core = new Core(); + onDataLoadedChange(loaded: boolean) { + this.dataLoaded = loaded; + } + + public async onInputFile(event: Event) { + const files = this.getInputFiles(event); await this.core.bootstrap(files); const viewersDiv = document.querySelector("div#viewers")!; @@ -99,4 +107,9 @@ export class AppComponent { return [files[0]]; } + + public clearData() { + this.dataLoaded = false; + this.core.clearData(); + } } diff --git a/tools/winscope-ng/src/app/app.module.ts b/tools/winscope-ng/src/app/app.module.ts index 444abe182..0cb216f58 100644 --- a/tools/winscope-ng/src/app/app.module.ts +++ b/tools/winscope-ng/src/app/app.module.ts @@ -18,10 +18,10 @@ import { HttpClientModule } from "@angular/common/http"; import { AppComponent } from "./app.component"; import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component"; -import { CollectTracesComponent } from "trace_collection/collect_traces.component"; -import { AdbProxyComponent } from "trace_collection/adb_proxy.component"; -import { WebAdbComponent } from "trace_collection/web_adb/web_adb.component"; -import { TraceConfigComponent } from "trace_collection/trace_config.component"; +import { CollectTracesComponent } from "./collect_traces.component"; +import { AdbProxyComponent } from "./adb_proxy.component"; +import { WebAdbComponent } from "./web_adb.component"; +import { TraceConfigComponent } from "./trace_config.component"; @NgModule({ declarations: [ diff --git a/tools/winscope-ng/src/trace_collection/collect_traces.component.spec.ts b/tools/winscope-ng/src/app/collect_traces.component.spec.ts similarity index 100% rename from tools/winscope-ng/src/trace_collection/collect_traces.component.spec.ts rename to tools/winscope-ng/src/app/collect_traces.component.spec.ts diff --git a/tools/winscope-ng/src/app/collect_traces.component.ts b/tools/winscope-ng/src/app/collect_traces.component.ts new file mode 100644 index 000000000..21bd42ab2 --- /dev/null +++ b/tools/winscope-ng/src/app/collect_traces.component.ts @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core"; +import { ProxyConnection, Device, configureTraces } from "../trace_collection/connection"; +import { ProxyState } from "../trace_collection/proxy_client"; +import { traceConfigurations, configMap, SelectionConfiguration } from "../trace_collection/trace_collection_utils"; +import { Core } from "app/core"; +import { PersistentStore } from "../common/persistent_store"; + + +@Component({ + selector: "collect-traces", + template: ` + Collect Traces + + +
Connecting...
+ +
+ + + + +
+ +
+
{{ devices().length > 0 ? "Connected devices:" : "No devices detected" }}
+ + + + {{ connect.proxy.devices[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }} + + + {{ connect.proxy.devices[deviceId].authorised ? connect.proxy.devices[deviceId].model : "unauthorised" }} ({{ deviceId }}) + + + +
+ +
+
+ + + smartphone + + {{ selectedDevice().model }} ({{ connect.proxy.selectedDevice }}) + + + +
+ +
+
+ + + +
+

Trace targets:

+ +
+ +
+

Dump targets:

+
+ {{connect.DUMPS[dumpKey].name}} +
+
+
+ +
+ error + Error: +
+            {{ connect.proxy.errorText }}
+        
+ +
+ +
+ Tracing... + + +
+ +
+ Loading data... + +
+ +
+ `, + styles: [".device-choice {cursor: pointer}"] +}) +export class CollectTracesComponent implements OnInit { + objectKeys = Object.keys; + isAdbProxy = true; + startTrace = false; + startDump = false; + traceConfigurations = traceConfigurations; + connect: any = new ProxyConnection(); + downloadProxyUrl = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py"; + + @Input() + store: PersistentStore = new PersistentStore(); + + @Input() + core: Core = new Core(); + + @Output() + coreChange = new EventEmitter(); + + @Input() + dataLoaded: boolean = false; + + @Output() + dataLoadedChange = new EventEmitter(); + + ngOnInit(): void { + if (this.isAdbProxy) { + this.connect = new ProxyConnection(); + } else { + //TODO: change to WebAdbConnection + this.connect = new ProxyConnection(); + } + } + + ngOnDestroy(): void { + this.connect.proxy.removeOnProxyChange(this.onProxyChange); + } + + public onAddKey(key: string) { + this.store.addToStore("adb.proxyKey", key); + this.connect.setProxyKey(key); + this.restart(); + } + + public onProxyChange(newState: ProxyState) { + this.connect.onConnectChange(newState); + } + + public adbSuccess() { + return this.connect.adbSuccess(); + } + + public devices(): Array { + return this.connect.devices(); + } + + public selectedDevice(): Device { + return this.connect.selectedDevice(); + } + + public restart() { + this.connect.restart(); + } + + public resetLastDevice() { + this.connect.resetLastDevice(); + } + + public selectDevice(id: string) { + this.connect.selectDevice(id); + } + + public displayAdbProxyTab() { + this.isAdbProxy = true; + this.connect = new ProxyConnection(); + } + + public displayWebAdbTab() { + this.isAdbProxy = false; + //TODO: change to WebAdbConnection + this.connect = new ProxyConnection(); + } + + public requestedTraces() { + const tracesFromCollection: Array = []; + const req = Object.keys(this.connect.DYNAMIC_TRACES()) + .filter((traceKey:string) => { + const traceConfig = this.connect.DYNAMIC_TRACES()[traceKey]; + if (traceConfig.isTraceCollection) { + traceConfig.config.enableConfigs.forEach((innerTrace:any) => { + if (innerTrace.enabled) { + tracesFromCollection.push(innerTrace.key); + } + }); + return false; + } + return traceConfig.run; + }); + return req.concat(tracesFromCollection); + } + + public requestedDumps() { + return Object.keys(this.connect.DUMPS) + .filter((dumpKey:any) => { + return this.connect.DUMPS[dumpKey].enabled; + }); + } + + public requestedEnableConfig(): Array | null{ + const req: Array = []; + Object.keys(this.connect.DYNAMIC_TRACES()) + .forEach((traceKey:any) => { + const trace = this.connect.DYNAMIC_TRACES()[traceKey]; + if(!trace.isTraceCollection + && trace.run + && trace.config + && trace.config.enableConfigs) { + trace.config.enableConfigs.forEach((con:any) => { + if (con.enabled) { + req.push(con.key); + } + }); + } + }); + if (req.length === 0) { + return null; + } + return req; + } + + public requestedSelection(traceType: string) { + if (!this.connect.DYNAMIC_TRACES()[traceType].run) { + return null; + } + const selected: configMap = {}; + this.connect.DYNAMIC_TRACES()[traceType].config.selectionConfigs.forEach( + (con: SelectionConfiguration) => { + selected[con.key] = con.value; + } + ); + return selected; + } + + public startTracing() { + this.startTrace = true; + console.log("begin tracing"); + configureTraces.reqTraces = this.requestedTraces(); + const reqEnableConfig = this.requestedEnableConfig(); + const reqSelectedSfConfig = this.requestedSelection("layers_trace"); + const reqSelectedWmConfig = this.requestedSelection("window_trace"); + if (configureTraces.reqTraces.length < 1) { + this.connect.throwNoTargetsError(); + return; + } + this.connect.startTrace( + reqEnableConfig, + reqSelectedSfConfig, + reqSelectedWmConfig + ); + } + + public async dumpState() { + this.startDump = true; + console.log("begin dump"); + configureTraces.reqDumps = this.requestedDumps(); + await this.connect.dumpState(); + while (!this.connect.proxy.dataReady) { + await this.waitForData(1000); + } + await this.loadFiles(); + } + + public async endTrace() { + console.log("end tracing"); + await this.connect.endTrace(); + while (!this.connect.proxy.dataReady) { + await this.waitForData(1000); + } + await this.loadFiles(); + } + + public async loadFiles() { + console.log("loading files", this.connect.adbData()); + await this.core.bootstrap(this.connect.adbData()); + this.dataLoaded = true; + this.dataLoadedChange.emit(this.dataLoaded); + this.coreChange.emit(this.core); + console.log("finished loading data!"); + } + + public tabClass(adbTab: boolean) { + let isActive: string; + if (adbTab) { + isActive = this.isAdbProxy ? "active" : "inactive"; + } else { + isActive = !this.isAdbProxy ? "active" : "inactive"; + } + return ["tab", isActive]; + } + + private waitForData(ms: number) { + return new Promise( resolve => setTimeout(resolve, ms) ); + } +} diff --git a/tools/winscope-ng/src/app/core.ts b/tools/winscope-ng/src/app/core.ts index d3a305573..1d023ebc1 100644 --- a/tools/winscope-ng/src/app/core.ts +++ b/tools/winscope-ng/src/app/core.ts @@ -71,6 +71,11 @@ class Core { viewer.notifyCurrentTraceEntries(traceEntries); }); } + + clearData() { + this.parsers = []; + this.viewers = []; + } } export { Core }; diff --git a/tools/winscope-ng/src/trace_collection/trace_config.component.spec.ts b/tools/winscope-ng/src/app/trace_config.component.spec.ts similarity index 100% rename from tools/winscope-ng/src/trace_collection/trace_config.component.spec.ts rename to tools/winscope-ng/src/app/trace_config.component.spec.ts diff --git a/tools/winscope-ng/src/app/trace_config.component.ts b/tools/winscope-ng/src/app/trace_config.component.ts new file mode 100644 index 000000000..213f85c12 --- /dev/null +++ b/tools/winscope-ng/src/app/trace_config.component.ts @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input, OnChanges, SimpleChanges } from "@angular/core"; +import { EnableConfiguration, SelectionConfiguration, TraceConfiguration } from "../trace_collection/trace_collection_utils"; + +@Component({ + selector: "trace-config", + template: ` +
+ {{trace.name}} + +
+ {{enableConfig.name}} + +
+ {{selectionConfig.name}} + + {{ option }} + + +
+
+
+ `, + styles: [".adv-config {margin-left: 5rem;}"], +}) + +export class TraceConfigComponent { + @Input() + trace: TraceConfiguration = {}; + + public traceEnableConfigs(): Array { + if (this.trace.config) { + return this.trace.config.enableConfigs; + } else { + return []; + } + } + + public traceSelectionConfigs(): Array { + if (this.trace.config) { + return this.trace.config.selectionConfigs; + } else { + return []; + } + } + + public someTraces(): boolean { + return this.traceEnableConfigs().filter(trace => trace.enabled).length > 0 + && !this.trace.run; + } + + public changeRunTrace(run: boolean): void { + this.trace.run = run; + if (this.trace.isTraceCollection) { + this.traceEnableConfigs().forEach((c: EnableConfiguration) => (c.enabled = run)); + } + } + + public changeTraceCollectionConfig(): void { + if (this.trace.isTraceCollection) { + this.trace.run = this.traceEnableConfigs().every((c: EnableConfiguration) => c.enabled); + } + } +} diff --git a/tools/winscope-ng/src/trace_collection/web_adb/web_adb.component.spec.ts b/tools/winscope-ng/src/app/web_adb.component.spec.ts similarity index 100% rename from tools/winscope-ng/src/trace_collection/web_adb/web_adb.component.spec.ts rename to tools/winscope-ng/src/app/web_adb.component.spec.ts diff --git a/tools/winscope-ng/src/trace_collection/web_adb/web_adb.component.ts b/tools/winscope-ng/src/app/web_adb.component.ts similarity index 100% rename from tools/winscope-ng/src/trace_collection/web_adb/web_adb.component.ts rename to tools/winscope-ng/src/app/web_adb.component.ts diff --git a/tools/winscope-ng/src/styles.css b/tools/winscope-ng/src/styles.css index 8c859d63e..7886217ba 100644 --- a/tools/winscope-ng/src/styles.css +++ b/tools/winscope-ng/src/styles.css @@ -49,6 +49,10 @@ mat-icon { margin: 5px; } +.icon-message { + vertical-align: middle; +} + .card-block { margin: 15px; } diff --git a/tools/winscope-ng/src/trace_collection/collect_traces.component.ts b/tools/winscope-ng/src/trace_collection/collect_traces.component.ts deleted file mode 100644 index aeb96ff55..000000000 --- a/tools/winscope-ng/src/trace_collection/collect_traces.component.ts +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Component, Input, OnInit} from "@angular/core"; -import { Connection, ProxyConnection, Device } from "./connection"; -import { ProxyState } from "./proxy_client"; -import { traceConfigurations, configMap } from "./trace_collection_utils"; -import { PersistentStore } from "../common/persistent_store"; - -@Component({ - selector: "collect-traces", - template: ` - Collect Traces - - -
Connecting...
- -
- - - - -
- -
-
{{ devices().length > 0 ? "Connected devices:" : "No devices detected" }}
- - - {{ connect.proxy.devices[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }} - {{ connect.proxy.devices[deviceId].authorised ? connect.proxy.devices[deviceId].model : "unauthorised" }} ({{ deviceId }}) - - -
- -
-
- - - smartphone - {{ selectedDevice().model }} ({{ connect.proxy.selectedDevice }}) - - -
- -
-
- - - -
-

Trace targets:

- -
- -
-

Dump targets:

-
- {{DUMPS[dumpKey]}} -
-
-
- -
- error - Error: -
-            {{ errorText }}
-        
- -
- -
- Tracing... - -
-            {{ errorText }}
-        
- -
- -
- Loading data... - -
- -
- `, - styles: [".device-choice {cursor: pointer}"] -}) -export class CollectTracesComponent implements OnInit { - objectKeys = Object.keys; - isAdbProxy = true; - startTrace = false; - startDump = false; - errorText = ""; - traceConfigurations = traceConfigurations; - - connect: any = new ProxyConnection(); - - downloadProxyUrl = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py"; - - states = ProxyState; - - @Input() - store: PersistentStore = new PersistentStore(); - - ngOnInit(): void { - if (this.isAdbProxy) { - this.connect = new ProxyConnection(); - } else { - //TODO: change to WebAdbConnection - this.connect = new ProxyConnection(); - } - } - - ngOnDestroy(): void { - this.connect.proxy.removeOnProxyChange(this.onProxyChange); - } - - public onAddKey(key: string) { - this.store.addToStore("adb.proxyKey", key); - this.connect.setProxyKey(key); - this.restart(); - } - - public onConnectChange(newState: Connection) { - this.connect.onConnectChange(newState); - } - - public onProxyChange(newState: ProxyState, errorText: string) { - this.connect.onConnectChange(newState); - } - - - public adbSuccess() { - return this.connect.adbSuccess(); - } - - public devices(): Array { - return this.connect.devices(); - } - - public selectedDevice(): Device { - return this.connect.selectedDevice(); - } - - public restart() { - this.connect.restart(); - } - - public resetLastDevice() { - this.connect.resetLastDevice(); - } - - public selectDevice(id: string) { - this.connect.selectDevice(id); - } - - public displayAdbProxyTab() { - this.isAdbProxy = true; - this.connect = new ProxyConnection(); - } - - public displayWebAdbTab() { - this.isAdbProxy = false; - //TODO: change to WebAdbConnection - this.connect = new ProxyConnection(); - } - - public startTracing() { - this.startTrace = true; - console.log("begin tracing"); - } - - public dumpState() { - this.startDump = true; - console.log("begin dump"); - } - - public endTrace() { - console.log("end trace"); - } - - public setAvailableTraces() { - this.connect.setAvailableTraces(); - } - - public tabClass(adbTab: boolean) { - let isActive: string; - if (adbTab) { - isActive = this.isAdbProxy ? "active" : "inactive"; - } else { - isActive = !this.isAdbProxy ? "active" : "inactive"; - } - return ["tab", isActive]; - } - - DYNAMIC_TRACES: any = null; - - DUMPS: configMap = { - "window_dump": "Window Manager", - "layers_dump": "Surface Flinger" - }; - -} \ No newline at end of file diff --git a/tools/winscope-ng/src/trace_collection/connection.ts b/tools/winscope-ng/src/trace_collection/connection.ts index 658d46c93..2ae50467f 100644 --- a/tools/winscope-ng/src/trace_collection/connection.ts +++ b/tools/winscope-ng/src/trace_collection/connection.ts @@ -1,5 +1,5 @@ -import { proxyClient, ProxyState, ProxyEndpoint, ProxyClient } from "trace_collection/proxy_client"; -import {TRACES, traceConfigurations} from "./trace_collection_utils"; +import { proxyRequest, proxyClient, ProxyState, ProxyEndpoint } from "trace_collection/proxy_client"; +import { TRACES } from "./trace_collection_utils"; export interface Device { authorised: boolean; @@ -13,24 +13,44 @@ export interface Connection { selectedDevice(): Device; restart(): any; selectDevice(id:string): any; - DYNAMIC_TRACES: any; + DYNAMIC_TRACES(): any; state(): ProxyState; onConnectChange(newState: any): any; - setAvailableTraces(): any; resetLastDevice(): any; + isDevicesState(): boolean; + isStartTraceState(): boolean; + isErrorState(): boolean; + isEndTraceState(): boolean; + isLoadDataState(): boolean; + isConnectingState(): boolean; + isNoProxy(): boolean; + isInvalidProxy(): boolean; + isUnauthProxy(): boolean; + throwNoTargetsError(): any; + startTrace( + reqEnableConfig?: Array, + reqSelectedSfConfig?: any, + reqSelectedWmConfig?: any + ): any; + DUMPS: any; + endTrace(): any; + dumpState(req:Array): any; + adbData(): any; } export class ProxyConnection implements Connection { - DYNAMIC_TRACES: any; proxy = proxyClient; - - public state() { - return this.proxy.state; - } - - public devices() { - return Object.keys(this.proxy.devices); - } + DUMPS: any = { + "window_dump": { + name: "Window Manager", + enabled: true, + }, + "layers_dump": { + name: "Surface Flinger", + enabled: true, + } + }; + keep_alive_worker: any = null; notConnected = [ ProxyState.NO_PROXY, ProxyState.UNAUTH, @@ -47,26 +67,68 @@ export class ProxyConnection implements Connection { this.proxy.getDevices(); } - public onConnectChange(newState: ProxyState) { - if (newState === ProxyState.CONNECTING) { - proxyClient.getDevices(); - } - if (newState == ProxyState.START_TRACE) { - proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request: any,view:any) { - try { - if(request.responseText == "true") { - //view.appendOptionalTraces('arc', view.TRACES); - } - } catch(err) { - console.error(err); - proxyClient.setState(ProxyState.ERROR, request.responseText); - } - }); - } + public devices() { + return Object.keys(this.proxy.devices); + } + + public adbData() { + return this.proxy.adbData; + } + + DYNAMIC_TRACES() { + return configureTraces.DYNAMIC_TRACES; + } + + public state() { + return this.proxy.state; + } + + public isDevicesState() { + return this.state() === ProxyState.DEVICES; + } + + public isStartTraceState() { + return this.state() === ProxyState.START_TRACE; + } + + public isErrorState() { + return this.state() === ProxyState.ERROR; + } + + public isEndTraceState() { + return this.state() === ProxyState.END_TRACE; + } + + public isLoadDataState() { + return this.state() === ProxyState.LOAD_DATA; + } + + public isConnectingState() { + return this.state() === ProxyState.CONNECTING; + } + + public isNoProxy() { + return this.state() === ProxyState.NO_PROXY; + } + + public isInvalidProxy() { + return this.state() === ProxyState.INVALID_VERSION; + } + + public isUnauthProxy() { + return this.state() === ProxyState.UNAUTH; + } + + public throwNoTargetsError() { + this.proxy.setState(ProxyState.ERROR, "No targets selected"); + } + + public dataReady() { + return this.proxy.dataReady; } public setProxyKey(key: string) { - proxyClient.proxyKey = key; + this.proxy.proxyKey = key; this.restart(); } @@ -83,7 +145,7 @@ export class ProxyConnection implements Connection { } public resetLastDevice() { - this.proxy.resetLastDevice(); + this.proxy.store.addToStore("adb.lastDevice", ""); this.restart(); } @@ -91,17 +153,82 @@ export class ProxyConnection implements Connection { this.proxy.selectDevice(id); } - public setAvailableTraces() { - this.DYNAMIC_TRACES = TRACES["default"]; - proxyClient.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, function(request:any, view:any) { - try { - if(request.responseText == "true") { - //view.appendOptionalTraces('arc'); - } - } catch(err) { - console.error(err); - proxyClient.setState(ProxyState.ERROR, request.responseText); + public keepAliveTrace(view:any) { + if (!view.isEndTraceState()) { + clearInterval(view.keep_alive_worker); + view.keep_alive_worker = null; + return; + } + proxyRequest.call("GET", `${ProxyEndpoint.STATUS}${view.proxy.selectedDevice}/`, view, function(request:any, newView:any) { + if (request.responseText !== "True") { + newView.endTrace(); + } else if (newView.keep_alive_worker === null) { + newView.keep_alive_worker = setInterval(newView.keepAliveTrace, 1000, newView); } }); } + + public startTrace( + reqEnableConfig: any, + reqSelectedSfConfig: any, + reqSelectedWmConfig: any + ) { + if (reqEnableConfig) { + proxyRequest.call("POST", `${ProxyEndpoint.CONFIG_TRACE}${this.proxy.selectedDevice}/`, this, null, null, reqEnableConfig); + } + if (reqSelectedSfConfig) { + proxyRequest.call("POST", `${ProxyEndpoint.SELECTED_SF_CONFIG_TRACE}${this.proxy.selectedDevice}/`, this, null, null, reqSelectedSfConfig); + } + if (reqSelectedWmConfig) { + proxyRequest.call("POST", `${ProxyEndpoint.SELECTED_WM_CONFIG_TRACE}${this.proxy.selectedDevice}/`, this, null, null, reqSelectedWmConfig); + } + proxyClient.setState(ProxyState.END_TRACE); + proxyRequest.call("POST", `${ProxyEndpoint.START_TRACE}${this.proxy.selectedDevice}/`, this, function(request:any, view:any) { + view.keepAliveTrace(view); + }, null, configureTraces.reqTraces); + } + + public async endTrace() { + this.proxy.setState(ProxyState.LOAD_DATA); + await proxyRequest.call("POST", `${ProxyEndpoint.END_TRACE}${this.proxy.selectedDevice}/`, this, + async function (request:any, view:any) { + await proxyClient.updateAdbData(configureTraces.reqTraces, 0, "trace", view); + }); + } + + public dumpState() { + if (configureTraces.reqDumps.length < 1) { + this.proxy.setState(ProxyState.ERROR, "No targets selected"); + return; + } + this.proxy.setState(ProxyState.LOAD_DATA); + proxyRequest.call("POST", `${ProxyEndpoint.DUMP}${this.proxy.selectedDevice}/`, this, function(request:any, view:any) { + view.proxy.updateAdbData(configureTraces.reqDumps, 0, "dump", view); + }, null, configureTraces.reqDumps); + } + + public onConnectChange(newState: ProxyState) { + if (newState === ProxyState.CONNECTING) { + proxyClient.getDevices(); + } + if (newState == ProxyState.START_TRACE) { + configureTraces.setAvailableTraces(); + } + } } + +class ConfigureTraces { + DYNAMIC_TRACES = TRACES["default"]; + reqTraces: string[] = []; + reqDumps: string[] = []; + + setAvailableTraces() { + proxyRequest.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, proxyRequest.setAvailableTraces); + } + appendOptionalTraces(view:any, device_key:string) { + for(const key in TRACES[device_key]) { + view.DYNAMIC_TRACES[key] = TRACES[device_key][key]; + } + } +} +export const configureTraces = new ConfigureTraces(); \ No newline at end of file diff --git a/tools/winscope-ng/src/trace_collection/proxy_client.ts b/tools/winscope-ng/src/trace_collection/proxy_client.ts index de906035a..43e0008ee 100644 --- a/tools/winscope-ng/src/trace_collection/proxy_client.ts +++ b/tools/winscope-ng/src/trace_collection/proxy_client.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { PersistentStore } from "common/persistent_store"; +import { PersistentStore } from "../common/persistent_store"; +import { TRACES } from "./trace_collection_utils"; export enum ProxyState { ERROR = 0, @@ -40,25 +41,11 @@ export enum ProxyEndpoint { CHECK_WAYLAND = "/checkwayland/", } -export class ProxyClient { - readonly WINSCOPE_PROXY_URL = "http://localhost:5544"; - readonly VERSION = "0.8"; - - state: ProxyState = ProxyState.CONNECTING; - stateChangeListeners: {(param:ProxyState, errorText:string): void;}[] = []; - refresh_worker: NodeJS.Timeout | undefined = undefined; - devices: any = {}; - selectedDevice = ""; - errorText = ""; - - proxyKey = ""; - lastDevice = ""; - - store = new PersistentStore(); - - call(method: string, path: string, view: any, onSuccess: any, type = null, jsonRequest = null) { +// from here, all requests to the proxy are made +class ProxyRequest { + async call(method: string, path: string, view: any, onSuccess: any, type: any = null, jsonRequest:any = null) { const request = new XMLHttpRequest(); - const client = this; + const client = proxyClient; request.onreadystatechange = function() { if (this.readyState !== 4) { return; @@ -79,7 +66,7 @@ export class ProxyClient { } else if (this.responseType === "arraybuffer") { client.errorText = String.fromCharCode.apply(null, new Array(this.response)); } - client.setState(ProxyState.ERROR); + client.setState(ProxyState.ERROR, client.errorText); } }; request.responseType = type || ""; @@ -98,6 +85,88 @@ export class ProxyClient { } } + getDevices = function(request: any, view: any) { + const client = proxyClient; + try { + client.devices = JSON.parse(request.responseText); + const last = client.store.getFromStore("adb.lastDevice"); + if (last && client.devices[last] && + client.devices[last].authorised) { + client.selectDevice(last); + } else { + if (client.refresh_worker === null) { + client.refresh_worker = setInterval(client.getDevices, 1000); + } + client.setState(ProxyState.DEVICES); + } + } catch (err) { + console.error(err); + client.errorText = request.responseText; + client.setState(ProxyState.ERROR, client.errorText); + } + }; + + setAvailableTraces = function(request:any, view:any) { + try { + view.DYNAMIC_TRACES = TRACES["default"]; + if(request.responseText == "true") { + view.appendOptionalTraces(view, "arc"); + } + } catch(err) { + proxyClient.setState(ProxyState.ERROR, request.responseText); + } + }; + + updateAdbData = async (request: any, view: any) => { + let idx = proxyClient.adbParams.idx; + let files = proxyClient.adbParams.files; + let traceType = proxyClient.adbParams.traceType; + try { + const enc = new TextDecoder("utf-8"); + const resp = enc.decode(request.response); + const filesByType = JSON.parse(resp); + + for (const filetype in filesByType) { + const files = filesByType[filetype]; + for (const encodedFileBuffer of files) { + const buffer = Uint8Array.from(atob(encodedFileBuffer), (c) => c.charCodeAt(0)); + const blob = new Blob([buffer]); + proxyClient.adbData.push(blob); + } + } + if (idx < files.length - 1) { + proxyClient.updateAdbData(files, idx + 1, traceType, view); + } else { + proxyClient.dataReady = true; + } + } catch (error) { + proxyClient.setState(ProxyState.ERROR, request.responseText); + } + } +} +export const proxyRequest = new ProxyRequest(); + +// stores all the changing variables from proxy and sets up calls from ProxyRequest +export class ProxyClient { + readonly WINSCOPE_PROXY_URL = "http://localhost:5544"; + readonly VERSION = "0.8"; + state: ProxyState = ProxyState.CONNECTING; + stateChangeListeners: {(param:ProxyState, errorText:string): void;}[] = []; + refresh_worker: NodeJS.Timer | null = null; + devices: any = {}; + selectedDevice = ""; + errorText = ""; + adbData: Array = []; + proxyKey = ""; + lastDevice = ""; + store = new PersistentStore(); + dataReady: boolean = false; + adbParams = { + files: [], + idx: -1, + traceType: null, + }; + setState(state:ProxyState, errorText = "") { this.state = state; this.errorText = errorText; @@ -117,46 +186,25 @@ export class ProxyClient { getDevices() { if (this.state !== ProxyState.DEVICES && this.state !== ProxyState.CONNECTING) { - clearInterval(this.refresh_worker); - this.refresh_worker = undefined; + clearInterval(this.refresh_worker!); + this.refresh_worker = null; return; } - const client = this; - this.call("GET", ProxyEndpoint.DEVICES, this, function(request: any, view: any) { - try { - client.devices = JSON.parse(request.responseText); - const last = client.store.getFromStore("adb.lastDevice"); - if (last && client.devices[last] && - client.devices[last].authorised) { - client.selectDevice(last); - } else { - if (client.refresh_worker === undefined) { - client.refresh_worker = setInterval(client.getDevices, 1000); - } - client.setState(ProxyState.DEVICES); - } - } catch (err) { - console.error(err); - client.errorText = request.responseText; - client.setState(ProxyState.ERROR); - } - }); + proxyRequest.call("GET", ProxyEndpoint.DEVICES, this, proxyRequest.getDevices); } selectDevice(device_id: string) { this.selectedDevice = device_id; this.store.addToStore("adb.lastDevice", device_id); - this.lastDevice = device_id; this.setState(ProxyState.START_TRACE); } - deviceId() { - return this.selectedDevice; - } - - resetLastDevice() { - this.lastDevice = ""; - this.store.addToStore("adb.lastDevice", ""); + async updateAdbData(files:any, idx:any, traceType:any, view: any) { + this.adbParams.files = files; + this.adbParams.idx = idx; + this.adbParams.traceType = traceType; + await proxyRequest.call("GET", `${ProxyEndpoint.FETCH}${this.selectedDevice}/${files[idx]}/`, view, + proxyRequest.updateAdbData, "arraybuffer"); } } diff --git a/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts b/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts index 99a78472a..28c4201a0 100644 --- a/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts +++ b/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts @@ -1,10 +1,14 @@ - -interface TraceConfiguration { - name: string, - defaultCheck?: boolean, +export interface TraceConfiguration { + name?: string, + run?: boolean, + isTraceCollection?: boolean, config?: ConfigurationOptions } +interface TraceConfigurationMap { + [key: string]: TraceConfiguration +} + export interface ConfigurationOptions { enableConfigs: Array, selectionConfigs: Array @@ -12,10 +16,12 @@ export interface ConfigurationOptions { export interface EnableConfiguration { name: string, - defaultCheck: boolean, + key: string, + enabled: boolean, } export interface SelectionConfiguration { + key: string, name: string, options: Array, value: string @@ -25,46 +31,10 @@ export type configMap = { [key: string]: Array | string; } -export const TRACES = { - "default": { - "window_trace": { - name: "Window Manager", - }, - "accessibility_trace": { - name: "Accessibility", - }, - "layers_trace": { - name: "Surface Flinger", - }, - "transactions": { - name: "Transaction", - }, - "proto_log": { - name: "ProtoLog", - }, - "screen_recording": { - name: "Screen Recording", - }, - "ime_trace_clients": { - name: "Input Method Clients", - }, - "ime_trace_service": { - name: "Input Method Service", - }, - "ime_trace_managerservice": { - name: "Input Method Manager Service", - }, - }, - "arc": { - "wayland_trace": { - name: "Wayland", - }, - }, -}; - -const wmTraceSelectionConfigs = [ +const wmTraceSelectionConfigs: Array = [ { - name: "wmbuffersize (KB)", + key: "wmbuffersize", + name: "buffer size (KB)", options: [ "4000", "8000", @@ -74,7 +44,8 @@ const wmTraceSelectionConfigs = [ value: "4000" }, { - name: "tracingtype", + key: "tracingtype", + name: "tracing type", options: [ "frame", "transaction", @@ -82,7 +53,8 @@ const wmTraceSelectionConfigs = [ value: "frame" }, { - name: "tracinglevel", + key: "tracinglevel", + name: "tracing level", options: [ "verbose", "debug", @@ -92,54 +64,126 @@ const wmTraceSelectionConfigs = [ }, ]; - -export const traceConfigurations: Array = [ +const sfTraceEnableConfigs: Array = [ { - name: "Surface Flinger", - defaultCheck: true, - config: { - enableConfigs: [ - {name: "composition", defaultCheck: false}, - {name: "metadata", defaultCheck: false}, - {name: "hwc", defaultCheck: false}, - {name: "tracebuffers", defaultCheck: false} - ], - selectionConfigs: [ - { - name: "sfbuffersize (KB)", - options: ["4000","8000","16000","32000",], - value: "4000" - } - ] - } + name: "composition", + key: "composition", + enabled: true }, { + name: "metadata", + key: "metadata", + enabled: true + }, + { + name: "hwc", + key: "hwc", + enabled: true + }, + { + name: "trace buffers", + key: "tracebuffers", + enabled: true + } +]; + +const sfTraceSelectionConfigs: Array = [ + { + key: "sfbuffersize", + name: "buffer size (KB)", + options: ["4000","8000","16000","32000"], + value: "4000" + } +]; + +export const traceConfigurations: TraceConfigurationMap = { + "layers_trace": { + name: "Surface Flinger", + run: true, + config: { + enableConfigs: sfTraceEnableConfigs, + selectionConfigs: sfTraceSelectionConfigs, + } + }, + "window_trace": { name: "Window Manager", - defaultCheck: true, + run: true, config: { enableConfigs: [], selectionConfigs: wmTraceSelectionConfigs, } }, - { + "screen_recording": { name: "Screen Recording", + run: true, }, - { - name: "Accessibility", - }, - { - name: "Transaction", - }, - { + "ime_tracing": { name: "IME Tracing", - defaultCheck: true, + run: true, + isTraceCollection: true, config: { enableConfigs: [ - {name: "Input Method Clients", defaultCheck: true}, - {name: "Input Method Service", defaultCheck: true}, - {name: "Input Method Manager Service", defaultCheck: true}, + { + name: "Input Method Clients", + key: "ime_trace_clients", + enabled: true, + }, + { + name: "Input Method Service", + key: "ime_trace_service", + enabled: true, + }, + { + name: "Input Method Manager Service", + key: "ime_trace_managerservice", + enabled: true, + }, ], selectionConfigs: [] } }, -]; + "ime_trace_clients": { + name: "Input Method Clients", + run: true, + }, + "ime_trace_service": { + name: "Input Method Service", + run: true, + }, + "ime_trace_managerservice": { + name: "Input Method Manager Service", + run: true, + }, + "accessibility_trace": { + name: "Accessibility", + run: false, + }, + "transactions": { + name: "Transaction", + run: false, + }, + "proto_log": { + name: "ProtoLog", + run: false, + }, + "wayland_trace": { + name: "Wayland", + run: false, + }, +}; + + +export const TRACES: { [key: string]: TraceConfigurationMap; } = { + "default": { + "window_trace": traceConfigurations["window_trace"], + "accessibility_trace": traceConfigurations["accessibility_trace"], + "layers_trace": traceConfigurations["layers_trace"], + "transactions": traceConfigurations["transactions"], + "proto_log": traceConfigurations["proto_log"], + "screen_recording": traceConfigurations["screen_recording"], + "ime_tracing": traceConfigurations["ime_tracing"], + }, + "arc": { + "wayland_trace": traceConfigurations["wayland_trace"], + }, +}; diff --git a/tools/winscope-ng/src/trace_collection/trace_config.component.ts b/tools/winscope-ng/src/trace_collection/trace_config.component.ts deleted file mode 100644 index 8b2ce9606..000000000 --- a/tools/winscope-ng/src/trace_collection/trace_config.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { ConfigurationOptions, SelectionConfiguration } from "./trace_collection_utils"; -import { Component, Input, Output, EventEmitter } from "@angular/core"; -import { ProxyState, ProxyClient } from "./proxy_client"; - - -@Component({ - selector: "trace-config", - template: ` -
- {{name}} -
- {{enableConfig.name}} -
- - {{con.name}} - - {{ option }} - - -
-
-
- `, - styles: [".adv-config {margin-left: 5rem;}"], -}) - -export class TraceConfigComponent { - states = ProxyState; - objectKeys = Object.keys; - - @Input() - name = ""; - - @Input() - configs: ConfigurationOptions | null = null; - - @Input() - defaultCheck: boolean | undefined = false; - - public traceEnableConfigs(): Array { - if (this.configs && this.configs.enableConfigs) { - return this.configs.enableConfigs; - } else { - return []; - } - } - - public traceSelectionConfigs(): Array { - if (this.configs) { - return this.configs.selectionConfigs; - } else { - return []; - } - } -} \ No newline at end of file From c40abfeb9086daf12fba6e0ebd52781ab2e046f6 Mon Sep 17 00:00:00 2001 From: Priyanka Patel Date: Wed, 27 Jul 2022 07:54:22 +0000 Subject: [PATCH 5/5] Add component tests and an upload card. Adding component spec tests for all new components involved in trace collection. This includes a new card for this CL, UploadTracesComponent, to make testing more methodical. Test: npm run test:component Bug: b/238981126 Change-Id: I46d1b24fed84ce35432dad36fd5ac24d35cd0542 --- .../src/app/adb_proxy.component.spec.ts | 57 +++- .../src/app/adb_proxy.component.ts | 51 ++-- .../winscope-ng/src/app/app.component.spec.ts | 59 ++++- tools/winscope-ng/src/app/app.component.ts | 53 ++-- tools/winscope-ng/src/app/app.module.ts | 3 + .../src/app/collect_traces.component.spec.ts | 243 ++++++++++++++++- .../src/app/collect_traces.component.ts | 163 +++++------- tools/winscope-ng/src/app/core.ts | 7 +- .../src/app/trace_config.component.spec.ts | 91 ++++++- .../src/app/trace_config.component.ts | 9 +- .../src/app/upload_traces.component.spec.ts | 42 +++ .../src/app/upload_traces.component.ts | 62 +++++ .../src/app/web_adb.component.spec.ts | 16 +- .../winscope-ng/src/app/web_adb.component.ts | 14 +- .../src/common/persistent_store.ts | 12 +- tools/winscope-ng/src/styles.css | 2 +- .../src/trace_collection/connection.ts | 244 +++--------------- .../src/trace_collection/proxy_client.ts | 101 ++++++-- .../src/trace_collection/proxy_connection.ts | 166 ++++++++++++ .../src/trace_collection/set_traces.ts | 49 ++++ .../trace_collection_utils.ts | 5 +- 21 files changed, 1013 insertions(+), 436 deletions(-) create mode 100644 tools/winscope-ng/src/app/upload_traces.component.spec.ts create mode 100644 tools/winscope-ng/src/app/upload_traces.component.ts create mode 100644 tools/winscope-ng/src/trace_collection/proxy_connection.ts create mode 100644 tools/winscope-ng/src/trace_collection/set_traces.ts diff --git a/tools/winscope-ng/src/app/adb_proxy.component.spec.ts b/tools/winscope-ng/src/app/adb_proxy.component.spec.ts index 20ddb46cb..f048b6b55 100644 --- a/tools/winscope-ng/src/app/adb_proxy.component.spec.ts +++ b/tools/winscope-ng/src/app/adb_proxy.component.spec.ts @@ -13,23 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {ComponentFixture, TestBed} from "@angular/core/testing"; -import {AdbProxyComponent} from "./adb_proxy.component"; +import { CommonModule } from "@angular/common"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { AdbProxyComponent } from "./adb_proxy.component"; +import { proxyClient, ProxyState } from "../trace_collection/proxy_client"; +import { MatIconModule } from "@angular/material/icon"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { MatButtonModule } from "@angular/material/button"; +import { NO_ERRORS_SCHEMA } from "@angular/core"; describe("AdbProxyComponent", () => { let fixture: ComponentFixture; let component: AdbProxyComponent; let htmlElement: HTMLElement; - beforeAll(async () => { + beforeEach(async () => { await TestBed.configureTestingModule({ + imports: [ + CommonModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + BrowserAnimationsModule, + MatButtonModule + ], declarations: [AdbProxyComponent], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); - }); - - beforeEach(() => { fixture = TestBed.createComponent(AdbProxyComponent); component = fixture.componentInstance; + component.proxy = proxyClient; htmlElement = fixture.nativeElement; }); @@ -37,5 +52,35 @@ describe("AdbProxyComponent", () => { expect(component).toBeTruthy(); }); + it("check correct icon and message displays if no proxy", () => { + component.proxy.setState(ProxyState.NO_PROXY); + fixture.detectChanges(); + expect(htmlElement.querySelector(".adb-info")?.innerHTML).toBe("Unable to connect to Winscope ADB proxy"); + expect(htmlElement.querySelector(".adb-icon")?.innerHTML).toBe("error"); + }); + it("check correct icon and message displays if invalid proxy", () => { + component.proxy.setState(ProxyState.INVALID_VERSION); + fixture.detectChanges(); + expect(htmlElement.querySelector(".adb-info")?.innerHTML).toBe("Your local proxy version is incompatible with Winscope."); + expect(htmlElement.querySelector(".adb-icon")?.innerHTML).toBe("update"); + }); + + it("check correct icon and message displays if unauthorised proxy", () => { + component.proxy.setState(ProxyState.UNAUTH); + fixture.detectChanges(); + expect(htmlElement.querySelector(".adb-info")?.innerHTML).toBe("Proxy authorisation required"); + expect(htmlElement.querySelector(".adb-icon")?.innerHTML).toBe("lock"); + }); + + it("check retry button acts as expected", async () => { + component.proxy.setState(ProxyState.NO_PROXY); + fixture.detectChanges(); + spyOn(component, "restart").and.callThrough(); + const button: HTMLButtonElement | null = htmlElement.querySelector(".retry"); + expect(button).toBeInstanceOf(HTMLButtonElement); + button?.dispatchEvent(new Event("click")); + await fixture.whenStable(); + expect(component.restart).toHaveBeenCalled(); + }); }); diff --git a/tools/winscope-ng/src/app/adb_proxy.component.ts b/tools/winscope-ng/src/app/adb_proxy.component.ts index c04923c22..81f6c79dd 100644 --- a/tools/winscope-ng/src/app/adb_proxy.component.ts +++ b/tools/winscope-ng/src/app/adb_proxy.component.ts @@ -14,17 +14,17 @@ * limitations under the License. */ import { Component, Input, Output, EventEmitter } from "@angular/core"; -import { ProxyClient, ProxyState } from "../trace_collection/proxy_client"; +import { proxyClient, ProxyClient, ProxyState } from "../trace_collection/proxy_client"; @Component({ selector: "adb-proxy", template: `
-
- error - Unable to connect to Winscope ADB proxy +
+ error + Unable to connect to Winscope ADB proxy
-
+

Launch the Winscope ADB Connect proxy to capture traces directly from your browser.

Python 3.5+ and ADB are required.

Run:

@@ -32,48 +32,48 @@ import { ProxyClient, ProxyState } from "../trace_collection/proxy_client";
$ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py

Or get it from the AOSP repository.

-
- - +
-
- update - Your local proxy version is incompatible with Winscope. +
+ update + Your local proxy version is incompatible with Winscope.
-
+

Please update the proxy to version {{ proxyVersion }}.

Run:

python3
$ANDROID_BUILD_TOP/development/tools/winscope-ng/src/adb/winscope_proxy.py

Or get it from the AOSP repository.

-
- - +
-
- lock - Proxy authorisation required +
+ lock + Proxy authorisation required
-
+

Enter Winscope proxy token:

The proxy token is printed to console on proxy launch, copy and paste it above.

-
- +
+
@@ -82,17 +82,18 @@ import { ProxyClient, ProxyState } from "../trace_collection/proxy_client"; }) export class AdbProxyComponent { @Input() - proxy: any = {}; + proxy: ProxyClient = proxyClient; @Output() - proxyChange = new EventEmitter(); + proxyChange = new EventEmitter(); - @Output() addKey = new EventEmitter(); + @Output() + addKey = new EventEmitter(); states = ProxyState; proxyKeyItem = ""; readonly proxyVersion = this.proxy.VERSION; - readonly downloadProxyUrl: string = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py"; + readonly downloadProxyUrl: string = "https://android.googlesource.com/platform/development/+/master/tools/winscope/adb_proxy/winscope_proxy.py"; public restart() { this.addKey.emit(this.proxyKeyItem); diff --git a/tools/winscope-ng/src/app/app.component.spec.ts b/tools/winscope-ng/src/app/app.component.spec.ts index e1e4aa1ad..a8e00c87d 100644 --- a/tools/winscope-ng/src/app/app.component.spec.ts +++ b/tools/winscope-ng/src/app/app.component.spec.ts @@ -13,21 +13,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ChangeDetectionStrategy } from "@angular/core"; import {ComponentFixture, TestBed} from "@angular/core/testing"; -import {AppComponent} from "./app.component"; +import { CommonModule } from "@angular/common"; +import { MatCardModule } from "@angular/material/card"; +import { MatButtonModule } from "@angular/material/button"; +import { MatGridListModule } from "@angular/material/grid-list"; + +import { AppComponent } from "./app.component"; +import { CollectTracesComponent } from "./collect_traces.component"; +import { UploadTracesComponent } from "./upload_traces.component"; +import { AdbProxyComponent } from "./adb_proxy.component"; +import { WebAdbComponent } from "./web_adb.component"; +import { TraceConfigComponent } from "./trace_config.component"; + +import { ComponentFixtureAutoDetect } from "@angular/core/testing"; + describe("AppComponent", () => { let fixture: ComponentFixture; let component: AppComponent; let htmlElement: HTMLElement; - beforeAll(async () => { + beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ - AppComponent + providers: [ + { provide: ComponentFixtureAutoDetect, useValue: true } ], + imports: [ + CommonModule, + MatCardModule, + MatButtonModule, + MatGridListModule, + ], + declarations: [ + AppComponent, + CollectTracesComponent, + UploadTracesComponent, + AdbProxyComponent, + WebAdbComponent, + TraceConfigComponent, + ], + }).overrideComponent(AppComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } }).compileComponents(); - fixture = TestBed.createComponent(AppComponent); component = fixture.componentInstance; htmlElement = fixture.nativeElement; @@ -41,7 +70,23 @@ describe("AppComponent", () => { expect(component.title).toEqual("winscope-ng"); }); - it("renders the title", () => { - expect(htmlElement.querySelector("div#title")?.innerHTML).toContain("Winscope Viewer 2.0"); + it("renders the page title", () => { + expect(htmlElement.querySelector("#title")?.innerHTML).toContain("Winscope Viewer 2.0"); + }); + + it("displays correct elements when no data loaded", async () => { + component.dataLoaded = false; + fixture.detectChanges(); + expect(htmlElement.querySelector("#collect-traces-card")).toBeTruthy(); + expect(htmlElement.querySelector("#upload-traces-card")).toBeTruthy(); + expect(htmlElement.querySelector("#loaded-data-card")).toBeFalsy(); + }); + + it("displays correct elements when data loaded", async () => { + component.dataLoaded = true; + fixture.detectChanges(); + expect(htmlElement.querySelector("#collect-traces-card")).toBeFalsy(); + expect(htmlElement.querySelector("#upload-traces-card")).toBeFalsy(); + expect(htmlElement.querySelector("#loaded-data-card")).toBeTruthy(); }); }); diff --git a/tools/winscope-ng/src/app/app.component.ts b/tools/winscope-ng/src/app/app.component.ts index e159f08c9..b2d586d21 100644 --- a/tools/winscope-ng/src/app/app.component.ts +++ b/tools/winscope-ng/src/app/app.component.ts @@ -26,21 +26,19 @@ import { PersistentStore } from "../common/persistent_store";
Winscope Viewer 2.0
-
- + +
+ - - Upload Traces -
- -
+ +
- - Loaded data + + Loaded data
@@ -55,21 +53,23 @@ import { PersistentStore } from "../common/persistent_store";
`, - styles: [".card-container{width: 100%; display:flex; flex-direction: row; overflow: auto;}"] + styles: [".home{width: 100%; display:flex; flex-direction: row; overflow: auto;}"] }) export class AppComponent { title = "winscope-ng"; - - core: Core = new Core(); + core: Core; states = ProxyState; store: PersistentStore = new PersistentStore(); - dataLoaded: boolean = false; + dataLoaded = false; constructor( @Inject(Injector) injector: Injector ) { - customElements.define("viewer-window-manager", - createCustomElement(ViewerWindowManagerComponent, {injector})); + this.core = new Core(); + if (!customElements.get("viewer-window-manager")) { + customElements.define("viewer-window-manager", + createCustomElement(ViewerWindowManagerComponent, {injector})); + } } onCoreChange(newCore: Core) { @@ -80,34 +80,11 @@ export class AppComponent { this.dataLoaded = loaded; } - public async onInputFile(event: Event) { - const files = this.getInputFiles(event); - await this.core.bootstrap(files); - - const viewersDiv = document.querySelector("div#viewers")!; - viewersDiv.innerHTML = ""; - this.core.getViews().forEach(view => viewersDiv!.appendChild(view) ); - - const timestampsDiv = document.querySelector("div#timestamps")!; - timestampsDiv.innerHTML = `Retrieved ${this.core.getTimestamps().length} unique timestamps`; - } - public notifyCurrentTimestamp() { const dummyTimestamp = 1000000; //TODO: get timestamp from time scrub this.core.notifyCurrentTimestamp(dummyTimestamp); } - //TODO: extend with support for multiple files, archives, etc... - private getInputFiles(event: Event): File[] { - const files: any = (event?.target as HTMLInputElement)?.files; - - if (!files || !files[0]) { - return []; - } - - return [files[0]]; - } - public clearData() { this.dataLoaded = false; this.core.clearData(); diff --git a/tools/winscope-ng/src/app/app.module.ts b/tools/winscope-ng/src/app/app.module.ts index 0cb216f58..659bd16ba 100644 --- a/tools/winscope-ng/src/app/app.module.ts +++ b/tools/winscope-ng/src/app/app.module.ts @@ -22,12 +22,15 @@ import { CollectTracesComponent } from "./collect_traces.component"; import { AdbProxyComponent } from "./adb_proxy.component"; import { WebAdbComponent } from "./web_adb.component"; import { TraceConfigComponent } from "./trace_config.component"; +import { UploadTracesComponent } from "./upload_traces.component"; + @NgModule({ declarations: [ AppComponent, ViewerWindowManagerComponent, CollectTracesComponent, + UploadTracesComponent, AdbProxyComponent, WebAdbComponent, TraceConfigComponent, diff --git a/tools/winscope-ng/src/app/collect_traces.component.spec.ts b/tools/winscope-ng/src/app/collect_traces.component.spec.ts index 381d01ff3..7f4189c32 100644 --- a/tools/winscope-ng/src/app/collect_traces.component.spec.ts +++ b/tools/winscope-ng/src/app/collect_traces.component.spec.ts @@ -15,26 +15,259 @@ */ import {ComponentFixture, TestBed} from "@angular/core/testing"; import {CollectTracesComponent} from "./collect_traces.component"; +import { MatIconModule } from "@angular/material/icon"; +import { MatCardModule } from "@angular/material/card"; +import { AdbProxyComponent } from "./adb_proxy.component"; +import { WebAdbComponent } from "./web_adb.component"; +import { TraceConfigComponent } from "./trace_config.component"; +import { MatListModule } from "@angular/material/list"; +import { MatButtonModule } from "@angular/material/button"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; describe("CollectTracesComponent", () => { let fixture: ComponentFixture; let component: CollectTracesComponent; let htmlElement: HTMLElement; - beforeAll(async () => { + beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [CollectTracesComponent], + imports: [ + MatIconModule, + MatCardModule, + MatListModule, + MatIconModule, + MatButtonModule, + MatProgressBarModule, + BrowserAnimationsModule + ], + declarations: [ + CollectTracesComponent, + AdbProxyComponent, + WebAdbComponent, + TraceConfigComponent, + ], }).compileComponents(); - }); - - beforeEach(() => { fixture = TestBed.createComponent(CollectTracesComponent); component = fixture.componentInstance; htmlElement = fixture.nativeElement; + component.isAdbProxy = true; }); it("can be created", () => { expect(component).toBeTruthy(); }); + it("renders the expected card title", () => { + fixture.detectChanges(); + expect(htmlElement.querySelector("#title")?.innerHTML).toContain("Collect Traces"); + }); + + it("displays connecting message", () => { + component.connect.isConnectingState = jasmine.createSpy().and.returnValue(true); + fixture.detectChanges(); + expect(htmlElement.querySelector(".connecting-message")?.innerHTML).toContain("Connecting..."); + }); + + it("displays adb set up", async () => { + component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false); + fixture.detectChanges(); + fixture.whenStable().then( () => { + expect(htmlElement.querySelector(".set-up-adb")).toBeTruthy(); + const proxyTab: HTMLButtonElement | null = htmlElement.querySelector("#proxy-tab"); + expect(proxyTab).toBeInstanceOf(HTMLButtonElement); + const webTab: HTMLButtonElement | null = htmlElement.querySelector("#web-tab"); + expect(webTab).toBeInstanceOf(HTMLButtonElement); + }); + }); + + it("displays adb proxy element", async () => { + component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false); + component.isAdbProxy = true; + fixture.detectChanges(); + fixture.whenStable().then( () => { + expect(htmlElement.querySelector("adb-proxy")).toBeTruthy(); + expect(htmlElement.querySelector("web-adb")).toBeFalsy(); + }); + }); + + it("displays web adb element", async () => { + component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false); + component.isAdbProxy = false; + fixture.detectChanges(); + fixture.whenStable().then( () => { + expect(htmlElement.querySelector("adb-proxy")).toBeFalsy(); + expect(htmlElement.querySelector("web-adb")).toBeTruthy(); + }); + }); + + it("changes to adb workflow tab", async () => { + component.connect.adbSuccess = jasmine.createSpy().and.returnValue(false); + component.isAdbProxy = true; + fixture.detectChanges(); + fixture.whenStable().then(() => { + const webTab: HTMLButtonElement | null = htmlElement.querySelector("#web-tab"); + expect(webTab).toBeInstanceOf(HTMLButtonElement); + webTab?.dispatchEvent(new Event("click")); + fixture.whenStable().then(() => { + expect(component.displayWebAdbTab).toHaveBeenCalled(); + }); + }); + }); + + it("displays no connected devices", async () => { + component.connect.isDevicesState = jasmine.createSpy().and.returnValue(true); + component.connect.devices = jasmine.createSpy().and.returnValue({}); + fixture.detectChanges(); + fixture.whenStable().then( () => { + const el = htmlElement.querySelector("devices-connecting"); + expect(el).toBeTruthy(); + expect(el?.innerHTML).toContain("No devices detected"); + }); + }); + + it("displays connected authorised devices", async () => { + component.connect.isDevicesState = jasmine.createSpy().and.returnValue(true); + component.connect.devices = jasmine.createSpy().and.returnValue({"35562": {model: "Pixel 6", authorised:true}}); + fixture.detectChanges(); + fixture.whenStable().then( () => { + const el = htmlElement.querySelector("devices-connecting"); + expect(el).toBeTruthy(); + expect(el?.innerHTML).toContain("Connected devices:"); + expect(el?.innerHTML).toContain("Pixel 6"); + expect(el?.innerHTML).toContain("smartphone"); + }); + }); + + it("displays connected unauthorised devices", async () => { + component.connect.isDevicesState = jasmine.createSpy().and.returnValue(true); + component.connect.devices = jasmine.createSpy().and.returnValue({"35562": {model: "Pixel 6", authorised:false}}); + fixture.detectChanges(); + fixture.whenStable().then( () => { + const el = htmlElement.querySelector("devices-connecting"); + expect(el).toBeTruthy(); + expect(el?.innerHTML).toContain("Connected devices:"); + expect(el?.innerHTML).toContain("unauthorised"); + expect(el?.innerHTML).toContain("screen_lock_portrait"); + }); + }); + + it("displays trace collection config elements", async () => { + component.connect.isStartTraceState = jasmine.createSpy().and.returnValue(true); + const mock = {model: "Pixel 6", authorised:true}; + component.connect.devices = jasmine.createSpy().and.returnValue({"35562": mock}); + component.connect.selectedDevice = jasmine.createSpy().and.returnValue(mock); + fixture.detectChanges(); + + fixture.whenStable().then( () => { + const el = htmlElement.querySelector("trace-collection-config"); + expect(el).toBeTruthy(); + expect(el?.innerHTML).toContain("smartphone"); + expect(el?.innerHTML).toContain("Pixel 6"); + expect(el?.innerHTML).toContain("35562"); + + const traceSection = htmlElement.querySelector("trace-section"); + expect(traceSection).toBeTruthy(); + + const dumpSection = htmlElement.querySelector("dump-section"); + expect(dumpSection).toBeTruthy(); + }); + }); + + it("start trace button works as expected", async () => { + component.connect.isStartTraceState = jasmine.createSpy().and.returnValue(true); + const mock = {model: "Pixel 6", authorised:true}; + component.connect.devices = jasmine.createSpy().and.returnValue({"35562": mock}); + component.connect.selectedDevice = jasmine.createSpy().and.returnValue(mock); + fixture.detectChanges(); + + fixture.whenStable().then( () => { + const start: HTMLButtonElement | null = htmlElement.querySelector(".start-btn"); + expect(start).toBeInstanceOf(HTMLButtonElement); + start?.dispatchEvent(new Event("click")); + fixture.whenStable().then(() => { + expect(component.startTracing).toHaveBeenCalled(); + expect(component.connect.startTrace).toHaveBeenCalled(); + }); + }); + }); + + it("dump state button works as expected", async () => { + component.connect.isStartTraceState = jasmine.createSpy().and.returnValue(true); + const mock = {model: "Pixel 6", authorised:true}; + component.connect.devices = jasmine.createSpy().and.returnValue({"35562": mock}); + component.connect.selectedDevice = jasmine.createSpy().and.returnValue(mock); + fixture.detectChanges(); + + fixture.whenStable().then( () => { + const dump: HTMLButtonElement | null = htmlElement.querySelector(".dump-btn"); + expect(dump).toBeInstanceOf(HTMLButtonElement); + dump?.dispatchEvent(new Event("click")); + fixture.whenStable().then(() => { + expect(component.dumpState).toHaveBeenCalled(); + expect(component.connect.dumpState).toHaveBeenCalled(); + }); + }); + }); + + it("change device button works as expected", async () => { + component.connect.isStartTraceState = jasmine.createSpy().and.returnValue(true); + const mock = {model: "Pixel 6", authorised:true}; + component.connect.devices = jasmine.createSpy().and.returnValue({"35562": mock}); + component.connect.selectedDevice = jasmine.createSpy().and.returnValue(mock); + fixture.detectChanges(); + + fixture.whenStable().then( () => { + const change: HTMLButtonElement | null = htmlElement.querySelector(".change-btn"); + expect(change).toBeInstanceOf(HTMLButtonElement); + change?.dispatchEvent(new Event("click")); + fixture.whenStable().then(() => { + expect(component.connect.resetLastDevice).toHaveBeenCalled(); + }); + }); + }); + + it("displays unknown error message", () => { + component.connect.isErrorState = jasmine.createSpy().and.returnValue(true); + component.connect.proxy!.errorText = "bad things are happening"; + fixture.detectChanges(); + fixture.whenStable().then( () => { + const el = htmlElement.querySelector(".unknown-error"); + expect(el?.innerHTML).toContain("Error:"); + expect(el?.innerHTML).toContain("bad things are happening"); + const retry: HTMLButtonElement | null = htmlElement.querySelector(".retry-btn"); + expect(retry).toBeInstanceOf(HTMLButtonElement); + retry?.dispatchEvent(new Event("click")); + fixture.whenStable().then(() => { + expect(component.connect.restart).toHaveBeenCalled(); + }); + }); + }); + + it("displays end tracing elements", () => { + component.connect.isEndTraceState = jasmine.createSpy().and.returnValue(true); + fixture.detectChanges(); + fixture.whenStable().then( () => { + const el = htmlElement.querySelector(".end-tracing"); + expect(el?.innerHTML).toContain("Tracing..."); + expect(htmlElement.querySelector("mat-progress-bar")).toBeTruthy(); + + const end: HTMLButtonElement | null = htmlElement.querySelector(".end"); + expect(end).toBeInstanceOf(HTMLButtonElement); + end?.dispatchEvent(new Event("click")); + fixture.whenStable().then(() => { + expect(component.endTrace).toHaveBeenCalled(); + expect(component.connect.endTrace).toHaveBeenCalled(); + }); + }); + }); + + it("displays loading data elements", () => { + component.connect.isLoadDataState = jasmine.createSpy().and.returnValue(true); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(htmlElement.querySelector(".load-data")?.innerHTML).toContain("Loading data..."); + expect(htmlElement.querySelector("mat-progress-bar")).toBeTruthy(); + }); + }); }); diff --git a/tools/winscope-ng/src/app/collect_traces.component.ts b/tools/winscope-ng/src/app/collect_traces.component.ts index 21bd42ab2..04fdca90d 100644 --- a/tools/winscope-ng/src/app/collect_traces.component.ts +++ b/tools/winscope-ng/src/app/collect_traces.component.ts @@ -14,9 +14,11 @@ * limitations under the License. */ import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core"; -import { ProxyConnection, Device, configureTraces } from "../trace_collection/connection"; +import { ProxyConnection } from "trace_collection/proxy_connection"; +import { Connection } from "trace_collection/connection"; +import { setTraces } from "trace_collection/set_traces"; import { ProxyState } from "../trace_collection/proxy_client"; -import { traceConfigurations, configMap, SelectionConfiguration } from "../trace_collection/trace_collection_utils"; +import { traceConfigurations, configMap, SelectionConfiguration, TraceConfigurationMap, EnableConfiguration } from "../trace_collection/trace_collection_utils"; import { Core } from "app/core"; import { PersistentStore } from "../common/persistent_store"; @@ -24,27 +26,27 @@ import { PersistentStore } from "../common/persistent_store"; @Component({ selector: "collect-traces", template: ` - Collect Traces + Collect Traces -
Connecting...
+
Connecting...
-
- - - +
+ + +
-
{{ devices().length > 0 ? "Connected devices:" : "No devices detected" }}
+
{{ objectKeys(connect.devices()).length > 0 ? "Connected devices:" : "No devices detected" }}
- + - {{ connect.proxy.devices[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }} + {{ connect.devices()[deviceId].authorised ? "smartphone" : "screen_lock_portrait" }} - {{ connect.proxy.devices[deviceId].authorised ? connect.proxy.devices[deviceId].model : "unauthorised" }} ({{ deviceId }}) + {{ connect.devices()[deviceId].authorised ? connect.devices()[deviceId].model : "unauthorised" }} ({{ deviceId }}) @@ -56,22 +58,22 @@ import { PersistentStore } from "../common/persistent_store"; smartphone - {{ selectedDevice().model }} ({{ connect.proxy.selectedDevice }}) + {{ connect.selectedDevice().model }} ({{ connect.selectedDeviceId() }})
-
- - - +
+ + +

Trace targets:

@@ -79,31 +81,30 @@ import { PersistentStore } from "../common/persistent_store";

Dump targets:

{{connect.DUMPS[dumpKey].name}} + *ngFor="let dumpKey of objectKeys(setTraces.DUMPS)" + [(ngModel)]="setTraces.DUMPS[dumpKey].run" + >{{setTraces.DUMPS[dumpKey].name}}
-
+
error Error:
-            {{ connect.proxy.errorText }}
+            {{ connect.proxy?.errorText }}
         
- +
-
- Tracing... +
+ Tracing... - +
-
- Loading data... +
+ Loading data...
@@ -114,26 +115,24 @@ import { PersistentStore } from "../common/persistent_store"; export class CollectTracesComponent implements OnInit { objectKeys = Object.keys; isAdbProxy = true; - startTrace = false; - startDump = false; traceConfigurations = traceConfigurations; - connect: any = new ProxyConnection(); - downloadProxyUrl = "https://android.googlesource.com/platform/development/+/master/tools/winscope-ng/adb/winscope_proxy.py"; + connect: Connection = new ProxyConnection(); + setTraces = setTraces; @Input() - store: PersistentStore = new PersistentStore(); + store: PersistentStore = new PersistentStore(); @Input() - core: Core = new Core(); + core: Core = new Core(); @Output() - coreChange = new EventEmitter(); + coreChange = new EventEmitter(); @Input() - dataLoaded: boolean = false; + dataLoaded = false; @Output() - dataLoadedChange = new EventEmitter(); + dataLoadedChange = new EventEmitter(); ngOnInit(): void { if (this.isAdbProxy) { @@ -145,43 +144,21 @@ export class CollectTracesComponent implements OnInit { } ngOnDestroy(): void { - this.connect.proxy.removeOnProxyChange(this.onProxyChange); + this.connect.proxy?.removeOnProxyChange(this.onProxyChange); } public onAddKey(key: string) { this.store.addToStore("adb.proxyKey", key); - this.connect.setProxyKey(key); - this.restart(); + if (this.connect.setProxyKey) { + this.connect.setProxyKey(key); + } + this.connect.restart(); } public onProxyChange(newState: ProxyState) { this.connect.onConnectChange(newState); } - public adbSuccess() { - return this.connect.adbSuccess(); - } - - public devices(): Array { - return this.connect.devices(); - } - - public selectedDevice(): Device { - return this.connect.selectedDevice(); - } - - public restart() { - this.connect.restart(); - } - - public resetLastDevice() { - this.connect.resetLastDevice(); - } - - public selectDevice(id: string) { - this.connect.selectDevice(id); - } - public displayAdbProxyTab() { this.isAdbProxy = true; this.connect = new ProxyConnection(); @@ -194,12 +171,12 @@ export class CollectTracesComponent implements OnInit { } public requestedTraces() { - const tracesFromCollection: Array = []; - const req = Object.keys(this.connect.DYNAMIC_TRACES()) + const tracesFromCollection: Array = []; + const req = Object.keys(setTraces.DYNAMIC_TRACES) .filter((traceKey:string) => { - const traceConfig = this.connect.DYNAMIC_TRACES()[traceKey]; + const traceConfig = setTraces.DYNAMIC_TRACES[traceKey]; if (traceConfig.isTraceCollection) { - traceConfig.config.enableConfigs.forEach((innerTrace:any) => { + traceConfig.config?.enableConfigs.forEach((innerTrace:EnableConfiguration) => { if (innerTrace.enabled) { tracesFromCollection.push(innerTrace.key); } @@ -212,22 +189,22 @@ export class CollectTracesComponent implements OnInit { } public requestedDumps() { - return Object.keys(this.connect.DUMPS) - .filter((dumpKey:any) => { - return this.connect.DUMPS[dumpKey].enabled; + return Object.keys(setTraces.DUMPS) + .filter((dumpKey:string) => { + return setTraces.DUMPS[dumpKey].run; }); } - public requestedEnableConfig(): Array | null{ + public requestedEnableConfig(): Array | undefined { const req: Array = []; - Object.keys(this.connect.DYNAMIC_TRACES()) - .forEach((traceKey:any) => { - const trace = this.connect.DYNAMIC_TRACES()[traceKey]; + Object.keys(setTraces.DYNAMIC_TRACES) + .forEach((traceKey:string) => { + const trace = setTraces.DYNAMIC_TRACES[traceKey]; if(!trace.isTraceCollection && trace.run && trace.config && trace.config.enableConfigs) { - trace.config.enableConfigs.forEach((con:any) => { + trace.config.enableConfigs.forEach((con:EnableConfiguration) => { if (con.enabled) { req.push(con.key); } @@ -235,17 +212,17 @@ export class CollectTracesComponent implements OnInit { } }); if (req.length === 0) { - return null; + return undefined; } return req; } - public requestedSelection(traceType: string) { - if (!this.connect.DYNAMIC_TRACES()[traceType].run) { - return null; + public requestedSelection(traceType: string): configMap | undefined { + if (!setTraces.DYNAMIC_TRACES[traceType].run) { + return undefined; } const selected: configMap = {}; - this.connect.DYNAMIC_TRACES()[traceType].config.selectionConfigs.forEach( + setTraces.DYNAMIC_TRACES[traceType].config?.selectionConfigs.forEach( (con: SelectionConfiguration) => { selected[con.key] = con.value; } @@ -254,13 +231,12 @@ export class CollectTracesComponent implements OnInit { } public startTracing() { - this.startTrace = true; console.log("begin tracing"); - configureTraces.reqTraces = this.requestedTraces(); + setTraces.reqTraces = this.requestedTraces(); const reqEnableConfig = this.requestedEnableConfig(); const reqSelectedSfConfig = this.requestedSelection("layers_trace"); const reqSelectedWmConfig = this.requestedSelection("window_trace"); - if (configureTraces.reqTraces.length < 1) { + if (setTraces.reqTraces.length < 1) { this.connect.throwNoTargetsError(); return; } @@ -272,20 +248,23 @@ export class CollectTracesComponent implements OnInit { } public async dumpState() { - this.startDump = true; console.log("begin dump"); - configureTraces.reqDumps = this.requestedDumps(); + setTraces.reqDumps = this.requestedDumps(); await this.connect.dumpState(); - while (!this.connect.proxy.dataReady) { + while (!setTraces.dataReady && !setTraces.dumpError) { await this.waitForData(1000); } - await this.loadFiles(); + if (!setTraces.dumpError) { + await this.loadFiles(); + } else { + this.core.clearData(); + } } public async endTrace() { console.log("end tracing"); await this.connect.endTrace(); - while (!this.connect.proxy.dataReady) { + while (!setTraces.dataReady) { await this.waitForData(1000); } await this.loadFiles(); diff --git a/tools/winscope-ng/src/app/core.ts b/tools/winscope-ng/src/app/core.ts index 1d023ebc1..57d476c61 100644 --- a/tools/winscope-ng/src/app/core.ts +++ b/tools/winscope-ng/src/app/core.ts @@ -16,6 +16,8 @@ import {TraceTypeId} from "common/trace/type_id"; import {Parser} from "parsers/parser"; import {ParserFactory} from "parsers/parser_factory"; +import { setTraces } from "trace_collection/set_traces"; +import { proxyClient } from "trace_collection/proxy_client"; import {Viewer} from "viewers/viewer"; import {ViewerFactory} from "viewers/viewer_factory"; @@ -29,6 +31,7 @@ class Core { } async bootstrap(traces: Blob[]) { + this.clearData(); this.parsers = await new ParserFactory().createParsers(traces); console.log("created parsers: ", this.parsers); @@ -75,7 +78,9 @@ class Core { clearData() { this.parsers = []; this.viewers = []; + setTraces.dataReady = false; + proxyClient.adbData = []; } } -export { Core }; +export { Core }; \ No newline at end of file diff --git a/tools/winscope-ng/src/app/trace_config.component.spec.ts b/tools/winscope-ng/src/app/trace_config.component.spec.ts index c2110fa4c..8b2cec2b4 100644 --- a/tools/winscope-ng/src/app/trace_config.component.spec.ts +++ b/tools/winscope-ng/src/app/trace_config.component.spec.ts @@ -13,28 +13,111 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { CommonModule } from "@angular/common"; import {ComponentFixture, TestBed} from "@angular/core/testing"; import {TraceConfigComponent} from "./trace_config.component"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { MatInputModule } from "@angular/material/input"; +import { MatSelectModule } from "@angular/material/select"; +import { NO_ERRORS_SCHEMA } from "@angular/core"; describe("TraceConfigComponent", () => { let fixture: ComponentFixture; let component: TraceConfigComponent; let htmlElement: HTMLElement; - beforeAll(async () => { + beforeEach(async () => { await TestBed.configureTestingModule({ + imports: [ + CommonModule, + MatCheckboxModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + BrowserAnimationsModule + ], declarations: [TraceConfigComponent], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); - }); - - beforeEach(() => { fixture = TestBed.createComponent(TraceConfigComponent); component = fixture.componentInstance; htmlElement = fixture.nativeElement; + component.trace = { + name: "layers_trace", + run: false, + config: { + enableConfigs: [{ + name:"trace buffers", + key:"tracebuffers", + enabled:true + }], + selectionConfigs: [{ + key: "tracinglevel", + name: "tracing level", + options: [ + "verbose", + "debug", + "critical", + ], + value: "debug" + }] + } + }; }); it("can be created", () => { expect(component).toBeTruthy(); }); + it("check that trace checkbox ticked on default run", () => { + component.trace.run = true; + fixture.detectChanges(); + const box = htmlElement.querySelector(".trace-box"); + expect(box?.innerHTML).toContain("aria-checked=\"true\""); + expect(box?.innerHTML).toContain("layers_trace"); + }); + + it("check that trace checkbox not ticked on default run", () => { + component.trace.run = false; + fixture.detectChanges(); + const box = htmlElement.querySelector(".trace-box"); + expect(box?.innerHTML).toContain("aria-checked=\"false\""); + }); + + it("check that correct advanced enable config only shows", () => { + component.trace.config!.selectionConfigs = []; + fixture.detectChanges(); + const adv = htmlElement.querySelector(".adv-config"); + expect(adv).toBeTruthy(); + expect(adv?.innerHTML).toContain("trace buffers"); + expect(adv?.innerHTML).not.toContain("tracing level"); + }); + + it("check that correct advanced selection config shows", () => { + component.trace.config!.enableConfigs = []; + fixture.detectChanges(); + const adv = htmlElement.querySelector(".adv-config"); + expect(adv).toBeTruthy(); + expect(adv?.innerHTML).not.toContain("trace buffers"); + expect(adv?.innerHTML).toContain("tracing level"); + }); + + it("check that changing enable config causes box to change", async () => { spyOn(component, "changeTraceCollectionConfig"); + component.trace.config!.enableConfigs[0].enabled = false; + fixture.detectChanges(); + await fixture.whenStable(); + expect(htmlElement.querySelector(".enable-config")?.innerHTML).toContain("aria-checked=\"false\""); + }); + + it("check that changing selected config causes select to change", async () => { + fixture.detectChanges(); + expect(htmlElement.querySelector(".selection")?.innerHTML).toContain("value=\"debug\""); + component.trace.config!.selectionConfigs[0].value = "verbose"; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(htmlElement.querySelector(".selection")?.innerHTML).toContain("value=\"verbose\""); + }); + }); }); diff --git a/tools/winscope-ng/src/app/trace_config.component.ts b/tools/winscope-ng/src/app/trace_config.component.ts index 213f85c12..b329781d2 100644 --- a/tools/winscope-ng/src/app/trace_config.component.ts +++ b/tools/winscope-ng/src/app/trace_config.component.ts @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, Input, OnChanges, SimpleChanges } from "@angular/core"; +import { Component, Input } from "@angular/core"; import { EnableConfiguration, SelectionConfiguration, TraceConfiguration } from "../trace_collection/trace_collection_utils"; @Component({ selector: "trace-config", template: `
+
{{selectionConfig.name}} - + { if (this.trace.config) { diff --git a/tools/winscope-ng/src/app/upload_traces.component.spec.ts b/tools/winscope-ng/src/app/upload_traces.component.spec.ts new file mode 100644 index 000000000..d617dbd38 --- /dev/null +++ b/tools/winscope-ng/src/app/upload_traces.component.spec.ts @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {ComponentFixture, TestBed} from "@angular/core/testing"; +import {UploadTracesComponent} from "./upload_traces.component"; +import { MatCardModule } from "@angular/material/card"; + +describe("CollectTracesComponent", () => { + let fixture: ComponentFixture; + let component: UploadTracesComponent; + let htmlElement: HTMLElement; + + beforeAll(async () => { + await TestBed.configureTestingModule({ + imports: [MatCardModule], + declarations: [UploadTracesComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UploadTracesComponent); + component = fixture.componentInstance; + htmlElement = fixture.nativeElement; + }); + + it("can be created", () => { + expect(component).toBeTruthy(); + }); + +}); diff --git a/tools/winscope-ng/src/app/upload_traces.component.ts b/tools/winscope-ng/src/app/upload_traces.component.ts new file mode 100644 index 000000000..4b3b5cd54 --- /dev/null +++ b/tools/winscope-ng/src/app/upload_traces.component.ts @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Component, Input, Output, EventEmitter } from "@angular/core"; +import { Core } from "app/core"; + + +@Component({ + selector: "upload-traces", + template: ` + Upload Traces + +
+ +
+
+ `, + +}) +export class UploadTracesComponent { + @Input() + core: Core = new Core(); + + @Output() + coreChange = new EventEmitter(); + + public async onInputFile(event: Event) { + const files = this.getInputFiles(event); + await this.core.bootstrap(files); + + const viewersDiv = document.querySelector("div#viewers")!; + viewersDiv.innerHTML = ""; + this.core.getViews().forEach(view => viewersDiv!.appendChild(view) ); + this.coreChange.emit(this.core); + + const timestampsDiv = document.querySelector("div#timestamps")!; + timestampsDiv.innerHTML = `Retrieved ${this.core.getTimestamps().length} unique timestamps`; + } + + //TODO: extend with support for multiple files, archives, etc... + private getInputFiles(event: Event): File[] { + const files: any = (event?.target as HTMLInputElement)?.files; + + if (!files || !files[0]) { + return []; + } + + return [files[0]]; + } +} diff --git a/tools/winscope-ng/src/app/web_adb.component.spec.ts b/tools/winscope-ng/src/app/web_adb.component.spec.ts index 0021a9e26..f5874e224 100644 --- a/tools/winscope-ng/src/app/web_adb.component.spec.ts +++ b/tools/winscope-ng/src/app/web_adb.component.spec.ts @@ -15,19 +15,19 @@ */ import {ComponentFixture, TestBed} from "@angular/core/testing"; import {WebAdbComponent} from "./web_adb.component"; +import { MatIconModule } from "@angular/material/icon"; +import { MatCardModule } from "@angular/material/card"; describe("WebAdbComponent", () => { let fixture: ComponentFixture; let component: WebAdbComponent; let htmlElement: HTMLElement; - beforeAll(async () => { + beforeEach(async () => { await TestBed.configureTestingModule({ + imports: [MatIconModule, MatCardModule], declarations: [WebAdbComponent], }).compileComponents(); - }); - - beforeEach(() => { fixture = TestBed.createComponent(WebAdbComponent); component = fixture.componentInstance; htmlElement = fixture.nativeElement; @@ -37,9 +37,9 @@ describe("WebAdbComponent", () => { expect(component).toBeTruthy(); }); - it("renders the title", () => { - const divTitle = htmlElement.querySelector(".web-adb div.title"); - expect(divTitle?.innerHTML).toContain("Unable to connect to Web ADB"); + it("renders the info message", () => { + fixture.detectChanges(); + expect(htmlElement.querySelector(".adb-info")?.innerHTML).toBe("Add new device"); + expect(htmlElement.querySelector(".adb-icon")?.innerHTML).toBe("info"); }); - }); diff --git a/tools/winscope-ng/src/app/web_adb.component.ts b/tools/winscope-ng/src/app/web_adb.component.ts index 0c18dd77f..08a6ca9d1 100644 --- a/tools/winscope-ng/src/app/web_adb.component.ts +++ b/tools/winscope-ng/src/app/web_adb.component.ts @@ -18,20 +18,20 @@ import {Component} from "@angular/core"; @Component({ selector: "web-adb", template: ` +
+ info + Add new device +
- info - Add new device -
-

Click the button below to follow instructions in the Chrome pop-up.

Selecting a device will kill all existing ADB connections.

-
- +
+
`, styles: [".icon-message {vertical-align: middle;}"] }) export class WebAdbComponent { - adbDevice: any = null; + adbDevice = null; } diff --git a/tools/winscope-ng/src/common/persistent_store.ts b/tools/winscope-ng/src/common/persistent_store.ts index 4ca5aaeb0..62a510fc8 100644 --- a/tools/winscope-ng/src/common/persistent_store.ts +++ b/tools/winscope-ng/src/common/persistent_store.ts @@ -14,10 +14,10 @@ * limitations under the License. */ export class PersistentStore { - public addToStore(key: string, value: string) { - localStorage.setItem(key, value); - } - public getFromStore(key: string) { - return localStorage.getItem(key); - } + public addToStore(key: string, value: string) { + localStorage.setItem(key, value); + } + public getFromStore(key: string) { + return localStorage.getItem(key); + } } \ No newline at end of file diff --git a/tools/winscope-ng/src/styles.css b/tools/winscope-ng/src/styles.css index 7886217ba..274d4f56c 100644 --- a/tools/winscope-ng/src/styles.css +++ b/tools/winscope-ng/src/styles.css @@ -49,7 +49,7 @@ mat-icon { margin: 5px; } -.icon-message { +.icon-message, .adb-icon, .adb-info { vertical-align: middle; } diff --git a/tools/winscope-ng/src/trace_collection/connection.ts b/tools/winscope-ng/src/trace_collection/connection.ts index 2ae50467f..80144a7b3 100644 --- a/tools/winscope-ng/src/trace_collection/connection.ts +++ b/tools/winscope-ng/src/trace_collection/connection.ts @@ -1,20 +1,39 @@ -import { proxyRequest, proxyClient, ProxyState, ProxyEndpoint } from "trace_collection/proxy_client"; -import { TRACES } from "./trace_collection_utils"; +/* + * Copyright 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ProxyClient } from "trace_collection/proxy_client"; +import { configMap } from "./trace_collection_utils"; export interface Device { + [key: string]: DeviceProperties +} + +export interface DeviceProperties { authorised: boolean; - model: string; + model: string; } export interface Connection { adbSuccess: () => boolean; - setProxyKey(key:string): any; - devices(): Array; - selectedDevice(): Device; + setProxyKey?(key:string): any; + devices(): Device; + selectedDevice(): DeviceProperties; + selectedDeviceId(): string; restart(): any; selectDevice(id:string): any; - DYNAMIC_TRACES(): any; - state(): ProxyState; + state(): any; onConnectChange(newState: any): any; resetLastDevice(): any; isDevicesState(): boolean; @@ -23,212 +42,15 @@ export interface Connection { isEndTraceState(): boolean; isLoadDataState(): boolean; isConnectingState(): boolean; - isNoProxy(): boolean; - isInvalidProxy(): boolean; - isUnauthProxy(): boolean; throwNoTargetsError(): any; startTrace( reqEnableConfig?: Array, - reqSelectedSfConfig?: any, - reqSelectedWmConfig?: any + reqSelectedSfConfig?: configMap, + reqSelectedWmConfig?: configMap ): any; - DUMPS: any; endTrace(): any; - dumpState(req:Array): any; - adbData(): any; + adbData(): Array; + dumpState(): any; + proxy?: ProxyClient; + loadProgress: number; } - -export class ProxyConnection implements Connection { - proxy = proxyClient; - DUMPS: any = { - "window_dump": { - name: "Window Manager", - enabled: true, - }, - "layers_dump": { - name: "Surface Flinger", - enabled: true, - } - }; - keep_alive_worker: any = null; - notConnected = [ - ProxyState.NO_PROXY, - ProxyState.UNAUTH, - ProxyState.INVALID_VERSION, - ]; - - constructor() { - this.proxy.setState(ProxyState.CONNECTING); - this.proxy.onProxyChange(this.onConnectChange); - const urlParams = new URLSearchParams(window.location.search); - if (urlParams.has("token")) { - this.proxy.proxyKey = urlParams.get("token")!; - } - this.proxy.getDevices(); - } - - public devices() { - return Object.keys(this.proxy.devices); - } - - public adbData() { - return this.proxy.adbData; - } - - DYNAMIC_TRACES() { - return configureTraces.DYNAMIC_TRACES; - } - - public state() { - return this.proxy.state; - } - - public isDevicesState() { - return this.state() === ProxyState.DEVICES; - } - - public isStartTraceState() { - return this.state() === ProxyState.START_TRACE; - } - - public isErrorState() { - return this.state() === ProxyState.ERROR; - } - - public isEndTraceState() { - return this.state() === ProxyState.END_TRACE; - } - - public isLoadDataState() { - return this.state() === ProxyState.LOAD_DATA; - } - - public isConnectingState() { - return this.state() === ProxyState.CONNECTING; - } - - public isNoProxy() { - return this.state() === ProxyState.NO_PROXY; - } - - public isInvalidProxy() { - return this.state() === ProxyState.INVALID_VERSION; - } - - public isUnauthProxy() { - return this.state() === ProxyState.UNAUTH; - } - - public throwNoTargetsError() { - this.proxy.setState(ProxyState.ERROR, "No targets selected"); - } - - public dataReady() { - return this.proxy.dataReady; - } - - public setProxyKey(key: string) { - this.proxy.proxyKey = key; - this.restart(); - } - - public adbSuccess() { - return !this.notConnected.includes(this.proxy.state); - } - - public selectedDevice(): Device { - return this.proxy.devices[this.proxy.selectedDevice]; - } - - public restart() { - this.proxy.setState(ProxyState.CONNECTING); - } - - public resetLastDevice() { - this.proxy.store.addToStore("adb.lastDevice", ""); - this.restart(); - } - - public selectDevice(id: string) { - this.proxy.selectDevice(id); - } - - public keepAliveTrace(view:any) { - if (!view.isEndTraceState()) { - clearInterval(view.keep_alive_worker); - view.keep_alive_worker = null; - return; - } - proxyRequest.call("GET", `${ProxyEndpoint.STATUS}${view.proxy.selectedDevice}/`, view, function(request:any, newView:any) { - if (request.responseText !== "True") { - newView.endTrace(); - } else if (newView.keep_alive_worker === null) { - newView.keep_alive_worker = setInterval(newView.keepAliveTrace, 1000, newView); - } - }); - } - - public startTrace( - reqEnableConfig: any, - reqSelectedSfConfig: any, - reqSelectedWmConfig: any - ) { - if (reqEnableConfig) { - proxyRequest.call("POST", `${ProxyEndpoint.CONFIG_TRACE}${this.proxy.selectedDevice}/`, this, null, null, reqEnableConfig); - } - if (reqSelectedSfConfig) { - proxyRequest.call("POST", `${ProxyEndpoint.SELECTED_SF_CONFIG_TRACE}${this.proxy.selectedDevice}/`, this, null, null, reqSelectedSfConfig); - } - if (reqSelectedWmConfig) { - proxyRequest.call("POST", `${ProxyEndpoint.SELECTED_WM_CONFIG_TRACE}${this.proxy.selectedDevice}/`, this, null, null, reqSelectedWmConfig); - } - proxyClient.setState(ProxyState.END_TRACE); - proxyRequest.call("POST", `${ProxyEndpoint.START_TRACE}${this.proxy.selectedDevice}/`, this, function(request:any, view:any) { - view.keepAliveTrace(view); - }, null, configureTraces.reqTraces); - } - - public async endTrace() { - this.proxy.setState(ProxyState.LOAD_DATA); - await proxyRequest.call("POST", `${ProxyEndpoint.END_TRACE}${this.proxy.selectedDevice}/`, this, - async function (request:any, view:any) { - await proxyClient.updateAdbData(configureTraces.reqTraces, 0, "trace", view); - }); - } - - public dumpState() { - if (configureTraces.reqDumps.length < 1) { - this.proxy.setState(ProxyState.ERROR, "No targets selected"); - return; - } - this.proxy.setState(ProxyState.LOAD_DATA); - proxyRequest.call("POST", `${ProxyEndpoint.DUMP}${this.proxy.selectedDevice}/`, this, function(request:any, view:any) { - view.proxy.updateAdbData(configureTraces.reqDumps, 0, "dump", view); - }, null, configureTraces.reqDumps); - } - - public onConnectChange(newState: ProxyState) { - if (newState === ProxyState.CONNECTING) { - proxyClient.getDevices(); - } - if (newState == ProxyState.START_TRACE) { - configureTraces.setAvailableTraces(); - } - } -} - -class ConfigureTraces { - DYNAMIC_TRACES = TRACES["default"]; - reqTraces: string[] = []; - reqDumps: string[] = []; - - setAvailableTraces() { - proxyRequest.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, proxyRequest.setAvailableTraces); - } - appendOptionalTraces(view:any, device_key:string) { - for(const key in TRACES[device_key]) { - view.DYNAMIC_TRACES[key] = TRACES[device_key][key]; - } - } -} -export const configureTraces = new ConfigureTraces(); \ No newline at end of file diff --git a/tools/winscope-ng/src/trace_collection/proxy_client.ts b/tools/winscope-ng/src/trace_collection/proxy_client.ts index 43e0008ee..b10d65e7a 100644 --- a/tools/winscope-ng/src/trace_collection/proxy_client.ts +++ b/tools/winscope-ng/src/trace_collection/proxy_client.ts @@ -14,7 +14,10 @@ * limitations under the License. */ import { PersistentStore } from "../common/persistent_store"; -import { TRACES } from "./trace_collection_utils"; +import { configMap, TRACES } from "./trace_collection_utils"; +import { setTraces, SetTraces } from "./set_traces"; +import { Device } from "./connection"; +import { ProxyConnection } from "./proxy_connection"; export enum ProxyState { ERROR = 0, @@ -32,7 +35,7 @@ export enum ProxyEndpoint { DEVICES = "/devices/", START_TRACE = "/start/", END_TRACE = "/end/", - CONFIG_TRACE = "/configtrace/", + ENABLE_CONFIG_TRACE = "/configtrace/", SELECTED_WM_CONFIG_TRACE = "/selectedwmconfigtrace/", SELECTED_SF_CONFIG_TRACE = "/selectedsfconfigtrace/", DUMP = "/dump/", @@ -43,7 +46,14 @@ export enum ProxyEndpoint { // from here, all requests to the proxy are made class ProxyRequest { - async call(method: string, path: string, view: any, onSuccess: any, type: any = null, jsonRequest:any = null) { + async call( + method: string, + path: string, + view: any, + onSuccess: any, + type?: XMLHttpRequestResponseType, + jsonRequest: any = null + ) { const request = new XMLHttpRequest(); const client = proxyClient; request.onreadystatechange = function() { @@ -85,7 +95,54 @@ class ProxyRequest { } } - getDevices = function(request: any, view: any) { + getDevices(view:any) { + proxyRequest.call("GET", ProxyEndpoint.DEVICES, view, proxyRequest.onSuccessGetDevices); + } + + async fetchFiles(dev:string, files: Array, idx: number, view:any) { + await proxyRequest.call("GET", `${ProxyEndpoint.FETCH}${dev}/${files[idx]}/`, view, + proxyRequest.onSuccessUpdateAdbData, "arraybuffer"); + } + + setEnabledConfig(view:any, req: Array) { + proxyRequest.call("POST", `${ProxyEndpoint.ENABLE_CONFIG_TRACE}${view.proxy.selectedDevice}/`, view, null, undefined, req); + } + + setSelectedConfig(endpoint: ProxyEndpoint, view:any, req: configMap) { + proxyRequest.call("POST", `${endpoint}${view.proxy.selectedDevice}/`, view, null, undefined, req); + } + + startTrace(view:any) { + proxyRequest.call("POST", `${ProxyEndpoint.START_TRACE}${view.proxy.selectedDevice}/`, view, function(request:XMLHttpRequest, newView:ProxyConnection) { + newView.keepAliveTrace(newView); + }, undefined, setTraces.reqTraces); + } + + async endTrace(view:any) { + await proxyRequest.call("POST", `${ProxyEndpoint.END_TRACE}${view.proxy.selectedDevice}/`, view, + async function (request:XMLHttpRequest, newView:ProxyConnection) { + await proxyClient.updateAdbData(setTraces.reqTraces, 0, "trace", newView); + }); + } + + keepTraceAlive(view:any) { + this.call("GET", `${ProxyEndpoint.STATUS}${view.proxy.selectedDevice}/`, view, function(request:XMLHttpRequest, newView:ProxyConnection) { + if (request.responseText !== "True") { + newView.endTrace(); + } else if (newView.keep_alive_worker === null) { + newView.keep_alive_worker = setInterval(newView.keepAliveTrace, 1000, newView); + } + }); + } + + async dumpState(view:any) { + await proxyRequest.call("POST", `${ProxyEndpoint.DUMP}${view.proxy.selectedDevice}/`, view, + async function(request:XMLHttpRequest, newView:ProxyConnection) { + await proxyClient.updateAdbData(setTraces.reqDumps, 0, "dump", newView); + }, undefined, setTraces.reqDumps); + } + + onSuccessGetDevices = function(request: XMLHttpRequest, view: ProxyClient) { const client = proxyClient; try { client.devices = JSON.parse(request.responseText); @@ -106,7 +163,7 @@ class ProxyRequest { } }; - setAvailableTraces = function(request:any, view:any) { + onSuccessSetAvailableTraces = function(request:XMLHttpRequest, view:SetTraces) { try { view.DYNAMIC_TRACES = TRACES["default"]; if(request.responseText == "true") { @@ -117,10 +174,10 @@ class ProxyRequest { } }; - updateAdbData = async (request: any, view: any) => { - let idx = proxyClient.adbParams.idx; - let files = proxyClient.adbParams.files; - let traceType = proxyClient.adbParams.traceType; + onSuccessUpdateAdbData = async (request: XMLHttpRequest, view: ProxyConnection) => { + const idx = proxyClient.adbParams.idx; + const files = proxyClient.adbParams.files; + const traceType = proxyClient.adbParams.traceType; try { const enc = new TextDecoder("utf-8"); const resp = enc.decode(request.response); @@ -137,15 +194,21 @@ class ProxyRequest { if (idx < files.length - 1) { proxyClient.updateAdbData(files, idx + 1, traceType, view); } else { - proxyClient.dataReady = true; + setTraces.dataReady = true; } } catch (error) { proxyClient.setState(ProxyState.ERROR, request.responseText); } - } + }; } export const proxyRequest = new ProxyRequest(); +interface AdbParams { + files: Array, + idx: number, + traceType: string +} + // stores all the changing variables from proxy and sets up calls from ProxyRequest export class ProxyClient { readonly WINSCOPE_PROXY_URL = "http://localhost:5544"; @@ -153,18 +216,17 @@ export class ProxyClient { state: ProxyState = ProxyState.CONNECTING; stateChangeListeners: {(param:ProxyState, errorText:string): void;}[] = []; refresh_worker: NodeJS.Timer | null = null; - devices: any = {}; + devices: Device = {}; selectedDevice = ""; errorText = ""; - adbData: Array = []; + adbData: Array = []; proxyKey = ""; lastDevice = ""; store = new PersistentStore(); - dataReady: boolean = false; - adbParams = { + adbParams: AdbParams = { files: [], idx: -1, - traceType: null, + traceType: "", }; setState(state:ProxyState, errorText = "") { @@ -190,7 +252,7 @@ export class ProxyClient { this.refresh_worker = null; return; } - proxyRequest.call("GET", ProxyEndpoint.DEVICES, this, proxyRequest.getDevices); + proxyRequest.getDevices(this); } selectDevice(device_id: string) { @@ -199,12 +261,11 @@ export class ProxyClient { this.setState(ProxyState.START_TRACE); } - async updateAdbData(files:any, idx:any, traceType:any, view: any) { + async updateAdbData(files:Array, idx:number, traceType:string, view: ProxyConnection) { this.adbParams.files = files; this.adbParams.idx = idx; this.adbParams.traceType = traceType; - await proxyRequest.call("GET", `${ProxyEndpoint.FETCH}${this.selectedDevice}/${files[idx]}/`, view, - proxyRequest.updateAdbData, "arraybuffer"); + await proxyRequest.fetchFiles(this.selectedDevice, files, idx, view); } } diff --git a/tools/winscope-ng/src/trace_collection/proxy_connection.ts b/tools/winscope-ng/src/trace_collection/proxy_connection.ts new file mode 100644 index 000000000..1efb02be2 --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/proxy_connection.ts @@ -0,0 +1,166 @@ +/* + * Copyright 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + proxyRequest, + proxyClient, + ProxyState, + ProxyEndpoint +} from "trace_collection/proxy_client"; +import { setTraces } from "./set_traces"; +import { Connection, DeviceProperties } from "./connection"; +import { configMap } from "./trace_collection_utils"; + +export class ProxyConnection implements Connection { + proxy = proxyClient; + keep_alive_worker: any = null; + notConnected = [ + ProxyState.NO_PROXY, + ProxyState.UNAUTH, + ProxyState.INVALID_VERSION, + ]; + loadProgress = 0; + + constructor() { + this.proxy.setState(ProxyState.CONNECTING); + this.proxy.onProxyChange(this.onConnectChange); + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.has("token")) { + this.proxy.proxyKey = urlParams.get("token")!; + } + this.proxy.getDevices(); + } + + public devices() { + return this.proxy.devices; + } + + public adbData() { + return this.proxy.adbData; + } + + public state() { + return this.proxy.state; + } + + public isDevicesState() { + return this.state() === ProxyState.DEVICES; + } + + public isStartTraceState() { + return this.state() === ProxyState.START_TRACE; + } + + public isErrorState() { + return this.state() === ProxyState.ERROR; + } + + public isEndTraceState() { + return this.state() === ProxyState.END_TRACE; + } + + public isLoadDataState() { + return this.state() === ProxyState.LOAD_DATA; + } + + public isConnectingState() { + return this.state() === ProxyState.CONNECTING; + } + + public throwNoTargetsError() { + this.proxy.setState(ProxyState.ERROR, "No targets selected"); + } + + public setProxyKey(key: string) { + this.proxy.proxyKey = key; + this.restart(); + } + + public adbSuccess() { + return !this.notConnected.includes(this.proxy.state); + } + + public selectedDevice(): DeviceProperties { + return this.proxy.devices[this.proxy.selectedDevice]; + } + + public selectedDeviceId(): string { + return this.proxy.selectedDevice; + } + + public restart() { + this.proxy.setState(ProxyState.CONNECTING); + } + + public resetLastDevice() { + this.proxy.store.addToStore("adb.lastDevice", ""); + this.restart(); + } + + public selectDevice(id: string) { + this.proxy.selectDevice(id); + } + + public keepAliveTrace(view:ProxyConnection) { + if (!view.isEndTraceState()) { + clearInterval(view.keep_alive_worker); + view.keep_alive_worker = null; + return; + } + proxyRequest.keepTraceAlive(view); + } + + public startTrace( + reqEnableConfig?: Array, + reqSelectedSfConfig?: configMap, + reqSelectedWmConfig?: configMap + ) { + if (reqEnableConfig) { + proxyRequest.setEnabledConfig(this, reqEnableConfig); + } + if (reqSelectedSfConfig) { + proxyRequest.setSelectedConfig(ProxyEndpoint.SELECTED_SF_CONFIG_TRACE, this, reqSelectedSfConfig); + } + if (reqSelectedWmConfig) { + proxyRequest.setSelectedConfig(ProxyEndpoint.SELECTED_WM_CONFIG_TRACE, this, reqSelectedWmConfig); + } + proxyClient.setState(ProxyState.END_TRACE); + proxyRequest.startTrace(this); + } + + public async endTrace() { + this.proxy.setState(ProxyState.LOAD_DATA); + await proxyRequest.endTrace(this); + } + + public async dumpState() { + if (setTraces.reqDumps.length < 1) { + this.proxy.setState(ProxyState.ERROR, "No targets selected"); + setTraces.dumpError = true; + return; + } + this.proxy.setState(ProxyState.LOAD_DATA); + await proxyRequest.dumpState(this); + } + + public onConnectChange(newState: ProxyState) { + if (newState === ProxyState.CONNECTING) { + proxyClient.getDevices(); + } + if (newState == ProxyState.START_TRACE) { + setTraces.setAvailableTraces(); + } + } +} diff --git a/tools/winscope-ng/src/trace_collection/set_traces.ts b/tools/winscope-ng/src/trace_collection/set_traces.ts new file mode 100644 index 000000000..283dbe333 --- /dev/null +++ b/tools/winscope-ng/src/trace_collection/set_traces.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TraceConfigurationMap, TRACES } from "./trace_collection_utils"; +import { + proxyRequest, + ProxyEndpoint +} from "trace_collection/proxy_client"; + +export class SetTraces { + DYNAMIC_TRACES = TRACES["default"]; + reqTraces: string[] = []; + reqDumps: string[] = []; + dataReady = false; + dumpError = false; + + DUMPS: TraceConfigurationMap = { + "window_dump": { + name: "Window Manager", + run: true, + }, + "layers_dump": { + name: "Surface Flinger", + run: true, + } + }; + + setAvailableTraces() { + proxyRequest.call("GET", ProxyEndpoint.CHECK_WAYLAND, this, proxyRequest.onSuccessSetAvailableTraces); + } + appendOptionalTraces(view:any, device_key:string) { + for(const key in TRACES[device_key]) { + view.DYNAMIC_TRACES[key] = TRACES[device_key][key]; + } + } +} +export const setTraces = new SetTraces(); diff --git a/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts b/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts index 28c4201a0..acc3e072e 100644 --- a/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts +++ b/tools/winscope-ng/src/trace_collection/trace_collection_utils.ts @@ -5,11 +5,12 @@ export interface TraceConfiguration { config?: ConfigurationOptions } -interface TraceConfigurationMap { + +export interface TraceConfigurationMap { [key: string]: TraceConfiguration } -export interface ConfigurationOptions { +interface ConfigurationOptions { enableConfigs: Array, selectionConfigs: Array }