obsidian-integration-testing 11.0.0 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/cjs/adb-device-list.cjs +54 -0
- package/dist/lib/cjs/adb-device-list.d.cts +72 -0
- package/dist/lib/cjs/android-sdk.cjs +52 -0
- package/dist/lib/cjs/android-sdk.d.cts +29 -0
- package/dist/lib/cjs/app-startup-progress.cjs +87 -0
- package/dist/lib/cjs/app-startup-progress.d.cts +128 -0
- package/dist/lib/cjs/appium-server-marker.cjs +112 -0
- package/dist/lib/cjs/appium-server-marker.d.cts +103 -0
- package/dist/lib/cjs/avd-probe-verdict.cjs +90 -0
- package/dist/lib/cjs/avd-probe-verdict.d.cts +175 -0
- package/dist/lib/cjs/cli.cjs +104 -19
- package/dist/lib/cjs/cli.d.cts +16 -8
- package/dist/lib/cjs/community-plugin-registry.cjs +52 -0
- package/dist/lib/cjs/community-plugin-registry.d.cts +116 -0
- package/dist/lib/cjs/config-directory-fallback-error.cjs +57 -0
- package/dist/lib/cjs/config-directory-fallback-error.d.cts +64 -0
- package/dist/lib/cjs/config-directory.cjs +46 -0
- package/dist/lib/cjs/config-directory.d.cts +45 -0
- package/dist/lib/cjs/connect-to-cdp.cjs +13 -7
- package/dist/lib/cjs/connect-to-cdp.d.cts +19 -0
- package/dist/lib/cjs/context-provider.cjs +12 -1
- package/dist/lib/cjs/context-provider.d.cts +26 -3
- package/dist/lib/cjs/demo-vault-bootstrap.cjs +148 -0
- package/dist/lib/cjs/demo-vault-bootstrap.d.cts +114 -0
- package/dist/lib/cjs/demo-vault-populate.cjs +38 -10
- package/dist/lib/cjs/demo-vault-populate.d.cts +70 -0
- package/dist/lib/cjs/device-readiness.cjs +38 -3
- package/dist/lib/cjs/device-readiness.d.cts +83 -9
- package/dist/lib/cjs/emulator-arguments.cjs +6 -2
- package/dist/lib/cjs/emulator-arguments.d.cts +26 -0
- package/dist/lib/cjs/emulator-backend.cjs +71 -0
- package/dist/lib/cjs/emulator-backend.d.cts +76 -0
- package/dist/lib/cjs/emulator-liveness.cjs +72 -0
- package/dist/lib/cjs/emulator-liveness.d.cts +158 -0
- package/dist/lib/cjs/emulator-snapshot.cjs +57 -0
- package/dist/lib/cjs/emulator-snapshot.d.cts +68 -0
- package/dist/lib/cjs/eval-in-obsidian.cjs +1 -1
- package/dist/lib/cjs/eval-in-obsidian.d.cts +42 -13
- package/dist/lib/cjs/global-setup-core.cjs +30 -30
- package/dist/lib/cjs/global-setup-core.d.cts +27 -2
- package/dist/lib/cjs/headless-vault-config.cjs +51 -0
- package/dist/lib/cjs/headless-vault-config.d.cts +67 -0
- package/dist/lib/cjs/index.cjs +24 -1
- package/dist/lib/cjs/index.d.cts +14 -5
- package/dist/lib/cjs/installer-asset.cjs +1 -1
- package/dist/lib/cjs/installer-asset.d.cts +1 -1
- package/dist/lib/cjs/integration-setup-failed-error.cjs +62 -0
- package/dist/lib/cjs/integration-setup-failed-error.d.cts +66 -0
- package/dist/lib/cjs/jest/global-setup.cjs +2 -2
- package/dist/lib/cjs/jest/global-setup.d.cts +5 -1
- package/dist/lib/cjs/kill-process-tree.cjs +18 -7
- package/dist/lib/cjs/kill-process-tree.d.cts +10 -2
- package/dist/lib/cjs/library.cjs +1 -1
- package/dist/lib/cjs/mobile-input.cjs +173 -0
- package/dist/lib/cjs/mobile-input.d.cts +156 -0
- package/dist/lib/cjs/namespace-bootstrap.cjs +92 -9
- package/dist/lib/cjs/obsidian-installer.cjs +1 -1
- package/dist/lib/cjs/obsidian-instance.cjs +52 -3
- package/dist/lib/cjs/obsidian-instance.d.cts +12 -0
- package/dist/lib/cjs/obsidian-metadata.cjs +123 -17
- package/dist/lib/cjs/obsidian-metadata.d.cts +17 -6
- package/dist/lib/cjs/obsidian-version-switch.cjs +3 -3
- package/dist/lib/cjs/open-obsidian-settings-tab.cjs +1 -1
- package/dist/lib/cjs/open-obsidian-settings-tab.d.cts +3 -2
- package/dist/lib/cjs/owned-instance-exit-marker.cjs +99 -0
- package/dist/lib/cjs/owned-instance-exit-marker.d.cts +106 -0
- package/dist/lib/cjs/owned-instance-exited-error.cjs +106 -0
- package/dist/lib/cjs/owned-instance-exited-error.d.cts +98 -0
- package/dist/lib/cjs/owned-vault-seed.cjs +9 -5
- package/dist/lib/cjs/owned-vault-seed.d.cts +24 -3
- package/dist/lib/cjs/parent-liveness.cjs +42 -5
- package/dist/lib/cjs/parent-liveness.d.cts +37 -5
- package/dist/lib/cjs/port-owner.cjs +61 -0
- package/dist/lib/cjs/port-owner.d.cts +47 -0
- package/dist/lib/cjs/process-capture.cjs +62 -0
- package/dist/lib/cjs/process-capture.d.cts +83 -0
- package/dist/lib/cjs/process-exit-message.cjs +14 -8
- package/dist/lib/cjs/process-exit-message.d.cts +37 -3
- package/dist/lib/cjs/process-liveness.cjs +51 -0
- package/dist/lib/cjs/process-liveness.d.cts +18 -0
- package/dist/lib/cjs/release-catalog.cjs +73 -0
- package/dist/lib/cjs/release-catalog.d.cts +125 -0
- package/dist/lib/cjs/run-version-matrix.cjs +1 -1
- package/dist/lib/cjs/run-version-matrix.d.cts +1 -1
- package/dist/lib/cjs/teardown-verdict.cjs +53 -0
- package/dist/lib/cjs/teardown-verdict.d.cts +69 -0
- package/dist/lib/cjs/temporary-vault.cjs +43 -8
- package/dist/lib/cjs/temporary-vault.d.cts +47 -5
- package/dist/lib/cjs/transport-appium.cjs +223 -17
- package/dist/lib/cjs/transport-appium.d.cts +104 -3
- package/dist/lib/cjs/transport-desktop-cdp.cjs +201 -6
- package/dist/lib/cjs/transport-desktop-cdp.d.cts +100 -0
- package/dist/lib/cjs/transport-factory.cjs +1165 -142
- package/dist/lib/cjs/transport-factory.d.cts +10 -1
- package/dist/lib/cjs/transport-options.cjs +11 -3
- package/dist/lib/cjs/transport-options.d.cts +131 -12
- package/dist/lib/cjs/transport.cjs +1 -1
- package/dist/lib/cjs/transport.d.cts +7 -1
- package/dist/lib/cjs/vault-plugin-install.cjs +40 -0
- package/dist/lib/cjs/vault-plugin-install.d.cts +64 -0
- package/dist/lib/cjs/vault-teardown-verdict.cjs +42 -0
- package/dist/lib/cjs/vault-teardown-verdict.d.cts +57 -0
- package/dist/lib/cjs/version-matrix.cjs +1 -1
- package/dist/lib/cjs/version-matrix.d.cts +2 -2
- package/dist/lib/cjs/vitest/global-setup.cjs +13 -7
- package/dist/lib/cjs/vitest/global-setup.d.cts +5 -1
- package/dist/lib/cjs/vitest/setup.cjs +2 -1
- package/dist/lib/cjs/vitest/setup.d.cts +5 -0
- package/dist/lib/cjs/vitest/typings.d.cts +6 -4
- package/dist/lib/cjs/webview-cdp.cjs +263 -0
- package/dist/lib/cjs/webview-cdp.d.cts +156 -0
- package/dist/lib/cjs/wedged-appium-server.cjs +113 -0
- package/dist/lib/cjs/wedged-appium-server.d.cts +187 -0
- package/dist/lib/esm/adb-device-list.d.mts +72 -0
- package/dist/lib/esm/adb-device-list.mjs +28 -0
- package/dist/lib/esm/android-sdk.d.mts +29 -0
- package/dist/lib/esm/android-sdk.mjs +18 -0
- package/dist/lib/esm/app-startup-progress.d.mts +128 -0
- package/dist/lib/esm/app-startup-progress.mjs +59 -0
- package/dist/lib/esm/appium-server-marker.d.mts +103 -0
- package/dist/lib/esm/appium-server-marker.mjs +89 -0
- package/dist/lib/esm/avd-probe-verdict.d.mts +175 -0
- package/dist/lib/esm/avd-probe-verdict.mjs +62 -0
- package/dist/lib/esm/cli.d.mts +16 -8
- package/dist/lib/esm/cli.mjs +110 -19
- package/dist/lib/esm/community-plugin-registry.d.mts +116 -0
- package/dist/lib/esm/community-plugin-registry.mjs +23 -0
- package/dist/lib/esm/config-directory-fallback-error.d.mts +64 -0
- package/dist/lib/esm/config-directory-fallback-error.mjs +33 -0
- package/dist/lib/esm/config-directory.d.mts +45 -0
- package/dist/lib/esm/config-directory.mjs +21 -0
- package/dist/lib/esm/connect-to-cdp.d.mts +19 -0
- package/dist/lib/esm/connect-to-cdp.mjs +13 -7
- package/dist/lib/esm/context-provider.d.mts +26 -3
- package/dist/lib/esm/context-provider.mjs +10 -1
- package/dist/lib/esm/demo-vault-bootstrap.d.mts +114 -0
- package/dist/lib/esm/demo-vault-bootstrap.mjs +127 -0
- package/dist/lib/esm/demo-vault-populate.d.mts +70 -0
- package/dist/lib/esm/demo-vault-populate.mjs +35 -9
- package/dist/lib/esm/device-readiness.d.mts +83 -9
- package/dist/lib/esm/device-readiness.mjs +34 -2
- package/dist/lib/esm/emulator-arguments.d.mts +26 -0
- package/dist/lib/esm/emulator-arguments.mjs +6 -2
- package/dist/lib/esm/emulator-backend.d.mts +76 -0
- package/dist/lib/esm/emulator-backend.mjs +45 -0
- package/dist/lib/esm/emulator-liveness.d.mts +158 -0
- package/dist/lib/esm/emulator-liveness.mjs +47 -0
- package/dist/lib/esm/emulator-snapshot.d.mts +68 -0
- package/dist/lib/esm/emulator-snapshot.mjs +32 -0
- package/dist/lib/esm/eval-in-obsidian.d.mts +42 -13
- package/dist/lib/esm/eval-in-obsidian.mjs +1 -1
- package/dist/lib/esm/global-setup-core.d.mts +27 -2
- package/dist/lib/esm/global-setup-core.mjs +34 -35
- package/dist/lib/esm/headless-vault-config.d.mts +67 -0
- package/dist/lib/esm/headless-vault-config.mjs +31 -0
- package/dist/lib/esm/index.d.mts +14 -5
- package/dist/lib/esm/index.mjs +26 -2
- package/dist/lib/esm/installer-asset.d.mts +1 -1
- package/dist/lib/esm/installer-asset.mjs +1 -1
- package/dist/lib/esm/integration-setup-failed-error.d.mts +66 -0
- package/dist/lib/esm/integration-setup-failed-error.mjs +38 -0
- package/dist/lib/esm/jest/global-setup.d.mts +5 -1
- package/dist/lib/esm/jest/global-setup.mjs +2 -2
- package/dist/lib/esm/kill-process-tree.d.mts +10 -2
- package/dist/lib/esm/kill-process-tree.mjs +16 -6
- package/dist/lib/esm/library.mjs +1 -1
- package/dist/lib/esm/mobile-input.d.mts +156 -0
- package/dist/lib/esm/mobile-input.mjs +144 -0
- package/dist/lib/esm/namespace-bootstrap.mjs +95 -9
- package/dist/lib/esm/obsidian-installer.mjs +1 -1
- package/dist/lib/esm/obsidian-instance.d.mts +12 -0
- package/dist/lib/esm/obsidian-instance.mjs +55 -3
- package/dist/lib/esm/obsidian-metadata.d.mts +17 -6
- package/dist/lib/esm/obsidian-metadata.mjs +123 -17
- package/dist/lib/esm/obsidian-version-switch.mjs +3 -3
- package/dist/lib/esm/open-obsidian-settings-tab.d.mts +3 -2
- package/dist/lib/esm/open-obsidian-settings-tab.mjs +1 -1
- package/dist/lib/esm/owned-instance-exit-marker.d.mts +106 -0
- package/dist/lib/esm/owned-instance-exit-marker.mjs +78 -0
- package/dist/lib/esm/owned-instance-exited-error.d.mts +98 -0
- package/dist/lib/esm/owned-instance-exited-error.mjs +81 -0
- package/dist/lib/esm/owned-vault-seed.d.mts +24 -3
- package/dist/lib/esm/owned-vault-seed.mjs +9 -5
- package/dist/lib/esm/parent-liveness.d.mts +37 -5
- package/dist/lib/esm/parent-liveness.mjs +42 -5
- package/dist/lib/esm/port-owner.d.mts +47 -0
- package/dist/lib/esm/port-owner.mjs +36 -0
- package/dist/lib/esm/process-capture.d.mts +83 -0
- package/dist/lib/esm/process-capture.mjs +38 -0
- package/dist/lib/esm/process-exit-message.d.mts +37 -3
- package/dist/lib/esm/process-exit-message.mjs +13 -8
- package/dist/lib/esm/process-liveness.d.mts +18 -0
- package/dist/lib/esm/process-liveness.mjs +17 -0
- package/dist/lib/esm/release-catalog.d.mts +125 -0
- package/dist/lib/esm/release-catalog.mjs +46 -0
- package/dist/lib/esm/run-version-matrix.d.mts +1 -1
- package/dist/lib/esm/run-version-matrix.mjs +1 -1
- package/dist/lib/esm/teardown-verdict.d.mts +69 -0
- package/dist/lib/esm/teardown-verdict.mjs +28 -0
- package/dist/lib/esm/temporary-vault.d.mts +47 -5
- package/dist/lib/esm/temporary-vault.mjs +43 -8
- package/dist/lib/esm/transport-appium.d.mts +104 -3
- package/dist/lib/esm/transport-appium.mjs +233 -17
- package/dist/lib/esm/transport-desktop-cdp.d.mts +100 -0
- package/dist/lib/esm/transport-desktop-cdp.mjs +208 -7
- package/dist/lib/esm/transport-factory.d.mts +10 -1
- package/dist/lib/esm/transport-factory.mjs +1220 -149
- package/dist/lib/esm/transport-options.d.mts +131 -12
- package/dist/lib/esm/transport-options.mjs +9 -2
- package/dist/lib/esm/transport.d.mts +7 -1
- package/dist/lib/esm/vault-plugin-install.d.mts +64 -0
- package/dist/lib/esm/vault-plugin-install.mjs +20 -0
- package/dist/lib/esm/vault-teardown-verdict.d.mts +57 -0
- package/dist/lib/esm/vault-teardown-verdict.mjs +18 -0
- package/dist/lib/esm/version-matrix.d.mts +2 -2
- package/dist/lib/esm/version-matrix.mjs +1 -1
- package/dist/lib/esm/vitest/global-setup.d.mts +5 -1
- package/dist/lib/esm/vitest/global-setup.mjs +14 -7
- package/dist/lib/esm/vitest/setup.d.mts +5 -0
- package/dist/lib/esm/vitest/setup.mjs +3 -1
- package/dist/lib/esm/vitest/typings.d.mts +6 -4
- package/dist/lib/esm/webview-cdp.d.mts +156 -0
- package/dist/lib/esm/webview-cdp.mjs +235 -0
- package/dist/lib/esm/wedged-appium-server.d.mts +187 -0
- package/dist/lib/esm/wedged-appium-server.mjs +86 -0
- package/package.json +25 -23
- package/dist/lib/cjs/native-dialog-monitor.cjs +0 -192
- package/dist/lib/cjs/native-dialog-monitor.d.cts +0 -66
- package/dist/lib/cjs/obsidian-namespace.cjs +0 -17
- package/dist/lib/cjs/obsidian-namespace.d.cts +0 -128
- package/dist/lib/esm/native-dialog-monitor.d.mts +0 -66
- package/dist/lib/esm/native-dialog-monitor.mjs +0 -158
- package/dist/lib/esm/obsidian-namespace.d.mts +0 -128
- package/dist/lib/esm/obsidian-namespace.mjs +0 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { exec } from "./exec.mjs";
|
|
3
|
+
import { log } from "./log.mjs";
|
|
4
|
+
import { pickFreePort } from "./obsidian-instance.mjs";
|
|
5
|
+
const ADB_TIMEOUT_IN_MILLISECONDS = 15e3;
|
|
6
|
+
const FORWARD_LIST_COLUMN_COUNT = 3;
|
|
7
|
+
const DECIMAL_RADIX = 10;
|
|
8
|
+
const CDP_COMMAND_TIMEOUT_IN_MILLISECONDS = 3e4;
|
|
9
|
+
const CDP_HOST = "127.0.0.1";
|
|
10
|
+
class WebViewCdpConnection {
|
|
11
|
+
/**
|
|
12
|
+
* Wraps an already-open debugger socket, plus what is needed to tear its port forward down.
|
|
13
|
+
*
|
|
14
|
+
* @param webSocket - The open debugger socket.
|
|
15
|
+
* @param deviceId - The adb device id, needed to drop the port forward on dispose.
|
|
16
|
+
* @param port - The forwarded local port.
|
|
17
|
+
*/
|
|
18
|
+
constructor(webSocket, deviceId, port) {
|
|
19
|
+
this.webSocket = webSocket;
|
|
20
|
+
this.deviceId = deviceId;
|
|
21
|
+
this.port = port;
|
|
22
|
+
this.webSocket.addEventListener("message", (event) => {
|
|
23
|
+
const message = JSON.parse(String(event.data));
|
|
24
|
+
if (message.method === void 0) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this.eventHandlers.get(message.method)?.(message.params ?? {});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
webSocket;
|
|
31
|
+
deviceId;
|
|
32
|
+
port;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the socket is still usable.
|
|
35
|
+
*
|
|
36
|
+
* Restarting the app tears its WebView down and takes the debugger target with it, so a cached
|
|
37
|
+
* connection has to be re-checked rather than assumed live.
|
|
38
|
+
*
|
|
39
|
+
* @returns `true` while the socket is open.
|
|
40
|
+
*/
|
|
41
|
+
get isOpen() {
|
|
42
|
+
return this.webSocket.readyState === WebSocket.OPEN;
|
|
43
|
+
}
|
|
44
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
45
|
+
messageId = 0;
|
|
46
|
+
/**
|
|
47
|
+
* Closes the socket and drops the adb port forward.
|
|
48
|
+
*/
|
|
49
|
+
async dispose() {
|
|
50
|
+
this.closeSocket();
|
|
51
|
+
await removeForward(this.deviceId, this.port);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Synchronous disposal, for `process.on('exit')` handlers where async work cannot run.
|
|
55
|
+
*
|
|
56
|
+
* Without this an abrupt worker exit leaves the port forward behind — the async {@link dispose} above
|
|
57
|
+
* never gets a turn.
|
|
58
|
+
*/
|
|
59
|
+
disposeSync() {
|
|
60
|
+
this.closeSocket();
|
|
61
|
+
try {
|
|
62
|
+
execFileSync("adb", ["-s", this.deviceId, "forward", "--remove", `tcp:${String(this.port)}`], {
|
|
63
|
+
stdio: "ignore",
|
|
64
|
+
timeout: ADB_TIMEOUT_IN_MILLISECONDS
|
|
65
|
+
});
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Subscribes to a CDP event. One handler per method; subscribing again replaces it.
|
|
71
|
+
*
|
|
72
|
+
* @param method - The CDP event name, e.g. `Runtime.bindingCalled`.
|
|
73
|
+
* @param handler - Called with the event parameters.
|
|
74
|
+
*/
|
|
75
|
+
on(method, handler) {
|
|
76
|
+
this.eventHandlers.set(method, handler);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Sends a CDP command and waits for its response.
|
|
80
|
+
*
|
|
81
|
+
* @param method - The CDP method name.
|
|
82
|
+
* @param params - The CDP method parameters.
|
|
83
|
+
* @returns The command result.
|
|
84
|
+
*/
|
|
85
|
+
send(method, params = {}) {
|
|
86
|
+
const id = ++this.messageId;
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const timeout = setTimeout(() => {
|
|
89
|
+
this.webSocket.removeEventListener("message", handler);
|
|
90
|
+
reject(new Error(`WebView CDP command timed out after ${String(CDP_COMMAND_TIMEOUT_IN_MILLISECONDS)}ms: ${method}`));
|
|
91
|
+
}, CDP_COMMAND_TIMEOUT_IN_MILLISECONDS);
|
|
92
|
+
const handler = (event) => {
|
|
93
|
+
const message = JSON.parse(String(event.data));
|
|
94
|
+
if (message.id !== id) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
this.webSocket.removeEventListener("message", handler);
|
|
99
|
+
if (message.error) {
|
|
100
|
+
reject(new Error(`WebView CDP command ${method} failed: ${message.error.message ?? "unknown error"}`));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
resolve(message.result ?? {});
|
|
104
|
+
};
|
|
105
|
+
this.webSocket.addEventListener("message", handler);
|
|
106
|
+
this.webSocket.send(JSON.stringify({ id, method, params }));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Sends an ordered command sequence, one command at a time.
|
|
111
|
+
*
|
|
112
|
+
* Each command is awaited before the next is sent, which is the only ordering guarantee an input
|
|
113
|
+
* sequence needs — a long-press holds itself for its own `duration` rather than asking the host to
|
|
114
|
+
* sleep between two touches, so there is no inter-command delay to honor.
|
|
115
|
+
*
|
|
116
|
+
* @param commands - The commands to send, in order.
|
|
117
|
+
*/
|
|
118
|
+
async sendAll(commands) {
|
|
119
|
+
for (const command of commands) {
|
|
120
|
+
await this.send(command.method, command.params);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Closes the debugger socket and stops routing its events.
|
|
125
|
+
*/
|
|
126
|
+
closeSocket() {
|
|
127
|
+
this.eventHandlers.clear();
|
|
128
|
+
if (this.webSocket.readyState === WebSocket.OPEN) {
|
|
129
|
+
this.webSocket.close();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function connectToWebViewCdp(params) {
|
|
134
|
+
const { appId, deviceId } = params;
|
|
135
|
+
const pidofOutput = await adb(deviceId, ["shell", "pidof", appId]);
|
|
136
|
+
const pid = pidofOutput.split(/\s+/, 1)[0] ?? "";
|
|
137
|
+
if (!pid) {
|
|
138
|
+
throw new Error(`${appId} is not running on ${deviceId}, so its WebView has no debugger to attach to.`);
|
|
139
|
+
}
|
|
140
|
+
const procNetUnix = await adb(deviceId, ["shell", "cat", "/proc/net/unix"]);
|
|
141
|
+
const socketName = parseWebViewDevtoolsSocketName(procNetUnix, pid);
|
|
142
|
+
const forwardList = await adb(deviceId, ["forward", "--list"]);
|
|
143
|
+
const existingPort = parseForwardedPort(forwardList, deviceId, socketName);
|
|
144
|
+
const port = existingPort ?? await pickFreePort();
|
|
145
|
+
if (existingPort === void 0) {
|
|
146
|
+
await adb(deviceId, ["forward", `tcp:${String(port)}`, `localabstract:${socketName}`]);
|
|
147
|
+
log(`[webview-cdp] Forwarded tcp:${String(port)} -> localabstract:${socketName} (${appId} pid ${pid})`);
|
|
148
|
+
} else {
|
|
149
|
+
log(`[webview-cdp] Reusing forward tcp:${String(port)} -> localabstract:${socketName} (${appId} pid ${pid})`);
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(`http://${CDP_HOST}:${String(port)}/json`);
|
|
153
|
+
const target = selectWebViewPageTarget(await response.json());
|
|
154
|
+
const webSocket = new WebSocket(target.webSocketDebuggerUrl ?? "");
|
|
155
|
+
await new Promise((resolve, reject) => {
|
|
156
|
+
webSocket.addEventListener("open", () => {
|
|
157
|
+
resolve();
|
|
158
|
+
}, { once: true });
|
|
159
|
+
webSocket.addEventListener("error", () => {
|
|
160
|
+
reject(new Error(`Could not open the WebView CDP socket at ${target.webSocketDebuggerUrl ?? ""}.`));
|
|
161
|
+
}, { once: true });
|
|
162
|
+
});
|
|
163
|
+
log(`[webview-cdp] Attached to WebView target "${target.title ?? ""}"`);
|
|
164
|
+
return new WebViewCdpConnection(webSocket, deviceId, port);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
await removeForward(deviceId, port);
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function parseForwardedPort(forwardList, deviceId, socketName) {
|
|
171
|
+
for (const line of forwardList.split("\n")) {
|
|
172
|
+
const [lineDeviceId, local, remote] = line.trim().split(/\s+/, FORWARD_LIST_COLUMN_COUNT);
|
|
173
|
+
if (lineDeviceId !== deviceId || remote !== `localabstract:${socketName}` || !local?.startsWith("tcp:")) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const port = Number.parseInt(local.slice("tcp:".length), DECIMAL_RADIX);
|
|
177
|
+
if (Number.isSafeInteger(port)) {
|
|
178
|
+
return port;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return void 0;
|
|
182
|
+
}
|
|
183
|
+
function parseWebViewDevtoolsSocketName(procNetUnix, pid) {
|
|
184
|
+
const names = [
|
|
185
|
+
...new Set([...procNetUnix.matchAll(/@webview_devtools_remote_\w+/g)].map((match) => match[0].slice("@".length)))
|
|
186
|
+
];
|
|
187
|
+
const [soleName, ...otherNames] = names;
|
|
188
|
+
if (soleName === void 0) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"No `webview_devtools_remote_*` socket on the device. Obsidian Mobile is not running, or its WebView debugger is disabled."
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const exactName = `webview_devtools_remote_${pid}`;
|
|
194
|
+
if (names.includes(exactName)) {
|
|
195
|
+
return exactName;
|
|
196
|
+
}
|
|
197
|
+
if (otherNames.length === 0) {
|
|
198
|
+
return soleName;
|
|
199
|
+
}
|
|
200
|
+
throw new Error(
|
|
201
|
+
`No \`${exactName}\` socket, and ${String(names.length)} other debuggable WebViews are present (${names.join(", ")}), so the right one cannot be chosen.`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
function selectWebViewPageTarget(targets) {
|
|
205
|
+
const target = targets.find((candidate) => candidate.type === "page" && Boolean(candidate.webSocketDebuggerUrl)) ?? targets.find((candidate) => Boolean(candidate.webSocketDebuggerUrl));
|
|
206
|
+
if (!target) {
|
|
207
|
+
throw new Error("The WebView debugger listed no target with a WebSocket endpoint.");
|
|
208
|
+
}
|
|
209
|
+
return target;
|
|
210
|
+
}
|
|
211
|
+
async function adb(deviceId, commandArguments) {
|
|
212
|
+
const stdout = await exec(["adb", "-s", deviceId, ...commandArguments], {
|
|
213
|
+
isQuiet: true,
|
|
214
|
+
timeoutInMilliseconds: ADB_TIMEOUT_IN_MILLISECONDS
|
|
215
|
+
});
|
|
216
|
+
return stdout.trim();
|
|
217
|
+
}
|
|
218
|
+
async function removeForward(deviceId, port) {
|
|
219
|
+
try {
|
|
220
|
+
await exec(["adb", "-s", deviceId, "forward", "--remove", `tcp:${String(port)}`], {
|
|
221
|
+
isQuiet: true,
|
|
222
|
+
shouldIgnoreExitCode: true,
|
|
223
|
+
timeoutInMilliseconds: ADB_TIMEOUT_IN_MILLISECONDS
|
|
224
|
+
});
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
export {
|
|
229
|
+
WebViewCdpConnection,
|
|
230
|
+
connectToWebViewCdp,
|
|
231
|
+
parseForwardedPort,
|
|
232
|
+
parseWebViewDevtoolsSocketName,
|
|
233
|
+
selectWebViewPageTarget
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3dlYnZpZXctY2RwLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogQSBzZWNvbmQsIGluZGVwZW5kZW50IENocm9tZSBEZXZUb29scyBQcm90b2NvbCBjb25uZWN0aW9uIHRvIE9ic2lkaWFuIE1vYmlsZSdzIFdlYlZpZXcsIHVzZWQgdG8gaW5qZWN0XG4gKiAqKnRydXN0ZWQqKiBpbnB1dCBvbiBBbmRyb2lkIChzZWUgKipMMzkqKikuXG4gKlxuICogSXQgaXMgZGVsaWJlcmF0ZWx5IHNlcGFyYXRlIGZyb20gdGhlIEFwcGl1bSBzZXNzaW9uIHJhdGhlciB0aGFuIGxheWVyZWQgb24gaXQuIGBBcHBpdW1UcmFuc3BvcnQuZXZhbHVhdGVgXG4gKiBydW5zIHRoZSB3aG9sZSBgZXZhbEluT2JzaWRpYW5gIGNsb3N1cmUgaW5zaWRlIG9uZSBXM0MgKkV4ZWN1dGUgU2NyaXB0Kiwgd2hpY2ggYXdhaXRzIHRoZSByZXR1cm5lZFxuICogcHJvbWlzZSBcdTIwMTQgc28gd2hpbGUgYSBjbG9zdXJlIHNpdHMgd2FpdGluZyBvbiBgbGliLmNsaWNrRWxlbWVudCguLi4pYCwgdGhlIFdlYkRyaXZlciBzZXNzaW9uIGlzIGJ1c3kgYW5kXG4gKiBjYW5ub3QgYmUgYXNrZWQgdG8gZG8gYW55dGhpbmcgZWxzZS4gQ0RQIGlzIGlkLW11bHRpcGxleGVkIGFuZCBDaHJvbWl1bSBhY2NlcHRzIG11bHRpcGxlIGRlYnVnZ2VyXG4gKiBjbGllbnRzLCBzbyB0aGlzIHNvY2tldCBzdGF5cyBmcmVlIGV4YWN0bHkgd2hlbiB0aGUgQXBwaXVtIG9uZSBkb2VzIG5vdC4gVGhhdCBpcyB3aGF0IGxldHMgdGhlIHJlbmRlcmVyXG4gKiBjYWxsIGJhY2sgb3V0IHRvIHRoZSBob3N0IG1pZC1jbG9zdXJlLlxuICpcbiAqIFJlYWNoaW5nIHRoZSBXZWJWaWV3J3MgZGVidWdnZXIgaXMgYSB0d28tc3RlcDogdGhlIGFwcCBwdWJsaXNoZXMgYW4gYWJzdHJhY3QgdW5peCBzb2NrZXQgbmFtZWRcbiAqIGB3ZWJ2aWV3X2RldnRvb2xzX3JlbW90ZV88cGlkPmAsIGBhZGIgZm9yd2FyZGAgbWFwcyBpdCB0byBhIGxvY2FsIFRDUCBwb3J0LCBhbmQgdGhlIHVzdWFsIGAvanNvbmBcbiAqIGVuZHBvaW50IHRoZW4gbGlzdHMgdGhlIHBhZ2UgdGFyZ2V0cy5cbiAqL1xuXG5pbXBvcnQgeyBleGVjRmlsZVN5bmMgfSBmcm9tICdub2RlOmNoaWxkX3Byb2Nlc3MnO1xuXG5pbXBvcnQgdHlwZSB7IENkcElucHV0Q29tbWFuZCB9IGZyb20gJy4vbW9iaWxlLWlucHV0Lm1qcyc7XG5cbmltcG9ydCB7IGV4ZWMgfSBmcm9tICcuL2V4ZWMubWpzJztcbmltcG9ydCB7IGxvZyB9IGZyb20gJy4vbG9nLm1qcyc7XG5pbXBvcnQgeyBwaWNrRnJlZVBvcnQgfSBmcm9tICcuL29ic2lkaWFuLWluc3RhbmNlLm1qcyc7XG5cbmNvbnN0IEFEQl9USU1FT1VUX0lOX01JTExJU0VDT05EUyA9IDE1XzAwMDtcbi8vIGBhZGIgZm9yd2FyZCAtLWxpc3RgIHByaW50cyB0aHJlZSBjb2x1bW5zOiBgPGRldmljZUlkPiB0Y3A6PHBvcnQ+IGxvY2FsYWJzdHJhY3Q6PHNvY2tldE5hbWU+YC5cbmNvbnN0IEZPUldBUkRfTElTVF9DT0xVTU5fQ09VTlQgPSAzO1xuY29uc3QgREVDSU1BTF9SQURJWCA9IDEwO1xuY29uc3QgQ0RQX0NPTU1BTkRfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMgPSAzMF8wMDA7XG5jb25zdCBDRFBfSE9TVCA9ICcxMjcuMC4wLjEnO1xuXG4vKipcbiAqIE9uZSBlbnRyeSBvZiB0aGUgZGVidWdnZXIncyBgL2pzb25gIHRhcmdldCBsaXN0LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIENkcFRhcmdldCB7XG4gIHJlYWRvbmx5IHRpdGxlPzogc3RyaW5nO1xuICByZWFkb25seSB0eXBlPzogc3RyaW5nO1xuICByZWFkb25seSB1cmw/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHdlYlNvY2tldERlYnVnZ2VyVXJsPzogc3RyaW5nO1xufVxuXG4vKipcbiAqIFBhcmFtZXRlcnMgZm9yIHtAbGluayBjb25uZWN0VG9XZWJWaWV3Q2RwfS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBDb25uZWN0VG9XZWJWaWV3Q2RwUGFyYW1zIHtcbiAgLyoqXG4gICAqIFRoZSBBbmRyb2lkIGFwcGxpY2F0aW9uIGlkIHdob3NlIFdlYlZpZXcgdG8gYXR0YWNoIHRvLCBlLmcuIGBtZC5vYnNpZGlhbmAuXG4gICAqL1xuICByZWFkb25seSBhcHBJZDogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBUaGUgYWRiIGRldmljZSBpZCAoYGVtdWxhdG9yLTU1NTRgLCBhIHNlcmlhbCkgdG8gcnVuIGFkYiBhZ2FpbnN0LlxuICAgKi9cbiAgcmVhZG9ubHkgZGV2aWNlSWQ6IHN0cmluZztcbn1cblxuLyoqXG4gKiBUaGUgZXJyb3IgaGFsZiBvZiBhIGZhaWxlZCBDRFAgY29tbWFuZCByZXNwb25zZS5cbiAqL1xuaW50ZXJmYWNlIENkcEVycm9yIHtcbiAgcmVhZG9ubHkgbWVzc2FnZT86IHN0cmluZztcbn1cblxuLyoqXG4gKiBIYW5kbGVzIGEgQ0RQIGV2ZW50LlxuICovXG50eXBlIENkcEV2ZW50SGFuZGxlciA9IChwYXJhbXM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KSA9PiB2b2lkO1xuXG4vKipcbiAqIEEgQ0RQIG1lc3NhZ2U6IGVpdGhlciBhIHJlc3BvbnNlIHRvIGEgY29tbWFuZCAoYGlkYCkgb3IgYW4gZXZlbnQgKGBtZXRob2RgKS5cbiAqL1xuaW50ZXJmYWNlIENkcE1lc3NhZ2Uge1xuICByZWFkb25seSBlcnJvcj86IENkcEVycm9yO1xuICByZWFkb25seSBpZD86IG51bWJlcjtcbiAgcmVhZG9ubHkgbWV0aG9kPzogc3RyaW5nO1xuICByZWFkb25seSBwYXJhbXM/OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbiAgcmVhZG9ubHkgcmVzdWx0PzogUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG59XG5cbi8qIHY4IGlnbm9yZSBzdGFydCAtLSBJbnRlZ3JhdGlvbi10aW1lIGNvZGUgKGEgbGl2ZSBXZWJTb2NrZXQgdG8gYSBXZWJWaWV3LCBhZGIgcG9ydCBmb3J3YXJkaW5nKS4gQ292ZXJlZCBieSB0aGUgQW5kcm9pZCBpbnRlZ3JhdGlvbiB0ZXN0cywgbm90IGJ5IHVuaXQgdGVzdHMuIEl0IGVuZHMgYmVmb3JlIGBwYXJzZUZvcndhcmRlZFBvcnRgOiBgcGVyZmVjdGlvbmlzdC9zb3J0LW1vZHVsZXNgIHBsYWNlcyB0aGUgcHVyZSwgdW5pdC10ZXN0ZWQgaGVscGVycyBBRlRFUiB0aGlzIGNsYXNzIGFuZCBgY29ubmVjdFRvV2ViVmlld0NkcGAsIHNvIHRoZXkgbXVzdCBzdGF5IG1lYXN1cmVkLiAqL1xuXG4vKipcbiAqIEFuIG9wZW4gQ0RQIGNvbm5lY3Rpb24gdG8gYSBXZWJWaWV3LlxuICpcbiAqIENvbW1hbmRzIGFyZSBpZC1tdWx0aXBsZXhlZCwgc28gYSBjb21tYW5kIGlzc3VlZCBmcm9tIGluc2lkZSBhbiBldmVudCBoYW5kbGVyIGNvbXBsZXRlcyB3aGlsZSBhbiBlYXJsaWVyXG4gKiBgUnVudGltZS5ldmFsdWF0ZWAgaXMgc3RpbGwgYXdhaXRpbmcgaXRzIHByb21pc2UgXHUyMDE0IHdoaWNoIGlzIHRoZSB3aG9sZSByZWFzb24gdGhpcyBjbGFzcyBleGlzdHMuXG4gKi9cbmV4cG9ydCBjbGFzcyBXZWJWaWV3Q2RwQ29ubmVjdGlvbiB7XG4gIC8qKlxuICAgKiBXaGV0aGVyIHRoZSBzb2NrZXQgaXMgc3RpbGwgdXNhYmxlLlxuICAgKlxuICAgKiBSZXN0YXJ0aW5nIHRoZSBhcHAgdGVhcnMgaXRzIFdlYlZpZXcgZG93biBhbmQgdGFrZXMgdGhlIGRlYnVnZ2VyIHRhcmdldCB3aXRoIGl0LCBzbyBhIGNhY2hlZFxuICAgKiBjb25uZWN0aW9uIGhhcyB0byBiZSByZS1jaGVja2VkIHJhdGhlciB0aGFuIGFzc3VtZWQgbGl2ZS5cbiAgICpcbiAgICogQHJldHVybnMgYHRydWVgIHdoaWxlIHRoZSBzb2NrZXQgaXMgb3Blbi5cbiAgICovXG4gIHB1YmxpYyBnZXQgaXNPcGVuKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLndlYlNvY2tldC5yZWFkeVN0YXRlID09PSBXZWJTb2NrZXQuT1BFTjtcbiAgfVxuXG4gIHByaXZhdGUgcmVhZG9ubHkgZXZlbnRIYW5kbGVycyA9IG5ldyBNYXA8c3RyaW5nLCBDZHBFdmVudEhhbmRsZXI+KCk7XG5cbiAgcHJpdmF0ZSBtZXNzYWdlSWQgPSAwO1xuXG4gIC8qKlxuICAgKiBXcmFwcyBhbiBhbHJlYWR5LW9wZW4gZGVidWdnZXIgc29ja2V0LCBwbHVzIHdoYXQgaXMgbmVlZGVkIHRvIHRlYXIgaXRzIHBvcnQgZm9yd2FyZCBkb3duLlxuICAgKlxuICAgKiBAcGFyYW0gd2ViU29ja2V0IC0gVGhlIG9wZW4gZGVidWdnZXIgc29ja2V0LlxuICAgKiBAcGFyYW0gZGV2aWNlSWQgLSBUaGUgYWRiIGRldmljZSBpZCwgbmVlZGVkIHRvIGRyb3AgdGhlIHBvcnQgZm9yd2FyZCBvbiBkaXNwb3NlLlxuICAgKiBAcGFyYW0gcG9ydCAtIFRoZSBmb3J3YXJkZWQgbG9jYWwgcG9ydC5cbiAgICovXG4gIHB1YmxpYyBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IHdlYlNvY2tldDogV2ViU29ja2V0LCBwcml2YXRlIHJlYWRvbmx5IGRldmljZUlkOiBzdHJpbmcsIHByaXZhdGUgcmVhZG9ubHkgcG9ydDogbnVtYmVyKSB7XG4gICAgdGhpcy53ZWJTb2NrZXQuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIChldmVudDogTWVzc2FnZUV2ZW50KSA9PiB7XG4gICAgICBjb25zdCBtZXNzYWdlID0gSlNPTi5wYXJzZShTdHJpbmcoZXZlbnQuZGF0YSkpIGFzIENkcE1lc3NhZ2U7XG4gICAgICBpZiAobWVzc2FnZS5tZXRob2QgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIHRoaXMuZXZlbnRIYW5kbGVycy5nZXQobWVzc2FnZS5tZXRob2QpPy4obWVzc2FnZS5wYXJhbXMgPz8ge30pO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENsb3NlcyB0aGUgc29ja2V0IGFuZCBkcm9wcyB0aGUgYWRiIHBvcnQgZm9yd2FyZC5cbiAgICovXG4gIHB1YmxpYyBhc3luYyBkaXNwb3NlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMuY2xvc2VTb2NrZXQoKTtcbiAgICBhd2FpdCByZW1vdmVGb3J3YXJkKHRoaXMuZGV2aWNlSWQsIHRoaXMucG9ydCk7XG4gIH1cblxuICAvKipcbiAgICogU3luY2hyb25vdXMgZGlzcG9zYWwsIGZvciBgcHJvY2Vzcy5vbignZXhpdCcpYCBoYW5kbGVycyB3aGVyZSBhc3luYyB3b3JrIGNhbm5vdCBydW4uXG4gICAqXG4gICAqIFdpdGhvdXQgdGhpcyBhbiBhYnJ1cHQgd29ya2VyIGV4aXQgbGVhdmVzIHRoZSBwb3J0IGZvcndhcmQgYmVoaW5kIFx1MjAxNCB0aGUgYXN5bmMge0BsaW5rIGRpc3Bvc2V9IGFib3ZlXG4gICAqIG5ldmVyIGdldHMgYSB0dXJuLlxuICAgKi9cbiAgcHVibGljIGRpc3Bvc2VTeW5jKCk6IHZvaWQge1xuICAgIHRoaXMuY2xvc2VTb2NrZXQoKTtcbiAgICB0cnkge1xuICAgICAgZXhlY0ZpbGVTeW5jKCdhZGInLCBbJy1zJywgdGhpcy5kZXZpY2VJZCwgJ2ZvcndhcmQnLCAnLS1yZW1vdmUnLCBgdGNwOiR7U3RyaW5nKHRoaXMucG9ydCl9YF0sIHtcbiAgICAgICAgc3RkaW86ICdpZ25vcmUnLFxuICAgICAgICB0aW1lb3V0OiBBREJfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFNcbiAgICAgIH0pO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gQmVzdCBlZmZvcnQ6IGFkYiBkcm9wcyBldmVyeSBmb3J3YXJkIHdoZW4gdGhlIGRldmljZSBkaXNjb25uZWN0cyBhbnl3YXkuXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFN1YnNjcmliZXMgdG8gYSBDRFAgZXZlbnQuIE9uZSBoYW5kbGVyIHBlciBtZXRob2Q7IHN1YnNjcmliaW5nIGFnYWluIHJlcGxhY2VzIGl0LlxuICAgKlxuICAgKiBAcGFyYW0gbWV0aG9kIC0gVGhlIENEUCBldmVudCBuYW1lLCBlLmcuIGBSdW50aW1lLmJpbmRpbmdDYWxsZWRgLlxuICAgKiBAcGFyYW0gaGFuZGxlciAtIENhbGxlZCB3aXRoIHRoZSBldmVudCBwYXJhbWV0ZXJzLlxuICAgKi9cbiAgcHVibGljIG9uKG1ldGhvZDogc3RyaW5nLCBoYW5kbGVyOiBDZHBFdmVudEhhbmRsZXIpOiB2b2lkIHtcbiAgICB0aGlzLmV2ZW50SGFuZGxlcnMuc2V0KG1ldGhvZCwgaGFuZGxlcik7XG4gIH1cblxuICAvKipcbiAgICogU2VuZHMgYSBDRFAgY29tbWFuZCBhbmQgd2FpdHMgZm9yIGl0cyByZXNwb25zZS5cbiAgICpcbiAgICogQHBhcmFtIG1ldGhvZCAtIFRoZSBDRFAgbWV0aG9kIG5hbWUuXG4gICAqIEBwYXJhbSBwYXJhbXMgLSBUaGUgQ0RQIG1ldGhvZCBwYXJhbWV0ZXJzLlxuICAgKiBAcmV0dXJucyBUaGUgY29tbWFuZCByZXN1bHQuXG4gICAqL1xuICBwdWJsaWMgc2VuZChtZXRob2Q6IHN0cmluZywgcGFyYW1zOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHt9KTogUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4ge1xuICAgIGNvbnN0IGlkID0gKyt0aGlzLm1lc3NhZ2VJZDtcblxuICAgIHJldHVybiBuZXcgUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgY29uc3QgdGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICB0aGlzLndlYlNvY2tldC5yZW1vdmVFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgaGFuZGxlcik7XG4gICAgICAgIHJlamVjdChuZXcgRXJyb3IoYFdlYlZpZXcgQ0RQIGNvbW1hbmQgdGltZWQgb3V0IGFmdGVyICR7U3RyaW5nKENEUF9DT01NQU5EX1RJTUVPVVRfSU5fTUlMTElTRUNPTkRTKX1tczogJHttZXRob2R9YCkpO1xuICAgICAgfSwgQ0RQX0NPTU1BTkRfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFMpO1xuXG4gICAgICBjb25zdCBoYW5kbGVyID0gKGV2ZW50OiBNZXNzYWdlRXZlbnQpOiB2b2lkID0+IHtcbiAgICAgICAgY29uc3QgbWVzc2FnZSA9IEpTT04ucGFyc2UoU3RyaW5nKGV2ZW50LmRhdGEpKSBhcyBDZHBNZXNzYWdlO1xuICAgICAgICBpZiAobWVzc2FnZS5pZCAhPT0gaWQpIHtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG4gICAgICAgIHRoaXMud2ViU29ja2V0LnJlbW92ZUV2ZW50TGlzdGVuZXIoJ21lc3NhZ2UnLCBoYW5kbGVyKTtcbiAgICAgICAgaWYgKG1lc3NhZ2UuZXJyb3IpIHtcbiAgICAgICAgICByZWplY3QobmV3IEVycm9yKGBXZWJWaWV3IENEUCBjb21tYW5kICR7bWV0aG9kfSBmYWlsZWQ6ICR7bWVzc2FnZS5lcnJvci5tZXNzYWdlID8/ICd1bmtub3duIGVycm9yJ31gKSk7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgcmVzb2x2ZShtZXNzYWdlLnJlc3VsdCA/PyB7fSk7XG4gICAgICB9O1xuXG4gICAgICB0aGlzLndlYlNvY2tldC5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgaGFuZGxlcik7XG4gICAgICB0aGlzLndlYlNvY2tldC5zZW5kKEpTT04uc3RyaW5naWZ5KHsgaWQsIG1ldGhvZCwgcGFyYW1zIH0pKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTZW5kcyBhbiBvcmRlcmVkIGNvbW1hbmQgc2VxdWVuY2UsIG9uZSBjb21tYW5kIGF0IGEgdGltZS5cbiAgICpcbiAgICogRWFjaCBjb21tYW5kIGlzIGF3YWl0ZWQgYmVmb3JlIHRoZSBuZXh0IGlzIHNlbnQsIHdoaWNoIGlzIHRoZSBvbmx5IG9yZGVyaW5nIGd1YXJhbnRlZSBhbiBpbnB1dFxuICAgKiBzZXF1ZW5jZSBuZWVkcyBcdTIwMTQgYSBsb25nLXByZXNzIGhvbGRzIGl0c2VsZiBmb3IgaXRzIG93biBgZHVyYXRpb25gIHJhdGhlciB0aGFuIGFza2luZyB0aGUgaG9zdCB0b1xuICAgKiBzbGVlcCBiZXR3ZWVuIHR3byB0b3VjaGVzLCBzbyB0aGVyZSBpcyBubyBpbnRlci1jb21tYW5kIGRlbGF5IHRvIGhvbm9yLlxuICAgKlxuICAgKiBAcGFyYW0gY29tbWFuZHMgLSBUaGUgY29tbWFuZHMgdG8gc2VuZCwgaW4gb3JkZXIuXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgc2VuZEFsbChjb21tYW5kczogcmVhZG9ubHkgQ2RwSW5wdXRDb21tYW5kW10pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBmb3IgKGNvbnN0IGNvbW1hbmQgb2YgY29tbWFuZHMpIHtcbiAgICAgIGF3YWl0IHRoaXMuc2VuZChjb21tYW5kLm1ldGhvZCwgY29tbWFuZC5wYXJhbXMpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDbG9zZXMgdGhlIGRlYnVnZ2VyIHNvY2tldCBhbmQgc3RvcHMgcm91dGluZyBpdHMgZXZlbnRzLlxuICAgKi9cbiAgcHJpdmF0ZSBjbG9zZVNvY2tldCgpOiB2b2lkIHtcbiAgICB0aGlzLmV2ZW50SGFuZGxlcnMuY2xlYXIoKTtcbiAgICBpZiAodGhpcy53ZWJTb2NrZXQucmVhZHlTdGF0ZSA9PT0gV2ViU29ja2V0Lk9QRU4pIHtcbiAgICAgIHRoaXMud2ViU29ja2V0LmNsb3NlKCk7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogT3BlbnMgYSBDRFAgY29ubmVjdGlvbiB0byB0aGUgYXBwJ3MgV2ViVmlldy5cbiAqXG4gKiBAcGFyYW0gcGFyYW1zIC0gV2hpY2ggYXBwIG9uIHdoaWNoIGRldmljZS5cbiAqIEByZXR1cm5zIFRoZSBvcGVuIGNvbm5lY3Rpb24uIERpc3Bvc2UgaXQgdG8gY2xvc2UgdGhlIHNvY2tldCBhbmQgZHJvcCB0aGUgYWRiIHBvcnQgZm9yd2FyZC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNvbm5lY3RUb1dlYlZpZXdDZHAocGFyYW1zOiBDb25uZWN0VG9XZWJWaWV3Q2RwUGFyYW1zKTogUHJvbWlzZTxXZWJWaWV3Q2RwQ29ubmVjdGlvbj4ge1xuICBjb25zdCB7IGFwcElkLCBkZXZpY2VJZCB9ID0gcGFyYW1zO1xuXG4gIC8vIGBwaWRvZmAgbGlzdHMgZXZlcnkgbWF0Y2hpbmcgcHJvY2VzczsgdGhlIGFwcCdzIG1haW4gb25lIGlzIGZpcnN0LlxuICBjb25zdCBwaWRvZk91dHB1dCA9IGF3YWl0IGFkYihkZXZpY2VJZCwgWydzaGVsbCcsICdwaWRvZicsIGFwcElkXSk7XG4gIGNvbnN0IHBpZCA9IHBpZG9mT3V0cHV0LnNwbGl0KC9cXHMrLywgMSlbMF0gPz8gJyc7XG4gIGlmICghcGlkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGAke2FwcElkfSBpcyBub3QgcnVubmluZyBvbiAke2RldmljZUlkfSwgc28gaXRzIFdlYlZpZXcgaGFzIG5vIGRlYnVnZ2VyIHRvIGF0dGFjaCB0by5gKTtcbiAgfVxuXG4gIGNvbnN0IHByb2NOZXRVbml4ID0gYXdhaXQgYWRiKGRldmljZUlkLCBbJ3NoZWxsJywgJ2NhdCcsICcvcHJvYy9uZXQvdW5peCddKTtcbiAgY29uc3Qgc29ja2V0TmFtZSA9IHBhcnNlV2ViVmlld0RldnRvb2xzU29ja2V0TmFtZShwcm9jTmV0VW5peCwgcGlkKTtcblxuICBjb25zdCBmb3J3YXJkTGlzdCA9IGF3YWl0IGFkYihkZXZpY2VJZCwgWydmb3J3YXJkJywgJy0tbGlzdCddKTtcbiAgY29uc3QgZXhpc3RpbmdQb3J0ID0gcGFyc2VGb3J3YXJkZWRQb3J0KGZvcndhcmRMaXN0LCBkZXZpY2VJZCwgc29ja2V0TmFtZSk7XG4gIGNvbnN0IHBvcnQgPSBleGlzdGluZ1BvcnQgPz8gYXdhaXQgcGlja0ZyZWVQb3J0KCk7XG4gIGlmIChleGlzdGluZ1BvcnQgPT09IHVuZGVmaW5lZCkge1xuICAgIGF3YWl0IGFkYihkZXZpY2VJZCwgWydmb3J3YXJkJywgYHRjcDoke1N0cmluZyhwb3J0KX1gLCBgbG9jYWxhYnN0cmFjdDoke3NvY2tldE5hbWV9YF0pO1xuICAgIGxvZyhgW3dlYnZpZXctY2RwXSBGb3J3YXJkZWQgdGNwOiR7U3RyaW5nKHBvcnQpfSAtPiBsb2NhbGFic3RyYWN0OiR7c29ja2V0TmFtZX0gKCR7YXBwSWR9IHBpZCAke3BpZH0pYCk7XG4gIH0gZWxzZSB7XG4gICAgbG9nKGBbd2Vidmlldy1jZHBdIFJldXNpbmcgZm9yd2FyZCB0Y3A6JHtTdHJpbmcocG9ydCl9IC0+IGxvY2FsYWJzdHJhY3Q6JHtzb2NrZXROYW1lfSAoJHthcHBJZH0gcGlkICR7cGlkfSlgKTtcbiAgfVxuXG4gIHRyeSB7XG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChgaHR0cDovLyR7Q0RQX0hPU1R9OiR7U3RyaW5nKHBvcnQpfS9qc29uYCk7XG4gICAgY29uc3QgdGFyZ2V0ID0gc2VsZWN0V2ViVmlld1BhZ2VUYXJnZXQoYXdhaXQgcmVzcG9uc2UuanNvbigpIGFzIENkcFRhcmdldFtdKTtcbiAgICBjb25zdCB3ZWJTb2NrZXQgPSBuZXcgV2ViU29ja2V0KHRhcmdldC53ZWJTb2NrZXREZWJ1Z2dlclVybCA/PyAnJyk7XG4gICAgYXdhaXQgbmV3IFByb21pc2U8dm9pZD4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgd2ViU29ja2V0LmFkZEV2ZW50TGlzdGVuZXIoJ29wZW4nLCAoKSA9PiB7XG4gICAgICAgIHJlc29sdmUoKTtcbiAgICAgIH0sIHsgb25jZTogdHJ1ZSB9KTtcbiAgICAgIHdlYlNvY2tldC5hZGRFdmVudExpc3RlbmVyKCdlcnJvcicsICgpID0+IHtcbiAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgQ291bGQgbm90IG9wZW4gdGhlIFdlYlZpZXcgQ0RQIHNvY2tldCBhdCAke3RhcmdldC53ZWJTb2NrZXREZWJ1Z2dlclVybCA/PyAnJ30uYCkpO1xuICAgICAgfSwgeyBvbmNlOiB0cnVlIH0pO1xuICAgIH0pO1xuICAgIGxvZyhgW3dlYnZpZXctY2RwXSBBdHRhY2hlZCB0byBXZWJWaWV3IHRhcmdldCBcIiR7dGFyZ2V0LnRpdGxlID8/ICcnfVwiYCk7XG5cbiAgICByZXR1cm4gbmV3IFdlYlZpZXdDZHBDb25uZWN0aW9uKHdlYlNvY2tldCwgZGV2aWNlSWQsIHBvcnQpO1xuICB9IGNhdGNoIChlcnJvcjogdW5rbm93bikge1xuICAgIGF3YWl0IHJlbW92ZUZvcndhcmQoZGV2aWNlSWQsIHBvcnQpO1xuICAgIHRocm93IGVycm9yO1xuICB9XG59XG5cbi8qIHY4IGlnbm9yZSBzdG9wICovXG5cbi8qKlxuICogRmluZHMgYSBwb3J0IGFscmVhZHkgZm9yd2FyZGVkIHRvIHRoaXMgZGV2aWNlIHNvY2tldCwgc28gcmVwZWF0ZWQgcnVucyByZXVzZSBvbmUgZm9yd2FyZC5cbiAqXG4gKiBXaXRob3V0IHRoaXMgZXZlcnkgcnVuIGFsbG9jYXRlcyBhIGZyZXNoIHBvcnQsIGFuZCBhbnkgcnVuIHRoYXQgZXhpdHMgd2l0aG91dCB0aGUgYXN5bmNcbiAqIHtAbGluayBXZWJWaWV3Q2RwQ29ubmVjdGlvbi5kaXNwb3NlfSBcdTIwMTQgYW4gYWJydXB0IHdvcmtlciBleGl0LCB3aGVyZSBvbmx5IHRoZSBzeW5jaHJvbm91cyB0ZWFyZG93blxuICogcGF0aCBydW5zIFx1MjAxNCBsZWF2ZXMgaXRzIGZvcndhcmQgYmVoaW5kIGZvcmV2ZXIuIFJldXNlIGJvdW5kcyB0aGF0IHRvIGEgc2luZ2xlIGZvcndhcmQgcGVyIHNvY2tldFxuICogcmF0aGVyIHRoYW4gb25lIHBlciBydW4uXG4gKlxuICogQHBhcmFtIGZvcndhcmRMaXN0IC0gVGhlIG91dHB1dCBvZiBgYWRiIGZvcndhcmQgLS1saXN0YC5cbiAqIEBwYXJhbSBkZXZpY2VJZCAtIFRoZSBhZGIgZGV2aWNlIGlkIHRvIG1hdGNoLlxuICogQHBhcmFtIHNvY2tldE5hbWUgLSBUaGUgYWJzdHJhY3Qgc29ja2V0IG5hbWUgdG8gbWF0Y2guXG4gKiBAcmV0dXJucyBUaGUgYWxyZWFkeS1mb3J3YXJkZWQgbG9jYWwgcG9ydCwgb3IgYHVuZGVmaW5lZGAgd2hlbiB0aGVyZSBpcyBub25lLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VGb3J3YXJkZWRQb3J0KGZvcndhcmRMaXN0OiBzdHJpbmcsIGRldmljZUlkOiBzdHJpbmcsIHNvY2tldE5hbWU6IHN0cmluZyk6IG51bWJlciB8IHVuZGVmaW5lZCB7XG4gIGZvciAoY29uc3QgbGluZSBvZiBmb3J3YXJkTGlzdC5zcGxpdCgnXFxuJykpIHtcbiAgICAvLyBgYWRiIGZvcndhcmQgLS1saXN0YCBwcmludHMgYDxkZXZpY2VJZD4gdGNwOjxwb3J0PiBsb2NhbGFic3RyYWN0Ojxzb2NrZXROYW1lPmAuXG4gICAgY29uc3QgW2xpbmVEZXZpY2VJZCwgbG9jYWwsIHJlbW90ZV0gPSBsaW5lLnRyaW0oKS5zcGxpdCgvXFxzKy8sIEZPUldBUkRfTElTVF9DT0xVTU5fQ09VTlQpO1xuICAgIGlmIChsaW5lRGV2aWNlSWQgIT09IGRldmljZUlkIHx8IHJlbW90ZSAhPT0gYGxvY2FsYWJzdHJhY3Q6JHtzb2NrZXROYW1lfWAgfHwgIWxvY2FsPy5zdGFydHNXaXRoKCd0Y3A6JykpIHtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGNvbnN0IHBvcnQgPSBOdW1iZXIucGFyc2VJbnQobG9jYWwuc2xpY2UoJ3RjcDonLmxlbmd0aCksIERFQ0lNQUxfUkFESVgpO1xuICAgIGlmIChOdW1iZXIuaXNTYWZlSW50ZWdlcihwb3J0KSkge1xuICAgICAgcmV0dXJuIHBvcnQ7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHVuZGVmaW5lZDtcbn1cblxuLyoqXG4gKiBQaWNrcyB0aGUgV2ViVmlldydzIGRldnRvb2xzIHNvY2tldCBuYW1lIG91dCBvZiBgL3Byb2MvbmV0L3VuaXhgLlxuICpcbiAqIEEgZGV2aWNlIGNhbiBob3N0IHNldmVyYWwgZGVidWdnYWJsZSBXZWJWaWV3cyAob3RoZXIgYXBwcywgYSBsZWZ0b3ZlciBwcm9jZXNzKSwgc28gdGhlIGFwcCdzIG93biBwaWQgaXNcbiAqIHdoYXQgZGlzYW1iaWd1YXRlcyB0aGVtLiBXaGVuIG5vIHNvY2tldCBtYXRjaGVzIHRoYXQgcGlkIHRoZSBzb2xlIHJlbWFpbmluZyBjYW5kaWRhdGUgaXMgdXNlZCBcdTIwMTQgYVxuICogV2ViVmlldyB3aG9zZSBzb2NrZXQgaXMgbmFtZWQgYWZ0ZXIgYSBjaGlsZCBwcm9jZXNzIHJhdGhlciB0aGFuIHRoZSBvbmUgYHBpZG9mYCByZXBvcnRlZCBzdGlsbCBoYXMgdG8gYmVcbiAqIHJlYWNoYWJsZSBcdTIwMTQgYnV0IHR3byB1bmF0dHJpYnV0YWJsZSBjYW5kaWRhdGVzIGlzIGFtYmlndW91cyBlbm91Z2ggdG8gcmVmdXNlLlxuICpcbiAqIEBwYXJhbSBwcm9jTmV0VW5peCAtIFRoZSBjb250ZW50cyBvZiBgL3Byb2MvbmV0L3VuaXhgLlxuICogQHBhcmFtIHBpZCAtIFRoZSBhcHBsaWNhdGlvbidzIHByb2Nlc3MgaWQuXG4gKiBAcmV0dXJucyBUaGUgc29ja2V0IG5hbWUsIGUuZy4gYHdlYnZpZXdfZGV2dG9vbHNfcmVtb3RlXzE4MDU2YC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlV2ViVmlld0RldnRvb2xzU29ja2V0TmFtZShwcm9jTmV0VW5peDogc3RyaW5nLCBwaWQ6IHN0cmluZyk6IHN0cmluZyB7XG4gIC8vIFRoZSB3aG9sZSBtYXRjaCBtaW51cyBpdHMgbGVhZGluZyBgQGAsIHJhdGhlciB0aGFuIGEgbmFtZWQgY2FwdHVyZSBncm91cDogYSBtYW5kYXRvcnkgZ3JvdXAgaXMgYWx3YXlzXG4gIC8vIFByZXNlbnQgb25jZSB0aGUgcGF0dGVybiBoYXMgbWF0Y2hlZCwgc28gYG1hdGNoLmdyb3Vwcz8uWyduYW1lJ10gPz8gJydgIHdvdWxkIGxlYXZlIGJlaGluZCB0d28gYnJhbmNoZXNcbiAgLy8gTm8gaW5wdXQgY2FuIGV2ZXIgcmVhY2ggXHUyMDE0IGFuZCB0aGlzIGZpbGUgaXMgbWVhc3VyZWQgYWdhaW5zdCBhIDEwMCUgYnJhbmNoIGdhdGUuXG4gIGNvbnN0IG5hbWVzID0gW1xuICAgIC4uLm5ldyBTZXQoWy4uLnByb2NOZXRVbml4Lm1hdGNoQWxsKC9Ad2Vidmlld19kZXZ0b29sc19yZW1vdGVfXFx3Ky9nKV1cbiAgICAgIC5tYXAoKG1hdGNoKSA9PiBtYXRjaFswXS5zbGljZSgnQCcubGVuZ3RoKSkpXG4gIF07XG5cbiAgLy8gRGVzdHJ1Y3R1cmVkIHJhdGhlciB0aGFuIGluZGV4ZWQgZm9yIHRoZSBzYW1lIHJlYXNvbjogYHNvbGVOYW1lYCBjYXJyaWVzIHRoZSBcInRoZXJlIGlzIGF0IGxlYXN0IG9uZVwiXG4gIC8vIEZhY3QgaW4gdGhlIHR5cGUsIHNvIG5laXRoZXIgdGhlIGVtcHR5IGNoZWNrIGJlbG93IG5vciB0aGUgc2luZ2xlLWNhbmRpZGF0ZSBvbmUgbGVhdmVzIGEgZGVhZCBicmFuY2guXG4gIGNvbnN0IFtzb2xlTmFtZSwgLi4ub3RoZXJOYW1lc10gPSBuYW1lcztcblxuICBpZiAoc29sZU5hbWUgPT09IHVuZGVmaW5lZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICdObyBgd2Vidmlld19kZXZ0b29sc19yZW1vdGVfKmAgc29ja2V0IG9uIHRoZSBkZXZpY2UuIE9ic2lkaWFuIE1vYmlsZSBpcyBub3QgcnVubmluZywgb3IgaXRzIFdlYlZpZXcgJ1xuICAgICAgICArICdkZWJ1Z2dlciBpcyBkaXNhYmxlZC4nXG4gICAgKTtcbiAgfVxuXG4gIGNvbnN0IGV4YWN0TmFtZSA9IGB3ZWJ2aWV3X2RldnRvb2xzX3JlbW90ZV8ke3BpZH1gO1xuICBpZiAobmFtZXMuaW5jbHVkZXMoZXhhY3ROYW1lKSkge1xuICAgIHJldHVybiBleGFjdE5hbWU7XG4gIH1cblxuICBpZiAob3RoZXJOYW1lcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gc29sZU5hbWU7XG4gIH1cblxuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgYE5vIFxcYCR7ZXhhY3ROYW1lfVxcYCBzb2NrZXQsIGFuZCAke1N0cmluZyhuYW1lcy5sZW5ndGgpfSBvdGhlciBkZWJ1Z2dhYmxlIFdlYlZpZXdzIGFyZSBwcmVzZW50IGBcbiAgICAgICsgYCgke25hbWVzLmpvaW4oJywgJyl9KSwgc28gdGhlIHJpZ2h0IG9uZSBjYW5ub3QgYmUgY2hvc2VuLmBcbiAgKTtcbn1cblxuLyoqXG4gKiBQaWNrcyB0aGUgT2JzaWRpYW4gcGFnZSB0YXJnZXQgb3V0IG9mIHRoZSBkZWJ1Z2dlcidzIGAvanNvbmAgbGlzdC5cbiAqXG4gKiBAcGFyYW0gdGFyZ2V0cyAtIFRoZSBgL2pzb25gIHJlc3BvbnNlLlxuICogQHJldHVybnMgVGhlIHBhZ2UgdGFyZ2V0IHRvIGF0dGFjaCB0by5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNlbGVjdFdlYlZpZXdQYWdlVGFyZ2V0KHRhcmdldHM6IHJlYWRvbmx5IENkcFRhcmdldFtdKTogQ2RwVGFyZ2V0IHtcbiAgY29uc3QgdGFyZ2V0ID0gdGFyZ2V0cy5maW5kKChjYW5kaWRhdGUpID0+IGNhbmRpZGF0ZS50eXBlID09PSAncGFnZScgJiYgQm9vbGVhbihjYW5kaWRhdGUud2ViU29ja2V0RGVidWdnZXJVcmwpKVxuICAgID8/IHRhcmdldHMuZmluZCgoY2FuZGlkYXRlKSA9PiBCb29sZWFuKGNhbmRpZGF0ZS53ZWJTb2NrZXREZWJ1Z2dlclVybCkpO1xuXG4gIGlmICghdGFyZ2V0KSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdUaGUgV2ViVmlldyBkZWJ1Z2dlciBsaXN0ZWQgbm8gdGFyZ2V0IHdpdGggYSBXZWJTb2NrZXQgZW5kcG9pbnQuJyk7XG4gIH1cblxuICByZXR1cm4gdGFyZ2V0O1xufVxuXG4vKiB2OCBpZ25vcmUgc3RhcnQgLS0gSW50ZWdyYXRpb24tdGltZSBjb2RlIChhZGIgc2hlbGwtb3V0cykuIENvdmVyZWQgYnkgdGhlIEFuZHJvaWQgaW50ZWdyYXRpb24gdGVzdHMsIG5vdCBieSB1bml0IHRlc3RzLiAqL1xuXG4vKipcbiAqIFJ1bnMgYW4gYWRiIGNvbW1hbmQgYWdhaW5zdCBvbmUgZGV2aWNlLlxuICpcbiAqIEBwYXJhbSBkZXZpY2VJZCAtIFRoZSBhZGIgZGV2aWNlIGlkLlxuICogQHBhcmFtIGNvbW1hbmRBcmd1bWVudHMgLSBUaGUgYWRiIGFyZ3VtZW50cyBhZnRlciBgLXMgPGRldmljZUlkPmAuXG4gKiBAcmV0dXJucyBUcmltbWVkIHN0ZG91dC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gYWRiKGRldmljZUlkOiBzdHJpbmcsIGNvbW1hbmRBcmd1bWVudHM6IHN0cmluZ1tdKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3Qgc3Rkb3V0ID0gYXdhaXQgZXhlYyhbJ2FkYicsICctcycsIGRldmljZUlkLCAuLi5jb21tYW5kQXJndW1lbnRzXSwge1xuICAgIGlzUXVpZXQ6IHRydWUsXG4gICAgdGltZW91dEluTWlsbGlzZWNvbmRzOiBBREJfVElNRU9VVF9JTl9NSUxMSVNFQ09ORFNcbiAgfSk7XG5cbiAgcmV0dXJuIHN0ZG91dC50cmltKCk7XG59XG5cbi8qKlxuICogRHJvcHMgYW4gYWRiIHBvcnQgZm9yd2FyZCwgaWdub3JpbmcgdGhlIFwibm90IGZvdW5kXCIgY2FzZS5cbiAqXG4gKiBAcGFyYW0gZGV2aWNlSWQgLSBUaGUgYWRiIGRldmljZSBpZC5cbiAqIEBwYXJhbSBwb3J0IC0gVGhlIGZvcndhcmRlZCBsb2NhbCBwb3J0LlxuICovXG5hc3luYyBmdW5jdGlvbiByZW1vdmVGb3J3YXJkKGRldmljZUlkOiBzdHJpbmcsIHBvcnQ6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICB0cnkge1xuICAgIGF3YWl0IGV4ZWMoWydhZGInLCAnLXMnLCBkZXZpY2VJZCwgJ2ZvcndhcmQnLCAnLS1yZW1vdmUnLCBgdGNwOiR7U3RyaW5nKHBvcnQpfWBdLCB7XG4gICAgICBpc1F1aWV0OiB0cnVlLFxuICAgICAgc2hvdWxkSWdub3JlRXhpdENvZGU6IHRydWUsXG4gICAgICB0aW1lb3V0SW5NaWxsaXNlY29uZHM6IEFEQl9USU1FT1VUX0lOX01JTExJU0VDT05EU1xuICAgIH0pO1xuICB9IGNhdGNoIHtcbiAgICAvLyBCZXN0IGVmZm9ydDogdGhlIGZvcndhcmQgaXMgcGVyLXJ1biBhbmQgYWRiIGRyb3BzIGl0IHdoZW4gdGhlIGRldmljZSBkaXNjb25uZWN0cyBhbnl3YXkuXG4gIH1cbn1cblxuLyogdjggaWdub3JlIHN0b3AgKi9cbiJdLAogICJtYXBwaW5ncyI6ICJBQWtCQSxTQUFTLG9CQUFvQjtBQUk3QixTQUFTLFlBQVk7QUFDckIsU0FBUyxXQUFXO0FBQ3BCLFNBQVMsb0JBQW9CO0FBRTdCLE1BQU0sOEJBQThCO0FBRXBDLE1BQU0sNEJBQTRCO0FBQ2xDLE1BQU0sZ0JBQWdCO0FBQ3RCLE1BQU0sc0NBQXNDO0FBQzVDLE1BQU0sV0FBVztBQTBEVixNQUFNLHFCQUFxQjtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUF3QnpCLFlBQTZCLFdBQXVDLFVBQW1DLE1BQWM7QUFBeEY7QUFBdUM7QUFBbUM7QUFDNUcsU0FBSyxVQUFVLGlCQUFpQixXQUFXLENBQUMsVUFBd0I7QUFDbEUsWUFBTSxVQUFVLEtBQUssTUFBTSxPQUFPLE1BQU0sSUFBSSxDQUFDO0FBQzdDLFVBQUksUUFBUSxXQUFXLFFBQVc7QUFDaEM7QUFBQSxNQUNGO0FBRUEsV0FBSyxjQUFjLElBQUksUUFBUSxNQUFNLElBQUksUUFBUSxVQUFVLENBQUMsQ0FBQztBQUFBLElBQy9ELENBQUM7QUFBQSxFQUNIO0FBQUEsRUFUb0M7QUFBQSxFQUF1QztBQUFBLEVBQW1DO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBZjlHLElBQVcsU0FBa0I7QUFDM0IsV0FBTyxLQUFLLFVBQVUsZUFBZSxVQUFVO0FBQUEsRUFDakQ7QUFBQSxFQUVpQixnQkFBZ0Isb0JBQUksSUFBNkI7QUFBQSxFQUUxRCxZQUFZO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUF1QnBCLE1BQWEsVUFBeUI7QUFDcEMsU0FBSyxZQUFZO0FBQ2pCLFVBQU0sY0FBYyxLQUFLLFVBQVUsS0FBSyxJQUFJO0FBQUEsRUFDOUM7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVFPLGNBQW9CO0FBQ3pCLFNBQUssWUFBWTtBQUNqQixRQUFJO0FBQ0YsbUJBQWEsT0FBTyxDQUFDLE1BQU0sS0FBSyxVQUFVLFdBQVcsWUFBWSxPQUFPLE9BQU8sS0FBSyxJQUFJLENBQUMsRUFBRSxHQUFHO0FBQUEsUUFDNUYsT0FBTztBQUFBLFFBQ1AsU0FBUztBQUFBLE1BQ1gsQ0FBQztBQUFBLElBQ0gsUUFBUTtBQUFBLElBRVI7QUFBQSxFQUNGO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFRTyxHQUFHLFFBQWdCLFNBQWdDO0FBQ3hELFNBQUssY0FBYyxJQUFJLFFBQVEsT0FBTztBQUFBLEVBQ3hDO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQVNPLEtBQUssUUFBZ0IsU0FBa0MsQ0FBQyxHQUFxQztBQUNsRyxVQUFNLEtBQUssRUFBRSxLQUFLO0FBRWxCLFdBQU8sSUFBSSxRQUFpQyxDQUFDLFNBQVMsV0FBVztBQUMvRCxZQUFNLFVBQVUsV0FBVyxNQUFNO0FBQy9CLGFBQUssVUFBVSxvQkFBb0IsV0FBVyxPQUFPO0FBQ3JELGVBQU8sSUFBSSxNQUFNLHVDQUF1QyxPQUFPLG1DQUFtQyxDQUFDLE9BQU8sTUFBTSxFQUFFLENBQUM7QUFBQSxNQUNySCxHQUFHLG1DQUFtQztBQUV0QyxZQUFNLFVBQVUsQ0FBQyxVQUE4QjtBQUM3QyxjQUFNLFVBQVUsS0FBSyxNQUFNLE9BQU8sTUFBTSxJQUFJLENBQUM7QUFDN0MsWUFBSSxRQUFRLE9BQU8sSUFBSTtBQUNyQjtBQUFBLFFBQ0Y7QUFFQSxxQkFBYSxPQUFPO0FBQ3BCLGFBQUssVUFBVSxvQkFBb0IsV0FBVyxPQUFPO0FBQ3JELFlBQUksUUFBUSxPQUFPO0FBQ2pCLGlCQUFPLElBQUksTUFBTSx1QkFBdUIsTUFBTSxZQUFZLFFBQVEsTUFBTSxXQUFXLGVBQWUsRUFBRSxDQUFDO0FBQ3JHO0FBQUEsUUFDRjtBQUVBLGdCQUFRLFFBQVEsVUFBVSxDQUFDLENBQUM7QUFBQSxNQUM5QjtBQUVBLFdBQUssVUFBVSxpQkFBaUIsV0FBVyxPQUFPO0FBQ2xELFdBQUssVUFBVSxLQUFLLEtBQUssVUFBVSxFQUFFLElBQUksUUFBUSxPQUFPLENBQUMsQ0FBQztBQUFBLElBQzVELENBQUM7QUFBQSxFQUNIO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsRUFXQSxNQUFhLFFBQVEsVUFBcUQ7QUFDeEUsZUFBVyxXQUFXLFVBQVU7QUFDOUIsWUFBTSxLQUFLLEtBQUssUUFBUSxRQUFRLFFBQVEsTUFBTTtBQUFBLElBQ2hEO0FBQUEsRUFDRjtBQUFBO0FBQUE7QUFBQTtBQUFBLEVBS1EsY0FBb0I7QUFDMUIsU0FBSyxjQUFjLE1BQU07QUFDekIsUUFBSSxLQUFLLFVBQVUsZUFBZSxVQUFVLE1BQU07QUFDaEQsV0FBSyxVQUFVLE1BQU07QUFBQSxJQUN2QjtBQUFBLEVBQ0Y7QUFDRjtBQVFBLGVBQXNCLG9CQUFvQixRQUFrRTtBQUMxRyxRQUFNLEVBQUUsT0FBTyxTQUFTLElBQUk7QUFHNUIsUUFBTSxjQUFjLE1BQU0sSUFBSSxVQUFVLENBQUMsU0FBUyxTQUFTLEtBQUssQ0FBQztBQUNqRSxRQUFNLE1BQU0sWUFBWSxNQUFNLE9BQU8sQ0FBQyxFQUFFLENBQUMsS0FBSztBQUM5QyxNQUFJLENBQUMsS0FBSztBQUNSLFVBQU0sSUFBSSxNQUFNLEdBQUcsS0FBSyxzQkFBc0IsUUFBUSxnREFBZ0Q7QUFBQSxFQUN4RztBQUVBLFFBQU0sY0FBYyxNQUFNLElBQUksVUFBVSxDQUFDLFNBQVMsT0FBTyxnQkFBZ0IsQ0FBQztBQUMxRSxRQUFNLGFBQWEsK0JBQStCLGFBQWEsR0FBRztBQUVsRSxRQUFNLGNBQWMsTUFBTSxJQUFJLFVBQVUsQ0FBQyxXQUFXLFFBQVEsQ0FBQztBQUM3RCxRQUFNLGVBQWUsbUJBQW1CLGFBQWEsVUFBVSxVQUFVO0FBQ3pFLFFBQU0sT0FBTyxnQkFBZ0IsTUFBTSxhQUFhO0FBQ2hELE1BQUksaUJBQWlCLFFBQVc7QUFDOUIsVUFBTSxJQUFJLFVBQVUsQ0FBQyxXQUFXLE9BQU8sT0FBTyxJQUFJLENBQUMsSUFBSSxpQkFBaUIsVUFBVSxFQUFFLENBQUM7QUFDckYsUUFBSSwrQkFBK0IsT0FBTyxJQUFJLENBQUMscUJBQXFCLFVBQVUsS0FBSyxLQUFLLFFBQVEsR0FBRyxHQUFHO0FBQUEsRUFDeEcsT0FBTztBQUNMLFFBQUkscUNBQXFDLE9BQU8sSUFBSSxDQUFDLHFCQUFxQixVQUFVLEtBQUssS0FBSyxRQUFRLEdBQUcsR0FBRztBQUFBLEVBQzlHO0FBRUEsTUFBSTtBQUNGLFVBQU0sV0FBVyxNQUFNLE1BQU0sVUFBVSxRQUFRLElBQUksT0FBTyxJQUFJLENBQUMsT0FBTztBQUN0RSxVQUFNLFNBQVMsd0JBQXdCLE1BQU0sU0FBUyxLQUFLLENBQWdCO0FBQzNFLFVBQU0sWUFBWSxJQUFJLFVBQVUsT0FBTyx3QkFBd0IsRUFBRTtBQUNqRSxVQUFNLElBQUksUUFBYyxDQUFDLFNBQVMsV0FBVztBQUMzQyxnQkFBVSxpQkFBaUIsUUFBUSxNQUFNO0FBQ3ZDLGdCQUFRO0FBQUEsTUFDVixHQUFHLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFDakIsZ0JBQVUsaUJBQWlCLFNBQVMsTUFBTTtBQUN4QyxlQUFPLElBQUksTUFBTSw0Q0FBNEMsT0FBTyx3QkFBd0IsRUFBRSxHQUFHLENBQUM7QUFBQSxNQUNwRyxHQUFHLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFBQSxJQUNuQixDQUFDO0FBQ0QsUUFBSSw2Q0FBNkMsT0FBTyxTQUFTLEVBQUUsR0FBRztBQUV0RSxXQUFPLElBQUkscUJBQXFCLFdBQVcsVUFBVSxJQUFJO0FBQUEsRUFDM0QsU0FBUyxPQUFnQjtBQUN2QixVQUFNLGNBQWMsVUFBVSxJQUFJO0FBQ2xDLFVBQU07QUFBQSxFQUNSO0FBQ0Y7QUFpQk8sU0FBUyxtQkFBbUIsYUFBcUIsVUFBa0IsWUFBd0M7QUFDaEgsYUFBVyxRQUFRLFlBQVksTUFBTSxJQUFJLEdBQUc7QUFFMUMsVUFBTSxDQUFDLGNBQWMsT0FBTyxNQUFNLElBQUksS0FBSyxLQUFLLEVBQUUsTUFBTSxPQUFPLHlCQUF5QjtBQUN4RixRQUFJLGlCQUFpQixZQUFZLFdBQVcsaUJBQWlCLFVBQVUsTUFBTSxDQUFDLE9BQU8sV0FBVyxNQUFNLEdBQUc7QUFDdkc7QUFBQSxJQUNGO0FBRUEsVUFBTSxPQUFPLE9BQU8sU0FBUyxNQUFNLE1BQU0sT0FBTyxNQUFNLEdBQUcsYUFBYTtBQUN0RSxRQUFJLE9BQU8sY0FBYyxJQUFJLEdBQUc7QUFDOUIsYUFBTztBQUFBLElBQ1Q7QUFBQSxFQUNGO0FBRUEsU0FBTztBQUNUO0FBY08sU0FBUywrQkFBK0IsYUFBcUIsS0FBcUI7QUFJdkYsUUFBTSxRQUFRO0FBQUEsSUFDWixHQUFHLElBQUksSUFBSSxDQUFDLEdBQUcsWUFBWSxTQUFTLCtCQUErQixDQUFDLEVBQ2pFLElBQUksQ0FBQyxVQUFVLE1BQU0sQ0FBQyxFQUFFLE1BQU0sSUFBSSxNQUFNLENBQUMsQ0FBQztBQUFBLEVBQy9DO0FBSUEsUUFBTSxDQUFDLFVBQVUsR0FBRyxVQUFVLElBQUk7QUFFbEMsTUFBSSxhQUFhLFFBQVc7QUFDMUIsVUFBTSxJQUFJO0FBQUEsTUFDUjtBQUFBLElBRUY7QUFBQSxFQUNGO0FBRUEsUUFBTSxZQUFZLDJCQUEyQixHQUFHO0FBQ2hELE1BQUksTUFBTSxTQUFTLFNBQVMsR0FBRztBQUM3QixXQUFPO0FBQUEsRUFDVDtBQUVBLE1BQUksV0FBVyxXQUFXLEdBQUc7QUFDM0IsV0FBTztBQUFBLEVBQ1Q7QUFFQSxRQUFNLElBQUk7QUFBQSxJQUNSLFFBQVEsU0FBUyxrQkFBa0IsT0FBTyxNQUFNLE1BQU0sQ0FBQywyQ0FDL0MsTUFBTSxLQUFLLElBQUksQ0FBQztBQUFBLEVBQzFCO0FBQ0Y7QUFRTyxTQUFTLHdCQUF3QixTQUEwQztBQUNoRixRQUFNLFNBQVMsUUFBUSxLQUFLLENBQUMsY0FBYyxVQUFVLFNBQVMsVUFBVSxRQUFRLFVBQVUsb0JBQW9CLENBQUMsS0FDMUcsUUFBUSxLQUFLLENBQUMsY0FBYyxRQUFRLFVBQVUsb0JBQW9CLENBQUM7QUFFeEUsTUFBSSxDQUFDLFFBQVE7QUFDWCxVQUFNLElBQUksTUFBTSxrRUFBa0U7QUFBQSxFQUNwRjtBQUVBLFNBQU87QUFDVDtBQVdBLGVBQWUsSUFBSSxVQUFrQixrQkFBNkM7QUFDaEYsUUFBTSxTQUFTLE1BQU0sS0FBSyxDQUFDLE9BQU8sTUFBTSxVQUFVLEdBQUcsZ0JBQWdCLEdBQUc7QUFBQSxJQUN0RSxTQUFTO0FBQUEsSUFDVCx1QkFBdUI7QUFBQSxFQUN6QixDQUFDO0FBRUQsU0FBTyxPQUFPLEtBQUs7QUFDckI7QUFRQSxlQUFlLGNBQWMsVUFBa0IsTUFBNkI7QUFDMUUsTUFBSTtBQUNGLFVBQU0sS0FBSyxDQUFDLE9BQU8sTUFBTSxVQUFVLFdBQVcsWUFBWSxPQUFPLE9BQU8sSUFBSSxDQUFDLEVBQUUsR0FBRztBQUFBLE1BQ2hGLFNBQVM7QUFBQSxNQUNULHNCQUFzQjtBQUFBLE1BQ3RCLHVCQUF1QjtBQUFBLElBQ3pCLENBQUM7QUFBQSxFQUNILFFBQVE7QUFBQSxFQUVSO0FBQ0Y7IiwKICAibmFtZXMiOiBbXQp9Cg==
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers for recognizing a **wedged** Appium server and deciding what to
|
|
5
|
+
* do about it. Kept separate from the integration-only `transport-factory` so
|
|
6
|
+
* the classification, the remedy decision and the message stay unit-testable
|
|
7
|
+
* (the factory itself needs a real device and is excluded from unit tests).
|
|
8
|
+
*
|
|
9
|
+
* A wedged server is one that is still **live** — it listens on its port and
|
|
10
|
+
* answers `/status` with `ready: true` — but is no longer **usable**: its
|
|
11
|
+
* `appium-adb` can no longer enumerate devices, so every session creation dies
|
|
12
|
+
* with `Could not find a connected Android device in <n>ms` while the host's own
|
|
13
|
+
* `adb devices` lists the device instantly. Liveness is not readiness, and the
|
|
14
|
+
* error names the wrong subject: it blames a device that is demonstrably
|
|
15
|
+
* present, which sends whoever reads it to `adb devices` — the one diagnostic
|
|
16
|
+
* that actively misleads here.
|
|
17
|
+
*
|
|
18
|
+
* The wedge develops over time in a long-lived server (one auto-started by an
|
|
19
|
+
* earlier run and left listening), so the preflight cannot see it coming: it can
|
|
20
|
+
* only be recognized from the failed session, cross-checked against the host's
|
|
21
|
+
* adb.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Parameters for {@link buildWedgedAppiumServerMessage}.
|
|
25
|
+
*/
|
|
26
|
+
export interface BuildWedgedAppiumServerMessageParams {
|
|
27
|
+
/**
|
|
28
|
+
Origin of the Appium server that could not see the device (e.g. `http://localhost:4723`).
|
|
29
|
+
*/
|
|
30
|
+
readonly appiumOrigin: string;
|
|
31
|
+
/**
|
|
32
|
+
The device the host's adb can see but Appium cannot.
|
|
33
|
+
*/
|
|
34
|
+
readonly deviceId: string;
|
|
35
|
+
/**
|
|
36
|
+
Why this run is reporting rather than restarting.
|
|
37
|
+
*/
|
|
38
|
+
readonly reason: WedgedAppiumServerReportReason;
|
|
39
|
+
/**
|
|
40
|
+
How long the server has been running, when known from its marker.
|
|
41
|
+
*/
|
|
42
|
+
readonly serverAgeInMilliseconds?: number | undefined;
|
|
43
|
+
/**
|
|
44
|
+
PID of the server, when known from its marker.
|
|
45
|
+
*/
|
|
46
|
+
readonly serverPid?: number | undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The server is not to blame: either the failure was a different error, or the
|
|
50
|
+
* host's adb cannot see the device either. The original error is rethrown.
|
|
51
|
+
*/
|
|
52
|
+
export interface NotWedgedVerdict {
|
|
53
|
+
/**
|
|
54
|
+
Which of the two honest-failure cases this is.
|
|
55
|
+
*/
|
|
56
|
+
readonly reason: 'device-absent' | 'other-error';
|
|
57
|
+
/**
|
|
58
|
+
Discriminant.
|
|
59
|
+
*/
|
|
60
|
+
readonly remedy: 'not-wedged';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The server is wedged but must not be touched by this run.
|
|
64
|
+
*/
|
|
65
|
+
export interface ReportServerVerdict {
|
|
66
|
+
/**
|
|
67
|
+
Why this run is reporting rather than restarting.
|
|
68
|
+
*/
|
|
69
|
+
readonly reason: WedgedAppiumServerReportReason;
|
|
70
|
+
/**
|
|
71
|
+
Discriminant.
|
|
72
|
+
*/
|
|
73
|
+
readonly remedy: 'report';
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parameters for {@link resolveWedgedAppiumServerRemedy}.
|
|
77
|
+
*/
|
|
78
|
+
export interface ResolveWedgedAppiumServerRemedyParams {
|
|
79
|
+
/**
|
|
80
|
+
Device IDs the **host's** adb currently lists (the cross-check that convicts the server).
|
|
81
|
+
*/
|
|
82
|
+
readonly connectedDeviceIds: readonly string[];
|
|
83
|
+
/**
|
|
84
|
+
The device the session was requested against.
|
|
85
|
+
*/
|
|
86
|
+
readonly deviceId: string;
|
|
87
|
+
/**
|
|
88
|
+
The error the session attempt failed with.
|
|
89
|
+
*/
|
|
90
|
+
readonly error: unknown;
|
|
91
|
+
/**
|
|
92
|
+
Whether the server was already listening and was adopted, rather than started by this run.
|
|
93
|
+
*/
|
|
94
|
+
readonly isAdoptedServer: boolean;
|
|
95
|
+
/**
|
|
96
|
+
Whether this run may start an Appium server (`shouldAutoStartAppium` is not `false`).
|
|
97
|
+
*/
|
|
98
|
+
readonly isAutoStartAllowed: boolean;
|
|
99
|
+
/**
|
|
100
|
+
Whether the adopted server's marker proves an earlier run of this harness started it.
|
|
101
|
+
*/
|
|
102
|
+
readonly isHarnessOwnedServer: boolean;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The server is wedged and an earlier run of this harness started it, so this
|
|
106
|
+
* run may replace it.
|
|
107
|
+
*/
|
|
108
|
+
export interface RestartServerVerdict {
|
|
109
|
+
/**
|
|
110
|
+
Always the harness-owned case.
|
|
111
|
+
*/
|
|
112
|
+
readonly reason: 'harness-owned';
|
|
113
|
+
/**
|
|
114
|
+
Discriminant.
|
|
115
|
+
*/
|
|
116
|
+
readonly remedy: 'restart';
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The classification of a failed session attempt.
|
|
120
|
+
*
|
|
121
|
+
* `'device-absent'` and `'other-error'` are the not-wedged verdicts: the
|
|
122
|
+
* original error was honest and is rethrown untouched.
|
|
123
|
+
*/
|
|
124
|
+
export type WedgedAppiumServerReason = 'device-absent' | 'harness-owned' | 'other-error' | WedgedAppiumServerReportReason;
|
|
125
|
+
/**
|
|
126
|
+
* What the factory should do with a failed session attempt.
|
|
127
|
+
*/
|
|
128
|
+
export type WedgedAppiumServerRemedy = 'not-wedged' | 'report' | 'restart';
|
|
129
|
+
/**
|
|
130
|
+
* Why a wedged server is only reported rather than restarted. Each maps to its
|
|
131
|
+
* own remedy sentence — the whole point of the rewrite is that the message says
|
|
132
|
+
* what *this* run can and cannot do about the server it found.
|
|
133
|
+
*/
|
|
134
|
+
export type WedgedAppiumServerReportReason = 'auto-start-disabled' | 'foreign-server' | 'freshly-started' | 'restart-did-not-help';
|
|
135
|
+
/**
|
|
136
|
+
* The verdict on a failed session attempt, discriminated by `remedy` so a
|
|
137
|
+
* `'report'` verdict carries a reason {@link buildWedgedAppiumServerMessage} can
|
|
138
|
+
* turn into advice.
|
|
139
|
+
*/
|
|
140
|
+
export type WedgedAppiumServerVerdict = NotWedgedVerdict | ReportServerVerdict | RestartServerVerdict;
|
|
141
|
+
/**
|
|
142
|
+
* Builds the error message that replaces `Could not find a connected Android
|
|
143
|
+
* device`, naming the **server** and stating the evidence — the host's adb can
|
|
144
|
+
* see the device, so the server is the thing that is stale.
|
|
145
|
+
*
|
|
146
|
+
* @param params - The server, the device, and why this run is not restarting it.
|
|
147
|
+
* @returns The message.
|
|
148
|
+
*/
|
|
149
|
+
export declare function buildWedgedAppiumServerMessage(params: BuildWedgedAppiumServerMessageParams): string;
|
|
150
|
+
/**
|
|
151
|
+
* Checks whether a failed session attempt is `appium-adb`'s device-enumeration
|
|
152
|
+
* failure. The whole `cause` chain is searched, so WebdriverIO's wrapping of the
|
|
153
|
+
* server's error does not hide it.
|
|
154
|
+
*
|
|
155
|
+
* @param error - The error the session attempt failed with.
|
|
156
|
+
* @returns `true` when it is the device-not-found failure.
|
|
157
|
+
*/
|
|
158
|
+
export declare function checkIsAppiumDeviceNotFoundError(error: unknown): boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Checks whether an Appium `/status` body reports a server that is accepting new
|
|
161
|
+
* sessions.
|
|
162
|
+
*
|
|
163
|
+
* Deliberately **tolerant**: only an explicit `ready: false` (Appium sets it
|
|
164
|
+
* while shutting down) is treated as not-ready. A body that cannot be parsed, or
|
|
165
|
+
* that omits the flag, reads as ready — an unrecognized shape must not make a
|
|
166
|
+
* healthy server look unreachable.
|
|
167
|
+
*
|
|
168
|
+
* This does not catch the wedge (a wedged server still answers `ready: true`);
|
|
169
|
+
* it closes the adjacent hole of adopting a server that is on its way down.
|
|
170
|
+
*
|
|
171
|
+
* @param statusBody - The raw `/status` response body.
|
|
172
|
+
* @returns `false` only when the server explicitly reports itself not ready.
|
|
173
|
+
*/
|
|
174
|
+
export declare function checkIsAppiumStatusReady(statusBody: string): boolean;
|
|
175
|
+
/**
|
|
176
|
+
* Decides what to do with a failed session attempt.
|
|
177
|
+
*
|
|
178
|
+
* The device-not-found error is only evidence against the server when the host's
|
|
179
|
+
* adb *can* still see the device; otherwise the error was honest and is left
|
|
180
|
+
* alone. A server this harness started in an earlier run is restarted (the
|
|
181
|
+
* confirmed remedy); every other wedged server is reported rather than killed,
|
|
182
|
+
* because terminating a process this run does not own is not its call to make.
|
|
183
|
+
*
|
|
184
|
+
* @param params - The failed attempt and what is known about the server.
|
|
185
|
+
* @returns The verdict.
|
|
186
|
+
*/
|
|
187
|
+
export declare function resolveWedgedAppiumServerRemedy(params: ResolveWedgedAppiumServerRemedyParams): WedgedAppiumServerVerdict;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { errorToString } from "./error-to-string.mjs";
|
|
2
|
+
const DEVICE_NOT_FOUND_MESSAGE_FRAGMENT = "Could not find a connected Android device";
|
|
3
|
+
const MILLISECONDS_PER_SECOND = 1e3;
|
|
4
|
+
const SECONDS_PER_MINUTE = 60;
|
|
5
|
+
const MILLISECONDS_PER_MINUTE = MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE;
|
|
6
|
+
function buildWedgedAppiumServerMessage(params) {
|
|
7
|
+
const lines = [
|
|
8
|
+
`The Appium server at ${params.appiumOrigin} cannot see Android device ${params.deviceId}, although this host's adb can.`,
|
|
9
|
+
"The server is stale \u2014 its adb connection is wedged \u2014 not the device, so `adb devices` will list the device and mislead you.",
|
|
10
|
+
resolveRemedyAdvice(params)
|
|
11
|
+
];
|
|
12
|
+
const provenance = describeServer(params);
|
|
13
|
+
if (provenance) {
|
|
14
|
+
lines.push(provenance);
|
|
15
|
+
}
|
|
16
|
+
return lines.join("\n");
|
|
17
|
+
}
|
|
18
|
+
function checkIsAppiumDeviceNotFoundError(error) {
|
|
19
|
+
return errorToString(error).includes(DEVICE_NOT_FOUND_MESSAGE_FRAGMENT);
|
|
20
|
+
}
|
|
21
|
+
function checkIsAppiumStatusReady(statusBody) {
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(statusBody);
|
|
25
|
+
} catch {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return readReadyFlag(parsed) !== false;
|
|
29
|
+
}
|
|
30
|
+
function resolveWedgedAppiumServerRemedy(params) {
|
|
31
|
+
if (!checkIsAppiumDeviceNotFoundError(params.error)) {
|
|
32
|
+
return { reason: "other-error", remedy: "not-wedged" };
|
|
33
|
+
}
|
|
34
|
+
if (!params.connectedDeviceIds.includes(params.deviceId)) {
|
|
35
|
+
return { reason: "device-absent", remedy: "not-wedged" };
|
|
36
|
+
}
|
|
37
|
+
if (!params.isAdoptedServer) {
|
|
38
|
+
return { reason: "freshly-started", remedy: "report" };
|
|
39
|
+
}
|
|
40
|
+
if (!params.isAutoStartAllowed) {
|
|
41
|
+
return { reason: "auto-start-disabled", remedy: "report" };
|
|
42
|
+
}
|
|
43
|
+
if (!params.isHarnessOwnedServer) {
|
|
44
|
+
return { reason: "foreign-server", remedy: "report" };
|
|
45
|
+
}
|
|
46
|
+
return { reason: "harness-owned", remedy: "restart" };
|
|
47
|
+
}
|
|
48
|
+
function describeServer(params) {
|
|
49
|
+
if (params.serverPid === void 0) {
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
const age = params.serverAgeInMilliseconds === void 0 ? "" : `, running for ${formatAge(params.serverAgeInMilliseconds)}`;
|
|
53
|
+
return `The server was started by an earlier obsidian-integration-testing run (pid ${String(params.serverPid)}${age}).`;
|
|
54
|
+
}
|
|
55
|
+
function formatAge(ageInMilliseconds) {
|
|
56
|
+
if (ageInMilliseconds < MILLISECONDS_PER_MINUTE) {
|
|
57
|
+
return `${String(Math.round(ageInMilliseconds / MILLISECONDS_PER_SECOND))}s`;
|
|
58
|
+
}
|
|
59
|
+
return `${String(Math.round(ageInMilliseconds / MILLISECONDS_PER_MINUTE))}min`;
|
|
60
|
+
}
|
|
61
|
+
function readReadyFlag(parsed) {
|
|
62
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
const record = parsed;
|
|
66
|
+
const value = record["value"];
|
|
67
|
+
const nested = typeof value === "object" && value !== null ? value["ready"] : void 0;
|
|
68
|
+
const ready = nested ?? record["ready"];
|
|
69
|
+
return typeof ready === "boolean" ? ready : void 0;
|
|
70
|
+
}
|
|
71
|
+
function resolveRemedyAdvice(params) {
|
|
72
|
+
const advice = {
|
|
73
|
+
"auto-start-disabled": `Appium auto-start is disabled (\`shouldAutoStartAppium: false\`), so this server is yours to manage: kill it and start a fresh one on ${params.appiumOrigin}, then re-run.`,
|
|
74
|
+
"foreign-server": `This server was not started by obsidian-integration-testing, so it was left running: kill it and start a fresh one on ${params.appiumOrigin}, then re-run.`,
|
|
75
|
+
"freshly-started": "This server was started by this run, so restarting it would change nothing: check that adb and the emulator agree (`adb kill-server && adb devices`), then re-run.",
|
|
76
|
+
"restart-did-not-help": `Restarting the server on ${params.appiumOrigin} did not help \u2014 a fresh one still could not serve this run, so the wedge is not the server alone: check adb itself (\`adb kill-server && adb devices\`) and that nothing else holds the port, then re-run.`
|
|
77
|
+
};
|
|
78
|
+
return advice[params.reason];
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
buildWedgedAppiumServerMessage,
|
|
82
|
+
checkIsAppiumDeviceNotFoundError,
|
|
83
|
+
checkIsAppiumStatusReady,
|
|
84
|
+
resolveWedgedAppiumServerRemedy
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL3dlZGdlZC1hcHBpdW0tc2VydmVyLnRzIl0sCiAgInNvdXJjZXNDb250ZW50IjogWyIvKipcbiAqIEBmaWxlXG4gKlxuICogUHVyZSBoZWxwZXJzIGZvciByZWNvZ25pemluZyBhICoqd2VkZ2VkKiogQXBwaXVtIHNlcnZlciBhbmQgZGVjaWRpbmcgd2hhdCB0b1xuICogZG8gYWJvdXQgaXQuIEtlcHQgc2VwYXJhdGUgZnJvbSB0aGUgaW50ZWdyYXRpb24tb25seSBgdHJhbnNwb3J0LWZhY3RvcnlgIHNvXG4gKiB0aGUgY2xhc3NpZmljYXRpb24sIHRoZSByZW1lZHkgZGVjaXNpb24gYW5kIHRoZSBtZXNzYWdlIHN0YXkgdW5pdC10ZXN0YWJsZVxuICogKHRoZSBmYWN0b3J5IGl0c2VsZiBuZWVkcyBhIHJlYWwgZGV2aWNlIGFuZCBpcyBleGNsdWRlZCBmcm9tIHVuaXQgdGVzdHMpLlxuICpcbiAqIEEgd2VkZ2VkIHNlcnZlciBpcyBvbmUgdGhhdCBpcyBzdGlsbCAqKmxpdmUqKiBcdTIwMTQgaXQgbGlzdGVucyBvbiBpdHMgcG9ydCBhbmRcbiAqIGFuc3dlcnMgYC9zdGF0dXNgIHdpdGggYHJlYWR5OiB0cnVlYCBcdTIwMTQgYnV0IGlzIG5vIGxvbmdlciAqKnVzYWJsZSoqOiBpdHNcbiAqIGBhcHBpdW0tYWRiYCBjYW4gbm8gbG9uZ2VyIGVudW1lcmF0ZSBkZXZpY2VzLCBzbyBldmVyeSBzZXNzaW9uIGNyZWF0aW9uIGRpZXNcbiAqIHdpdGggYENvdWxkIG5vdCBmaW5kIGEgY29ubmVjdGVkIEFuZHJvaWQgZGV2aWNlIGluIDxuPm1zYCB3aGlsZSB0aGUgaG9zdCdzIG93blxuICogYGFkYiBkZXZpY2VzYCBsaXN0cyB0aGUgZGV2aWNlIGluc3RhbnRseS4gTGl2ZW5lc3MgaXMgbm90IHJlYWRpbmVzcywgYW5kIHRoZVxuICogZXJyb3IgbmFtZXMgdGhlIHdyb25nIHN1YmplY3Q6IGl0IGJsYW1lcyBhIGRldmljZSB0aGF0IGlzIGRlbW9uc3RyYWJseVxuICogcHJlc2VudCwgd2hpY2ggc2VuZHMgd2hvZXZlciByZWFkcyBpdCB0byBgYWRiIGRldmljZXNgIFx1MjAxNCB0aGUgb25lIGRpYWdub3N0aWNcbiAqIHRoYXQgYWN0aXZlbHkgbWlzbGVhZHMgaGVyZS5cbiAqXG4gKiBUaGUgd2VkZ2UgZGV2ZWxvcHMgb3ZlciB0aW1lIGluIGEgbG9uZy1saXZlZCBzZXJ2ZXIgKG9uZSBhdXRvLXN0YXJ0ZWQgYnkgYW5cbiAqIGVhcmxpZXIgcnVuIGFuZCBsZWZ0IGxpc3RlbmluZyksIHNvIHRoZSBwcmVmbGlnaHQgY2Fubm90IHNlZSBpdCBjb21pbmc6IGl0IGNhblxuICogb25seSBiZSByZWNvZ25pemVkIGZyb20gdGhlIGZhaWxlZCBzZXNzaW9uLCBjcm9zcy1jaGVja2VkIGFnYWluc3QgdGhlIGhvc3Qnc1xuICogYWRiLlxuICovXG5cbmltcG9ydCB7IGVycm9yVG9TdHJpbmcgfSBmcm9tICcuL2Vycm9yLXRvLXN0cmluZy5tanMnO1xuXG4vKipcbiAqIFRoZSBmcmFnbWVudCBgYXBwaXVtLWFkYmAgdGhyb3dzIHdoZW4gaXQgY2Fubm90IGVudW1lcmF0ZSBkZXZpY2VzIFx1MjAxNFxuICogYGAgYENvdWxkIG5vdCBmaW5kIGEgY29ubmVjdGVkIEFuZHJvaWQgZGV2aWNlIGluICR7dGltZW91dE1zfW1zYCBgYCBmcm9tIGl0c1xuICogYGdldERldmljZXNXaXRoUmV0cnlgIChgbGliL3Rvb2xzL3N5c3RlbS1jYWxscy50c2ApLiBNYXRjaGVkIGFzIGEgc3Vic3RyaW5nIHNvXG4gKiB0aGUgdGltZW91dCB2YWx1ZSBhbmQgYW55IHdyYXBwaW5nIGJ5IFdlYmRyaXZlcklPIGRvIG5vdCBhZmZlY3QgaXQuXG4gKi9cbmNvbnN0IERFVklDRV9OT1RfRk9VTkRfTUVTU0FHRV9GUkFHTUVOVCA9ICdDb3VsZCBub3QgZmluZCBhIGNvbm5lY3RlZCBBbmRyb2lkIGRldmljZSc7XG5cbmNvbnN0IE1JTExJU0VDT05EU19QRVJfU0VDT05EID0gMTAwMDtcbmNvbnN0IFNFQ09ORFNfUEVSX01JTlVURSA9IDYwO1xuY29uc3QgTUlMTElTRUNPTkRTX1BFUl9NSU5VVEUgPSBNSUxMSVNFQ09ORFNfUEVSX1NFQ09ORCAqIFNFQ09ORFNfUEVSX01JTlVURTtcblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgYnVpbGRXZWRnZWRBcHBpdW1TZXJ2ZXJNZXNzYWdlfS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBCdWlsZFdlZGdlZEFwcGl1bVNlcnZlck1lc3NhZ2VQYXJhbXMge1xuICAvKipcbiAgT3JpZ2luIG9mIHRoZSBBcHBpdW0gc2VydmVyIHRoYXQgY291bGQgbm90IHNlZSB0aGUgZGV2aWNlIChlLmcuIGBodHRwOi8vbG9jYWxob3N0OjQ3MjNgKS5cbiAgICovXG4gIHJlYWRvbmx5IGFwcGl1bU9yaWdpbjogc3RyaW5nO1xuXG4gIC8qKlxuICBUaGUgZGV2aWNlIHRoZSBob3N0J3MgYWRiIGNhbiBzZWUgYnV0IEFwcGl1bSBjYW5ub3QuXG4gICAqL1xuICByZWFkb25seSBkZXZpY2VJZDogc3RyaW5nO1xuXG4gIC8qKlxuICBXaHkgdGhpcyBydW4gaXMgcmVwb3J0aW5nIHJhdGhlciB0aGFuIHJlc3RhcnRpbmcuXG4gICAqL1xuICByZWFkb25seSByZWFzb246IFdlZGdlZEFwcGl1bVNlcnZlclJlcG9ydFJlYXNvbjtcblxuICAvKipcbiAgSG93IGxvbmcgdGhlIHNlcnZlciBoYXMgYmVlbiBydW5uaW5nLCB3aGVuIGtub3duIGZyb20gaXRzIG1hcmtlci5cbiAgICovXG4gIHJlYWRvbmx5IHNlcnZlckFnZUluTWlsbGlzZWNvbmRzPzogbnVtYmVyIHwgdW5kZWZpbmVkO1xuXG4gIC8qKlxuICBQSUQgb2YgdGhlIHNlcnZlciwgd2hlbiBrbm93biBmcm9tIGl0cyBtYXJrZXIuXG4gICAqL1xuICByZWFkb25seSBzZXJ2ZXJQaWQ/OiBudW1iZXIgfCB1bmRlZmluZWQ7XG59XG5cbi8qKlxuICogVGhlIHNlcnZlciBpcyBub3QgdG8gYmxhbWU6IGVpdGhlciB0aGUgZmFpbHVyZSB3YXMgYSBkaWZmZXJlbnQgZXJyb3IsIG9yIHRoZVxuICogaG9zdCdzIGFkYiBjYW5ub3Qgc2VlIHRoZSBkZXZpY2UgZWl0aGVyLiBUaGUgb3JpZ2luYWwgZXJyb3IgaXMgcmV0aHJvd24uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgTm90V2VkZ2VkVmVyZGljdCB7XG4gIC8qKlxuICBXaGljaCBvZiB0aGUgdHdvIGhvbmVzdC1mYWlsdXJlIGNhc2VzIHRoaXMgaXMuXG4gICAqL1xuICByZWFkb25seSByZWFzb246ICdkZXZpY2UtYWJzZW50JyB8ICdvdGhlci1lcnJvcic7XG5cbiAgLyoqXG4gIERpc2NyaW1pbmFudC5cbiAgICovXG4gIHJlYWRvbmx5IHJlbWVkeTogJ25vdC13ZWRnZWQnO1xufVxuXG4vKipcbiAqIFRoZSBzZXJ2ZXIgaXMgd2VkZ2VkIGJ1dCBtdXN0IG5vdCBiZSB0b3VjaGVkIGJ5IHRoaXMgcnVuLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlcG9ydFNlcnZlclZlcmRpY3Qge1xuICAvKipcbiAgV2h5IHRoaXMgcnVuIGlzIHJlcG9ydGluZyByYXRoZXIgdGhhbiByZXN0YXJ0aW5nLlxuICAgKi9cbiAgcmVhZG9ubHkgcmVhc29uOiBXZWRnZWRBcHBpdW1TZXJ2ZXJSZXBvcnRSZWFzb247XG5cbiAgLyoqXG4gIERpc2NyaW1pbmFudC5cbiAgICovXG4gIHJlYWRvbmx5IHJlbWVkeTogJ3JlcG9ydCc7XG59XG5cbi8qKlxuICogUGFyYW1ldGVycyBmb3Ige0BsaW5rIHJlc29sdmVXZWRnZWRBcHBpdW1TZXJ2ZXJSZW1lZHl9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc29sdmVXZWRnZWRBcHBpdW1TZXJ2ZXJSZW1lZHlQYXJhbXMge1xuICAvKipcbiAgRGV2aWNlIElEcyB0aGUgKipob3N0J3MqKiBhZGIgY3VycmVudGx5IGxpc3RzICh0aGUgY3Jvc3MtY2hlY2sgdGhhdCBjb252aWN0cyB0aGUgc2VydmVyKS5cbiAgICovXG4gIHJlYWRvbmx5IGNvbm5lY3RlZERldmljZUlkczogcmVhZG9ubHkgc3RyaW5nW107XG5cbiAgLyoqXG4gIFRoZSBkZXZpY2UgdGhlIHNlc3Npb24gd2FzIHJlcXVlc3RlZCBhZ2FpbnN0LlxuICAgKi9cbiAgcmVhZG9ubHkgZGV2aWNlSWQ6IHN0cmluZztcblxuICAvKipcbiAgVGhlIGVycm9yIHRoZSBzZXNzaW9uIGF0dGVtcHQgZmFpbGVkIHdpdGguXG4gICAqL1xuICByZWFkb25seSBlcnJvcjogdW5rbm93bjtcblxuICAvKipcbiAgV2hldGhlciB0aGUgc2VydmVyIHdhcyBhbHJlYWR5IGxpc3RlbmluZyBhbmQgd2FzIGFkb3B0ZWQsIHJhdGhlciB0aGFuIHN0YXJ0ZWQgYnkgdGhpcyBydW4uXG4gICAqL1xuICByZWFkb25seSBpc0Fkb3B0ZWRTZXJ2ZXI6IGJvb2xlYW47XG5cbiAgLyoqXG4gIFdoZXRoZXIgdGhpcyBydW4gbWF5IHN0YXJ0IGFuIEFwcGl1bSBzZXJ2ZXIgKGBzaG91bGRBdXRvU3RhcnRBcHBpdW1gIGlzIG5vdCBgZmFsc2VgKS5cbiAgICovXG4gIHJlYWRvbmx5IGlzQXV0b1N0YXJ0QWxsb3dlZDogYm9vbGVhbjtcblxuICAvKipcbiAgV2hldGhlciB0aGUgYWRvcHRlZCBzZXJ2ZXIncyBtYXJrZXIgcHJvdmVzIGFuIGVhcmxpZXIgcnVuIG9mIHRoaXMgaGFybmVzcyBzdGFydGVkIGl0LlxuICAgKi9cbiAgcmVhZG9ubHkgaXNIYXJuZXNzT3duZWRTZXJ2ZXI6IGJvb2xlYW47XG59XG5cbi8qKlxuICogVGhlIHNlcnZlciBpcyB3ZWRnZWQgYW5kIGFuIGVhcmxpZXIgcnVuIG9mIHRoaXMgaGFybmVzcyBzdGFydGVkIGl0LCBzbyB0aGlzXG4gKiBydW4gbWF5IHJlcGxhY2UgaXQuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzdGFydFNlcnZlclZlcmRpY3Qge1xuICAvKipcbiAgQWx3YXlzIHRoZSBoYXJuZXNzLW93bmVkIGNhc2UuXG4gICAqL1xuICByZWFkb25seSByZWFzb246ICdoYXJuZXNzLW93bmVkJztcblxuICAvKipcbiAgRGlzY3JpbWluYW50LlxuICAgKi9cbiAgcmVhZG9ubHkgcmVtZWR5OiAncmVzdGFydCc7XG59XG5cbi8qKlxuICogVGhlIGNsYXNzaWZpY2F0aW9uIG9mIGEgZmFpbGVkIHNlc3Npb24gYXR0ZW1wdC5cbiAqXG4gKiBgJ2RldmljZS1hYnNlbnQnYCBhbmQgYCdvdGhlci1lcnJvcidgIGFyZSB0aGUgbm90LXdlZGdlZCB2ZXJkaWN0czogdGhlXG4gKiBvcmlnaW5hbCBlcnJvciB3YXMgaG9uZXN0IGFuZCBpcyByZXRocm93biB1bnRvdWNoZWQuXG4gKi9cbmV4cG9ydCB0eXBlIFdlZGdlZEFwcGl1bVNlcnZlclJlYXNvbiA9ICdkZXZpY2UtYWJzZW50JyB8ICdoYXJuZXNzLW93bmVkJyB8ICdvdGhlci1lcnJvcicgfCBXZWRnZWRBcHBpdW1TZXJ2ZXJSZXBvcnRSZWFzb247XG5cbi8qKlxuICogV2hhdCB0aGUgZmFjdG9yeSBzaG91bGQgZG8gd2l0aCBhIGZhaWxlZCBzZXNzaW9uIGF0dGVtcHQuXG4gKi9cbmV4cG9ydCB0eXBlIFdlZGdlZEFwcGl1bVNlcnZlclJlbWVkeSA9ICdub3Qtd2VkZ2VkJyB8ICdyZXBvcnQnIHwgJ3Jlc3RhcnQnO1xuXG4vKipcbiAqIFdoeSBhIHdlZGdlZCBzZXJ2ZXIgaXMgb25seSByZXBvcnRlZCByYXRoZXIgdGhhbiByZXN0YXJ0ZWQuIEVhY2ggbWFwcyB0byBpdHNcbiAqIG93biByZW1lZHkgc2VudGVuY2UgXHUyMDE0IHRoZSB3aG9sZSBwb2ludCBvZiB0aGUgcmV3cml0ZSBpcyB0aGF0IHRoZSBtZXNzYWdlIHNheXNcbiAqIHdoYXQgKnRoaXMqIHJ1biBjYW4gYW5kIGNhbm5vdCBkbyBhYm91dCB0aGUgc2VydmVyIGl0IGZvdW5kLlxuICovXG5leHBvcnQgdHlwZSBXZWRnZWRBcHBpdW1TZXJ2ZXJSZXBvcnRSZWFzb24gPVxuICB8ICdhdXRvLXN0YXJ0LWRpc2FibGVkJ1xuICB8ICdmb3JlaWduLXNlcnZlcidcbiAgfCAnZnJlc2hseS1zdGFydGVkJ1xuICB8ICdyZXN0YXJ0LWRpZC1ub3QtaGVscCc7XG5cbi8qKlxuICogVGhlIHZlcmRpY3Qgb24gYSBmYWlsZWQgc2Vzc2lvbiBhdHRlbXB0LCBkaXNjcmltaW5hdGVkIGJ5IGByZW1lZHlgIHNvIGFcbiAqIGAncmVwb3J0J2AgdmVyZGljdCBjYXJyaWVzIGEgcmVhc29uIHtAbGluayBidWlsZFdlZGdlZEFwcGl1bVNlcnZlck1lc3NhZ2V9IGNhblxuICogdHVybiBpbnRvIGFkdmljZS5cbiAqL1xuZXhwb3J0IHR5cGUgV2VkZ2VkQXBwaXVtU2VydmVyVmVyZGljdCA9IE5vdFdlZGdlZFZlcmRpY3QgfCBSZXBvcnRTZXJ2ZXJWZXJkaWN0IHwgUmVzdGFydFNlcnZlclZlcmRpY3Q7XG5cbi8qKlxuICogQnVpbGRzIHRoZSBlcnJvciBtZXNzYWdlIHRoYXQgcmVwbGFjZXMgYENvdWxkIG5vdCBmaW5kIGEgY29ubmVjdGVkIEFuZHJvaWRcbiAqIGRldmljZWAsIG5hbWluZyB0aGUgKipzZXJ2ZXIqKiBhbmQgc3RhdGluZyB0aGUgZXZpZGVuY2UgXHUyMDE0IHRoZSBob3N0J3MgYWRiIGNhblxuICogc2VlIHRoZSBkZXZpY2UsIHNvIHRoZSBzZXJ2ZXIgaXMgdGhlIHRoaW5nIHRoYXQgaXMgc3RhbGUuXG4gKlxuICogQHBhcmFtIHBhcmFtcyAtIFRoZSBzZXJ2ZXIsIHRoZSBkZXZpY2UsIGFuZCB3aHkgdGhpcyBydW4gaXMgbm90IHJlc3RhcnRpbmcgaXQuXG4gKiBAcmV0dXJucyBUaGUgbWVzc2FnZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJ1aWxkV2VkZ2VkQXBwaXVtU2VydmVyTWVzc2FnZShwYXJhbXM6IEJ1aWxkV2VkZ2VkQXBwaXVtU2VydmVyTWVzc2FnZVBhcmFtcyk6IHN0cmluZyB7XG4gIGNvbnN0IGxpbmVzID0gW1xuICAgIGBUaGUgQXBwaXVtIHNlcnZlciBhdCAke3BhcmFtcy5hcHBpdW1PcmlnaW59IGNhbm5vdCBzZWUgQW5kcm9pZCBkZXZpY2UgJHtwYXJhbXMuZGV2aWNlSWR9LCBhbHRob3VnaCB0aGlzIGhvc3QncyBhZGIgY2FuLmAsXG4gICAgJ1RoZSBzZXJ2ZXIgaXMgc3RhbGUgXHUyMDE0IGl0cyBhZGIgY29ubmVjdGlvbiBpcyB3ZWRnZWQgXHUyMDE0IG5vdCB0aGUgZGV2aWNlLCBzbyBgYWRiIGRldmljZXNgIHdpbGwgbGlzdCB0aGUgZGV2aWNlIGFuZCBtaXNsZWFkIHlvdS4nLFxuICAgIHJlc29sdmVSZW1lZHlBZHZpY2UocGFyYW1zKVxuICBdO1xuXG4gIGNvbnN0IHByb3ZlbmFuY2UgPSBkZXNjcmliZVNlcnZlcihwYXJhbXMpO1xuICBpZiAocHJvdmVuYW5jZSkge1xuICAgIGxpbmVzLnB1c2gocHJvdmVuYW5jZSk7XG4gIH1cblxuICByZXR1cm4gbGluZXMuam9pbignXFxuJyk7XG59XG5cbi8qKlxuICogQ2hlY2tzIHdoZXRoZXIgYSBmYWlsZWQgc2Vzc2lvbiBhdHRlbXB0IGlzIGBhcHBpdW0tYWRiYCdzIGRldmljZS1lbnVtZXJhdGlvblxuICogZmFpbHVyZS4gVGhlIHdob2xlIGBjYXVzZWAgY2hhaW4gaXMgc2VhcmNoZWQsIHNvIFdlYmRyaXZlcklPJ3Mgd3JhcHBpbmcgb2YgdGhlXG4gKiBzZXJ2ZXIncyBlcnJvciBkb2VzIG5vdCBoaWRlIGl0LlxuICpcbiAqIEBwYXJhbSBlcnJvciAtIFRoZSBlcnJvciB0aGUgc2Vzc2lvbiBhdHRlbXB0IGZhaWxlZCB3aXRoLlxuICogQHJldHVybnMgYHRydWVgIHdoZW4gaXQgaXMgdGhlIGRldmljZS1ub3QtZm91bmQgZmFpbHVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNoZWNrSXNBcHBpdW1EZXZpY2VOb3RGb3VuZEVycm9yKGVycm9yOiB1bmtub3duKTogYm9vbGVhbiB7XG4gIHJldHVybiBlcnJvclRvU3RyaW5nKGVycm9yKS5pbmNsdWRlcyhERVZJQ0VfTk9UX0ZPVU5EX01FU1NBR0VfRlJBR01FTlQpO1xufVxuXG4vKipcbiAqIENoZWNrcyB3aGV0aGVyIGFuIEFwcGl1bSBgL3N0YXR1c2AgYm9keSByZXBvcnRzIGEgc2VydmVyIHRoYXQgaXMgYWNjZXB0aW5nIG5ld1xuICogc2Vzc2lvbnMuXG4gKlxuICogRGVsaWJlcmF0ZWx5ICoqdG9sZXJhbnQqKjogb25seSBhbiBleHBsaWNpdCBgcmVhZHk6IGZhbHNlYCAoQXBwaXVtIHNldHMgaXRcbiAqIHdoaWxlIHNodXR0aW5nIGRvd24pIGlzIHRyZWF0ZWQgYXMgbm90LXJlYWR5LiBBIGJvZHkgdGhhdCBjYW5ub3QgYmUgcGFyc2VkLCBvclxuICogdGhhdCBvbWl0cyB0aGUgZmxhZywgcmVhZHMgYXMgcmVhZHkgXHUyMDE0IGFuIHVucmVjb2duaXplZCBzaGFwZSBtdXN0IG5vdCBtYWtlIGFcbiAqIGhlYWx0aHkgc2VydmVyIGxvb2sgdW5yZWFjaGFibGUuXG4gKlxuICogVGhpcyBkb2VzIG5vdCBjYXRjaCB0aGUgd2VkZ2UgKGEgd2VkZ2VkIHNlcnZlciBzdGlsbCBhbnN3ZXJzIGByZWFkeTogdHJ1ZWApO1xuICogaXQgY2xvc2VzIHRoZSBhZGphY2VudCBob2xlIG9mIGFkb3B0aW5nIGEgc2VydmVyIHRoYXQgaXMgb24gaXRzIHdheSBkb3duLlxuICpcbiAqIEBwYXJhbSBzdGF0dXNCb2R5IC0gVGhlIHJhdyBgL3N0YXR1c2AgcmVzcG9uc2UgYm9keS5cbiAqIEByZXR1cm5zIGBmYWxzZWAgb25seSB3aGVuIHRoZSBzZXJ2ZXIgZXhwbGljaXRseSByZXBvcnRzIGl0c2VsZiBub3QgcmVhZHkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjaGVja0lzQXBwaXVtU3RhdHVzUmVhZHkoc3RhdHVzQm9keTogc3RyaW5nKTogYm9vbGVhbiB7XG4gIGxldCBwYXJzZWQ6IHVua25vd247XG4gIHRyeSB7XG4gICAgcGFyc2VkID0gSlNPTi5wYXJzZShzdGF0dXNCb2R5KTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICByZXR1cm4gcmVhZFJlYWR5RmxhZyhwYXJzZWQpICE9PSBmYWxzZTtcbn1cblxuLyoqXG4gKiBEZWNpZGVzIHdoYXQgdG8gZG8gd2l0aCBhIGZhaWxlZCBzZXNzaW9uIGF0dGVtcHQuXG4gKlxuICogVGhlIGRldmljZS1ub3QtZm91bmQgZXJyb3IgaXMgb25seSBldmlkZW5jZSBhZ2FpbnN0IHRoZSBzZXJ2ZXIgd2hlbiB0aGUgaG9zdCdzXG4gKiBhZGIgKmNhbiogc3RpbGwgc2VlIHRoZSBkZXZpY2U7IG90aGVyd2lzZSB0aGUgZXJyb3Igd2FzIGhvbmVzdCBhbmQgaXMgbGVmdFxuICogYWxvbmUuIEEgc2VydmVyIHRoaXMgaGFybmVzcyBzdGFydGVkIGluIGFuIGVhcmxpZXIgcnVuIGlzIHJlc3RhcnRlZCAodGhlXG4gKiBjb25maXJtZWQgcmVtZWR5KTsgZXZlcnkgb3RoZXIgd2VkZ2VkIHNlcnZlciBpcyByZXBvcnRlZCByYXRoZXIgdGhhbiBraWxsZWQsXG4gKiBiZWNhdXNlIHRlcm1pbmF0aW5nIGEgcHJvY2VzcyB0aGlzIHJ1biBkb2VzIG5vdCBvd24gaXMgbm90IGl0cyBjYWxsIHRvIG1ha2UuXG4gKlxuICogQHBhcmFtIHBhcmFtcyAtIFRoZSBmYWlsZWQgYXR0ZW1wdCBhbmQgd2hhdCBpcyBrbm93biBhYm91dCB0aGUgc2VydmVyLlxuICogQHJldHVybnMgVGhlIHZlcmRpY3QuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlV2VkZ2VkQXBwaXVtU2VydmVyUmVtZWR5KHBhcmFtczogUmVzb2x2ZVdlZGdlZEFwcGl1bVNlcnZlclJlbWVkeVBhcmFtcyk6IFdlZGdlZEFwcGl1bVNlcnZlclZlcmRpY3Qge1xuICBpZiAoIWNoZWNrSXNBcHBpdW1EZXZpY2VOb3RGb3VuZEVycm9yKHBhcmFtcy5lcnJvcikpIHtcbiAgICByZXR1cm4geyByZWFzb246ICdvdGhlci1lcnJvcicsIHJlbWVkeTogJ25vdC13ZWRnZWQnIH07XG4gIH1cblxuICBpZiAoIXBhcmFtcy5jb25uZWN0ZWREZXZpY2VJZHMuaW5jbHVkZXMocGFyYW1zLmRldmljZUlkKSkge1xuICAgIHJldHVybiB7IHJlYXNvbjogJ2RldmljZS1hYnNlbnQnLCByZW1lZHk6ICdub3Qtd2VkZ2VkJyB9O1xuICB9XG5cbiAgaWYgKCFwYXJhbXMuaXNBZG9wdGVkU2VydmVyKSB7XG4gICAgcmV0dXJuIHsgcmVhc29uOiAnZnJlc2hseS1zdGFydGVkJywgcmVtZWR5OiAncmVwb3J0JyB9O1xuICB9XG5cbiAgaWYgKCFwYXJhbXMuaXNBdXRvU3RhcnRBbGxvd2VkKSB7XG4gICAgcmV0dXJuIHsgcmVhc29uOiAnYXV0by1zdGFydC1kaXNhYmxlZCcsIHJlbWVkeTogJ3JlcG9ydCcgfTtcbiAgfVxuXG4gIGlmICghcGFyYW1zLmlzSGFybmVzc093bmVkU2VydmVyKSB7XG4gICAgcmV0dXJuIHsgcmVhc29uOiAnZm9yZWlnbi1zZXJ2ZXInLCByZW1lZHk6ICdyZXBvcnQnIH07XG4gIH1cblxuICByZXR1cm4geyByZWFzb246ICdoYXJuZXNzLW93bmVkJywgcmVtZWR5OiAncmVzdGFydCcgfTtcbn1cblxuZnVuY3Rpb24gZGVzY3JpYmVTZXJ2ZXIocGFyYW1zOiBCdWlsZFdlZGdlZEFwcGl1bVNlcnZlck1lc3NhZ2VQYXJhbXMpOiBzdHJpbmcge1xuICBpZiAocGFyYW1zLnNlcnZlclBpZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgcmV0dXJuICcnO1xuICB9XG5cbiAgY29uc3QgYWdlID0gcGFyYW1zLnNlcnZlckFnZUluTWlsbGlzZWNvbmRzID09PSB1bmRlZmluZWQgPyAnJyA6IGAsIHJ1bm5pbmcgZm9yICR7Zm9ybWF0QWdlKHBhcmFtcy5zZXJ2ZXJBZ2VJbk1pbGxpc2Vjb25kcyl9YDtcbiAgcmV0dXJuIGBUaGUgc2VydmVyIHdhcyBzdGFydGVkIGJ5IGFuIGVhcmxpZXIgb2JzaWRpYW4taW50ZWdyYXRpb24tdGVzdGluZyBydW4gKHBpZCAke1N0cmluZyhwYXJhbXMuc2VydmVyUGlkKX0ke2FnZX0pLmA7XG59XG5cbmZ1bmN0aW9uIGZvcm1hdEFnZShhZ2VJbk1pbGxpc2Vjb25kczogbnVtYmVyKTogc3RyaW5nIHtcbiAgaWYgKGFnZUluTWlsbGlzZWNvbmRzIDwgTUlMTElTRUNPTkRTX1BFUl9NSU5VVEUpIHtcbiAgICByZXR1cm4gYCR7U3RyaW5nKE1hdGgucm91bmQoYWdlSW5NaWxsaXNlY29uZHMgLyBNSUxMSVNFQ09ORFNfUEVSX1NFQ09ORCkpfXNgO1xuICB9XG5cbiAgcmV0dXJuIGAke1N0cmluZyhNYXRoLnJvdW5kKGFnZUluTWlsbGlzZWNvbmRzIC8gTUlMTElTRUNPTkRTX1BFUl9NSU5VVEUpKX1taW5gO1xufVxuXG5mdW5jdGlvbiByZWFkUmVhZHlGbGFnKHBhcnNlZDogdW5rbm93bik6IGJvb2xlYW4gfCB1bmRlZmluZWQge1xuICBpZiAodHlwZW9mIHBhcnNlZCAhPT0gJ29iamVjdCcgfHwgcGFyc2VkID09PSBudWxsKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIGNvbnN0IHJlY29yZCA9IHBhcnNlZCBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbiAgY29uc3QgdmFsdWUgPSByZWNvcmRbJ3ZhbHVlJ107XG4gIGNvbnN0IG5lc3RlZCA9IHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcgJiYgdmFsdWUgIT09IG51bGwgPyAodmFsdWUgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pWydyZWFkeSddIDogdW5kZWZpbmVkO1xuICBjb25zdCByZWFkeSA9IG5lc3RlZCA/PyByZWNvcmRbJ3JlYWR5J107XG4gIHJldHVybiB0eXBlb2YgcmVhZHkgPT09ICdib29sZWFuJyA/IHJlYWR5IDogdW5kZWZpbmVkO1xufVxuXG5mdW5jdGlvbiByZXNvbHZlUmVtZWR5QWR2aWNlKHBhcmFtczogQnVpbGRXZWRnZWRBcHBpdW1TZXJ2ZXJNZXNzYWdlUGFyYW1zKTogc3RyaW5nIHtcbiAgY29uc3QgYWR2aWNlOiBSZWNvcmQ8V2VkZ2VkQXBwaXVtU2VydmVyUmVwb3J0UmVhc29uLCBzdHJpbmc+ID0ge1xuICAgICdhdXRvLXN0YXJ0LWRpc2FibGVkJzogYEFwcGl1bSBhdXRvLXN0YXJ0IGlzIGRpc2FibGVkIChcXGBzaG91bGRBdXRvU3RhcnRBcHBpdW06IGZhbHNlXFxgKSwgc28gdGhpcyBzZXJ2ZXIgaXMgeW91cnMgdG8gbWFuYWdlOiBraWxsIGl0IGFuZCBzdGFydCBhIGZyZXNoIG9uZSBvbiAke3BhcmFtcy5hcHBpdW1PcmlnaW59LCB0aGVuIHJlLXJ1bi5gLFxuICAgICdmb3JlaWduLXNlcnZlcic6IGBUaGlzIHNlcnZlciB3YXMgbm90IHN0YXJ0ZWQgYnkgb2JzaWRpYW4taW50ZWdyYXRpb24tdGVzdGluZywgc28gaXQgd2FzIGxlZnQgcnVubmluZzoga2lsbCBpdCBhbmQgc3RhcnQgYSBmcmVzaCBvbmUgb24gJHtwYXJhbXMuYXBwaXVtT3JpZ2lufSwgdGhlbiByZS1ydW4uYCxcbiAgICAnZnJlc2hseS1zdGFydGVkJzogJ1RoaXMgc2VydmVyIHdhcyBzdGFydGVkIGJ5IHRoaXMgcnVuLCBzbyByZXN0YXJ0aW5nIGl0IHdvdWxkIGNoYW5nZSBub3RoaW5nOiBjaGVjayB0aGF0IGFkYiBhbmQgdGhlIGVtdWxhdG9yIGFncmVlIChgYWRiIGtpbGwtc2VydmVyICYmIGFkYiBkZXZpY2VzYCksIHRoZW4gcmUtcnVuLicsXG4gICAgJ3Jlc3RhcnQtZGlkLW5vdC1oZWxwJzogYFJlc3RhcnRpbmcgdGhlIHNlcnZlciBvbiAke3BhcmFtcy5hcHBpdW1PcmlnaW59IGRpZCBub3QgaGVscCBcdTIwMTQgYSBmcmVzaCBvbmUgc3RpbGwgY291bGQgbm90IHNlcnZlIHRoaXMgcnVuLCBzbyB0aGUgd2VkZ2UgaXMgbm90IHRoZSBzZXJ2ZXIgYWxvbmU6IGNoZWNrIGFkYiBpdHNlbGYgKFxcYGFkYiBraWxsLXNlcnZlciAmJiBhZGIgZGV2aWNlc1xcYCkgYW5kIHRoYXQgbm90aGluZyBlbHNlIGhvbGRzIHRoZSBwb3J0LCB0aGVuIHJlLXJ1bi5gXG4gIH07XG5cbiAgcmV0dXJuIGFkdmljZVtwYXJhbXMucmVhc29uXTtcbn1cbiJdLAogICJtYXBwaW5ncyI6ICJBQXVCQSxTQUFTLHFCQUFxQjtBQVE5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
|